# yaco **Repository Path**: RonxBulld/yaco ## Basic Information - **Project Name**: yaco - **Description**: Yaco - Yet Another C++ commandline Option library. - **Primary Language**: C++ - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-04-26 - **Last Updated**: 2026-09-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # yaco `yaco` is a header-only C++11 command-line library built around the declarative option model of **LLVM CommandLine 2.0**, with a small dependency-free parser kernel. Builtin `help`/`help-hidden`/`version` actions follow LLVM CommandLine by default — the parser renders them to stdout and exits with status 0 — and `exit_on_builtin(false)` restores application-owned control flow. Version **3.0** is a deep internal rewrite. The public DSL still looks like LLVM CommandLine, but the implementation is now schema-driven: ```text Opt + modifiers | v OptionConstruction |-> OptionBuilder ---> immutable OptionSpec `-> TypedBinding ---> TypedOptionNode | v RegistryState | compile on change v CompiledProgram / \ CompiledSchema runtime bindings / | \ NameIndex Positional Categories \ | / v ArgSource -> spelling match -> occurrence collection -> events -> ParseEngine | ParseOutcome | v ParseSession | typed prepare | constraints | validation barrier | internal commit | external sync -> callbacks ``` The parser does not vendor, inherit from, or call any third-party parser. Token recognition and its event stream, typed binding, registry lifetime, diagnostics, help rendering, and input-source tokenization are separate components; `ParseEngine` coordinates recognition events with session allocation and the commit pipeline. Documentation: - [`docs/YACO_API.md`](docs/YACO_API.md) — the public API: declarations, parsing, and diagnostics; - [`docs/YACO_3_ARCHITECTURE.md`](docs/YACO_3_ARCHITECTURE.md) — kernel architecture and invariants. **Current quality baseline (3.0.0):** 21 CTest scenarios: 19 `unit` tests and 2 `integration` tests. The integration tests build and run build-tree consumers: one uses the exported build-tree package and one uses the source tree. An installed-prefix consumer is a separate manual check. The checked-in LLVM coverage gate requires at least 80% for both lines and branches; run the coverage command below to obtain the current toolchain-specific report. ## TL;DR `yaco` is a **header-only, dependency-free C++11 command-line library** that keeps LLVM CommandLine's compact declarative option style without pulling in the LLVM support library or a third-party parser runtime. The parser kernel is schema-driven: `Spec -> Schema -> Session -> Binding -> Commit`. In practice: - declare typed options with `yaco::Opt` / `yaco::List`; - use positional arguments, prefix options, explicit short-option grouping, named/unnamed alternatives, environment parsing, callbacks, and external storage; - organize help with `OptionCategory`, `Hidden`, and `ReallyHidden`; - handle `--help`, `--help-hidden`, and `--version` LLVM-style: by default the parser renders them to stdout and exits with status 0; call `exit_on_builtin(false)` to receive the action through `ParseResult` instead; - configure option prefix characters per parser or per option with `prefix_style(...)`: `-` (default), Windows-style `-/`, or domain styles such as `+`; declaration names remain prefix-free for their effective style; - organize options into hierarchical `Command` scopes with aliases and contextual help; - enforce prepared cross-option rules with `Constraint` and copyable `Candidate` snapshots; - inspect structured `Diagnostic` records in addition to the compatible text error; - add per-option pre-commit validation with `validate_with(...)`; - use typed alternatives with `yaco::val(...)` instead of integer-encoding application values; - rely on a precise transaction boundary: syntax, conversion, occurrence, and validator failures do not mutate committed option state. External `location` synchronization and callbacks execute only after all internal values commit and therefore are intentionally **post-commit side effects**, not rollback participants. Class-valued `Opt` uses composition: it does not inherit from `T`, which allows `final` application types. Use `opt->member` or `opt.getValue().member` for member access. `List` keeps common vector-like forwarding (`size`, `empty`, `operator[]`, iterators). Version **3.0.0** has C++11 as its minimum language standard. The formal regression matrix covers GCC and Clang in C++11, C++14, C++17, C++20, and C++23 modes. The compiler-matrix helper requires CMake and CTest 3.20 or newer; the standalone project itself still declares CMake 3.10 as its minimum. The checked-in Travis configuration declares ten compiler-standard jobs plus an independent Clang C++11 coverage job. A local result does not equal a Travis remote result; only the actual Travis job logs can establish remote success. The coverage gate enforces an 80% minimum for both lines and branches. If you only need to start using yaco, continue with [QUICK START](#quick-start). For the full API reference, see [`docs/YACO_API.md`](docs/YACO_API.md). For the architectural rationale, see [`docs/YACO_3_ARCHITECTURE.md`](docs/YACO_3_ARCHITECTURE.md). ## QUICK START ### 1. Add yaco to the build `yaco` is header-only. The shortest direct integration is to add `include/` to your compiler's include path and include the umbrella header: ```cpp #include ``` With CMake, consume either the source tree: ```cmake add_subdirectory(path/to/yaco) target_link_libraries(my_tool PRIVATE yaco::yaco) ``` or an installed package: ```cmake find_package(yaco 3.0 CONFIG REQUIRED) target_link_libraries(my_tool PRIVATE yaco::yaco) ``` ### 2. Declare options and parse argv ```cpp #include #include #include #include int main(int argc, char **argv) { yaco::Parser parser( "compiler", yaco::overview("Small compiler driver"), yaco::version("compiler 1.0")); const yaco::OptionCategory general("General Options"); const yaco::OptionCategory internal( "Internal Options", "Developer-only diagnostics."); yaco::Opt verbose( "v,verbose", yaco::cat(general), yaco::desc("enable verbose diagnostics"), yaco::inject(parser)); yaco::Opt jobs( "j,jobs", yaco::cat(general), yaco::init(1), yaco::value_desc("N"), yaco::inject(parser)); yaco::Opt trace( "trace-parser", yaco::cat(internal), yaco::Hidden, yaco::desc("trace internal parser decisions"), yaco::inject(parser)); yaco::Opt input( yaco::Positional, yaco::Required, yaco::value_desc("INPUT"), yaco::inject(parser)); yaco::ParseResult result = parser.parse(argc, argv); if (!result) { std::cerr << result.error << "\n\n" << parser.help(); return EXIT_FAILURE; } // `-help` / `-help-hidden` / `-version` already printed to stdout and exited // with status 0, LLVM-CommandLine-style. Call `parser.exit_on_builtin(false)` // before parsing to receive those actions through `result.action` instead. std::cout << input << " jobs=" << static_cast(jobs) << " verbose=" << (static_cast(verbose) ? "true" : "false") << '\n'; return EXIT_SUCCESS; } ``` ### 2b. Parse an environment variable with argv Environment parsing is folded into the same `parse` call, following LLVM CommandLine 12+ (which removed the standalone `ParseEnvironmentOptions` and folded environment parsing into `ParseCommandLineOptions(..., EnvVar)`): ```cpp yaco::ParseResult result = parser.parse(argc, argv, "COMPILER_OPTIONS"); ``` The variable's text is tokenized and parsed before the argv tokens, so command-line arguments win for options that give precedence to later occurrences. The environment string uses whitespace tokenization with single/double quoting and backslash escaping, so `COMPILER_OPTIONS="--jobs=8" ./compiler input.cpp` behaves as expected. An empty or unset variable parses argv alone. ### 3. Build and run For a direct header-only build: ```bash c++ -std=c++11 -I/path/to/yaco/include main.cpp -o compiler ./compiler --help ./compiler -v --jobs=4 input.cpp ./compiler --trace-parser input.cpp ./compiler --help-hidden COMPILER_OPTIONS="--jobs=8" ./compiler input.cpp ``` The third command is accepted even though `--trace-parser` is absent from normal `--help`; it appears in `--help-hidden` because the option was declared with `yaco::Hidden`. Normal named options accept GNU long spelling and LLVM-style single-dash long spelling: ```text --verbose -verbose --jobs=8 --jobs 8 -j=8 -j 8 -- ``` `-j8` is **not** inferred merely because `j` is one character. Attached compiler/linker syntax is declared explicitly with `Prefix`. `--` disables option recognition and routes all following tokens through positional allocation. ## Requirements and integration Runtime/library requirements are intentionally small: - C++11 or newer; - the C++ standard library; - no parser, regex, ICU, Boost, or generated-code dependency. `yaco` is header-only. For a direct integration, add `include/` to the compiler include path and include the umbrella header: ```cpp #include ``` With CMake, either consume the source tree: ```cmake add_subdirectory(path/to/yaco) target_link_libraries(my_tool PRIVATE yaco::yaco) ``` or install/export the package and use: ```cmake find_package(yaco 3.0 CONFIG REQUIRED) target_link_libraries(my_tool PRIVATE yaco::yaco) ``` The exported target carries the include directory and the `cxx_std_11` feature requirement. Building yaco's own tests requires CMake 3.10 or newer. Coverage report generation additionally requires CMake and CTest 3.20 or newer, Clang, `llvm-profdata`, and `llvm-cov`. ## Design goals - **LLVM CommandLine 2.0 semantics, not LLVM weight**: declarative typed options, occurrence/value policies, positional arguments, alternatives, prefix/grouping modes, list positions, environment parsing, and generated help. - **Portable C++11**: standard library only; no parser runtime dependency, generated sources, regex engine, or platform-specific backend. - **Explicit syntax**: `Grouping`, `CommaSeparated`, `Prefix`, and `CommaJoined` are declared behavior. The parser does not infer surprising syntax from a one-letter name or from `std::vector`. - **Schema-driven core**: declaration objects normalize into `OptionSpec`; a parser-local registry lazily compiles immutable lookup/positional/category structures, and `RegistryState` is the sole persistent owner of the parser-wide prefix style; each parse owns an isolated `ParseSession`. - **Precise transaction boundary**: syntax, occurrence, conversion, and validator failures and constraints happen before any internal option value commits. External storage and callbacks are post-commit sinks and are deliberately outside rollback. - **Stable constraint snapshots**: `Candidate` owns an ordinary copy of `T` and its occurrence metadata, so a constraint can copy or retain it after the parse session is gone. This is not a deep copy of pointers, references, views, or shared objects held inside `T`. A copy/allocation exception escapes before commit; application-visible side effects of `T`'s copy operation are not rollback participants. A constraint callable must be directly callable through `const F&`; this outer const check does not make captured application state immutable. - **Process policy**: by default the parser follows LLVM CommandLine — builtin `help`/`help-hidden`/`version` render to stdout and call `exit(0)`. Disable that with `exit_on_builtin(false)` and builtin actions and input errors are returned as constrained `ParseOutcome` values for the application to handle. Input errors never trigger an exit; only the public throwing adapter creates a parse exception for normal input failure. - **Safe option lifetime**: option registration uses a weak registry handle and monotonic ID referring to an address-stable internal node. Public option and constraint facades own their nodes; registry entries and compiled snapshots keep only weak ownership, raw pointers, and registration IDs. Each public parse or reset atomically verifies those IDs and pointers against the live registry, then holds transient owner pins until all raw calls for that operation finish. A generation guard rejects a changed snapshot before another runtime binding is used. Direct detail-level raw bindings remain caller-owned and are not implicitly pinned. `ResetAllOptions()` and `Opt::Reset()` apply the same in-flight phase and restore the previous idle/callback phase around reset and external sync. Parser grammar, registry, cache, phases, commit tracking, and facade state share one state owner, so releasing the last `Parser` from a callback or version printer cannot invalidate the remainder of that public call. - **Trusted command selection**: command handles retain an independent registry identity token, and parse results retain that token plus a private selected command ID. Selection queries do not trust the mutable public command path. - **Side-effect-free failed declarations**: construction-time `init(...)` and `location(...)` synchronization is deferred until registration succeeds, so a rejected declaration does not write its initial value to external storage. - **Presentation is separate from grammar**: help/category/visibility rendering consumes the compiled schema but does not participate in option lookup or typed conversion. Diagnostic and help spelling formatters consume structured option facts; recognition never parses their output strings. ## Internal architecture The 3.0 kernel is organized by responsibility rather than by the old Parser/Option object boundary: ```text include/yaco.h umbrella + global facade include/yaco/core.h public common enums/result/diagnostic/category include/yaco/alternatives.h typed alternative descriptors (`val`) include/yaco/error.h Status + specification/parse exception hierarchy include/yaco/value.h built-in conversion + value_parser include/yaco/option.h public Opt/List DSL and modifier facade include/yaco/command.h public command-tree handles include/yaco/constraint.h public cross-option constraint API include/yaco/parser.h Parser facade + shared state + cache + last_* compatibility include/yaco/detail/spec.h normalized OptionSpec + OptionBuilder include/yaco/detail/spelling.h diagnostic/help spelling formatters include/yaco/detail/builtin.h canonical builtin action/help metadata include/yaco/detail/model.h OptionId/state/registration/registry model include/yaco/detail/schema.h CompiledProgram/CompiledSchema/scopes/indexes include/yaco/detail/arg_source.h normalized token-source abstraction include/yaco/detail/recognition.h spelling match, occurrence collection, events include/yaco/detail/environment_lexer.h environment -> tokens include/yaco/detail/parse_engine.h stateless grammar/session/transaction coordinator include/yaco/detail/binding.h construction context + typed runtime node/binding include/yaco/detail/help.h help model rendering ``` The ownership/data-flow rule is: ```text DSL -> construction context -> typed node -> Registry -> CompiledProgram | | | | CompiledSchema runtime bindings Command tree + constraints -> Registry -> CompiledProgram | | active scopes constraint table ArgSource -> command selection -> spelling match -> occurrence collection -> ParseEngine -> ParseSession | TypedBinding | commit -> sinks -> callbacks CompiledSchema -----------------> Help renderer ``` Important invariants: 1. grammar lookup never depends on `T`; 2. typed conversion never performs argv name recognition; 3. transient parse state belongs to `ParseSession`, not `Opt`; 4. `CompiledProgram` is rebuilt from one live-registry snapshot; its `CompiledSchema` owns copied grammar values while its aligned runtime table stores only raw nodes and registration IDs. Public parse/reset calls acquire transient pins from the live registry, so stale category metadata cannot survive after the last option in a category unregisters; 5. help rendering consumes schema metadata and cannot mutate parsing visibility; 6. option registration uses a weak registry lifetime, monotonic registration ID, stable runtime node, and `OptionId`-based session indexing rather than a public facade address; raw internal bindings have no implicit pin and are responsible for their own lifetime; 7. built-in help/version reservation, recognition, and presentation consume one canonical POD table instead of separate hard-coded copies. 8. command selection chooses one active path before scoped option recognition; 9. command results keep trusted registry/command identity separate from the mutable diagnostic path; 10. recognition first resolves each token to a deterministic structured spelling match, then a separate collector applies value policy and emits token-span events; `ParseEngine` owns event application/replay and coordinates positional, tail, validation, and commit sequencing. See [`docs/YACO_3_ARCHITECTURE.md`](docs/YACO_3_ARCHITECTURE.md) for the detailed component contract and extension points. ## Build, test, and coverage ### Normal development build The following commands use CMake 3.10-compatible configure, build, and test forms: ```bash mkdir -p build cd build cmake .. \ -DYACO_BUILD_TESTS=ON \ -DYACO_BUILD_EXAMPLES=ON \ -DYACO_ENABLE_WARNINGS=ON cmake --build . ctest --output-on-failure ``` Top-level CMake switches are: | Option | Standalone default | Meaning | | --- | ---: | --- | | `YACO_BUILD_EXAMPLES` | `ON` | build bundled examples/demos | | `YACO_BUILD_TESTS` | `ON` | build and register the CTest suite | | `YACO_ENABLE_INSTALL` | `ON` | generate install/export package targets | | `YACO_ENABLE_WARNINGS` | `ON` | enable strict warnings on yaco-owned targets | When yaco is included through `add_subdirectory()`, those standalone-oriented defaults are `OFF`, so embedding the library does not unexpectedly add tests, examples, install rules, or warning policy to the parent project. ### Compiler-standard regression matrix C++11 is the minimum language standard. The formal compiler regression matrix is the following GCC/Clang by language-mode combination: | Compiler | Language modes | | --- | --- | | `gcc` | C++11, C++14, C++17, C++20, C++23 | | `clang` | C++11, C++14, C++17, C++20, C++23 | The compiler-matrix helper requires CMake and CTest 3.20 or newer. The standalone project itself declares `cmake_minimum_required(VERSION 3.10)`. The local matrix entry point accepts no arguments for the full matrix, or a compiler and standard for one cell: ```bash # Run all GCC/Clang x C++11/14/17/20/23 cells. ./test/compiler_matrix.sh # Run one cell; the standard is 11, 14, 17, 20, or 23. ./test/compiler_matrix.sh --compiler "$(command -v g++)" --standard 17 # Optionally keep fresh per-cell build directories under a chosen root. ./test/compiler_matrix.sh --build-root /tmp/yaco-matrix ``` The checked-in `.travis.yml` invokes the same script in one-cell mode for each of its ten compiler-standard jobs. It is a separate remote configuration; a local matrix result must not be reported as a Travis result. `YACO_ENABLE_WARNINGS` enables strict warnings on yaco-owned executables. In release/CI jobs it is reasonable to add `-Werror` through the normal compiler flags. The regression suite is intentionally compiler-neutral and is regularly checked with both GCC and Clang. The current CTest inventory is split by labels: | Label | Tests | Purpose | | --- | ---: | --- | | `unit` | 15 | parser/value semantics, error paths, API surface, commands, constraints, help, categories, state/lifetime, architecture, multi-TU, prefix styles | | `integration` | 2 | build-tree exported `find_package(yaco)` and source-tree `add_subdirectory()` consumption | When `YACO_BUILD_TESTS` is enabled, configuration also runs a CMake 3.10 compatible pair of compile checks: a const-callable constraint fixture must compile and a direct mutable-lambda fixture must fail. These are configuration gates, so they intentionally do not add CTest entries. Both integration entries are real build-tree consumer runs, not registration-only checks. `find-package-test` builds a consumer with `find_package(yaco)` against the exported build-tree package (`yaco_DIR=${PROJECT_BINARY_DIR}`) and runs `library-test --flag --count 42`; `add-subdirectory-test` builds a consumer with `add_subdirectory(...)` and runs `library-test --value=11`. Neither test uses an installed prefix. From the repository root, useful focused runs are: ```bash cd build ctest -L unit --output-on-failure ctest -L integration --output-on-failure ctest -R 'value-parser|parser-edge|specification' --output-on-failure ``` The dedicated unit suites cover: - built-in value conversion, ranges, malformed values, stream extraction, and unsupported custom types; - option declaration/specification failures, duplicate modifiers, parser binding, storage binding, alternatives, categories, and `ConsumeAfter` restrictions; - exact names, single-dash long names, longest-prefix matching, explicit short grouping, positional allocation, `--`, occurrence policies, optional values, transactional rollback, and parser state; - environment quoting/escaping, missing variables, malformed environment text, and environment-driven help/version actions; - help usage shapes, category filtering, hidden options, alternative help, prefix/value formatting, overview/extra-help newline behavior, and all version output paths; - internal/external storage, callbacks, reset semantics, argv positions, parser destruction before option destruction, global facade functions, and multi-TU header-only linkage; - command-tree routing, aliases, scoped prefixes, contextual help, active-path option reuse, command tails, callback reentry, and transactional cross-option constraints; - schema-cache invalidation, live category reconstruction, `final` class-valued options, typed non-integer alternatives, pre-commit validators, explicit post-commit callback behavior, and structured diagnostic origin tracking. ### Coverage gate Run the checked-in LLVM coverage driver: ```bash ./test/coverage.sh ``` This is an independent Clang C++11 gate, separate from the compiler-standard matrix. It creates a dedicated `build-coverage/` tree, runs only the 19 tests labeled `unit`, and reports coverage only for the public implementation headers; the 2 integration consumers are neither executed nor included in this gate's denominator: ```text include/yaco.h include/yaco/core.h include/yaco/alternatives.h include/yaco/error.h include/yaco/command.h include/yaco/constraint.h include/yaco/option.h include/yaco/parser.h include/yaco/value.h include/yaco/detail/*.h ``` The coverage report prints the current line, branch, function, and region percentages for the selected compiler and working tree. These values can change with source changes, compiler versions, and test selection, so the stable checked-in contract is that **both line and branch coverage are at least 80%**. For LLVM versions that report unused header-inline coverage mappings as zero-hash mismatches, the driver expands every diagnostic and accepts it only when the reported count is complete and every hash is exactly zero. Any nonzero, incomplete, inconsistent, or unrelated diagnostic fails the gate. Thresholds can be raised locally or in CI: ```bash YACO_COVERAGE_MIN=95 \ YACO_BRANCH_COVERAGE_MIN=90 \ ./test/coverage.sh ``` Artifacts are written under the coverage build directory: ```text build-coverage/coverage-report.txt build-coverage/coverage-html/index.html build-coverage/coverage-diagnostics.log ``` The coverage percentage deliberately excludes tests, examples, CMake helper projects, and standard-library headers; it measures the yaco implementation itself. Integration tests are kept outside the coverage denominator because their role is package-consumption validation rather than exercising parser branches. ### Install-tree check A normal installation is: ```bash mkdir -p build-install cd build-install cmake .. \ -DYACO_BUILD_TESTS=OFF \ -DYACO_BUILD_EXAMPLES=OFF \ -DYACO_ENABLE_INSTALL=ON \ -DCMAKE_INSTALL_PREFIX="$PWD/../install" cmake --build . cmake --build . --target install ``` A consumer can then use: ```cmake find_package(yaco 3.0 CONFIG REQUIRED) target_link_libraries(my_tool PRIVATE yaco::yaco) ``` The regular `find-package-test` and `add-subdirectory-test` CTest entries test the build-tree package export and source-tree consumption described above; they do not test the installed prefix. To verify the installation, configure a separate consumer with `CMAKE_PREFIX_PATH` set to the `install/` directory and call `find_package(yaco 3.0 CONFIG REQUIRED)`. That installed-prefix consumer is an additional manual check, not part of the regular CTest suite. ## Acknowledgements The command-line model is intentionally derived from the LLVM CommandLine 2.0 design.