From 1b1df0fc4de3ff43ebb94312bf0a4e2a3a57064d Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Sun, 7 Jun 2026 14:00:06 +0300 Subject: [PATCH 01/12] feat: add unified rix facade --- .gitignore | 54 ++-------- CMakeLists.txt | 39 ++++++++ Makefile | 216 ---------------------------------------- examples/CMakeLists.txt | 8 ++ examples/basic.cpp | 38 +++++++ include/rix.hpp | 53 ++++++++++ modules/.gitkeep | 0 packages/csv/README.md | 74 ++++++++++++++ packages/csv/vix.json | 26 +++++ tests/CMakeLists.txt | 13 +++ tests/rix_tests.cpp | 66 ++++++++++++ vix.json | 32 ++++++ vix.lock | 14 +++ 13 files changed, 369 insertions(+), 264 deletions(-) delete mode 100644 Makefile create mode 100644 examples/CMakeLists.txt create mode 100644 examples/basic.cpp create mode 100644 include/rix.hpp delete mode 100644 modules/.gitkeep create mode 100644 packages/csv/README.md create mode 100644 packages/csv/vix.json create mode 100644 tests/CMakeLists.txt create mode 100644 tests/rix_tests.cpp create mode 100644 vix.json create mode 100644 vix.lock diff --git a/.gitignore b/.gitignore index 16818c2..37fe30e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,54 +1,12 @@ -# 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/CMakeLists.txt b/CMakeLists.txt index e69de29..7c727bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.20) + +project(rix + VERSION 0.1.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 +) + +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/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..fb6cade --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,8 @@ +add_executable(rix_basic + basic.cpp +) + +target_link_libraries(rix_basic + PRIVATE + rix::rix +) diff --git a/examples/basic.cpp b/examples/basic.cpp new file mode 100644 index 0000000..84baca4 --- /dev/null +++ b/examples/basic.cpp @@ -0,0 +1,38 @@ +/** + * @file basic.cpp + * @brief Basic example for the Rix facade. + */ + +#include + +#include +#include + +namespace +{ + void run_basic_example() + { + const std::string input = + "name,language\n" + "Ada,C++\n" + "Gaspard,Vix\n"; + + const auto table = rix.csv.parse(input); + + for (const auto &row : table) + { + for (const auto &field : row) + { + std::cout << field << ' '; + } + + std::cout << '\n'; + } + } +} + +int main() +{ + run_basic_example(); + return 0; +} diff --git a/include/rix.hpp b/include/rix.hpp new file mode 100644 index 0000000..957c4fc --- /dev/null +++ b/include/rix.hpp @@ -0,0 +1,53 @@ +/** + * @file rix.hpp + * @brief Unified Rix facade. + * + * This header exposes the global `rix` object. + * + * The package `@rix/rix` is the optional unified facade for Rix packages. + * Individual packages such as `@rix/csv` stay independent, but this facade + * mounts them into one object-style API. + * + * Example: + * + * @code + * auto table = rix.csv.parse("name,lang\nAda,C++\n"); + * @endcode + * + * @author Gaspard Kirira + */ + +#ifndef RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED +#define RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED + +#include + +namespace rixlib +{ + /** + * @brief Unified Rix API facade. + * + * This class groups independent Rix components into one object. + */ + class Rix + { + public: + /** + * @brief CSV reader and writer component. + */ + rixlib::csv::Csv csv{}; + }; +} + +/** + * @brief Global Rix facade object. + * + * This object allows API usage such as: + * + * @code + * 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/csv/README.md b/packages/csv/README.md new file mode 100644 index 0000000..e7a9480 --- /dev/null +++ b/packages/csv/README.md @@ -0,0 +1,74 @@ +# rix/csv + +Small CSV reader and writer for Vix C++ projects. + +`rix/csv` is part of Rix, the userland package layer for the Vix.cpp ecosystem. + +The source code lives in its own repository. This folder only keeps the official package entry used by the Rix umbrella repository. + +## Install + +```bash +vix add @rix/csv +vix install +``` + +## Use + +```cpp +#include + +int main() +{ + rixlib::csv::Csv csv; + auto table = csv.parse("name,lang\nAda,C++\n"); +} +``` + +## Run + +```bash +vix run main.cpp +``` + +## Repository + +```txt +https://github.com/rixcpp/csv +``` + +## Package + +```txt +rix/csv +``` + +## Namespace + +```cpp +rix::csv +``` + +## CMake target + +```cmake +rix::csv +``` + +## Manifest + +```txt +packages/csv/vix.json +``` + +## Role in Rix + +Rix does not embed package source code. + +The real library lives in: + +```txt +github.com/rixcpp/csv +``` + +The Rix umbrella keeps only the public package entry, documentation, and metadata. diff --git a/packages/csv/vix.json b/packages/csv/vix.json new file mode 100644 index 0000000..5af5b19 --- /dev/null +++ b/packages/csv/vix.json @@ -0,0 +1,26 @@ +{ + "name": "csv", + "namespace": "rix", + "version": "0.2.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/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..3536b6d --- /dev/null +++ b/tests/rix_tests.cpp @@ -0,0 +1,66 @@ +/** + * @file rix_tests.cpp + * @brief Basic tests for the Rix facade. + */ + +#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[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 run_tests() + { + test_global_rix_csv_parse(); + test_global_rix_csv_write(); + } +} + +int main() +{ + run_tests(); + + std::cout << "rix tests passed\n"; + return 0; +} diff --git a/vix.json b/vix.json new file mode 100644 index 0000000..39e3590 --- /dev/null +++ b/vix.json @@ -0,0 +1,32 @@ +{ + "cmake": { + "target": "rix::rix" + }, + "deps": [ + { + "id": "rix/csv", + "version": "0.2.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.5.0" +} diff --git a/vix.lock b/vix.lock new file mode 100644 index 0000000..d975496 --- /dev/null +++ b/vix.lock @@ -0,0 +1,14 @@ +{ + "dependencies": [ + { + "commit": "fb876411810310180692666593eecf05a7962128", + "hash": "42e26288f206955e68fbbff1a83ffb8b0b2cb5539a9436dff258959e809a3c80", + "id": "rix/csv", + "repo": "https://github.com/rixcpp/csv", + "requested": "0.2.0", + "tag": "v0.2.0", + "version": "0.2.0" + } + ], + "lockVersion": 1 +} From 7cfc94ae70d9c5e64cec366900a70ac0227eca6c Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Sun, 7 Jun 2026 14:35:26 +0300 Subject: [PATCH 02/12] feat: add debug module to rix facade --- CMakeLists.txt | 3 +- examples/CMakeLists.txt | 18 +++++++ examples/basic.cpp | 32 ++---------- examples/csv.cpp | 39 ++++++++++++++ examples/debug.cpp | 22 ++++++++ include/rix.hpp | 15 +++++- packages/debug/README.md | 83 ++++++++++++++++++++++++++++++ packages/debug/vix.json | 30 +++++++++++ tests/rix_tests.cpp | 107 ++++++++++++++++++++++++++++++++++++++- vix.json | 6 ++- vix.lock | 9 ++++ 11 files changed, 330 insertions(+), 34 deletions(-) create mode 100644 examples/csv.cpp create mode 100644 examples/debug.cpp create mode 100644 packages/debug/README.md create mode 100644 packages/debug/vix.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c727bc..30c54c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.20) project(rix - VERSION 0.1.0 + VERSION 0.5.0 DESCRIPTION "Unified Rix facade for Vix C++ projects" LANGUAGES CXX ) @@ -25,6 +25,7 @@ target_include_directories(rix target_link_libraries(rix INTERFACE rix::csv + rix::debug ) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fb6cade..12ef08f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -6,3 +6,21 @@ 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 +) diff --git a/examples/basic.cpp b/examples/basic.cpp index 84baca4..5f196b3 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -1,38 +1,14 @@ /** * @file basic.cpp - * @brief Basic example for the Rix facade. + * @brief Basic example for the unified Rix facade. */ #include -#include -#include - -namespace -{ - void run_basic_example() - { - const std::string input = - "name,language\n" - "Ada,C++\n" - "Gaspard,Vix\n"; - - const auto table = rix.csv.parse(input); - - for (const auto &row : table) - { - for (const auto &field : row) - { - std::cout << field << ' '; - } - - std::cout << '\n'; - } - } -} - int main() { - run_basic_example(); + 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..5ca1de2 --- /dev/null +++ b/examples/csv.cpp @@ -0,0 +1,39 @@ +/** + * @file csv.cpp + * @brief CSV example for the unified Rix facade. + */ + +#include + +#include + +namespace +{ + void print_table(const rixlib::csv::Table &table) + { + for (const auto &row : table) + { + for (const auto &field : row) + { + rix.debug.print.inline_text("{} ", field); + } + + rix.debug.print(); + } + } +} + +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..0a4e845 --- /dev/null +++ b/examples/debug.cpp @@ -0,0 +1,22 @@ +/** + * @file debug.cpp + * @brief Debug example for the unified Rix facade. + */ + +#include + +#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/include/rix.hpp b/include/rix.hpp index 957c4fc..91cbca6 100644 --- a/include/rix.hpp +++ b/include/rix.hpp @@ -5,13 +5,14 @@ * This header exposes the global `rix` object. * * The package `@rix/rix` is the optional unified facade for Rix packages. - * Individual packages such as `@rix/csv` stay independent, but this facade - * mounts them into one object-style API. + * Individual packages such as `@rix/csv` and `@rix/debug` stay independent, + * but this facade mounts them into one object-style API. * * Example: * * @code * auto table = rix.csv.parse("name,lang\nAda,C++\n"); + * rix.debug.print("loaded {} rows", table.size()); * @endcode * * @author Gaspard Kirira @@ -21,6 +22,7 @@ #define RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED #include +#include namespace rixlib { @@ -36,6 +38,11 @@ namespace rixlib * @brief CSV reader and writer component. */ rixlib::csv::Csv csv{}; + + /** + * @brief Debug printing, formatting, logging, and inspection component. + */ + rixlib::debug::Debug debug{}; }; } @@ -46,6 +53,10 @@ namespace rixlib * * @code * rix.csv.parse(...) + * rix.debug.print(...) + * rix.debug.format(...) + * rix.debug.log(...) + * rix.debug.inspect(...) * @endcode */ inline constexpr rixlib::Rix rix{}; diff --git a/packages/debug/README.md b/packages/debug/README.md new file mode 100644 index 0000000..47e7ec3 --- /dev/null +++ b/packages/debug/README.md @@ -0,0 +1,83 @@ +# rix/debug + +Debug printing, formatting, logging, and inspection utilities for Rix. + +`rix/debug` is part of Rix, the userland package layer for the Vix.cpp ecosystem. + +The source code lives in its own repository. This folder only keeps the official package entry used by the Rix umbrella repository. + +## Install + +```bash +vix add @rix/debug +vix install +``` + +## Use + +```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); +} +``` + +## Repository + +```txt +https://github.com/rixcpp/debug +``` + +## Package + +```txt +rix/debug +``` + +## Namespace + +```cpp +rixlib::debug +``` + +## CMake target + +```cmake +rix::debug +``` + +## Manifest + +```txt +packages/debug/vix.json +``` + +## Role in Rix + +Rix does not embed package source code. + +The real library lives in: + +```txt +github.com/rixcpp/debug +``` + +The Rix facade mounts this package as: + +```cpp +rix.debug.print(...) +rix.debug.format(...) +rix.debug.log(...) +rix.debug.inspect(...) +``` diff --git a/packages/debug/vix.json b/packages/debug/vix.json new file mode 100644 index 0000000..156432c --- /dev/null +++ b/packages/debug/vix.json @@ -0,0 +1,30 @@ +{ + "name": "debug", + "namespace": "rix", + "version": "0.1.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": "Rix maintainers", + "github": "rixcpp" + } + ], + "cmake": { + "target": "rix::debug" + }, + "deps": [] +} diff --git a/tests/rix_tests.cpp b/tests/rix_tests.cpp index 3536b6d..14e269d 100644 --- a/tests/rix_tests.cpp +++ b/tests/rix_tests.cpp @@ -1,12 +1,15 @@ /** * @file rix_tests.cpp - * @brief Basic tests for the Rix facade. + * @brief Basic tests for the unified Rix facade. + * + * @author Gaspard Kirira */ #include #include #include +#include #include namespace @@ -30,6 +33,7 @@ namespace 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++"); } @@ -50,10 +54,109 @@ namespace 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; + + rix.debug.print.to(out, "Hello {}", "Rix"); + + expect_true( + out.str() == "Hello Rix\n", + "rix.debug.print.to should write formatted output 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(); } } @@ -61,6 +164,6 @@ int main() { run_tests(); - std::cout << "rix tests passed\n"; + std::cout << "rix facade tests passed\n"; return 0; } diff --git a/vix.json b/vix.json index 39e3590..97d0f85 100644 --- a/vix.json +++ b/vix.json @@ -6,6 +6,10 @@ { "id": "rix/csv", "version": "0.2.0" + }, + { + "id": "rix/debug", + "version": "0.1.0" } ], "description": "Unified Rix facade for Vix C++ projects.", @@ -28,5 +32,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.5.0" + "version": "0.5.1" } diff --git a/vix.lock b/vix.lock index d975496..75a54ea 100644 --- a/vix.lock +++ b/vix.lock @@ -8,6 +8,15 @@ "requested": "0.2.0", "tag": "v0.2.0", "version": "0.2.0" + }, + { + "commit": "c6fe0b357d97832bfc3f768635971d2dd10374cf", + "hash": "5f5210a66dc1518fc529e3e7dd26b24b1b2621e180df836f78e79b6635bcad7d", + "id": "rix/debug", + "repo": "https://github.com/rixcpp/debug", + "requested": "0.1.0", + "tag": "v0.1.0", + "version": "0.1.0" } ], "lockVersion": 1 From 7f50b7b9c236e9d2ffe5b94bcf39c65f455e5104 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Sun, 7 Jun 2026 14:37:27 +0300 Subject: [PATCH 03/12] feat: add debug module to rix facade --- README.md | 271 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) diff --git a/README.md b/README.md index 504b137..98509b9 100644 --- a/README.md +++ b/README.md @@ -1 +1,272 @@ # Rix + +Rix is the unified userland library layer for Vix.cpp. + +It gives Vix C++ projects a single optional 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()); +} +``` + +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. + +## Design + +Rix is built around two levels. + +```txt +@rix/csv +@rix/debug +@rix/config +@rix/table +``` + +These are independent packages. + +```txt +@rix/rix +``` + +This is the unified facade package. + +The facade mounts independent packages into one object-style API: + +```cpp +rix.csv.parse(...) +rix.debug.print(...) +rix.debug.format(...) +rix.debug.log(...) +rix.debug.inspect(...) +``` + +## Install + +```bash +vix add @rix/rix +vix install +``` + +## Use + +```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; +} +``` + +## 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. | + +## 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.inline_text("{} ", field); + } + + rix.debug.print(); + } + + 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; +} +``` + +## Independent packages + +Each Rix module can also be used independently. + +For example: + +```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 0; +} +``` + +And: + +```bash +vix add @rix/debug +vix install +``` + +```cpp +#include + +int main() +{ + rixlib::debug::Debug debug; + debug.print("Hello {}", "Rix"); + + return 0; +} +``` + +## Repository layout + +```txt +rix/ +├── include/ +│ └── rix.hpp +├── examples/ +│ ├── basic.cpp +│ ├── csv.cpp +│ └── debug.cpp +├── tests/ +│ └── rix_tests.cpp +├── packages/ +│ ├── csv/ +│ │ ├── README.md +│ │ └── vix.json +│ └── debug/ +│ ├── 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 +``` + +The real source code of each independent package lives in its own repository: + +```txt +https://github.com/rixcpp/csv +https://github.com/rixcpp/debug +``` + +## 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 +``` + +## Tests + +```bash +vix tests +``` + +## Package model + +```txt +Vix -> runtime, CLI, build workflow, registry client +Rix -> userland libraries and 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 standard userland layer. + +## Current facade API + +```cpp +rix.csv.parse(...) +rix.csv.write(...) + +rix.debug.print(...) +rix.debug.format(...) +rix.debug.log(...) +rix.debug.inspect(...) +``` + +## License + +MIT From 784bea70c30fd58b1b3ad1cc2db083b3901cabdf Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Sun, 7 Jun 2026 21:58:00 +0300 Subject: [PATCH 04/12] feat(rix): update debug facade integration --- README.md | 48 ++++++++-- examples/basic.cpp | 2 +- examples/csv.cpp | 14 ++- examples/debug.cpp | 4 +- include/rix.hpp | 3 +- packages/debug/README.md | 183 ++++++++++++++++++++++++++++++++------- packages/debug/vix.json | 6 +- tests/rix_tests.cpp | 4 +- vix.json | 4 +- vix.lock | 10 +-- 10 files changed, 222 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 98509b9..7c65f04 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,10 @@ It gives Vix C++ projects a single optional facade object: int main() { - rix.debug.print("Hello {}", "Rix"); + rix.debug.print("Hello", "Rix"); + auto table = rix.csv.parse("name,language\nAda,C++\n"); + rix.debug.log("loaded {} rows", table.size()); } ``` @@ -64,7 +66,7 @@ vix install int main() { - rix.debug.print("Hello {}", "Rix"); + rix.debug.print("Hello", "Rix"); const auto message = rix.debug.format("Package: {}", "rix/rix"); @@ -95,16 +97,15 @@ int main() "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.inline_text("{} ", field); + rix.debug.print(field); } - - rix.debug.print(); } return 0; @@ -118,7 +119,7 @@ int main() int main() { - rix.debug.print("Hello {}", "Rix"); + rix.debug.print("Hello", "Rix"); auto text = rix.debug.format("Package: {}", "rix/rix"); @@ -131,6 +132,37 @@ int main() } ``` +## 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 module can also be used independently. @@ -167,7 +199,9 @@ vix install int main() { rixlib::debug::Debug debug; - debug.print("Hello {}", "Rix"); + + debug.print("Hello", "Rix"); + debug.log("loaded {} rows", 3); return 0; } diff --git a/examples/basic.cpp b/examples/basic.cpp index 5f196b3..9f1b8b2 100644 --- a/examples/basic.cpp +++ b/examples/basic.cpp @@ -7,7 +7,7 @@ int main() { - rix.debug.print("Hello {}", "Rix"); + rix.debug.print("Hello", "Rix"); rix.debug.log("facade is ready"); return 0; diff --git a/examples/csv.cpp b/examples/csv.cpp index 5ca1de2..853aa20 100644 --- a/examples/csv.cpp +++ b/examples/csv.cpp @@ -5,6 +5,7 @@ #include +#include #include namespace @@ -13,12 +14,19 @@ namespace { for (const auto &row : table) { - for (const auto &field : row) + std::ostringstream line; + + for (std::size_t i = 0; i < row.size(); ++i) { - rix.debug.print.inline_text("{} ", field); + if (i > 0) + { + line << " "; + } + + line << row[i]; } - rix.debug.print(); + rix.debug.print(line.str()); } } } diff --git a/examples/debug.cpp b/examples/debug.cpp index 0a4e845..37aa164 100644 --- a/examples/debug.cpp +++ b/examples/debug.cpp @@ -5,11 +5,9 @@ #include -#include - int main() { - rix.debug.print("Hello {}", "Rix"); + rix.debug.print("Hello", "Rix"); const std::string package = rix.debug.format("Package: {}", "rix/rix"); diff --git a/include/rix.hpp b/include/rix.hpp index 91cbca6..53d0788 100644 --- a/include/rix.hpp +++ b/include/rix.hpp @@ -12,7 +12,8 @@ * * @code * auto table = rix.csv.parse("name,lang\nAda,C++\n"); - * rix.debug.print("loaded {} rows", table.size()); + * rix.debug.print("loaded rows:", table.size()); + * rix.debug.log("loaded {} rows", table.size()); * @endcode * * @author Gaspard Kirira diff --git a/packages/debug/README.md b/packages/debug/README.md index 47e7ec3..8c4199e 100644 --- a/packages/debug/README.md +++ b/packages/debug/README.md @@ -1,28 +1,33 @@ -# rix/debug +# @rix/debug -Debug printing, formatting, logging, and inspection utilities for Rix. +Debug utilities for Rix. -`rix/debug` is part of Rix, the userland package layer for the Vix.cpp ecosystem. +`@rix/debug` provides a small set of developer-focused tools for C++ projects: -The source code lives in its own repository. This folder only keeps the official package entry used by the Rix umbrella repository. +- formatted strings +- simple logging +- value inspection +- print helpers -## Install +It can be used as an independent Rix package, or through the unified `@rix/rix` facade. + +## Installation ```bash vix add @rix/debug vix install ``` -## Use +## Basic usage ```cpp #include int main() { - rixlib::debug::Debug debug; + rixlib::debug::Debug debug{}; - debug.print("Hello {}", "Rix"); + debug.print("Hello", "Rix"); auto text = debug.format("Package: {}", "rix/debug"); @@ -30,54 +35,174 @@ int main() debug.log.warn("slow request: {}ms", 120); debug.inspect(text); + + return 0; } ``` -## Repository +Output: ```txt -https://github.com/rixcpp/debug +Hello Rix +[debug] loaded 3 rows +[warn] slow request: 120ms +Package: rix/debug ``` -## Package +## 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 -rix/debug +{} automatic argument indexing +{0} explicit positional indexing +{{ escaped opening brace +}} escaped closing brace ``` -## Namespace +Format specifiers such as `{:>10}` or `{:.2f}` are intentionally not supported. + +## Logging ```cpp -rixlib::debug +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 ``` -## CMake target +## Printing + +`debug.print` is not a formatter. + +It prints values separated by spaces. -```cmake -rix::debug +```cpp +debug.print("Hello", "Rix"); +debug.print(1, 2, 3); ``` -## Manifest +Output: ```txt -packages/debug/vix.json +Hello Rix +1 2 3 ``` -## Role in Rix +For formatted strings, use `debug.format` or `debug.log`. -Rix does not embed package source code. +```cpp +debug.print(debug.format("Hello {}", "Rix")); +``` -The real library lives in: +## Inspection -```txt -github.com/rixcpp/debug +```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; +} ``` -The Rix facade mounts this package as: +## Unified Rix facade + +When used through `@rix/rix`, the debug package is mounted under `rix.debug`. ```cpp -rix.debug.print(...) -rix.debug.format(...) -rix.debug.log(...) -rix.debug.inspect(...) +#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 index 156432c..d6f2d54 100644 --- a/packages/debug/vix.json +++ b/packages/debug/vix.json @@ -1,7 +1,7 @@ { "name": "debug", "namespace": "rix", - "version": "0.1.0", + "version": "0.2.0", "type": "header-only", "include": "include", "license": "MIT", @@ -19,8 +19,8 @@ "repository": "https://github.com/rixcpp/debug", "maintainers": [ { - "name": "Rix maintainers", - "github": "rixcpp" + "name": "Gaspard kirira", + "github": "Gaspardkirira" } ], "cmake": { diff --git a/tests/rix_tests.cpp b/tests/rix_tests.cpp index 14e269d..42a143f 100644 --- a/tests/rix_tests.cpp +++ b/tests/rix_tests.cpp @@ -107,11 +107,11 @@ namespace { std::ostringstream out; - rix.debug.print.to(out, "Hello {}", "Rix"); + rixlib::print_to(out, "Hello", "Rix"); expect_true( out.str() == "Hello Rix\n", - "rix.debug.print.to should write formatted output with newline"); + "rixlib::print_to should write arguments separated by spaces with newline"); } void test_global_rix_debug_inspect_to_string() diff --git a/vix.json b/vix.json index 97d0f85..81bd3e9 100644 --- a/vix.json +++ b/vix.json @@ -9,7 +9,7 @@ }, { "id": "rix/debug", - "version": "0.1.0" + "version": "0.2.0" } ], "description": "Unified Rix facade for Vix C++ projects.", @@ -32,5 +32,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.5.1" + "version": "0.6.0" } diff --git a/vix.lock b/vix.lock index 75a54ea..3063844 100644 --- a/vix.lock +++ b/vix.lock @@ -10,13 +10,13 @@ "version": "0.2.0" }, { - "commit": "c6fe0b357d97832bfc3f768635971d2dd10374cf", - "hash": "5f5210a66dc1518fc529e3e7dd26b24b1b2621e180df836f78e79b6635bcad7d", + "commit": "7ce988db8e20a1d0bcedda81dcb88e4daf01c8a7", + "hash": "a5fa071b814c6beb90a3ed49091307651f6ce996b2bf041d009400fca7746f42", "id": "rix/debug", "repo": "https://github.com/rixcpp/debug", - "requested": "0.1.0", - "tag": "v0.1.0", - "version": "0.1.0" + "requested": "0.2.0", + "tag": "v0.2.0", + "version": "0.2.0" } ], "lockVersion": 1 From 74139d68eccd2021d656b478ed94beead77ae6c6 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Mon, 8 Jun 2026 11:26:08 +0300 Subject: [PATCH 05/12] fix(rix): update csv facade dependency --- packages/csv/README.md | 381 ++++++++++++++++++++++++++++++++++++++--- packages/csv/vix.json | 2 +- vix.json | 4 +- vix.lock | 12 +- 4 files changed, 363 insertions(+), 36 deletions(-) diff --git a/packages/csv/README.md b/packages/csv/README.md index e7a9480..0db591c 100644 --- a/packages/csv/README.md +++ b/packages/csv/README.md @@ -1,74 +1,401 @@ -# rix/csv +# @rix/csv -Small CSV reader and writer for Vix C++ projects. +CSV parser and writer for Rix. -`rix/csv` is part of Rix, the userland package layer for the Vix.cpp ecosystem. +`@rix/csv` is a small header-only C++ package for reading, writing, transforming, and inspecting CSV data. -The source code lives in its own repository. This folder only keeps the official package entry used by the Rix umbrella repository. +It can be used independently, or through the unified `@rix/rix` facade. -## Install +## Installation ```bash vix add @rix/csv vix install ``` -## Use +## Basic usage ```cpp #include +#include +#include + int main() { - rixlib::csv::Csv csv; - auto table = csv.parse("name,lang\nAda,C++\n"); + 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; } ``` -## Run +Output: -```bash -vix run main.cpp +```txt +rows: 3 +name language +Ada C++ +Gaspard Vix ``` -## Repository +## 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 -https://github.com/rixcpp/csv +name,language +Ada,C++ ``` -## Package +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 -rix/csv +Ada,C++,expert ``` -## Namespace +## Parsing options ```cpp -rix::csv +rixlib::csv::Options options{}; +options.skip_empty_lines = true; +options.trim_whitespace = true; + +const auto table = rixlib::csv::parse(input, options); ``` -## CMake target +Available options include: -```cmake -rix::csv +```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; ``` -## Manifest +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 -packages/csv/vix.json +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 + "]"; + }); ``` -## Role in Rix +## Describe a table -Rix does not embed package source code. +```cpp +std::cout << rixlib::csv::describe(table); +``` -The real library lives in: +Example output: ```txt -github.com/rixcpp/csv +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 ``` -The Rix umbrella keeps only the public package entry, documentation, and metadata. +## Tests + +```bash +vix tests +``` + +## License + +MIT diff --git a/packages/csv/vix.json b/packages/csv/vix.json index 5af5b19..b9fab32 100644 --- a/packages/csv/vix.json +++ b/packages/csv/vix.json @@ -1,7 +1,7 @@ { "name": "csv", "namespace": "rix", - "version": "0.2.0", + "version": "0.3.0", "type": "header-only", "include": "include", "license": "MIT", diff --git a/vix.json b/vix.json index 81bd3e9..5fede15 100644 --- a/vix.json +++ b/vix.json @@ -5,7 +5,7 @@ "deps": [ { "id": "rix/csv", - "version": "0.2.0" + "version": "0.3.1" }, { "id": "rix/debug", @@ -32,5 +32,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.6.0" + "version": "0.6.1" } diff --git a/vix.lock b/vix.lock index 3063844..8b55794 100644 --- a/vix.lock +++ b/vix.lock @@ -1,17 +1,17 @@ { "dependencies": [ { - "commit": "fb876411810310180692666593eecf05a7962128", - "hash": "42e26288f206955e68fbbff1a83ffb8b0b2cb5539a9436dff258959e809a3c80", + "commit": "e5e7f817a37d815c61962e6be88259f105d4f54d", + "hash": "207c4541664aa32e1ae83730d8ebacc204b10fa0e796cd8b9bcaca3395ae8f5c", "id": "rix/csv", "repo": "https://github.com/rixcpp/csv", - "requested": "0.2.0", - "tag": "v0.2.0", - "version": "0.2.0" + "requested": "0.3.1", + "tag": "v0.3.1", + "version": "0.3.1" }, { "commit": "7ce988db8e20a1d0bcedda81dcb88e4daf01c8a7", - "hash": "a5fa071b814c6beb90a3ed49091307651f6ce996b2bf041d009400fca7746f42", + "hash": "729461835557e5bf4c332374a08e8773a1fd18d9521a0e46f0304ee9ee7bac59", "id": "rix/debug", "repo": "https://github.com/rixcpp/debug", "requested": "0.2.0", From a16df43e60fc30f0a4b57e955206188b370247b0 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Tue, 9 Jun 2026 18:58:41 +0300 Subject: [PATCH 06/12] feat(rix): expose auth through unified facade --- .gitignore | 1 + CMakeLists.txt | 6 +- README.md | 166 +++++- examples/CMakeLists.txt | 2 + examples/auth/01_memory_register_login.cpp | 86 +++ examples/auth/02_password_hash.cpp | 69 +++ examples/auth/03_session_refresh_logout.cpp | 101 ++++ examples/auth/04_token_issue.cpp | 60 ++ examples/auth/CMakeLists.txt | 31 + include/rix.hpp | 18 +- packages/auth/README.md | 621 ++++++++++++++++++++ packages/auth/vix.json | 31 + vix.json | 6 +- vix.lock | 13 +- 14 files changed, 1202 insertions(+), 9 deletions(-) create mode 100644 examples/auth/01_memory_register_login.cpp create mode 100644 examples/auth/02_password_hash.cpp create mode 100644 examples/auth/03_session_refresh_logout.cpp create mode 100644 examples/auth/04_token_issue.cpp create mode 100644 examples/auth/CMakeLists.txt create mode 100644 packages/auth/README.md create mode 100644 packages/auth/vix.json diff --git a/.gitignore b/.gitignore index 37fe30e..6c4aaa6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ build*/ *.dll *.dylib *.exe +cmd.md diff --git a/CMakeLists.txt b/CMakeLists.txt index 30c54c6..9365224 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,10 @@ 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_compile_features(rix + INTERFACE + cxx_std_20 +) target_include_directories(rix INTERFACE @@ -26,6 +29,7 @@ target_link_libraries(rix INTERFACE rix::csv rix::debug + rix::auth ) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) diff --git a/README.md b/README.md index 7c65f04..2f71e5b 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,19 @@ int main() 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()); + } + + return 0; } ``` @@ -30,6 +43,7 @@ Rix is built around two levels. ```txt @rix/csv @rix/debug +@rix/auth @rix/config @rix/table ``` @@ -50,6 +64,9 @@ rix.debug.print(...) rix.debug.format(...) rix.debug.log(...) rix.debug.inspect(...) +rix.auth.memory(...) +rix.auth.database(...) +rix.auth.password.hash(...) ``` ## Install @@ -82,6 +99,7 @@ int main() | ------------ | ----------- | -------------------------------------------------------------- | | `@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. | ## CSV example @@ -132,6 +150,95 @@ int main() } ``` +## Auth example + +```cpp +#include + +int main() +{ + auto auth = rix.auth.memory(); + auto registered = auth.register_user(rixlib::auth::RegisterRequest{"ada@example.com","correct-password"}); + + if (registered.failed()) + { + rix.debug.eprint("auth error:",rixlib::auth::to_string(registered.error().code()),registered.error().message()); + return 1; + } + + rix.debug.print("registered user:", registered.value().email()); + auto login = auth.login(rixlib::auth::LoginRequest{"ada@example.com", "correct-password"}); + + if (login.failed()) + { + rix.debug.eprint("auth error:", rixlib::auth::to_string(login.error().code()),login.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()) + { + rix.debug.eprint( + "auth error:", + rixlib::auth::to_string(hashed.error().code()), + hashed.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; +} +``` + +## Auth facade API + +```cpp +rix.auth.memory() +rix.auth.memory(config) + +rix.auth.database(db) +rix.auth.database(db, config) + +rix.auth.create(users, sessions) +rix.auth.create(users, sessions, config) + +rix.auth.password.hash(...) +rix.auth.password.verify(...) +rix.auth.password.hasher() + +rix.auth.config.development() +rix.auth.config.production() + +rix.auth.version() +``` + ## Print and format `rix.debug.print` does not replace `{}` placeholders. @@ -207,6 +314,34 @@ int main() } ``` +And: + +```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; +} +``` + ## Repository layout ```txt @@ -216,14 +351,22 @@ rix/ ├── examples/ │ ├── basic.cpp │ ├── csv.cpp -│ └── debug.cpp +│ ├── debug.cpp +│ └── auth/ +│ ├── 01_memory_register_login.cpp +│ ├── 02_password_hash.cpp +│ ├── 03_session_refresh_logout.cpp +│ └── 04_token_issue.cpp ├── tests/ │ └── rix_tests.cpp ├── packages/ │ ├── csv/ │ │ ├── README.md │ │ └── vix.json -│ └── debug/ +│ ├── debug/ +│ │ ├── README.md +│ │ └── vix.json +│ └── auth/ │ ├── README.md │ └── vix.json ├── CMakeLists.txt @@ -247,6 +390,7 @@ Second, it is the official human-readable index of Rix packages: ```txt packages/csv packages/debug +packages/auth ``` The real source code of each independent package lives in its own repository: @@ -254,6 +398,7 @@ 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 ``` ## Build @@ -271,6 +416,10 @@ Because this repository contains multiple examples, run one explicitly: 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 ``` ## Tests @@ -287,7 +436,7 @@ Rix -> userland libraries and 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 standard userland layer. +Rix keeps packages modular while still offering one clean entry point for users who want the full userland layer. ## Current facade API @@ -296,9 +445,20 @@ rix.csv.parse(...) rix.csv.write(...) rix.debug.print(...) +rix.debug.eprint(...) +rix.debug.dprint(...) +rix.debug.sprint(...) rix.debug.format(...) rix.debug.log(...) rix.debug.inspect(...) + +rix.auth.memory(...) +rix.auth.database(...) +rix.auth.create(...) +rix.auth.password.hash(...) +rix.auth.password.verify(...) +rix.auth.config.development(...) +rix.auth.config.production(...) ``` ## License diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 12ef08f..0fd3501 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,6 +20,8 @@ add_executable(rix_debug_example debug.cpp ) +add_subdirectory(auth) + target_link_libraries(rix_debug_example PRIVATE rix::rix diff --git a/examples/auth/01_memory_register_login.cpp b/examples/auth/01_memory_register_login.cpp new file mode 100644 index 0000000..b73d81a --- /dev/null +++ b/examples/auth/01_memory_register_login.cpp @@ -0,0 +1,86 @@ +/** + * + * @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 + +namespace +{ + void print_auth_error(const rixlib::auth::AuthError &error) + { + rix.debug.eprint( + "auth error:", + rixlib::auth::to_string(error.code()), + error.message()); + } + + int run_example() + { + rix.debug.print("== rix/auth memory register and login =="); + + auto auth = rix.auth.memory(); + + auto registered = auth.register_user( + rixlib::auth::RegisterRequest{ + "ada@example.com", + "correct-password"}); + + if (registered.failed()) + { + print_auth_error(registered.error()); + 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( + rixlib::auth::LoginRequest{ + "ada@example.com", + "correct-password"}); + + if (login.failed()) + { + print_auth_error(login.error()); + 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()) + { + print_auth_error(session.error()); + return 1; + } + + rix.debug.print("----------------------------------------"); + rix.debug.print("OK:", "session authenticated"); + rix.debug.print("session user id:", session.value().user_id()); + + return 0; + } +} // namespace + +int main() +{ + return run_example(); +} diff --git a/examples/auth/02_password_hash.cpp b/examples/auth/02_password_hash.cpp new file mode 100644 index 0000000..6b45836 --- /dev/null +++ b/examples/auth/02_password_hash.cpp @@ -0,0 +1,69 @@ +/** + * + * @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 + +namespace +{ + void print_auth_error(const rixlib::auth::AuthError &error) + { + rix.debug.eprint( + "auth error:", + rixlib::auth::to_string(error.code()), + error.message()); + } + + int run_example() + { + rix.debug.print("== rix/auth password hashing =="); + + const auto password = "correct-password"; + + auto hashed = rix.auth.password.hash(password); + + if (hashed.failed()) + { + print_auth_error(hashed.error()); + 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; + } +} // namespace + +int main() +{ + return run_example(); +} diff --git a/examples/auth/03_session_refresh_logout.cpp b/examples/auth/03_session_refresh_logout.cpp new file mode 100644 index 0000000..762a97a --- /dev/null +++ b/examples/auth/03_session_refresh_logout.cpp @@ -0,0 +1,101 @@ +/** + * + * @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 + +namespace +{ + void print_auth_error(const rixlib::auth::AuthError &error) + { + rix.debug.eprint( + "auth error:", + rixlib::auth::to_string(error.code()), + error.message()); + } + + int run_example() + { + rix.debug.print("== rix/auth session refresh and logout =="); + + auto auth = rix.auth.memory(); + + auto registered = auth.register_user( + rixlib::auth::RegisterRequest{ + "grace@example.com", + "correct-password"}); + + if (registered.failed()) + { + print_auth_error(registered.error()); + return 1; + } + + auto login = auth.login( + rixlib::auth::LoginRequest{ + "grace@example.com", + "correct-password"}); + + if (login.failed()) + { + print_auth_error(login.error()); + 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()) + { + print_auth_error(refreshed.error()); + 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()) + { + print_auth_error(logout.error()); + 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; + } +} // namespace + +int main() +{ + return run_example(); +} diff --git a/examples/auth/04_token_issue.cpp b/examples/auth/04_token_issue.cpp new file mode 100644 index 0000000..e736d30 --- /dev/null +++ b/examples/auth/04_token_issue.cpp @@ -0,0 +1,60 @@ +/** + * + * @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 + +namespace +{ + void print_auth_error(const rixlib::auth::AuthError &error) + { + rix.debug.eprint("auth error:", rixlib::auth::to_string(error.code()), error.message()); + } + + int run_example() + { + rix.debug.print("== rix/auth token issue =="); + + auto auth = rix.auth.memory(); + auto registered = auth.register_user(rixlib::auth::RegisterRequest{"linus@example.com", "correct-password"}); + + if (registered.failed()) + { + print_auth_error(registered.error()); + return 1; + } + + auto token = auth.issue_token(registered.value().id()); + + if (token.failed()) + { + print_auth_error(token.error()); + 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; + } +} // namespace + +int main() +{ + return run_example(); +} 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/include/rix.hpp b/include/rix.hpp index 53d0788..ed0ce43 100644 --- a/include/rix.hpp +++ b/include/rix.hpp @@ -5,8 +5,8 @@ * This header exposes the global `rix` object. * * The package `@rix/rix` is the optional unified facade for Rix packages. - * Individual packages such as `@rix/csv` and `@rix/debug` stay independent, - * but this facade mounts them into one object-style API. + * Individual packages such as `@rix/csv`, `@rix/debug`, and `@rix/auth` + * stay independent, but this facade mounts them into one object-style API. * * Example: * @@ -14,6 +14,9 @@ * auto table = rix.csv.parse("name,lang\nAda,C++\n"); * rix.debug.print("loaded rows:", table.size()); * rix.debug.log("loaded {} rows", table.size()); + * + * auto auth = rix.auth.memory(); + * auto user = auth.register_user({"ada@example.com", "correct-password"}); * @endcode * * @author Gaspard Kirira @@ -22,6 +25,7 @@ #ifndef RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED #define RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED +#include #include #include @@ -44,6 +48,11 @@ namespace rixlib * @brief Debug printing, formatting, logging, and inspection component. */ rixlib::debug::Debug debug{}; + + /** + * @brief Authentication component. + */ + rixlib::auth::AuthModule auth{}; }; } @@ -58,6 +67,11 @@ namespace rixlib * rix.debug.format(...) * rix.debug.log(...) * rix.debug.inspect(...) + * rix.auth.memory() + * rix.auth.database(db) + * rix.auth.create(users, sessions) + * rix.auth.password.hash(...) + * rix.auth.config.production() * @endcode */ inline constexpr rixlib::Rix rix{}; 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/vix.json b/vix.json index 5fede15..9f22764 100644 --- a/vix.json +++ b/vix.json @@ -10,6 +10,10 @@ { "id": "rix/debug", "version": "0.2.0" + }, + { + "id": "rix/auth", + "version": "0.2.0" } ], "description": "Unified Rix facade for Vix C++ projects.", @@ -32,5 +36,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.6.1" + "version": "0.7.0" } diff --git a/vix.lock b/vix.lock index 8b55794..53bb8aa 100644 --- a/vix.lock +++ b/vix.lock @@ -1,8 +1,17 @@ { "dependencies": [ + { + "commit": "dd4d859d5c50ab37d6e31c1e3c1f514bce35d699", + "hash": "0177bce2329f11222aa678f9393264608fc3bf0ad29ae8c5b423f8a303f8bc13", + "id": "rix/auth", + "repo": "https://github.com/rixcpp/auth", + "requested": "0.2.0", + "tag": "v0.2.0", + "version": "0.2.0" + }, { "commit": "e5e7f817a37d815c61962e6be88259f105d4f54d", - "hash": "207c4541664aa32e1ae83730d8ebacc204b10fa0e796cd8b9bcaca3395ae8f5c", + "hash": "b9a99bd0ac64d0a3affcb410e067f5b745d892f4c59844e0df4b1a4b9b8bf380", "id": "rix/csv", "repo": "https://github.com/rixcpp/csv", "requested": "0.3.1", @@ -11,7 +20,7 @@ }, { "commit": "7ce988db8e20a1d0bcedda81dcb88e4daf01c8a7", - "hash": "729461835557e5bf4c332374a08e8773a1fd18d9521a0e46f0304ee9ee7bac59", + "hash": "fd1fee375bab12cf6a51a53ebc8f41b93abc757328fda0e7d20272d560de6c5e", "id": "rix/debug", "repo": "https://github.com/rixcpp/debug", "requested": "0.2.0", From f6946c487d17b46142f7d610c45b1f743ea6c1a1 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Wed, 10 Jun 2026 09:52:55 +0300 Subject: [PATCH 07/12] docs(auth): simplify public auth facade examples --- README.md | 146 +++++++++++++++++++- examples/auth/01_memory_register_login.cpp | 92 +++++------- examples/auth/02_password_hash.cpp | 63 +++------ examples/auth/03_session_refresh_logout.cpp | 112 +++++++-------- examples/auth/04_token_issue.cpp | 57 +++----- vix.json | 2 +- vix.lock | 10 +- 7 files changed, 272 insertions(+), 210 deletions(-) diff --git a/README.md b/README.md index 2f71e5b..897935d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Rix Rix is the unified userland library layer for Vix.cpp. - It gives Vix C++ projects a single optional facade object: ```cpp @@ -64,11 +63,19 @@ rix.debug.print(...) rix.debug.format(...) rix.debug.log(...) rix.debug.inspect(...) + rix.auth.memory(...) rix.auth.database(...) rix.auth.password.hash(...) +rix.auth.password.verify(...) +rix.auth.config.production(...) +rix.auth.error.to_string(...) ``` +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. + ## Install ```bash @@ -158,20 +165,38 @@ int main() int main() { auto auth = rix.auth.memory(); - auto registered = auth.register_user(rixlib::auth::RegisterRequest{"ada@example.com","correct-password"}); + + auto registered = auth.register_user({ + "ada@example.com", + "correct-password"}); if (registered.failed()) { - rix.debug.eprint("auth error:",rixlib::auth::to_string(registered.error().code()),registered.error().message()); + 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(rixlib::auth::LoginRequest{"ada@example.com", "correct-password"}); + + auto login = auth.login({ + "ada@example.com", + "correct-password"}); if (login.failed()) { - rix.debug.eprint("auth error:", rixlib::auth::to_string(login.error().code()),login.error().message()); + const auto &error = login.error(); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); + return 1; } @@ -194,10 +219,12 @@ int main() if (hashed.failed()) { + const auto &error = hashed.error(); + rix.debug.eprint( "auth error:", - rixlib::auth::to_string(hashed.error().code()), - hashed.error().message()); + rix.auth.error.to_string(error), + error.message()); return 1; } @@ -226,19 +253,116 @@ 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. + ## Print and format `rix.debug.print` does not replace `{}` placeholders. @@ -342,6 +466,10 @@ int main() } ``` +Independent packages expose their own lower-level APIs. + +For the unified public facade, prefer `@rix/rix` and `#include `. + ## Repository layout ```txt @@ -454,11 +582,15 @@ rix.debug.inspect(...) rix.auth.memory(...) rix.auth.database(...) +rix.auth.managed(...) rix.auth.create(...) rix.auth.password.hash(...) rix.auth.password.verify(...) rix.auth.config.development(...) rix.auth.config.production(...) +rix.auth.error.to_string(...) +rix.auth.stores.memory_users(...) +rix.auth.stores.memory_sessions(...) ``` ## License diff --git a/examples/auth/01_memory_register_login.cpp b/examples/auth/01_memory_register_login.cpp index b73d81a..2ebcce2 100644 --- a/examples/auth/01_memory_register_login.cpp +++ b/examples/auth/01_memory_register_login.cpp @@ -16,71 +16,51 @@ #include -namespace +int main() { - void print_auth_error(const rixlib::auth::AuthError &error) - { - rix.debug.eprint( - "auth error:", - rixlib::auth::to_string(error.code()), - error.message()); - } - - int run_example() - { - rix.debug.print("== rix/auth memory register and login =="); - - auto auth = rix.auth.memory(); - - auto registered = auth.register_user( - rixlib::auth::RegisterRequest{ - "ada@example.com", - "correct-password"}); - - if (registered.failed()) - { - print_auth_error(registered.error()); - return 1; - } + rix.debug.print("== rix/auth memory register and login =="); - rix.debug.print("registered user"); - rix.debug.print("id:", registered.value().id()); - rix.debug.print("email:", registered.value().email()); + auto auth = rix.auth.memory(); + auto registered = auth.register_user({"ada@example.com", "correct-password"}); - auto login = auth.login( - rixlib::auth::LoginRequest{ - "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; + } - if (login.failed()) - { - print_auth_error(login.error()); - return 1; - } + rix.debug.print("registered user"); + rix.debug.print("id:", registered.value().id()); + rix.debug.print("email:", registered.value().email()); - 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 login = auth.login({"ada@example.com", "correct-password"}); - auto session = auth.authenticate_session(login.value().session.id()); + if (login.failed()) + { + const auto &error = login.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } - if (session.failed()) - { - print_auth_error(session.error()); - 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()); - rix.debug.print("----------------------------------------"); - rix.debug.print("OK:", "session authenticated"); - rix.debug.print("session user id:", session.value().user_id()); + auto session = auth.authenticate_session(login.value().session.id()); - return 0; + if (session.failed()) + { + const auto &error = session.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; } -} // namespace -int main() -{ - return run_example(); + 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 index 6b45836..7226899 100644 --- a/examples/auth/02_password_hash.cpp +++ b/examples/auth/02_password_hash.cpp @@ -16,54 +16,33 @@ #include -namespace +int main() { - void print_auth_error(const rixlib::auth::AuthError &error) - { - rix.debug.eprint( - "auth error:", - rixlib::auth::to_string(error.code()), - error.message()); - } - - int run_example() - { - rix.debug.print("== rix/auth password hashing =="); - - const auto password = "correct-password"; - - auto hashed = rix.auth.password.hash(password); + rix.debug.print("== rix/auth password hashing =="); - if (hashed.failed()) - { - print_auth_error(hashed.error()); - return 1; - } + const auto password = "correct-password"; + auto hashed = rix.auth.password.hash(password); - const bool valid = rix.auth.password.verify( - password, - hashed.value()); - - const bool invalid = rix.auth.password.verify( - "wrong-password", - hashed.value()); + if (hashed.failed()) + { + const auto &error = hashed.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } - rix.debug.print("hash:", hashed.value()); - rix.debug.print("valid password:", valid ? "yes" : "no"); - rix.debug.print("wrong password:", invalid ? "yes" : "no"); + const bool valid = rix.auth.password.verify(password, hashed.value()); + const bool invalid = rix.auth.password.verify("wrong-password", hashed.value()); - if (!valid || invalid) - { - rix.debug.eprint("ERROR:", "password verification failed"); - return 1; - } + rix.debug.print("hash:", hashed.value()); + rix.debug.print("valid password:", valid ? "yes" : "no"); + rix.debug.print("wrong password:", invalid ? "yes" : "no"); - rix.debug.print("OK:", "password verification successful"); - return 0; + if (!valid || invalid) + { + rix.debug.eprint("ERROR:", "password verification failed"); + return 1; } -} // namespace -int main() -{ - return run_example(); + 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 index 762a97a..7c8b104 100644 --- a/examples/auth/03_session_refresh_logout.cpp +++ b/examples/auth/03_session_refresh_logout.cpp @@ -16,86 +16,68 @@ #include -namespace +int main() { - void print_auth_error(const rixlib::auth::AuthError &error) - { - rix.debug.eprint( - "auth error:", - rixlib::auth::to_string(error.code()), - error.message()); - } - - int run_example() - { - rix.debug.print("== rix/auth session refresh and logout =="); + rix.debug.print("== rix/auth session refresh and logout =="); - auto auth = rix.auth.memory(); + auto auth = rix.auth.memory(); + auto registered = auth.register_user({"grace@example.com", "correct-password"}); - auto registered = auth.register_user( - rixlib::auth::RegisterRequest{ - "grace@example.com", - "correct-password"}); - - if (registered.failed()) - { - print_auth_error(registered.error()); - return 1; - } - - auto login = auth.login( - rixlib::auth::LoginRequest{ - "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; + } - if (login.failed()) - { - print_auth_error(login.error()); - return 1; - } + auto login = auth.login({"grace@example.com", "correct-password"}); - const auto session_id = login.value().session.id(); + 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("created session:", session_id); - rix.debug.print("expires at:", login.value().session.expires_at()); + const auto session_id = login.value().session.id(); - auto refreshed = auth.refresh_session(session_id); + rix.debug.print("created session:", session_id); + rix.debug.print("expires at:", login.value().session.expires_at()); - if (refreshed.failed()) - { - print_auth_error(refreshed.error()); - return 1; - } + auto refreshed = auth.refresh_session(session_id); - rix.debug.print("----------------------------------------"); - rix.debug.print("OK:", "session refreshed"); - rix.debug.print("new expires at:", refreshed.value().expires_at()); + if (refreshed.failed()) + { + const auto &error = refreshed.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } - auto logout = auth.logout(session_id); + rix.debug.print("----------------------------------------"); + rix.debug.print("OK:", "session refreshed"); + rix.debug.print("new expires at:", refreshed.value().expires_at()); - if (logout.failed()) - { - print_auth_error(logout.error()); - return 1; - } + auto logout = auth.logout(session_id); - rix.debug.print("----------------------------------------"); - rix.debug.print("OK:", "logout successful"); + if (logout.failed()) + { + const auto &error = logout.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; + } - auto after_logout = auth.authenticate_session(session_id); + rix.debug.print("----------------------------------------"); + rix.debug.print("OK:", "logout successful"); - if (after_logout.ok()) - { - rix.debug.eprint("ERROR:", "session should not be valid after logout"); - return 1; - } + auto after_logout = auth.authenticate_session(session_id); - rix.debug.print("session rejected after logout"); - return 0; + if (after_logout.ok()) + { + rix.debug.eprint("ERROR:", "session should not be valid after logout"); + return 1; } -} // namespace -int main() -{ - return run_example(); + 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 index e736d30..c6a7ff5 100644 --- a/examples/auth/04_token_issue.cpp +++ b/examples/auth/04_token_issue.cpp @@ -16,45 +16,34 @@ #include -namespace +int main() { - void print_auth_error(const rixlib::auth::AuthError &error) - { - rix.debug.eprint("auth error:", rixlib::auth::to_string(error.code()), error.message()); - } - - int run_example() - { - rix.debug.print("== rix/auth token issue =="); - - auto auth = rix.auth.memory(); - auto registered = auth.register_user(rixlib::auth::RegisterRequest{"linus@example.com", "correct-password"}); - - if (registered.failed()) - { - print_auth_error(registered.error()); - return 1; - } + rix.debug.print("== rix/auth token issue =="); - auto token = auth.issue_token(registered.value().id()); + auto auth = rix.auth.memory(); + auto registered = auth.register_user({"linus@example.com", "correct-password"}); - if (token.failed()) - { - print_auth_error(token.error()); - return 1; - } + 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("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()); + auto token = auth.issue_token(registered.value().id()); - return 0; + if (token.failed()) + { + const auto &error = token.error(); + rix.debug.eprint("auth error:", rix.auth.error.to_string(error), error.message()); + return 1; } -} // namespace -int main() -{ - return run_example(); + 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/vix.json b/vix.json index 9f22764..e51f8bb 100644 --- a/vix.json +++ b/vix.json @@ -13,7 +13,7 @@ }, { "id": "rix/auth", - "version": "0.2.0" + "version": "0.3.0" } ], "description": "Unified Rix facade for Vix C++ projects.", diff --git a/vix.lock b/vix.lock index 53bb8aa..ba7e10a 100644 --- a/vix.lock +++ b/vix.lock @@ -1,13 +1,13 @@ { "dependencies": [ { - "commit": "dd4d859d5c50ab37d6e31c1e3c1f514bce35d699", - "hash": "0177bce2329f11222aa678f9393264608fc3bf0ad29ae8c5b423f8a303f8bc13", + "commit": "c7406e0cc5b16e60acb8dfcfd8b33fa249551e18", + "hash": "f34b37a2a5da3cf5dc1a26337b6c1193014eec4633346da5677782dea916bcb7", "id": "rix/auth", "repo": "https://github.com/rixcpp/auth", - "requested": "0.2.0", - "tag": "v0.2.0", - "version": "0.2.0" + "requested": "0.3.0", + "tag": "v0.3.0", + "version": "0.3.0" }, { "commit": "e5e7f817a37d815c61962e6be88259f105d4f54d", From 3f6ffb78050fa02677421d8d6a8a2edcd4a9f280 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Wed, 10 Jun 2026 09:53:19 +0300 Subject: [PATCH 08/12] docs(auth): simplify public auth facade examples --- vix.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vix.json b/vix.json index e51f8bb..4ca11fd 100644 --- a/vix.json +++ b/vix.json @@ -36,5 +36,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.7.0" + "version": "0.8.0" } From 2e9e190621285710f3adc9582d9b2a1b576755ae Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Wed, 10 Jun 2026 09:58:14 +0300 Subject: [PATCH 09/12] Update README --- README.md | 69 ++++++++++++++----------------------------------------- 1 file changed, 17 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 897935d..80840ed 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,7 @@ int main() rix.debug.log("loaded {} rows", table.size()); auto auth = rix.auth.memory(); - - auto registered = auth.register_user({ - "ada@example.com", - "correct-password"}); + auto registered = auth.register_user({"ada@example.com","correct-password"}); if (registered.ok()) { @@ -91,9 +88,7 @@ vix install int main() { rix.debug.print("Hello", "Rix"); - const auto message = rix.debug.format("Package: {}", "rix/rix"); - rix.debug.log("message: {}", message); return 0; @@ -165,38 +160,24 @@ int main() int main() { auto auth = rix.auth.memory(); - - auto registered = auth.register_user({ - "ada@example.com", - "correct-password"}); + 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()); + 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"}); + 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()); - + rix.debug.eprint("auth error:",rix.auth.error.to_string(error),error.message()); return 1; } @@ -220,22 +201,12 @@ int main() if (hashed.failed()) { const auto &error = hashed.error(); - - rix.debug.eprint( - "auth error:", - rix.auth.error.to_string(error), - error.message()); - + 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()); + 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"); @@ -303,18 +274,12 @@ For custom stores where Rix should own the store lifetime: 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)); +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()); + rix.debug.eprint("auth error:",rix.auth.error.to_string(error),error.message()); return 1; } @@ -333,10 +298,7 @@ Correct usage: ```cpp auto users = rix.auth.stores.memory_users(); auto sessions = rix.auth.stores.memory_sessions(); - -auto auth = rix.auth.create( - *users, - *sessions); +auto auth = rix.auth.create(*users,*sessions); ``` In this example, `users` and `sessions` must remain alive while `auth` is used. @@ -358,7 +320,8 @@ or: ```cpp auto auth = rix.auth.managed( rix.auth.stores.memory_users(), - rix.auth.stores.memory_sessions()); + rix.auth.stores.memory_sessions() +); ``` Use `create(...)` only when you intentionally want to manage store lifetime yourself. @@ -456,11 +419,13 @@ int main() rixlib::auth::Auth auth{ users, sessions, - rixlib::auth::AuthConfig::development()}; + rixlib::auth::AuthConfig::development() + }; auto registered = auth.register_user({ "ada@example.com", - "correct-password"}); + "correct-password" + }); return registered.ok() ? 0 : 1; } From 8a7906f2d617ddfb334f61c4005b2ef96b7b3e87 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Wed, 10 Jun 2026 15:22:15 +0300 Subject: [PATCH 10/12] feat(pdf): expose PDF module through Rix facade --- CHANGELOG.md | 64 +++++ CMakeLists.txt | 1 + README.md | 359 +++++++++++++++++++++++++++-- examples/CMakeLists.txt | 5 +- examples/pdf/01_basic.cpp | 49 ++++ examples/pdf/02_text.cpp | 72 ++++++ examples/pdf/03_table.cpp | 76 ++++++ examples/pdf/04_drawing.cpp | 104 +++++++++ examples/pdf/05_metadata.cpp | 58 +++++ examples/pdf/06_make_text.cpp | 38 +++ examples/pdf/07_error_handling.cpp | 43 ++++ examples/pdf/CMakeLists.txt | 62 +++++ include/rix.hpp | 26 ++- vix.json | 6 +- vix.lock | 9 + 15 files changed, 944 insertions(+), 28 deletions(-) create mode 100644 examples/pdf/01_basic.cpp create mode 100644 examples/pdf/02_text.cpp create mode 100644 examples/pdf/03_table.cpp create mode 100644 examples/pdf/04_drawing.cpp create mode 100644 examples/pdf/05_metadata.cpp create mode 100644 examples/pdf/06_make_text.cpp create mode 100644 examples/pdf/07_error_handling.cpp create mode 100644 examples/pdf/CMakeLists.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e69de29..21d6b02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -0,0 +1,64 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## 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 9365224..ec9ee86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ target_link_libraries(rix rix::csv rix::debug rix::auth + rix::pdf ) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) diff --git a/README.md b/README.md index 80840ed..e7cc157 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Rix Rix is the unified userland library layer for Vix.cpp. + It gives Vix C++ projects a single optional facade object: ```cpp @@ -15,13 +16,32 @@ int main() rix.debug.log("loaded {} rows", table.size()); auto auth = rix.auth.memory(); - auto registered = auth.register_user({"ada@example.com","correct-password"}); + + 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; } ``` @@ -40,6 +60,7 @@ Rix is built around two levels. @rix/csv @rix/debug @rix/auth +@rix/pdf @rix/config @rix/table ``` @@ -67,6 +88,13 @@ rix.auth.password.hash(...) rix.auth.password.verify(...) rix.auth.config.production(...) rix.auth.error.to_string(...) + +rix.pdf.document() +rix.pdf.save(...) +rix.pdf.write(...) +rix.pdf.make_text(...) +rix.pdf.image.load_jpeg(...) +rix.pdf.error.to_string(...) ``` Public examples should prefer the unified `rix` facade. @@ -88,7 +116,9 @@ vix install int main() { rix.debug.print("Hello", "Rix"); + const auto message = rix.debug.format("Package: {}", "rix/rix"); + rix.debug.log("message: {}", message); return 0; @@ -102,6 +132,7 @@ int main() | `@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. | ## CSV example @@ -160,24 +191,38 @@ int main() int main() { auto auth = rix.auth.memory(); - auto registered = auth.register_user({"ada@example.com","correct-password"}); + + 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()); + + 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"}); + 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()); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); + return 1; } @@ -201,12 +246,22 @@ int main() if (hashed.failed()) { const auto &error = hashed.error(); - rix.debug.eprint("auth error:",rix.auth.error.to_string(error),error.message()); + + 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()); + 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"); @@ -215,6 +270,145 @@ int main() } ``` +## 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 @@ -274,12 +468,18 @@ For custom stores where Rix should own the store lifetime: 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)); +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()); + + rix.debug.eprint( + "auth error:", + rix.auth.error.to_string(error), + error.message()); return 1; } @@ -298,7 +498,10 @@ Correct usage: ```cpp auto users = rix.auth.stores.memory_users(); auto sessions = rix.auth.stores.memory_sessions(); -auto auth = rix.auth.create(*users,*sessions); + +auto auth = rix.auth.create( + *users, + *sessions); ``` In this example, `users` and `sessions` must remain alive while `auth` is used. @@ -320,12 +523,66 @@ or: ```cpp auto auth = rix.auth.managed( rix.auth.stores.memory_users(), - rix.auth.stores.memory_sessions() -); + 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. @@ -419,18 +676,45 @@ int main() rixlib::auth::Auth auth{ users, sessions, - rixlib::auth::AuthConfig::development() - }; + rixlib::auth::AuthConfig::development()}; auto registered = auth.register_user({ "ada@example.com", - "correct-password" - }); + "correct-password"}); return registered.ok() ? 0 : 1; } ``` +And: + +```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 `. @@ -445,11 +729,19 @@ rix/ │ ├── 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 +│ ├── 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/ @@ -459,7 +751,10 @@ rix/ │ ├── debug/ │ │ ├── README.md │ │ └── vix.json -│ └── auth/ +│ ├── auth/ +│ │ ├── README.md +│ │ └── vix.json +│ └── pdf/ │ ├── README.md │ └── vix.json ├── CMakeLists.txt @@ -484,6 +779,7 @@ Second, it is the official human-readable index of Rix packages: packages/csv packages/debug packages/auth +packages/pdf ``` The real source code of each independent package lives in its own repository: @@ -492,6 +788,7 @@ The real source code of each independent package lives in its own repository: https://github.com/rixcpp/csv https://github.com/rixcpp/debug https://github.com/rixcpp/auth +https://github.com/rixcpp/pdf ``` ## Build @@ -509,10 +806,19 @@ Because this repository contains multiple examples, run one explicitly: 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 @@ -556,6 +862,13 @@ rix.auth.config.production(...) rix.auth.error.to_string(...) rix.auth.stores.memory_users(...) rix.auth.stores.memory_sessions(...) + +rix.pdf.document(...) +rix.pdf.write(...) +rix.pdf.save(...) +rix.pdf.make_text(...) +rix.pdf.image.load_jpeg(...) +rix.pdf.error.to_string(...) ``` ## License diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0fd3501..b821f50 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,9 +20,10 @@ add_executable(rix_debug_example debug.cpp ) -add_subdirectory(auth) - target_link_libraries(rix_debug_example PRIVATE rix::rix ) + +add_subdirectory(auth) +add_subdirectory(pdf) 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 index ed0ce43..ab0f857 100644 --- a/include/rix.hpp +++ b/include/rix.hpp @@ -5,8 +5,9 @@ * This header exposes the global `rix` object. * * The package `@rix/rix` is the optional unified facade for Rix packages. - * Individual packages such as `@rix/csv`, `@rix/debug`, and `@rix/auth` - * stay independent, but this facade mounts them into one object-style API. + * Individual packages such as `@rix/csv`, `@rix/debug`, `@rix/auth`, and + * `@rix/pdf` stay independent, but this facade mounts them into one + * object-style API. * * Example: * @@ -17,6 +18,11 @@ * * auto auth = rix.auth.memory(); * auto user = auth.register_user({"ada@example.com", "correct-password"}); + * + * 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"); * @endcode * * @author Gaspard Kirira @@ -28,6 +34,7 @@ #include #include #include +#include namespace rixlib { @@ -53,6 +60,11 @@ namespace rixlib * @brief Authentication component. */ rixlib::auth::AuthModule auth{}; + + /** + * @brief PDF generation and document utilities component. + */ + rixlib::pdf::PdfModule pdf{}; }; } @@ -67,11 +79,21 @@ namespace rixlib * rix.debug.format(...) * rix.debug.log(...) * rix.debug.inspect(...) + * * rix.auth.memory() * rix.auth.database(db) + * rix.auth.managed(users, sessions) * rix.auth.create(users, sessions) * rix.auth.password.hash(...) * rix.auth.config.production() + * rix.auth.error.to_string(...) + * + * rix.pdf.document() + * rix.pdf.write(doc) + * rix.pdf.save(doc, "output.pdf") + * rix.pdf.make_text("text.pdf", "Hello", "Title") + * rix.pdf.image.load_jpeg("image.jpg") + * rix.pdf.error.to_string(...) * @endcode */ inline constexpr rixlib::Rix rix{}; diff --git a/vix.json b/vix.json index 4ca11fd..6219dc3 100644 --- a/vix.json +++ b/vix.json @@ -14,6 +14,10 @@ { "id": "rix/auth", "version": "0.3.0" + }, + { + "id": "rix/pdf", + "version": "0.1.0" } ], "description": "Unified Rix facade for Vix C++ projects.", @@ -36,5 +40,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.8.0" + "version": "0.9.0" } diff --git a/vix.lock b/vix.lock index ba7e10a..d6dccf7 100644 --- a/vix.lock +++ b/vix.lock @@ -26,6 +26,15 @@ "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 From a062b30ab9c7bf76d83f89011c568640ad97669d Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Fri, 12 Jun 2026 09:44:27 +0300 Subject: [PATCH 11/12] feat(facade): add selective module mounting for v0.9.1 --- README.md | 490 +++++++++++++++++++++++++++++++++++++++--------- include/rix.hpp | 117 ++++++++---- vix.json | 2 +- 3 files changed, 481 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index e7cc157..0f2e687 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Rix is the unified userland library layer for Vix.cpp. -It gives Vix C++ projects a single optional facade object: +It gives Vix C++ projects a set of optional userland packages and a single clean facade object: ```cpp #include @@ -10,38 +10,9 @@ It gives Vix C++ projects a single optional facade object: 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; } ``` @@ -52,10 +23,23 @@ Vix provides the runtime, CLI, build workflow, registry integration, and core fo 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 @@ -65,50 +49,79 @@ Rix is built around two levels. @rix/table ``` -These are independent packages. +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 is the unified facade package. - -The facade mounts independent packages into one object-style API: +This package provides the optional unified facade: ```cpp rix.csv.parse(...) rix.debug.print(...) -rix.debug.format(...) -rix.debug.log(...) -rix.debug.inspect(...) - rix.auth.memory(...) -rix.auth.database(...) -rix.auth.password.hash(...) -rix.auth.password.verify(...) -rix.auth.config.production(...) -rix.auth.error.to_string(...) - rix.pdf.document() -rix.pdf.save(...) -rix.pdf.write(...) -rix.pdf.make_text(...) -rix.pdf.image.load_jpeg(...) -rix.pdf.error.to_string(...) ``` +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 ``` -## Use +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 @@ -116,15 +129,118 @@ vix install 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 | @@ -134,6 +250,54 @@ int main() | `@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 @@ -163,6 +327,8 @@ int main() } ``` +--- + ## Debug example ```cpp @@ -183,6 +349,8 @@ int main() } ``` +--- + ## Auth example ```cpp @@ -234,6 +402,8 @@ int main() } ``` +--- + ## Auth password example ```cpp @@ -270,6 +440,8 @@ int main() } ``` +--- + ## PDF example ```cpp @@ -306,10 +478,13 @@ int main() } rix.debug.print("created:", "rix_pdf_basic.pdf"); + return 0; } ``` +--- + ## PDF text example ```cpp @@ -357,6 +532,8 @@ int main() } ``` +--- + ## PDF table example ```cpp @@ -409,6 +586,8 @@ int main() } ``` +--- + ## Auth facade API ```cpp @@ -446,6 +625,8 @@ rix.auth.stores.database_sessions(db) rix.auth.version() ``` +--- + ## Auth service creation Most applications should use the safe managed APIs. @@ -528,6 +709,8 @@ auto auth = rix.auth.managed( Use `create(...)` only when you intentionally want to manage store lifetime yourself. +--- + ## PDF facade API ```cpp @@ -552,6 +735,8 @@ rix.pdf.writer.create() rix.pdf.version() ``` +--- + ## PDF error handling `rix.pdf` uses explicit result values for normal failures. @@ -583,6 +768,8 @@ if (bytes.ok()) } ``` +--- + ## Print and format `rix.debug.print` does not replace `{}` placeholders. @@ -614,11 +801,13 @@ Or use `rix.debug.log` directly: rix.debug.log("Hello {}", "Rix"); ``` +--- + ## Independent packages -Each Rix module can also be used independently. +Each Rix package can also be used independently. -For example: +### CSV ```bash vix add @rix/csv @@ -631,13 +820,14 @@ vix install int main() { rixlib::csv::Csv csv; + auto table = csv.parse("name,language\nAda,C++\n"); - return 0; + return table.empty() ? 1 : 0; } ``` -And: +### Debug ```bash vix add @rix/debug @@ -658,7 +848,7 @@ int main() } ``` -And: +### Auth ```bash vix add @rix/auth @@ -686,7 +876,7 @@ int main() } ``` -And: +### PDF ```bash vix add @rix/pdf @@ -719,6 +909,8 @@ Independent packages expose their own lower-level APIs. For the unified public facade, prefer `@rix/rix` and `#include `. +--- + ## Repository layout ```txt @@ -763,6 +955,8 @@ rix/ └── README.md ``` +--- + ## Role of this repository This repository has two roles. @@ -791,6 +985,8 @@ https://github.com/rixcpp/auth https://github.com/rixcpp/pdf ``` +--- + ## Build ```bash @@ -798,6 +994,8 @@ vix install vix build --build-target all -v ``` +--- + ## Run examples Because this repository contains multiple examples, run one explicitly: @@ -821,56 +1019,168 @@ vix run rix_pdf_06_make_text vix run rix_pdf_07_error_handling ``` +--- + ## Tests ```bash vix tests ``` -## Package model +--- + +## 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 -> runtime, CLI, build workflow, registry client -Rix -> userland libraries and unified facade -Registry -> package metadata and version resolution +Vix = core runtime and primitives +Rix = optional userland libraries ``` -Rix keeps packages modular while still offering one clean entry point for users who want the full userland layer. +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 -## Current facade API +Package name: + +```txt +@rix/name +``` + +Include path: ```cpp -rix.csv.parse(...) -rix.csv.write(...) +#include +``` -rix.debug.print(...) -rix.debug.eprint(...) -rix.debug.dprint(...) -rix.debug.sprint(...) -rix.debug.format(...) -rix.debug.log(...) -rix.debug.inspect(...) +Namespace: -rix.auth.memory(...) -rix.auth.database(...) -rix.auth.managed(...) -rix.auth.create(...) -rix.auth.password.hash(...) -rix.auth.password.verify(...) -rix.auth.config.development(...) -rix.auth.config.production(...) -rix.auth.error.to_string(...) -rix.auth.stores.memory_users(...) -rix.auth.stores.memory_sessions(...) +```cpp +rixlib::name +``` + +Facade access: + +```cpp +rix.name +``` + +Examples: -rix.pdf.document(...) -rix.pdf.write(...) -rix.pdf.save(...) -rix.pdf.make_text(...) -rix.pdf.image.load_jpeg(...) -rix.pdf.error.to_string(...) +```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/include/rix.hpp b/include/rix.hpp index ab0f857..9e6b1e8 100644 --- a/include/rix.hpp +++ b/include/rix.hpp @@ -4,26 +4,28 @@ * * This header exposes the global `rix` object. * - * The package `@rix/rix` is the optional unified facade for Rix packages. - * Individual packages such as `@rix/csv`, `@rix/debug`, `@rix/auth`, and - * `@rix/pdf` stay independent, but this facade mounts them into one - * object-style API. + * By default, it keeps the historical behavior and enables all available + * mounted Rix packages. * - * Example: + * 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"); - * rix.debug.print("loaded rows:", table.size()); - * rix.debug.log("loaded {} rows", table.size()); + * @endcode * - * auto auth = rix.auth.memory(); - * auto user = auth.register_user({"ada@example.com", "correct-password"}); + * Available feature macros: * - * 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"); - * @endcode + * - 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 */ @@ -31,69 +33,110 @@ #ifndef RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED #define RIXCPP_RIX_INCLUDE_RIX_HPP_INCLUDED -#include +/* + * 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 independent Rix components into one object. + * 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. * - * This object allows API usage such as: + * Default usage: * * @code - * rix.csv.parse(...) - * rix.debug.print(...) - * rix.debug.format(...) - * rix.debug.log(...) - * rix.debug.inspect(...) - * - * rix.auth.memory() - * rix.auth.database(db) - * rix.auth.managed(users, sessions) - * rix.auth.create(users, sessions) - * rix.auth.password.hash(...) - * rix.auth.config.production() - * rix.auth.error.to_string(...) - * - * rix.pdf.document() - * rix.pdf.write(doc) - * rix.pdf.save(doc, "output.pdf") - * rix.pdf.make_text("text.pdf", "Hello", "Title") - * rix.pdf.image.load_jpeg("image.jpg") - * rix.pdf.error.to_string(...) + * #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{}; diff --git a/vix.json b/vix.json index 6219dc3..17442bf 100644 --- a/vix.json +++ b/vix.json @@ -40,5 +40,5 @@ "namespace": "rix", "repository": "https://github.com/rixcpp/rix", "type": "header-only", - "version": "0.9.0" + "version": "0.9.1" } From 9362345a98e2828448d9c8899f0c51d4fd3cbe55 Mon Sep 17 00:00:00 2001 From: Gaspard Kirira Date: Fri, 12 Jun 2026 09:45:03 +0300 Subject: [PATCH 12/12] feat(facade): add selective module mounting for v0.9.1 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21d6b02..990b1f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ 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