diff --git a/.gitignore b/.gitignore index 16818c2..6c4aaa6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,54 +1,13 @@ -# Prerequisites -*.d +build*/ +.vix/ +.cache/ -# Compiled Object files -*.slo -*.lo *.o *.obj - -# Precompiled Headers -*.gch -*.pch - -# Linker files -*.ilk - -# Debugger Files -*.pdb - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la *.a *.lib - -# Executables +*.so +*.dll +*.dylib *.exe -*.out -*.app - -# debug information files -*.dwo -build-ninja/ -build/ -docker/ -.github/ -infra/ -scripts/ -db.sql -.env -vix.log -configs/ -docs/ cmd.md -.vscode/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..990b1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,76 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## v0.9.1 + +### Added + +- Added selective Rix facade module mounting through `RIX_ENABLE_*` macros. +- Kept backward compatibility: including `` without feature macros still enables all current facade modules. + +### Changed + +- Updated README with the Rix package model, lightweight facade usage, and documentation link. +- Added Rix documentation link: https://rix.vixcpp.com + +## v0.9.0 + +### Added + +- Added `@rix/pdf` to the unified Rix facade. +- Added `rix.pdf` public API for PDF document generation. +- Added PDF document creation through `rix.pdf.document()`. +- Added PDF saving through `rix.pdf.save(document, path)`. +- Added PDF byte serialization through `rix.pdf.write(document)`. +- Added high-level text PDF helper through `rix.pdf.make_text(...)`. +- Added PDF image helpers through `rix.pdf.image.load_jpeg(...)` and `rix.pdf.image.from_jpeg_bytes(...)`. +- Added PDF error helpers through `rix.pdf.error`. +- Added public PDF examples under `examples/pdf`. + +### Changed + +- Updated the unified `rix` facade to mount the PDF module as `rix.pdf`. +- Updated README documentation with `@rix/pdf`, `rix.pdf`, PDF examples, and PDF error handling. +- Updated example CMake configuration to build PDF examples. +- Updated package metadata and lock file for the new PDF dependency. + +### Notes + +- `rix.pdf` is the high-level public API for users of `@rix/rix`. +- Lower-level `rixlib::pdf` APIs remain available for independent package usage and advanced integration. + +## v0.7.0 + +### Added + +- Added `@rix/pdf` to the unified Rix facade. +- Added `rix.pdf` public API for PDF document generation. +- Added PDF document creation through `rix.pdf.document()`. +- Added PDF saving through `rix.pdf.save(document, path)`. +- Added PDF byte serialization through `rix.pdf.write(document)`. +- Added high-level text PDF helper through `rix.pdf.make_text(...)`. +- Added PDF image helpers through `rix.pdf.image.load_jpeg(...)` and `rix.pdf.image.from_jpeg_bytes(...)`. +- Added PDF error helpers through `rix.pdf.error`. +- Added PDF examples under `examples/pdf`. +- Added PDF example targets: + - `rix_pdf_01_basic` + - `rix_pdf_02_text` + - `rix_pdf_03_table` + - `rix_pdf_04_drawing` + - `rix_pdf_05_metadata` + - `rix_pdf_06_make_text` + - `rix_pdf_07_error_handling` + +### Changed + +- Updated the unified `rix` facade to mount the PDF module as `rix.pdf`. +- Updated public examples to prefer the stable `rix.*` facade API. +- Updated README documentation with `@rix/pdf`, `rix.pdf`, PDF examples, and PDF error handling. +- Updated repository layout documentation to include `examples/pdf` and `packages/pdf`. + +### Notes + +- `rix.pdf` is the public high-level PDF API for users of `@rix/rix`. +- The lower-level `rixlib::pdf` API remains available for independent package usage and advanced integration. +- PDF writer internals stay hidden behind explicit `PdfResult` and `PdfStatus` values. diff --git a/CMakeLists.txt b/CMakeLists.txt index e69de29..ec9ee86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -0,0 +1,45 @@ +cmake_minimum_required(VERSION 3.20) + +project(rix + VERSION 0.5.0 + DESCRIPTION "Unified Rix facade for Vix C++ projects" + LANGUAGES CXX +) + +option(RIX_BUILD_EXAMPLES "Build Rix examples" ON) +option(RIX_BUILD_TESTS "Build Rix tests" ON) + +include(${CMAKE_CURRENT_SOURCE_DIR}/.vix/vix_deps.cmake OPTIONAL) + +add_library(rix INTERFACE) +add_library(rix::rix ALIAS rix) + +target_compile_features(rix + INTERFACE + cxx_std_20 +) + +target_include_directories(rix + INTERFACE + $ + $ +) + +target_link_libraries(rix + INTERFACE + rix::csv + rix::debug + rix::auth + rix::pdf +) + +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + if(RIX_BUILD_EXAMPLES) + add_subdirectory(examples) + endif() + + if(RIX_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) + endif() +endif() diff --git a/Makefile b/Makefile deleted file mode 100644 index 2e29289..0000000 --- a/Makefile +++ /dev/null @@ -1,216 +0,0 @@ -# ============================================================= -# Vix App — Cross-platform build helper -# ============================================================= -# Usage: -# make build → configure + build (ALL) -# make run → build + run (target 'run') -# make clean → delete build folders -# make rebuild → full rebuild -# make preset=name run → override configure preset (ex: dev-msvc) -# make BUILD_PRESET=name → override build preset (ex: build-msvc) -# ============================================================= - -# ---------------- Base Shell ---------------- -SHELL := /bin/bash -.ONESHELL: -.SHELLFLAGS := -eu -o pipefail -c - -# ---------------- Variables ---------------- -VERSION ?= v0.1.0 -BRANCH_DEV ?= dev -BRANCH_MAIN ?= main -REMOTE ?= origin - -# CMake presets / build -PRESET ?= dev-ninja -BUILD_PRESET ?= $(PRESET) -ifeq ($(PRESET),dev-ninja) - BUILD_PRESET := build-ninja -endif -ifeq ($(PRESET),dev-msvc) - BUILD_PRESET := build-msvc -endif -RUN_PRESET ?= $(BUILD_PRESET) -ifeq ($(PRESET),dev-ninja) - RUN_PRESET := run-ninja -endif -ifeq ($(PRESET),dev-msvc) - RUN_PRESET := run-msvc -endif - -CMAKE ?= cmake - -# ---------------- PHONY ---------------- -.PHONY: force_ssh_remote preflight ensure-branch ensure-clean commit push merge tag release \ - test changelog help build run clean rebuild preset \ - coverage publish-mods publish-mods-force - -# ---------------- Help ---------------- -help: - @echo "Targets:" - @echo " release VERSION=vX.Y.Z Run full release flow (commit -> sync -> push/merge -> tag)" - @echo " commit Commit all changes on $(BRANCH_DEV)" - @echo " preflight Sync branches with retries (fetch & rebase)" - @echo " push Push $(BRANCH_DEV) with retries" - @echo " merge Merge $(BRANCH_DEV) -> $(BRANCH_MAIN) and push with retries" - @echo " tag VERSION=vX.Y.Z Create and push annotated tag" - @echo " test Run ctest if build/ exists, otherwise composer test" - @echo " changelog Run scripts/update_changelog.sh if present" - @echo " build Configure + build (CMake preset: $(PRESET))" - @echo " run Build + run (uses run preset: $(RUN_PRESET))" - @echo " clean Remove build artifacts" - @echo " rebuild Full rebuild (clean + build)" - @echo " preset Placeholder target to override PRESET variable" - -# ---------------- Git Remote (force SSH) ---------------- -force_ssh_remote: - @echo "🔐 Forcing SSH for GitHub remotes..." - @git config --global url."git@github.com:".insteadOf https://github.com/ - @url="$$(git remote get-url $(REMOTE))"; \ - if [[ "$$url" =~ ^https://github.com/ ]]; then \ - new="$${url/https:\/\/github.com\//git@github.com:}"; \ - echo "🔁 Switching $(REMOTE) to $$new"; \ - git remote set-url $(REMOTE) "$$new"; \ - fi - @echo "Remote $(REMOTE): $$(git remote get-url $(REMOTE))" - @ssh -T git@github.com >/dev/null 2>&1 || true - -# ---------------- Guards ---------------- -ensure-branch: - @if [ "$$(git rev-parse --abbrev-ref HEAD)" != "$(BRANCH_DEV)" ]; then \ - echo "❌ You must be on $(BRANCH_DEV) to run this target."; \ - exit 1; \ - fi - -ensure-clean: - @if [ -n "$$(git status --porcelain)" ]; then \ - echo "❌ Working tree not clean. Commit or stash first."; \ - git status --porcelain; \ - exit 1; \ - fi - -# ---------------- Sync (avec retries) ---------------- -preflight: force_ssh_remote - @echo "🔎 Sync $(BRANCH_DEV) & $(BRANCH_MAIN) ..." - @tries=0; until git fetch $(REMOTE); do \ - tries=$$((tries+1)); \ - if [ $$tries -ge 5 ]; then echo "❌ git fetch failed after $$tries tries"; exit 128; fi; \ - echo "⏳ Retry $$tries (fetch)..."; sleep 3; \ - done - @git show-ref --verify --quiet refs/heads/$(BRANCH_DEV) || git branch $(BRANCH_DEV) $(REMOTE)/$(BRANCH_DEV) || true - @git show-ref --verify --quiet refs/heads/$(BRANCH_MAIN) || git branch $(BRANCH_MAIN) $(REMOTE)/$(BRANCH_MAIN) || true - - @tries=0; until git checkout $(BRANCH_DEV) && git pull --rebase $(REMOTE) $(BRANCH_DEV); do \ - tries=$$((tries+1)); \ - if [ $$tries -ge 5 ]; then echo "❌ rebase $(BRANCH_DEV) failed after $$tries tries"; exit 128; fi; \ - echo "⏳ Retry $$tries (pull --rebase $(BRANCH_DEV))..."; sleep 3; \ - done - - @tries=0; until git checkout $(BRANCH_MAIN) && git pull --rebase $(REMOTE) $(BRANCH_MAIN); do \ - tries=$$((tries+1)); \ - if [ $$tries -ge 5 ]; then echo "❌ rebase $(BRANCH_MAIN) failed after $$tries tries"; exit 128; fi; \ - echo "⏳ Retry $$tries (pull --rebase $(BRANCH_MAIN))..."; sleep 3; \ - done - - @git checkout $(BRANCH_DEV) - @echo "✅ Preflight sync OK" - -# ---------------- Core Flow ---------------- -commit: ensure-branch - @if [ -n "$$(git status --porcelain)" ]; then \ - echo "📝 Committing changes..."; \ - git add -A; \ - git commit -m "chore(release): prepare $(VERSION)"; \ - else \ - echo "✅ Nothing to commit."; \ - fi - -push: force_ssh_remote - @tries=0; until git push $(REMOTE) $(BRANCH_DEV); do \ - tries=$$((tries+1)); \ - if [ $$tries -ge 5 ]; then echo "❌ push $(BRANCH_DEV) failed after $$tries tries"; exit 128; fi; \ - echo "⏳ Retry $$tries..."; sleep 3; \ - done - -merge: force_ssh_remote - git checkout $(BRANCH_MAIN) - git merge --no-ff --no-edit $(BRANCH_DEV) - @tries=0; until git push $(REMOTE) $(BRANCH_MAIN); do \ - tries=$$((tries+1)); \ - if [ $$tries -ge 5 ]; then echo "❌ push $(BRANCH_MAIN) failed after $$tries tries"; exit 128; fi; \ - echo "⏳ Retry $$tries..."; sleep 3; \ - done - git checkout $(BRANCH_DEV) - @echo "✅ Merge & push to $(BRANCH_MAIN) OK" - -tag: force_ssh_remote - @if ! [[ "$(VERSION)" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$$ ]]; then \ - echo "❌ VERSION must look like vX.Y.Z (got '$(VERSION)')"; exit 1; \ - fi - @if git rev-parse -q --verify "refs/tags/$(VERSION)" >/dev/null; then \ - echo "❌ Tag $(VERSION) already exists."; exit 1; \ - fi - @echo "🏷️ Creating annotated tag $(VERSION)..." - git tag -a $(VERSION) -m "chore(release): $(VERSION)" - @tries=0; until git push $(REMOTE) $(VERSION); do \ - tries=$$((tries+1)); \ - if [ $$tries -ge 5 ]; then echo "❌ push tag $(VERSION) failed after $$tries tries"; exit 128; fi; \ - echo "⏳ Retry $$tries..."; sleep 3; \ - done - @echo "✅ Tag $(VERSION) pushed" - -# ---------------- Orchestration ---------------- -# Ordre sûr : commit -> preflight(sync) -> ensure-clean -> push dev -> merge to main -> tag -release: ensure-branch force_ssh_remote commit preflight ensure-clean push merge tag - @echo "🎉 Release $(VERSION) done!" - -# ---------------- Extras / Tests ---------------- -# Try ctest if build dir exists, otherwise fallback to composer test -test: - @if [ -d "./build" ] || ls build-* 1>/dev/null 2>&1; then \ - echo "🔬 Running ctest..."; \ - ctest --test-dir ./build || exit $$?; \ - else \ - echo "🔬 No build folder found — running composer test fallback..."; \ - @composer test || true; \ - fi - -coverage: - @XDEBUG_MODE=coverage vendor/bin/phpunit || true - -publish-mods: - php bin/ivi modules:publish-assets || true - -publish-mods-force: - php bin/ivi modules:publish-assets --force || true - -changelog: - @bash scripts/update_changelog.sh || true - -# ---------------- CMake / Build section ---------------- -# Configure preset (CMake 'configurePresets') -# Build preset (CMake 'buildPresets') is mapped above -all: build - -build: - @echo "⚙️ Configuring with preset '$(PRESET)' and building '$(BUILD_PRESET)'..." - @$(CMAKE) --preset $(PRESET) - @$(CMAKE) --build --preset $(BUILD_PRESET) - -run: - @echo "▶ Building and running (preset: $(RUN_PRESET))..." - @$(CMAKE) --preset $(PRESET) - @$(CMAKE) --build --preset $(RUN_PRESET) --target run - -clean: - @echo "🧹 Cleaning build artifacts..." - @rm -rf build-* CMakeFiles CMakeCache.txt || true - -rebuild: clean build - -preset: - @: - -# ---------------- Misc ---------------- -# Keep this file easy to extend for CI or dev machines. - diff --git a/README.md b/README.md index 504b137..0f2e687 100644 --- a/README.md +++ b/README.md @@ -1 +1,1186 @@ # Rix + +Rix is the unified userland library layer for Vix.cpp. + +It gives Vix C++ projects a set of optional userland packages and a single clean facade object: + +```cpp +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + auto table = rix.csv.parse("name,language\nAda,C++\n"); + rix.debug.log("loaded {} rows", table.size()); + + return 0; +} +``` + +Rix does not replace Vix. + +Vix provides the runtime, CLI, build workflow, registry integration, and core foundations. + +Rix provides optional userland packages and a unified facade over them. + +--- + +## Links + +- Website: https://vixcpp.com +- Rix Docs: https://rix.vixcpp.com +- Registry: https://registry.vixcpp.com +- Vix Docs: https://docs.vixcpp.com + +--- + +## Design + +Rix is built around two levels. + +### Independent packages + +```txt +@rix/csv +@rix/debug +@rix/auth +@rix/pdf +@rix/config +@rix/table +``` + +These packages are independent. + +They can be installed and used directly when an application only needs one package. + +### Unified facade package + +```txt +@rix/rix +``` + +This package provides the optional unified facade: + +```cpp +rix.csv.parse(...) +rix.debug.print(...) +rix.auth.memory(...) +rix.pdf.document() +``` + +The facade is for applications that want one clean object-style API. + +Public examples should prefer the unified `rix` facade. + +The lower-level `rixlib` namespace is reserved for package implementation details, independent package usage, and advanced integration code. + +--- + +## Package model + +```txt +Vix -> runtime, CLI, build workflow, registry client, core modules +Rix -> userland libraries and optional unified facade +Registry -> package metadata and version resolution +``` + +Rix keeps packages modular while still offering one clean entry point for users who want the full userland layer. + +--- + +## Install + +Install the unified facade: + +```bash +vix add @rix/rix +vix install +``` + +Or install an independent package: + +```bash +vix add @rix/csv +vix install +``` + +```bash +vix add @rix/debug +vix install +``` + +```bash +vix add @rix/auth +vix install +``` + +```bash +vix add @rix/pdf +vix install +``` + +--- + +## Use the unified facade + +```cpp +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + const auto message = rix.debug.format("Package: {}", "rix/rix"); + rix.debug.log("message: {}", message); + + return 0; +} +``` + +--- + +## Lightweight facade usage + +By default, `#include ` enables all current mounted facade modules for backward compatibility. + +```cpp +#include + +int main() +{ + rix.csv.parse("name,language\nAda,C++\n"); + rix.debug.print("Hello", "Rix"); + + auto auth = rix.auth.memory(); + + auto doc = rix.pdf.document(); + + return 0; +} +``` + +For lighter usage, define only the modules you want before including `rix.hpp`. + +```cpp +#define RIX_ENABLE_CSV +#include + +int main() +{ + auto table = rix.csv.parse("name,language\nAda,C++\n"); + + return table.empty() ? 1 : 0; +} +``` + +Multiple modules can be enabled together: + +```cpp +#define RIX_ENABLE_CSV +#define RIX_ENABLE_DEBUG +#include + +int main() +{ + auto table = rix.csv.parse("name,language\nAda,C++\n"); + + rix.debug.log("loaded {} rows", table.size()); + + return 0; +} +``` + +Available facade macros: + +```txt +RIX_ENABLE_CSV +RIX_ENABLE_DEBUG +RIX_ENABLE_AUTH +RIX_ENABLE_PDF +``` + +If no `RIX_ENABLE_*` macro is defined, all current facade modules are enabled. + +This keeps existing code working: + +```cpp +#include +``` + +This enables only CSV: + +```cpp +#define RIX_ENABLE_CSV +#include +``` + +This enables CSV and Debug: + +```cpp +#define RIX_ENABLE_CSV +#define RIX_ENABLE_DEBUG +#include +``` + +Do not define facade macros after including `rix.hpp`. + +Wrong: + +```cpp +#include + +#define RIX_ENABLE_CSV +#include +``` + +Correct: + +```cpp +#define RIX_ENABLE_CSV +#include +``` + +--- + +## Current modules + +| Package | Facade API | Description | +| ------------ | ----------- | -------------------------------------------------------------- | +| `@rix/csv` | `rix.csv` | Small CSV reader and writer for Vix C++ projects. | +| `@rix/debug` | `rix.debug` | Debug printing, formatting, logging, and inspection utilities. | +| `@rix/auth` | `rix.auth` | Production-oriented authentication for Vix C++ applications. | +| `@rix/pdf` | `rix.pdf` | PDF generation and document utilities for Vix C++ apps. | + +--- + +## Full facade example + +```cpp +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + + auto table = rix.csv.parse("name,language\nAda,C++\n"); + + rix.debug.log("loaded {} rows", table.size()); + + auto auth = rix.auth.memory(); + + auto registered = auth.register_user({ + "ada@example.com", + "correct-password"}); + + if (registered.ok()) + { + rix.debug.print("registered:", registered.value().email()); + } + + auto doc = rix.pdf.document(); + + auto &page = doc.add_page(); + + page.text( + page.x_left(), + page.y_top(), + "Hello from rix.pdf"); + + auto saved = rix.pdf.save(doc, "hello.pdf"); + + if (saved.ok()) + { + rix.debug.print("created:", "hello.pdf"); + } + + return 0; +} +``` + +--- + +## CSV example + +```cpp +#include +#include + +int main() +{ + const std::string input = + "name,language\n" + "Ada,C++\n" + "Gaspard,Vix\n"; + + const auto table = rix.csv.parse(input); + + rix.debug.log("loaded {} rows", table.size()); + + for (const auto &row : table) + { + for (const auto &field : row) + { + rix.debug.print(field); + } + } + + return 0; +} +``` + +--- + +## Debug example + +```cpp +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + + auto text = rix.debug.format("Package: {}", "rix/rix"); + + rix.debug.log("loaded {} APIs", 4); + rix.debug.log.warn("slow path: {}ms", 120); + + rix.debug.inspect(text); + + return 0; +} +``` + +--- + +## Auth example + +```cpp +#include + +int main() +{ + auto auth = rix.auth.memory(); + + auto registered = auth.register_user({ + "ada@example.com", + "correct-password"}); + + if (registered.failed()) + { + const auto &error = registered.error(); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); + + return 1; + } + + rix.debug.print("registered user:", registered.value().email()); + + auto login = auth.login({ + "ada@example.com", + "correct-password"}); + + if (login.failed()) + { + const auto &error = login.error(); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); + + return 1; + } + + rix.debug.print("login successful"); + rix.debug.print("session:", login.value().session.id()); + rix.debug.print("token issuer:", login.value().token.issuer()); + + return 0; +} +``` + +--- + +## Auth password example + +```cpp +#include + +int main() +{ + auto hashed = rix.auth.password.hash("correct-password"); + + if (hashed.failed()) + { + const auto &error = hashed.error(); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); + + return 1; + } + + const bool valid = rix.auth.password.verify( + "correct-password", + hashed.value()); + + const bool invalid = rix.auth.password.verify( + "wrong-password", + hashed.value()); + + rix.debug.print("valid password:", valid ? "yes" : "no"); + rix.debug.print("wrong password:", invalid ? "yes" : "no"); + + return valid && !invalid ? 0 : 1; +} +``` + +--- + +## PDF example + +```cpp +#include + +int main() +{ + auto doc = rix.pdf.document(); + + auto &page = doc.add_page(); + + page.text( + page.x_left(), + page.y_top(), + "Hello from rix.pdf"); + + page.text( + page.x_left(), + page.y_top() - 30.0F, + "This PDF was generated through the unified Rix facade."); + + auto saved = rix.pdf.save(doc, "rix_pdf_basic.pdf"); + + if (saved.failed()) + { + const auto &error = saved.error(); + + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(error), + error.message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_basic.pdf"); + + return 0; +} +``` + +--- + +## PDF text example + +```cpp +#include + +int main() +{ + auto doc = rix.pdf.document(); + + doc.set_title("Rix PDF Text Example"); + doc.set_author("Rix"); + + auto &page = doc.add_page(); + + auto y = page.heading( + page.x_left(), + page.y_top(), + "Rix PDF", + 1); + + y -= 10.0F; + + page.paragraph( + page.x_left(), + y, + page.content_width(), + "Rix gives Vix C++ projects a unified userland facade. " + "The PDF module keeps the public API simple while the writer internals stay hidden."); + + auto saved = rix.pdf.save(doc, "rix_pdf_text.pdf"); + + if (saved.failed()) + { + const auto &error = saved.error(); + + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(error), + error.message()); + + return 1; + } + + return 0; +} +``` + +--- + +## PDF table example + +```cpp +#include + +int main() +{ + auto doc = rix.pdf.document(); + + auto &page = doc.add_page(); + + auto y = page.heading( + page.x_left(), + page.y_top(), + "Project table", + 1); + + y -= 20.0F; + + rixlib::pdf::Table table; + + table.set_column_widths({ + 160.0F, + 160.0F, + 160.0F}); + + table.add_header({ + "Name", + "Language", + "Project"}); + + table.add_row({ + "Ada", + "C++", + "Rix"}); + + table.add_row({ + "Gaspard", + "C++", + "Vix.cpp"}); + + page.table( + page.x_left(), + y, + table); + + auto saved = rix.pdf.save(doc, "rix_pdf_table.pdf"); + + return saved.ok() ? 0 : 1; +} +``` + +--- + +## Auth facade API + +```cpp +rix.auth.memory() +rix.auth.memory(config) + +rix.auth.database(db) +rix.auth.database(db, config) + +rix.auth.managed(users, sessions) +rix.auth.managed(users, sessions, config) + +rix.auth.create(users, sessions) +rix.auth.create(users, sessions, config) + +rix.auth.password.hash(...) +rix.auth.password.verify(...) +rix.auth.password.accepts(...) +rix.auth.password.hasher() + +rix.auth.config.development() +rix.auth.config.production() +rix.auth.config.validate(...) +rix.auth.config.valid(...) + +rix.auth.error.to_string(...) +rix.auth.error.make(...) +rix.auth.error.none(...) + +rix.auth.stores.memory_users() +rix.auth.stores.memory_sessions() +rix.auth.stores.database_users(db) +rix.auth.stores.database_sessions(db) + +rix.auth.version() +``` + +--- + +## Auth service creation + +Most applications should use the safe managed APIs. + +For local examples, tests, and small applications: + +```cpp +auto auth = rix.auth.memory(); +``` + +For durable applications backed by a Vix database: + +```cpp +auto auth = rix.auth.database(db); +``` + +For custom stores where Rix should own the store lifetime: + +```cpp +auto users = rix.auth.stores.memory_users(); +auto sessions = rix.auth.stores.memory_sessions(); + +auto auth = rix.auth.managed( + std::move(users), + std::move(sessions)); + +if (auth.failed()) +{ + const auto &error = auth.error(); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); + + return 1; +} +``` + +`rix.auth.managed(...)` is the preferred custom-store API because it owns the stores inside the returned auth service. + +### Advanced: `rix.auth.create(...)` + +`rix.auth.create(...)` is an advanced API for caller-owned stores. + +It does not own the user store or session store. The caller must ensure both stores live longer than the returned auth service. + +Correct usage: + +```cpp +auto users = rix.auth.stores.memory_users(); +auto sessions = rix.auth.stores.memory_sessions(); + +auto auth = rix.auth.create( + *users, + *sessions); +``` + +In this example, `users` and `sessions` must remain alive while `auth` is used. + +For public application code, prefer: + +```cpp +auto auth = rix.auth.memory(); +``` + +or: + +```cpp +auto auth = rix.auth.database(db); +``` + +or: + +```cpp +auto auth = rix.auth.managed( + rix.auth.stores.memory_users(), + rix.auth.stores.memory_sessions()); +``` + +Use `create(...)` only when you intentionally want to manage store lifetime yourself. + +--- + +## PDF facade API + +```cpp +rix.pdf.document() +rix.pdf.document(page_size, margins) + +rix.pdf.write(document) +rix.pdf.save(document, "output.pdf") +rix.pdf.make_text("output.pdf", "content", "title") + +rix.pdf.error.to_string(error) +rix.pdf.error.make(code, message) +rix.pdf.error.none() + +rix.pdf.image.load_jpeg("image.jpg") +rix.pdf.image.from_jpeg_bytes(bytes) + +rix.pdf.writer.write(document) +rix.pdf.writer.save(document, "output.pdf") +rix.pdf.writer.create() + +rix.pdf.version() +``` + +--- + +## PDF error handling + +`rix.pdf` uses explicit result values for normal failures. + +```cpp +auto saved = rix.pdf.save(doc, "output.pdf"); + +if (saved.failed()) +{ + const auto &error = saved.error(); + + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(error), + error.message()); + + return 1; +} +``` + +For operations that return data: + +```cpp +auto bytes = rix.pdf.write(doc); + +if (bytes.ok()) +{ + rix.debug.print("pdf bytes:", bytes.value().size()); +} +``` + +--- + +## Print and format + +`rix.debug.print` does not replace `{}` placeholders. + +It prints values separated by spaces: + +```cpp +rix.debug.print("Hello", "Rix"); +rix.debug.print(1, 2, 3); +``` + +Output: + +```txt +Hello Rix +1 2 3 +``` + +For placeholder formatting, use `rix.debug.format`: + +```cpp +auto text = rix.debug.format("Hello {}", "Rix"); +rix.debug.print(text); +``` + +Or use `rix.debug.log` directly: + +```cpp +rix.debug.log("Hello {}", "Rix"); +``` + +--- + +## Independent packages + +Each Rix package can also be used independently. + +### CSV + +```bash +vix add @rix/csv +vix install +``` + +```cpp +#include + +int main() +{ + rixlib::csv::Csv csv; + + auto table = csv.parse("name,language\nAda,C++\n"); + + return table.empty() ? 1 : 0; +} +``` + +### Debug + +```bash +vix add @rix/debug +vix install +``` + +```cpp +#include + +int main() +{ + rixlib::debug::Debug debug; + + debug.print("Hello", "Rix"); + debug.log("loaded {} rows", 3); + + return 0; +} +``` + +### Auth + +```bash +vix add @rix/auth +vix install +``` + +```cpp +#include + +int main() +{ + rixlib::auth::MemoryUserStore users; + rixlib::auth::MemorySessionStore sessions; + + rixlib::auth::Auth auth{ + users, + sessions, + rixlib::auth::AuthConfig::development()}; + + auto registered = auth.register_user({ + "ada@example.com", + "correct-password"}); + + return registered.ok() ? 0 : 1; +} +``` + +### PDF + +```bash +vix add @rix/pdf +vix install +``` + +```cpp +#include + +int main() +{ + auto pdf = rixlib::pdf::module(); + + auto doc = pdf.document(); + + auto &page = doc.add_page(); + + page.text( + page.x_left(), + page.y_top(), + "Hello from rix/pdf"); + + auto saved = pdf.save(doc, "hello.pdf"); + + return saved.ok() ? 0 : 1; +} +``` + +Independent packages expose their own lower-level APIs. + +For the unified public facade, prefer `@rix/rix` and `#include `. + +--- + +## Repository layout + +```txt +rix/ +├── include/ +│ └── rix.hpp +├── examples/ +│ ├── basic.cpp +│ ├── csv.cpp +│ ├── debug.cpp +│ ├── auth/ +│ │ ├── 01_memory_register_login.cpp +│ │ ├── 02_password_hash.cpp +│ │ ├── 03_session_refresh_logout.cpp +│ │ └── 04_token_issue.cpp +│ └── pdf/ +│ ├── 01_basic.cpp +│ ├── 02_text.cpp +│ ├── 03_table.cpp +│ ├── 04_drawing.cpp +│ ├── 05_metadata.cpp +│ ├── 06_make_text.cpp +│ └── 07_error_handling.cpp +├── tests/ +│ └── rix_tests.cpp +├── packages/ +│ ├── csv/ +│ │ ├── README.md +│ │ └── vix.json +│ ├── debug/ +│ │ ├── README.md +│ │ └── vix.json +│ ├── auth/ +│ │ ├── README.md +│ │ └── vix.json +│ └── pdf/ +│ ├── README.md +│ └── vix.json +├── CMakeLists.txt +├── vix.json +├── vix.lock +└── README.md +``` + +--- + +## Role of this repository + +This repository has two roles. + +First, it is the source package for the unified facade: + +```txt +@rix/rix +``` + +Second, it is the official human-readable index of Rix packages: + +```txt +packages/csv +packages/debug +packages/auth +packages/pdf +``` + +The real source code of each independent package lives in its own repository: + +```txt +https://github.com/rixcpp/csv +https://github.com/rixcpp/debug +https://github.com/rixcpp/auth +https://github.com/rixcpp/pdf +``` + +--- + +## Build + +```bash +vix install +vix build --build-target all -v +``` + +--- + +## Run examples + +Because this repository contains multiple examples, run one explicitly: + +```bash +vix run rix_basic +vix run rix_csv_example +vix run rix_debug_example + +vix run rix_example_auth_01_memory_register_login +vix run rix_example_auth_02_password_hash +vix run rix_example_auth_03_session_refresh_logout +vix run rix_example_auth_04_token_issue + +vix run rix_pdf_01_basic +vix run rix_pdf_02_text +vix run rix_pdf_03_table +vix run rix_pdf_04_drawing +vix run rix_pdf_05_metadata +vix run rix_pdf_06_make_text +vix run rix_pdf_07_error_handling +``` + +--- + +## Tests + +```bash +vix tests +``` + +--- + +## Rix roadmap + +Rix should not duplicate Vix Core modules. + +If a feature already exists in Vix Core, Rix may provide a higher-level application library around it, but not a duplicate. + +```txt +Vix = core runtime and primitives +Rix = optional userland libraries +``` + +Existing Vix Core modules such as `json`, `fs`, `path`, `time`, `env`, `crypto`, `db`, `orm`, `kv`, `net`, `process`, `threadpool`, `validation`, `middleware`, `websocket`, `template`, `log`, `error`, `async`, `cache`, `io`, and `os` should not be copied directly into Rix. + +Rix packages should focus on application-level libraries built on top of Vix. + +### Current direction + +```txt +@rix/debug +@rix/csv +@rix/auth +@rix/pdf +@rix/table +@rix/config +@rix/rix +``` + +### Production app packages + +```txt +@rix/password +@rix/session +@rix/token +@rix/repository +@rix/migration +@rix/api +@rix/form +@rix/upload +@rix/rate-limit +``` + +### Reliability packages + +```txt +@rix/health +@rix/status +@rix/metrics +@rix/audit +@rix/events +@rix/retry +@rix/backoff +@rix/idempotency +@rix/outbox +``` + +### Developer tooling packages + +```txt +@rix/console +@rix/progress +@rix/bench +@rix/test +@rix/mock +@rix/fake +``` + +--- + +## Package rules + +Every Rix package should follow these principles: + +- small public API +- clear documentation +- simple examples +- independent installation +- stable include path +- stable namespace +- no hidden heavy dependency +- usable without the unified facade +- compatible with the unified facade when installed + +--- + +## Naming convention + +Package name: + +```txt +@rix/name +``` + +Include path: + +```cpp +#include +``` + +Namespace: + +```cpp +rixlib::name +``` + +Facade access: + +```cpp +rix.name +``` + +Examples: + +```txt +Package : @rix/csv +Header : +Namespace: rixlib::csv +Facade : rix.csv +``` + +```txt +Package : @rix/auth +Header : +Namespace: rixlib::auth +Facade : rix.auth +``` + +```txt +Package : @rix/pdf +Header : +Namespace: rixlib::pdf +Facade : rix.pdf +``` + +--- + +## Main rule + +```txt +If it is a primitive, it belongs in Vix. +If it is an application helper, it belongs in Rix. +If it is a complete reliability framework, it belongs in Cnerium. +``` + +And: + +```txt +One application problem = one small Rix package. +Many Rix packages = one optional rix.* facade. +``` + +--- + +## License + +MIT diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..b821f50 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,29 @@ +add_executable(rix_basic + basic.cpp +) + +target_link_libraries(rix_basic + PRIVATE + rix::rix +) + +add_executable(rix_csv_example + csv.cpp +) + +target_link_libraries(rix_csv_example + PRIVATE + rix::rix +) + +add_executable(rix_debug_example + debug.cpp +) + +target_link_libraries(rix_debug_example + PRIVATE + rix::rix +) + +add_subdirectory(auth) +add_subdirectory(pdf) diff --git a/examples/auth/01_memory_register_login.cpp b/examples/auth/01_memory_register_login.cpp new file mode 100644 index 0000000..2ebcce2 --- /dev/null +++ b/examples/auth/01_memory_register_login.cpp @@ -0,0 +1,66 @@ +/** + * + * @file 01_memory_register_login.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + rix.debug.print("== rix/auth memory register and login =="); + + auto auth = rix.auth.memory(); + auto registered = auth.register_user({"ada@example.com", "correct-password"}); + + if (registered.failed()) + { + const auto &error = registered.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + rix.debug.print("registered user"); + rix.debug.print("id:", registered.value().id()); + rix.debug.print("email:", registered.value().email()); + + auto login = auth.login({"ada@example.com", "correct-password"}); + + if (login.failed()) + { + const auto &error = login.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + rix.debug.print("----------------------------------------"); + rix.debug.print("login successful"); + rix.debug.print("user:", login.value().user.email()); + rix.debug.print("session:", login.value().session.id()); + rix.debug.print("token issuer:", login.value().token.issuer()); + + auto session = auth.authenticate_session(login.value().session.id()); + + if (session.failed()) + { + const auto &error = session.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + rix.debug.print("----------------------------------------"); + rix.debug.print("OK:", "session authenticated"); + rix.debug.print("session user id:", session.value().user_id()); + + return 0; +} diff --git a/examples/auth/02_password_hash.cpp b/examples/auth/02_password_hash.cpp new file mode 100644 index 0000000..7226899 --- /dev/null +++ b/examples/auth/02_password_hash.cpp @@ -0,0 +1,48 @@ +/** + * + * @file 02_password_hash.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + rix.debug.print("== rix/auth password hashing =="); + + const auto password = "correct-password"; + auto hashed = rix.auth.password.hash(password); + + if (hashed.failed()) + { + const auto &error = hashed.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + const bool valid = rix.auth.password.verify(password, hashed.value()); + const bool invalid = rix.auth.password.verify("wrong-password", hashed.value()); + + rix.debug.print("hash:", hashed.value()); + rix.debug.print("valid password:", valid ? "yes" : "no"); + rix.debug.print("wrong password:", invalid ? "yes" : "no"); + + if (!valid || invalid) + { + rix.debug.eprint("ERROR:", "password verification failed"); + return 1; + } + + rix.debug.print("OK:", "password verification successful"); + return 0; +} diff --git a/examples/auth/03_session_refresh_logout.cpp b/examples/auth/03_session_refresh_logout.cpp new file mode 100644 index 0000000..7c8b104 --- /dev/null +++ b/examples/auth/03_session_refresh_logout.cpp @@ -0,0 +1,83 @@ +/** + * + * @file 03_session_refresh_logout.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + rix.debug.print("== rix/auth session refresh and logout =="); + + auto auth = rix.auth.memory(); + auto registered = auth.register_user({"grace@example.com", "correct-password"}); + + if (registered.failed()) + { + const auto &error = registered.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + auto login = auth.login({"grace@example.com", "correct-password"}); + + if (login.failed()) + { + const auto &error = login.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + const auto session_id = login.value().session.id(); + + rix.debug.print("created session:", session_id); + rix.debug.print("expires at:", login.value().session.expires_at()); + + auto refreshed = auth.refresh_session(session_id); + + if (refreshed.failed()) + { + const auto &error = refreshed.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + rix.debug.print("----------------------------------------"); + rix.debug.print("OK:", "session refreshed"); + rix.debug.print("new expires at:", refreshed.value().expires_at()); + + auto logout = auth.logout(session_id); + + if (logout.failed()) + { + const auto &error = logout.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + rix.debug.print("----------------------------------------"); + rix.debug.print("OK:", "logout successful"); + + auto after_logout = auth.authenticate_session(session_id); + + if (after_logout.ok()) + { + rix.debug.eprint("ERROR:", "session should not be valid after logout"); + return 1; + } + + rix.debug.print("session rejected after logout"); + + return 0; +} diff --git a/examples/auth/04_token_issue.cpp b/examples/auth/04_token_issue.cpp new file mode 100644 index 0000000..c6a7ff5 --- /dev/null +++ b/examples/auth/04_token_issue.cpp @@ -0,0 +1,49 @@ +/** + * + * @file 04_token_issue.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + rix.debug.print("== rix/auth token issue =="); + + auto auth = rix.auth.memory(); + auto registered = auth.register_user({"linus@example.com", "correct-password"}); + + if (registered.failed()) + { + const auto &error = registered.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + auto token = auth.issue_token(registered.value().id()); + + if (token.failed()) + { + const auto &error = token.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } + + rix.debug.print("OK:", "token issued"); + rix.debug.print("user id:", token.value().user_id()); + rix.debug.print("issuer:", token.value().issuer()); + rix.debug.print("issued at:", token.value().issued_at()); + rix.debug.print("expires at:", token.value().expires_at()); + + return 0; +} diff --git a/examples/auth/CMakeLists.txt b/examples/auth/CMakeLists.txt new file mode 100644 index 0000000..c1116f7 --- /dev/null +++ b/examples/auth/CMakeLists.txt @@ -0,0 +1,31 @@ +set(RIX_AUTH_EXAMPLES + 01_memory_register_login + 02_password_hash + 03_session_refresh_logout + 04_token_issue +) + +foreach(example_name IN LISTS RIX_AUTH_EXAMPLES) + add_executable(rix_example_auth_${example_name} + ${example_name}.cpp + ) + + target_compile_features(rix_example_auth_${example_name} + PRIVATE + cxx_std_20 + ) + + target_link_libraries(rix_example_auth_${example_name} + PRIVATE + rix::rix + ) + + if(NOT MSVC) + target_compile_options(rix_example_auth_${example_name} + PRIVATE + -Wall + -Wextra + -Wpedantic + ) + endif() +endforeach() diff --git a/examples/basic.cpp b/examples/basic.cpp new file mode 100644 index 0000000..9f1b8b2 --- /dev/null +++ b/examples/basic.cpp @@ -0,0 +1,14 @@ +/** + * @file basic.cpp + * @brief Basic example for the unified Rix facade. + */ + +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + rix.debug.log("facade is ready"); + + return 0; +} diff --git a/examples/csv.cpp b/examples/csv.cpp new file mode 100644 index 0000000..853aa20 --- /dev/null +++ b/examples/csv.cpp @@ -0,0 +1,47 @@ +/** + * @file csv.cpp + * @brief CSV example for the unified Rix facade. + */ + +#include + +#include +#include + +namespace +{ + void print_table(const rixlib::csv::Table &table) + { + for (const auto &row : table) + { + std::ostringstream line; + + for (std::size_t i = 0; i < row.size(); ++i) + { + if (i > 0) + { + line << " "; + } + + line << row[i]; + } + + rix.debug.print(line.str()); + } + } +} + +int main() +{ + const std::string input = + "name,language\n" + "Ada,C++\n" + "Gaspard,Vix\n"; + + const auto table = rix.csv.parse(input); + + rix.debug.log("loaded {} rows", table.size()); + print_table(table); + + return 0; +} diff --git a/examples/debug.cpp b/examples/debug.cpp new file mode 100644 index 0000000..37aa164 --- /dev/null +++ b/examples/debug.cpp @@ -0,0 +1,20 @@ +/** + * @file debug.cpp + * @brief Debug example for the unified Rix facade. + */ + +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + + const std::string package = rix.debug.format("Package: {}", "rix/rix"); + + rix.debug.print(package); + rix.debug.log("loaded {} debug APIs", 4); + rix.debug.log.warn("this is a warning: {}", "slow path"); + rix.debug.inspect(package); + + return 0; +} diff --git a/examples/pdf/01_basic.cpp b/examples/pdf/01_basic.cpp new file mode 100644 index 0000000..c7faea2 --- /dev/null +++ b/examples/pdf/01_basic.cpp @@ -0,0 +1,49 @@ +/** + * + * @file 01_basic.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto doc = rix.pdf.document(); + + auto &page = doc.add_page(); + + page.text( + page.x_left(), + page.y_top(), + "Hello from rix.pdf"); + + page.text( + page.x_left(), + page.y_top() - 30.0F, + "This PDF was generated through the unified Rix facade."); + + auto saved = rix.pdf.save(doc, "rix_pdf_basic.pdf"); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_basic.pdf"); + return 0; +} diff --git a/examples/pdf/02_text.cpp b/examples/pdf/02_text.cpp new file mode 100644 index 0000000..ef1accd --- /dev/null +++ b/examples/pdf/02_text.cpp @@ -0,0 +1,72 @@ +/** + * + * @file 02_text.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto doc = rix.pdf.document(); + + doc.set_title("Rix PDF Text Example"); + doc.set_author("Rix"); + + auto &page = doc.add_page(); + + auto y = page.y_top(); + + y = page.heading( + page.x_left(), + y, + "Rix PDF", + 1); + + y -= 10.0F; + + y = page.paragraph( + page.x_left(), + y, + page.content_width(), + "Rix gives Vix C++ projects a unified userland facade. " + "The PDF module keeps the public API simple while the writer internals stay hidden."); + + y -= 20.0F; + + page.paragraph( + page.x_left(), + y, + page.content_width(), + "This paragraph is centered to show text alignment.", + rixlib::pdf::Align::Center, + rixlib::pdf::TextStyle{ + rixlib::pdf::Font::Helvetica, + 12.0F, + rixlib::pdf::Color::blue_color()}); + + auto saved = rix.pdf.save(doc, "rix_pdf_text.pdf"); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_text.pdf"); + return 0; +} diff --git a/examples/pdf/03_table.cpp b/examples/pdf/03_table.cpp new file mode 100644 index 0000000..ddddace --- /dev/null +++ b/examples/pdf/03_table.cpp @@ -0,0 +1,76 @@ +/** + * + * @file 03_table.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto doc = rix.pdf.document(); + + doc.set_title("Rix PDF Table Example"); + + auto &page = doc.add_page(); + + auto y = page.heading( + page.x_left(), + page.y_top(), + "Project table", + 1); + + y -= 20.0F; + + rixlib::pdf::Table table; + + table.set_column_widths({160.0F, + 160.0F, + 160.0F}); + + table.add_header({"Name", + "Language", + "Project"}); + + table.add_row({"Ada", + "C++", + "Rix"}); + + table.add_row({"Gaspard", + "C++", + "Vix.cpp"}); + + table.add_row({"Grace", + "Systems", + "PDF"}); + + page.table( + page.x_left(), + y, + table); + + auto saved = rix.pdf.save(doc, "rix_pdf_table.pdf"); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_table.pdf"); + return 0; +} diff --git a/examples/pdf/04_drawing.cpp b/examples/pdf/04_drawing.cpp new file mode 100644 index 0000000..0f3b6ec --- /dev/null +++ b/examples/pdf/04_drawing.cpp @@ -0,0 +1,104 @@ +/** + * + * @file 04_drawing.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto doc = rix.pdf.document(); + + doc.set_title("Rix PDF Drawing Example"); + + auto &page = doc.add_page(); + + auto y = page.heading( + page.x_left(), + page.y_top(), + "Drawing primitives", + 1); + + y -= 20.0F; + + page.line( + page.x_left(), + y, + page.x_right(), + y, + 1.5F, + rixlib::pdf::Color::blue_color()); + + y -= 70.0F; + + page.rect( + page.x_left(), + y, + 140.0F, + 50.0F); + + page.fill_rect( + page.x_left() + 170.0F, + y, + 140.0F, + 50.0F, + rixlib::pdf::Color::light_gray()); + + page.fill_stroke_rect( + page.x_left() + 340.0F, + y, + 140.0F, + 50.0F, + rixlib::pdf::Color::white(), + rixlib::pdf::Color::black()); + + y -= 100.0F; + + page.circle( + page.x_left() + 70.0F, + y, + 35.0F, + 1.0F, + rixlib::pdf::Color::red_color()); + + page.circle( + page.x_left() + 230.0F, + y, + 35.0F, + 1.0F, + rixlib::pdf::Color::green_color(), + true); + + page.circle( + page.x_left() + 390.0F, + y, + 35.0F, + 1.0F, + rixlib::pdf::Color::blue_color()); + + auto saved = rix.pdf.save(doc, "rix_pdf_drawing.pdf"); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_drawing.pdf"); + return 0; +} diff --git a/examples/pdf/05_metadata.cpp b/examples/pdf/05_metadata.cpp new file mode 100644 index 0000000..6ad3bfb --- /dev/null +++ b/examples/pdf/05_metadata.cpp @@ -0,0 +1,58 @@ +/** + * + * @file 05_metadata.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto doc = rix.pdf.document(); + + doc.set_title("Rix PDF Metadata Example") + .set_author("Rix") + .set_subject("PDF metadata") + .set_keywords("rix,pdf,vix,cpp"); + + auto &page = doc.add_page(); + + auto y = page.heading( + page.x_left(), + page.y_top(), + "Metadata", + 1); + + y -= 10.0F; + + page.paragraph( + page.x_left(), + y, + page.content_width(), + "This PDF includes title, author, subject, creator, and keyword metadata."); + + auto saved = rix.pdf.save(doc, "rix_pdf_metadata.pdf"); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_metadata.pdf"); + return 0; +} diff --git a/examples/pdf/06_make_text.cpp b/examples/pdf/06_make_text.cpp new file mode 100644 index 0000000..b96393a --- /dev/null +++ b/examples/pdf/06_make_text.cpp @@ -0,0 +1,38 @@ +/** + * + * @file 06_make_text.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto saved = rix.pdf.make_text( + "rix_pdf_make_text.pdf", + "This file was generated with the high-level rix.pdf.make_text helper.", + "Rix PDF"); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 1; + } + + rix.debug.print("created:", "rix_pdf_make_text.pdf"); + return 0; +} diff --git a/examples/pdf/07_error_handling.cpp b/examples/pdf/07_error_handling.cpp new file mode 100644 index 0000000..8d6e8d8 --- /dev/null +++ b/examples/pdf/07_error_handling.cpp @@ -0,0 +1,43 @@ +/** + * + * @file 07_error_handling.cpp + * @author Gaspard Kirira + * + * Copyright 2026, Gaspard Kirira. + * All rights reserved. + * https://github.com/rixcpp/rix + * + * Use of this source code is governed by a MIT license + * that can be found in the License file. + * + * Rix + * + */ + +#include + +int main() +{ + auto doc = rix.pdf.document(); + + auto &page = doc.add_page(); + + page.text( + page.x_left(), + page.y_top(), + "This example intentionally uses an invalid output path."); + + auto saved = rix.pdf.save(doc, ""); + + if (saved.failed()) + { + rix.debug.eprint( + "pdf error:", + rix.pdf.error.to_string(saved.error()), + saved.error().message()); + + return 0; + } + + return 1; +} diff --git a/examples/pdf/CMakeLists.txt b/examples/pdf/CMakeLists.txt new file mode 100644 index 0000000..dc5880b --- /dev/null +++ b/examples/pdf/CMakeLists.txt @@ -0,0 +1,62 @@ +add_executable(rix_pdf_01_basic + 01_basic.cpp +) + +target_link_libraries(rix_pdf_01_basic + PRIVATE + rix::rix +) + +add_executable(rix_pdf_02_text + 02_text.cpp +) + +target_link_libraries(rix_pdf_02_text + PRIVATE + rix::rix +) + +add_executable(rix_pdf_03_table + 03_table.cpp +) + +target_link_libraries(rix_pdf_03_table + PRIVATE + rix::rix +) + +add_executable(rix_pdf_04_drawing + 04_drawing.cpp +) + +target_link_libraries(rix_pdf_04_drawing + PRIVATE + rix::rix +) + +add_executable(rix_pdf_05_metadata + 05_metadata.cpp +) + +target_link_libraries(rix_pdf_05_metadata + PRIVATE + rix::rix +) + +add_executable(rix_pdf_06_make_text + 06_make_text.cpp +) + +target_link_libraries(rix_pdf_06_make_text + PRIVATE + rix::rix +) + +add_executable(rix_pdf_07_error_handling + 07_error_handling.cpp +) + +target_link_libraries(rix_pdf_07_error_handling + PRIVATE + rix::rix +) diff --git a/include/rix.hpp b/include/rix.hpp new file mode 100644 index 0000000..9e6b1e8 --- /dev/null +++ b/include/rix.hpp @@ -0,0 +1,144 @@ +/** + * @file rix.hpp + * @brief Unified Rix facade. + * + * This header exposes the global `rix` object. + * + * By default, it keeps the historical behavior and enables all available + * mounted Rix packages. + * + * For lighter builds, define only the packages you want before including + * this header: + * + * @code + * #define RIX_ENABLE_CSV + * #include + * + * auto table = rix.csv.parse("name,lang\nAda,C++\n"); + * @endcode + * + * Available feature macros: + * + * - RIX_ENABLE_CSV + * - RIX_ENABLE_DEBUG + * - RIX_ENABLE_AUTH + * - RIX_ENABLE_PDF + * + * To keep backward compatibility, if no RIX_ENABLE_* macro is defined, + * all current facade modules are enabled. + * + * @author Gaspard Kirira + */ + +#ifndef RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED +#define RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED + +/* + * Backward compatibility mode. + * + * Existing code that does: + * + * #include + * + * should keep working exactly as before. + * + * If the user defines at least one RIX_ENABLE_* macro, only those selected + * modules are mounted into the facade. + */ +#if !defined(RIX_ENABLE_CSV) && \ + !defined(RIX_ENABLE_DEBUG) && \ + !defined(RIX_ENABLE_AUTH) && \ + !defined(RIX_ENABLE_PDF) + +#define RIX_ENABLE_CSV +#define RIX_ENABLE_DEBUG +#define RIX_ENABLE_AUTH +#define RIX_ENABLE_PDF + +#endif + +#ifdef RIX_ENABLE_CSV +#include +#endif + +#ifdef RIX_ENABLE_DEBUG +#include +#endif + +#ifdef RIX_ENABLE_AUTH +#include +#endif + +#ifdef RIX_ENABLE_PDF +#include +#endif + +namespace rixlib +{ + /** + * @brief Unified Rix API facade. + * + * This class groups selected independent Rix components into one object. + * + * The mounted components depend on the RIX_ENABLE_* macros defined before + * including . + */ + class Rix + { + public: +#ifdef RIX_ENABLE_CSV + /** + * @brief CSV reader and writer component. + */ + rixlib::csv::Csv csv{}; +#endif + +#ifdef RIX_ENABLE_DEBUG + /** + * @brief Debug printing, formatting, logging, and inspection component. + */ + rixlib::debug::Debug debug{}; +#endif + +#ifdef RIX_ENABLE_AUTH + /** + * @brief Authentication component. + */ + rixlib::auth::AuthModule auth{}; +#endif + +#ifdef RIX_ENABLE_PDF + /** + * @brief PDF generation and document utilities component. + */ + rixlib::pdf::PdfModule pdf{}; +#endif + }; +} + +/** + * @brief Global Rix facade object. + * + * Default usage: + * + * @code + * #include + * + * rix.csv.parse(...); + * rix.debug.print(...); + * rix.auth.memory(); + * rix.pdf.document(); + * @endcode + * + * Lightweight usage: + * + * @code + * #define RIX_ENABLE_CSV + * #include + * + * rix.csv.parse(...); + * @endcode + */ +inline constexpr rixlib::Rix rix{}; + +#endif // RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED diff --git a/modules/.gitkeep b/modules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 0000000..88edf89 --- /dev/null +++ b/packages/auth/README.md @@ -0,0 +1,621 @@ +# rix/auth + +`rix/auth` is the official authentication library for Rix and Vix.cpp applications. + +It provides a small production-oriented authentication layer for C++ apps: + +- user registration +- login +- password hashing +- server-side sessions +- short-lived auth tokens +- logout +- session validation +- memory stores for tests and small apps +- database stores for durable production usage +- explicit `AuthResult` and `AuthStatus` error handling + +`rix/auth` is built to use Vix modules instead of reinventing low-level foundations: + +- `vix::crypto` for secure random ids and password hashing helpers +- `vix::validation` for input validation +- `vix::time` for timestamps +- `vix::db` for persistent database-backed stores + +## Install + +Add the package to your Vix project: + +```bash +vix add rix/auth +``` + +Then include it: + +```cpp +#include +``` + +## Basic usage + +```cpp +#include +#include +#include +#include + +#include +#include + +int main() +{ + rixlib::auth::MemoryUserStore users; + rixlib::auth::MemorySessionStore sessions; + + auto config = rixlib::auth::AuthConfig::development(); + + rixlib::auth::Auth auth{ + users, + sessions, + config}; + + auto registered_user = auth.register_user( + rixlib::auth::RegisterRequest{ + "ada@example.com", + "correct-password"}); + + if (registered_user.failed()) + { + std::cerr << registered_user.error().message() << '\n'; + return 1; + } + + auto login = auth.login( + rixlib::auth::LoginRequest{ + "ada@example.com", + "correct-password"}); + + if (login.failed()) + { + std::cerr << login.error().message() << '\n'; + return 1; + } + + std::cout << "user: " << login.value().user.email() << '\n'; + std::cout << "session: " << login.value().session.id() << '\n'; + std::cout << "token issuer: " << login.value().token.issuer() << '\n'; + + return 0; +} +``` + +Run: + +```bash +vix run examples/basic.cpp +``` + +## Public API + +### Main facade + +```cpp +#include +``` + +The main class is: + +```cpp +rixlib::auth::Auth +``` + +It exposes: + +- `register_user(...)` +- `login(...)` +- `logout(...)` +- `logout_user(...)` +- `authenticate_session(...)` +- `refresh_session(...)` +- `issue_token(...)` +- `config()` +- `password_hasher()` + +## Configuration + +Use `AuthConfig` to configure authentication behavior. + +```cpp +auto config = rixlib::auth::AuthConfig::production(); + +config.set_min_password_length(12); +config.set_session_ttl_seconds(60 * 60 * 24 * 7); +config.set_token_ttl_seconds(60 * 10); +config.set_issuer("my-app"); +config.set_require_email_verification(true); +config.set_rotate_sessions(true); +``` + +### Development config + +```cpp +auto config = rixlib::auth::AuthConfig::development(); +``` + +Development defaults are useful for local apps, examples, and tests. + +### Production config + +```cpp +auto config = rixlib::auth::AuthConfig::production(); +``` + +Production defaults are stricter and should be preferred for real applications. + +## Register a user + +```cpp +auto result = auth.register_user( + rixlib::auth::RegisterRequest{ + "ada@example.com", + "correct-password"}); + +if (result.failed()) +{ + std::cerr << result.error().message() << '\n'; + return 1; +} + +const auto &user = result.value(); +``` + +Registration validates the email, validates the password policy, hashes the password, creates the user, and stores it through the configured `UserStore`. + +## Login + +```cpp +auto result = auth.login( + rixlib::auth::LoginRequest{ + "ada@example.com", + "correct-password"}); + +if (result.failed()) +{ + std::cerr << result.error().message() << '\n'; + return 1; +} + +const auto &login = result.value(); + +std::cout << login.user.email() << '\n'; +std::cout << login.session.id() << '\n'; +std::cout << login.token.value() << '\n'; +``` + +A successful login returns: + +```cpp +rixlib::auth::LoginResult +``` + +It contains: + +```cpp +User user; +Session session; +Token token; +``` + +## Authenticate a session + +```cpp +auto session = auth.authenticate_session(session_id); + +if (session.failed()) +{ + std::cerr << session.error().message() << '\n'; + return 1; +} + +std::cout << "session user: " + << session.value().user_id() + << '\n'; +``` + +A usable session must be: + +- valid +- not revoked +- not expired + +## Refresh a session + +```cpp +auto refreshed = auth.refresh_session(session_id); + +if (refreshed.failed()) +{ + std::cerr << refreshed.error().message() << '\n'; + return 1; +} +``` + +Refreshing updates the session expiration and `last_seen_at`. + +## Logout + +```cpp +auto status = auth.logout(session_id); + +if (status.failed()) +{ + std::cerr << status.error().message() << '\n'; + return 1; +} +``` + +Logout revokes the session instead of deleting it. + +## Logout all sessions for a user + +```cpp +auto status = auth.logout_user(user_id); + +if (status.failed()) +{ + std::cerr << status.error().message() << '\n'; + return 1; +} +``` + +This is useful after password changes, account compromise, or admin actions. + +## Issue a token + +```cpp +auto token = auth.issue_token(user_id); + +if (token.failed()) +{ + std::cerr << token.error().message() << '\n'; + return 1; +} +``` + +Tokens are short-lived values attached to a user id and issuer. + +## Error handling + +`rix/auth` does not throw for normal authentication failures. + +It uses: + +- `AuthResult` +- `AuthStatus` +- `AuthError` +- `AuthErrorCode` + +Example: + +```cpp +auto result = auth.login( + rixlib::auth::LoginRequest{ + "ada@example.com", + "wrong-password"}); + +if (result.failed()) +{ + if (result.error().code() == rixlib::auth::AuthErrorCode::InvalidCredentials) + { + std::cerr << "invalid credentials\n"; + } +} +``` + +### Error codes + +Common error codes include: + +- `None` +- `InvalidInput` +- `InvalidEmail` +- `InvalidPassword` +- `UserNotFound` +- `UserAlreadyExists` +- `InvalidCredentials` +- `InvalidSession` +- `SessionExpired` +- `InvalidToken` +- `TokenExpired` +- `InvalidState` +- `StoreError` +- `CryptoError` +- `Unknown` + +## Stores + +`rix/auth` separates authentication logic from storage. + +The main interfaces are: + +- `UserStore` +- `SessionStore` + +Available store implementations: + +- `MemoryUserStore` +- `MemorySessionStore` +- `DbUserStore` +- `DbSessionStore` + +## Memory stores + +Memory stores are useful for tests, examples, and small temporary apps. + +```cpp +#include +#include + +rixlib::auth::MemoryUserStore users; +rixlib::auth::MemorySessionStore sessions; + +rixlib::auth::Auth auth{ + users, + sessions, + rixlib::auth::AuthConfig::development()}; +``` + +Memory stores do not persist data after the process exits. + +## Database stores + +Database stores are intended for durable applications. + +```cpp +#include +#include +#include +#include + +int main() +{ + auto database = vix::db::Database::sqlite("data/auth.db"); + + rixlib::auth::DbUserStore users{database}; + rixlib::auth::DbSessionStore sessions{database}; + + rixlib::auth::Auth auth{ + users, + sessions, + rixlib::auth::AuthConfig::production()}; + + return 0; +} +``` + +By default, database stores create their required tables automatically. + +### Database tables + +`DbUserStore` uses: + +```text +rix_auth_users +``` + +`DbSessionStore` uses: + +```text +rix_auth_sessions +``` + +The stores create the schema automatically when constructed with `create_schema = true`. + +```cpp +rixlib::auth::DbUserStore users{database, true}; +rixlib::auth::DbSessionStore sessions{database, true}; +``` + +## Password hashing + +Use `Auth` for normal application code. + +```cpp +auto result = auth.register_user(...); +``` + +`PasswordHasher` is available for lower-level usage and tests: + +```cpp +rixlib::auth::PasswordHasher hasher; + +auto hash = hasher.hash("correct-password"); + +if (hash.ok()) +{ + bool valid = hasher.verify("correct-password", hash.value()); +} +``` + +Password hashes are encoded and must never be returned to clients or logged. + +## User model + +```cpp +rixlib::auth::User +``` + +Fields include: + +- id +- email +- password hash +- email verification state +- active state +- created timestamp +- updated timestamp + +Useful methods: + +- `valid()` +- `has_id(...)` +- `has_email(...)` +- `active()` +- `email_verified()` + +## Session model + +```cpp +rixlib::auth::Session +``` + +Fields include: + +- id +- user id +- created timestamp +- expiration timestamp +- last seen timestamp +- revoked state + +Useful methods: + +- `valid()` +- `belongs_to(...)` +- `expired(...)` +- `usable(...)` +- `refreshable(...)` +- `refresh(...)` +- `revoke()` + +## Token model + +```cpp +rixlib::auth::Token +``` + +Fields include: + +- value +- user id +- issuer +- issued timestamp +- expiration timestamp +- revoked state + +Useful methods: + +- `valid()` +- `belongs_to(...)` +- `matches(...)` +- `expired(...)` +- `usable(...)` +- `revoke()` + +## Testing + +Build and run tests with Vix: + +```bash +vix tests +``` + +The test suite covers: + +- users +- sessions +- tokens +- password hashing +- memory user store +- memory session store +- auth registration +- login +- logout +- session authentication +- token creation + +## Project structure + +```text +include/rix/auth/ +├── Auth.hpp +├── AuthConfig.hpp +├── AuthError.hpp +├── AuthResult.hpp +├── PasswordHasher.hpp +├── Session.hpp +├── SessionStore.hpp +├── Token.hpp +├── User.hpp +├── UserStore.hpp +├── Version.hpp +└── stores/ + ├── DbSessionStore.hpp + ├── DbUserStore.hpp + ├── MemorySessionStore.hpp + └── MemoryUserStore.hpp + +src/ +├── Auth.cpp +├── AuthConfig.cpp +├── AuthError.cpp +├── PasswordHasher.cpp +├── Session.cpp +├── SessionStore.cpp +├── Token.cpp +├── User.cpp +├── UserStore.cpp +├── Version.cpp +└── stores/ + ├── DbSessionStore.cpp + ├── DbUserStore.cpp + ├── MemorySessionStore.cpp + └── MemoryUserStore.cpp + +tests/ +├── AuthTests.cpp +├── MemorySessionStoreTests.cpp +├── MemoryUserStoreTests.cpp +├── PasswordHasherTests.cpp +├── SessionTests.cpp +├── TokenTests.cpp +└── UserTests.cpp + +examples/ +└── basic.cpp +``` + +## Security notes + +Use `AuthConfig::production()` for real applications. + +Do not log: + +- plain-text passwords +- password hashes +- session ids +- token values + +Use durable database stores when sessions must survive restart. + +Use HTTPS when sending sessions or tokens over the network. + +Keep password hashing and token generation inside `rix/auth` instead of implementing them in application code. + +## Status + +Current version: + +```cpp +rixlib::auth::version() +``` + +Current package version: + +```text +0.1.0 +``` + +## License + +MIT diff --git a/packages/auth/vix.json b/packages/auth/vix.json new file mode 100644 index 0000000..15ddf1c --- /dev/null +++ b/packages/auth/vix.json @@ -0,0 +1,31 @@ +{ + "name": "auth", + "namespace": "rix", + "version": "0.1.0", + "type": "library", + "include": "include", + "license": "MIT", + "description": "Ready-to-use authentication package for Vix C++ applications.", + "keywords": [ + "cpp", + "auth", + "authentication", + "users", + "sessions", + "tokens", + "password", + "rix", + "vix" + ], + "repository": "https://github.com/rixcpp/auth", + "authors": [ + { + "name": "Gaspard Kirira", + "github": "rixcpp" + } + ], + "cmake": { + "target": "rix::auth" + }, + "deps": [] +} diff --git a/packages/csv/README.md b/packages/csv/README.md new file mode 100644 index 0000000..0db591c --- /dev/null +++ b/packages/csv/README.md @@ -0,0 +1,401 @@ +# @rix/csv + +CSV parser and writer for Rix. + +`@rix/csv` is a small header-only C++ package for reading, writing, transforming, and inspecting CSV data. + +It can be used independently, or through the unified `@rix/rix` facade. + +## Installation + +```bash +vix add @rix/csv +vix install +``` + +## Basic usage + +```cpp +#include + +#include +#include + +int main() +{ + const std::string input = + "name,language\n" + "Ada,C++\n" + "Gaspard,Vix\n"; + + const rixlib::csv::Table table = rixlib::csv::parse(input); + + std::cout << "rows: " << table.size() << '\n'; + + for (const auto &row : table) + { + for (std::size_t i = 0; i < row.size(); ++i) + { + if (i > 0) + { + std::cout << ' '; + } + + std::cout << row[i]; + } + + std::cout << '\n'; + } + + return 0; +} +``` + +Output: + +```txt +rows: 3 +name language +Ada C++ +Gaspard Vix +``` + +## Parse CSV + +```cpp +const std::string input = + "name,language\n" + "Ada,C++\n"; + +const rixlib::csv::Table table = rixlib::csv::parse(input); +``` + +A table is represented as: + +```cpp +using Row = std::vector; +using Table = std::vector; +``` + +So this CSV: + +```txt +name,language +Ada,C++ +``` + +becomes: + +```cpp +{ + {"name", "language"}, + {"Ada", "C++"} +} +``` + +## Write CSV + +```cpp +const rixlib::csv::Table table = { + {"name", "language"}, + {"Ada", "C++"}, + {"Gaspard", "Vix"}, +}; + +const std::string output = rixlib::csv::write(table); +``` + +Output: + +```txt +name,language +Ada,C++ +Gaspard,Vix +``` + +## Write a single row + +```cpp +const rixlib::csv::Row row = {"Ada", "C++", "expert"}; + +const std::string line = rixlib::csv::write_row(row); +``` + +Output: + +```txt +Ada,C++,expert +``` + +## Parsing options + +```cpp +rixlib::csv::Options options{}; +options.skip_empty_lines = true; +options.trim_whitespace = true; + +const auto table = rixlib::csv::parse(input, options); +``` + +Available options include: + +```cpp +options.separator = ','; +options.quote_char = '"'; +options.trim_whitespace = false; +options.skip_empty_lines = false; +options.skip_comments = false; +options.comment_char = '#'; +options.max_field_size = 0; +options.max_fields_per_row = 0; +``` + +You can also transform fields while parsing: + +```cpp +rixlib::csv::Options options{}; + +options.field_transformer = [](std::string &field) +{ + for (char &ch : field) + { + ch = static_cast(std::toupper(static_cast(ch))); + } +}; + +const auto table = rixlib::csv::parse(input, options); +``` + +And filter rows: + +```cpp +rixlib::csv::Options options{}; + +options.row_filter = [](const rixlib::csv::Row &row) +{ + return row.size() == 2; +}; + +const auto table = rixlib::csv::parse(input, options); +``` + +## Writing options + +```cpp +rixlib::csv::WriteOptions options{}; +options.separator = ';'; +options.line_ending = "\r\n"; +options.always_quote = true; + +const std::string output = rixlib::csv::write(table, options); +``` + +Available write options include: + +```cpp +options.separator = ','; +options.quote_char = '"'; +options.line_ending = "\n"; +options.always_quote = false; +options.trim_before_write = false; +``` + +## Load and save files + +```cpp +const rixlib::csv::Table table = rixlib::csv::load("data.csv"); + +rixlib::csv::save("output.csv", table); +``` + +## Stream support + +Parse from an input stream: + +```cpp +std::ifstream file("data.csv", std::ios::binary); + +const rixlib::csv::Table table = rixlib::csv::parse(file); +``` + +Write to an output stream: + +```cpp +std::ofstream file("output.csv", std::ios::binary); + +rixlib::csv::write_to(file, table); +``` + +## Dict-style reading + +The first row can be treated as a header. + +```cpp +const auto table = rixlib::csv::parse( + "name,language\n" + "Ada,C++\n" + "Gaspard,Vix\n"); + +rixlib::csv::DictReader reader{table}; + +for (const rixlib::csv::RowView row : reader) +{ + std::cout << row["name"] << " uses " << row["language"] << '\n'; +} +``` + +Output: + +```txt +Ada uses C++ +Gaspard uses Vix +``` + +## Select columns + +```cpp +const auto selected = rixlib::csv::select_columns(table, {"name"}); +``` + +## Read a column + +```cpp +const std::vector names = rixlib::csv::column(table, "name"); +``` + +## Filter rows + +```cpp +const auto filtered = rixlib::csv::filter_rows( + table, + [](const rixlib::csv::Row &row) + { + return row.size() >= 2 && row[1] == "C++"; + }); +``` + +## Transform fields + +```cpp +const auto transformed = rixlib::csv::transform_fields( + table, + [](std::string &field) + { + field = "[" + field + "]"; + }); +``` + +## Describe a table + +```cpp +std::cout << rixlib::csv::describe(table); +``` + +Example output: + +```txt +csv::Table: 3 row(s), 2 column(s) + [header] "name" | "language" + [1] "Ada" | "C++" + [2] "Gaspard" | "Vix" +``` + +## Dialect detection + +```cpp +const auto dialect = rixlib::csv::sniff(sample); + +rixlib::csv::Options options{}; +options.separator = dialect.separator; + +const auto table = rixlib::csv::parse(full_text, options); +``` + +## Streaming parser + +Use `StreamingParser` when you want to process rows without storing the full table in memory. + +```cpp +std::size_t count = 0; + +rixlib::csv::StreamingParser parser{ + [&](const rixlib::csv::Row &row) + { + ++count; + }}; + +parser.parse(input); + +std::cout << "rows: " << count << '\n'; +``` + +## Version + +```cpp +std::cout << rixlib::csv::version() << '\n'; +``` + +## Unified Rix facade + +When used through `@rix/rix`, the package is mounted under `rix.csv`. + +```cpp +#include + +int main() +{ + const auto table = rix.csv.parse("name,language\nAda,C++\n"); + + rix.debug.log("loaded {} rows", table.size()); + + return 0; +} +``` + +## Design + +`@rix/csv` is header-only and keeps the data model simple. + +```cpp +rixlib::csv::Row +rixlib::csv::Table +``` + +The package exposes free functions such as: + +```cpp +rixlib::csv::parse(...) +rixlib::csv::write(...) +rixlib::csv::write_row(...) +rixlib::csv::load(...) +rixlib::csv::save(...) +``` + +The unified facade package `@rix/rix` can mount these functions as: + +```cpp +rix.csv.parse(...) +rix.csv.write(...) +``` + +## Build + +```bash +vix build +``` + +## Run example + +```bash +vix run +``` + +## Tests + +```bash +vix tests +``` + +## License + +MIT diff --git a/packages/csv/vix.json b/packages/csv/vix.json new file mode 100644 index 0000000..b9fab32 --- /dev/null +++ b/packages/csv/vix.json @@ -0,0 +1,26 @@ +{ + "name": "csv", + "namespace": "rix", + "version": "0.3.0", + "type": "header-only", + "include": "include", + "license": "MIT", + "description": "Small CSV reader and writer for Vix C++ projects.", + "keywords": [ + "cpp", + "csv", + "rix", + "vix" + ], + "repository": "https://github.com/rixcpp/csv", + "authors": [ + { + "name": "Gaspard Kirira", + "github": "rixcpp" + } + ], + "cmake": { + "target": "rix::csv" + }, + "deps": [] +} diff --git a/packages/debug/README.md b/packages/debug/README.md new file mode 100644 index 0000000..8c4199e --- /dev/null +++ b/packages/debug/README.md @@ -0,0 +1,208 @@ +# @rix/debug + +Debug utilities for Rix. + +`@rix/debug` provides a small set of developer-focused tools for C++ projects: + +- formatted strings +- simple logging +- value inspection +- print helpers + +It can be used as an independent Rix package, or through the unified `@rix/rix` facade. + +## Installation + +```bash +vix add @rix/debug +vix install +``` + +## Basic usage + +```cpp +#include + +int main() +{ + rixlib::debug::Debug debug{}; + + debug.print("Hello", "Rix"); + + auto text = debug.format("Package: {}", "rix/debug"); + + debug.log("loaded {} rows", 3); + debug.log.warn("slow request: {}ms", 120); + + debug.inspect(text); + + return 0; +} +``` + +Output: + +```txt +Hello Rix +[debug] loaded 3 rows +[warn] slow request: 120ms +Package: rix/debug +``` + +## Formatting + +`debug.format` supports simple placeholder-based formatting. + +```cpp +auto message = debug.format("Hello {}", "Rix"); +auto result = debug.format("{0} + {0} = {1}", 2, 4); +auto escaped = debug.format("{{ value }} = {}", 42); +``` + +Supported placeholders: + +```txt +{} automatic argument indexing +{0} explicit positional indexing +{{ escaped opening brace +}} escaped closing brace +``` + +Format specifiers such as `{:>10}` or `{:.2f}` are intentionally not supported. + +## Logging + +```cpp +debug.log("loaded {} rows", 3); +debug.log.info("server started"); +debug.log.warn("slow request: {}ms", 120); +debug.log.error("failed: {}", "timeout"); +``` + +Output: + +```txt +[debug] loaded 3 rows +[info] server started +[warn] slow request: 120ms +[error] failed: timeout +``` + +## Printing + +`debug.print` is not a formatter. + +It prints values separated by spaces. + +```cpp +debug.print("Hello", "Rix"); +debug.print(1, 2, 3); +``` + +Output: + +```txt +Hello Rix +1 2 3 +``` + +For formatted strings, use `debug.format` or `debug.log`. + +```cpp +debug.print(debug.format("Hello {}", "Rix")); +``` + +## Inspection + +```cpp +debug.inspect(42); + +auto value = debug.inspect.to_string(true); + +bool ok = debug.inspect.check(42, 42); +``` + +Inspection is useful for debugging values, containers, optional values, variants, and other supported C++ types. + +## Independent API + +You can also use the free functions directly. + +```cpp +#include + +int main() +{ + rixlib::print("Hello", "Rix"); + + auto text = rixlib::format("Package: {}", "rix/debug"); + + rixlib::inspect(text); + + return 0; +} +``` + +## Unified Rix facade + +When used through `@rix/rix`, the debug package is mounted under `rix.debug`. + +```cpp +#include + +int main() +{ + rix.debug.print("Hello", "Rix"); + rix.debug.log("loaded {} rows", 3); + + return 0; +} +``` + +## Design + +`@rix/debug` is intentionally small and focused. + +It does not try to replace full logging frameworks or formatting libraries. Its role is to provide simple debugging primitives that are easy to use in Rix-based C++ projects. + +The package exposes two layers: + +```txt +rixlib::format(...) +rixlib::print(...) +rixlib::inspect(...) +``` + +and the object-style API: + +```txt +rixlib::debug::Debug +``` + +This keeps the package usable on its own while allowing the unified Rix facade to mount it cleanly as: + +```txt +rix.debug +``` + +## Build + +```bash +vix build +``` + +## Run example + +```bash +vix run +``` + +## Tests + +```bash +vix tests +``` + +## License + +MIT diff --git a/packages/debug/vix.json b/packages/debug/vix.json new file mode 100644 index 0000000..d6f2d54 --- /dev/null +++ b/packages/debug/vix.json @@ -0,0 +1,30 @@ +{ + "name": "debug", + "namespace": "rix", + "version": "0.2.0", + "type": "header-only", + "include": "include", + "license": "MIT", + "description": "Debug printing, formatting, logging, and inspection utilities for Rix.", + "keywords": [ + "cpp", + "debug", + "print", + "format", + "log", + "inspect", + "rix", + "vix" + ], + "repository": "https://github.com/rixcpp/debug", + "maintainers": [ + { + "name": "Gaspard kirira", + "github": "Gaspardkirira" + } + ], + "cmake": { + "target": "rix::debug" + }, + "deps": [] +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..bb739d8 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(rix_tests + rix_tests.cpp +) + +target_link_libraries(rix_tests + PRIVATE + rix::rix +) + +add_test( + NAME rix_tests + COMMAND rix_tests +) diff --git a/tests/rix_tests.cpp b/tests/rix_tests.cpp new file mode 100644 index 0000000..42a143f --- /dev/null +++ b/tests/rix_tests.cpp @@ -0,0 +1,169 @@ +/** + * @file rix_tests.cpp + * @brief Basic tests for the unified Rix facade. + * + * @author Gaspard Kirira + */ + +#include + +#include +#include +#include +#include + +namespace +{ + void expect_true(bool condition, const std::string &message) + { + if (!condition) + { + std::cerr << "FAILED: " << message << '\n'; + std::exit(1); + } + } + + void test_global_rix_csv_parse() + { + const std::string input = + "name,language\n" + "Ada,C++\n"; + + const auto table = rix.csv.parse(input); + + expect_true(table.size() == 2, "rix.csv.parse should return 2 rows"); + expect_true(table[0][0] == "name", "header first field should be name"); + expect_true(table[0][1] == "language", "header second field should be language"); + expect_true(table[1][0] == "Ada", "data first field should be Ada"); + expect_true(table[1][1] == "C++", "data second field should be C++"); + } + + void test_global_rix_csv_write() + { + const rixlib::csv::Table table = { + {"name", "language"}, + {"Ada", "C++"}, + }; + + const std::string output = rix.csv.write(table); + + const std::string expected = + "name,language\n" + "Ada,C++\n"; + + expect_true(output == expected, "rix.csv.write should serialize CSV data"); + } + + void test_global_rix_debug_format() + { + const std::string output = rix.debug.format("Hello {}", "Rix"); + + expect_true( + output == "Hello Rix", + "rix.debug.format should format automatic placeholders"); + } + + void test_global_rix_debug_format_explicit_indexes() + { + const std::string output = rix.debug.format("{0} + {0} = {1}", 2, 4); + + expect_true( + output == "2 + 2 = 4", + "rix.debug.format should format explicit placeholders"); + } + + void test_global_rix_debug_format_escaped_braces() + { + const std::string output = rix.debug.format("{{ value }} = {}", 42); + + expect_true( + output == "{ value } = 42", + "rix.debug.format should support escaped braces"); + } + + void test_global_rix_debug_format_append() + { + std::string output = "prefix: "; + + rix.debug.format.append(output, "{}", "ready"); + + expect_true( + output == "prefix: ready", + "rix.debug.format.append should append formatted text"); + } + + void test_global_rix_debug_format_to() + { + std::string output = "old"; + + rix.debug.format.to(output, "status: {}", "ok"); + + expect_true( + output == "status: ok", + "rix.debug.format.to should replace destination content"); + } + + void test_global_rix_debug_print_to_stream() + { + std::ostringstream out; + + rixlib::print_to(out, "Hello", "Rix"); + + expect_true( + out.str() == "Hello Rix\n", + "rixlib::print_to should write arguments separated by spaces with newline"); + } + + void test_global_rix_debug_inspect_to_string() + { + expect_true( + rix.debug.inspect.to_string(42) == "42", + "rix.debug.inspect.to_string should inspect integers"); + + expect_true( + rix.debug.inspect.to_string(true) == "true", + "rix.debug.inspect.to_string should inspect booleans"); + } + + void test_global_rix_debug_inspect_check_pass() + { + const bool ok = rix.debug.inspect.check(42, 42); + + expect_true( + ok, + "rix.debug.inspect.check should return true when values are equal"); + } + + void test_global_rix_debug_inspect_check_fail() + { + const bool ok = rix.debug.inspect.check(42, 24); + + expect_true( + !ok, + "rix.debug.inspect.check should return false when values are different"); + } + + void run_tests() + { + test_global_rix_csv_parse(); + test_global_rix_csv_write(); + + test_global_rix_debug_format(); + test_global_rix_debug_format_explicit_indexes(); + test_global_rix_debug_format_escaped_braces(); + test_global_rix_debug_format_append(); + test_global_rix_debug_format_to(); + test_global_rix_debug_print_to_stream(); + test_global_rix_debug_inspect_to_string(); + test_global_rix_debug_inspect_check_pass(); + test_global_rix_debug_inspect_check_fail(); + } +} + +int main() +{ + run_tests(); + + std::cout << "rix facade tests passed\n"; + return 0; +} diff --git a/vix.json b/vix.json new file mode 100644 index 0000000..17442bf --- /dev/null +++ b/vix.json @@ -0,0 +1,44 @@ +{ + "cmake": { + "target": "rix::rix" + }, + "deps": [ + { + "id": "rix/csv", + "version": "0.3.1" + }, + { + "id": "rix/debug", + "version": "0.2.0" + }, + { + "id": "rix/auth", + "version": "0.3.0" + }, + { + "id": "rix/pdf", + "version": "0.1.0" + } + ], + "description": "Unified Rix facade for Vix C++ projects.", + "include": "include", + "keywords": [ + "cpp", + "rix", + "vix", + "facade", + "standard-library" + ], + "license": "MIT", + "maintainers": [ + { + "github": "rixcpp", + "name": "Rix maintainers" + } + ], + "name": "rix", + "namespace": "rix", + "repository": "https://github.com/rixcpp/rix", + "type": "header-only", + "version": "0.9.1" +} diff --git a/vix.lock b/vix.lock new file mode 100644 index 0000000..d6dccf7 --- /dev/null +++ b/vix.lock @@ -0,0 +1,41 @@ +{ + "dependencies": [ + { + "commit": "c7406e0cc5b16e60acb8dfcfd8b33fa249551e18", + "hash": "f34b37a2a5da3cf5dc1a26337b6c1193014eec4633346da5677782dea916bcb7", + "id": "rix/auth", + "repo": "https://github.com/rixcpp/auth", + "requested": "0.3.0", + "tag": "v0.3.0", + "version": "0.3.0" + }, + { + "commit": "e5e7f817a37d815c61962e6be88259f105d4f54d", + "hash": "b9a99bd0ac64d0a3affcb410e067f5b745d892f4c59844e0df4b1a4b9b8bf380", + "id": "rix/csv", + "repo": "https://github.com/rixcpp/csv", + "requested": "0.3.1", + "tag": "v0.3.1", + "version": "0.3.1" + }, + { + "commit": "7ce988db8e20a1d0bcedda81dcb88e4daf01c8a7", + "hash": "fd1fee375bab12cf6a51a53ebc8f41b93abc757328fda0e7d20272d560de6c5e", + "id": "rix/debug", + "repo": "https://github.com/rixcpp/debug", + "requested": "0.2.0", + "tag": "v0.2.0", + "version": "0.2.0" + }, + { + "commit": "c7c56efc6a04f285f05c52849186680aa7f342e7", + "hash": "d58fb505d6e9ecd780e1dad4c7916761a5710a350bc1ea8c11678a7a1f843550", + "id": "rix/pdf", + "repo": "https://github.com/rixcpp/pdf", + "requested": "0.1.0", + "tag": "v0.1.0", + "version": "0.1.0" + } + ], + "lockVersion": 1 +}