diff --git a/DOCS.MD b/DOCS.MD new file mode 100644 index 0000000..4398615 --- /dev/null +++ b/DOCS.MD @@ -0,0 +1,866 @@ +# Argument Parser API Documentation + +This document covers the public API exposed by: + +- `src/headers/parser/argument_parser.hpp`: the original v1 parser surface. +- `src/headers/parser/parser_v2.hpp`: the v2 map/initializer-list facade. +- `src/headers/parser/argument_builder.hpp`: the staged fluent builder for v2. + +The lower-level v1 API still powers the platform parsers and v2. New code will +usually be clearest with `argument_parser::v2` and +`argument_parser::builder::new_argument()`, but the v1 types remain public and +useful when you need direct control over actions, stored values, or positional +registration. + +## Common Concepts + +Argument names are registered without command-line prefixes. Use `"v"` and +`"verbose"`, not `"-v"` or `"--verbose"`. The active convention objects decide +which concrete syntaxes are accepted during parsing and how help text is +rendered. + +Typed values are parsed through +`argument_parser::parsing_traits::parser_trait::parse(std::string const&)`. +The built-in traits cover common scalar types such as `std::string`, `bool`, +`int`, `float`, and `double`; custom value types need a trait specialization. +Trait `format_hint` and `purpose_hint` members are used in help text and parse +error messages when available. + +Stored values are type-erased internally. Retrieve them with the same type used +when the argument was registered. + +## v1 API: `argument_parser.hpp` + +The public v1 types live in the `argument_parser` namespace unless otherwise +noted. + +### `action_base` + +```cpp +class action_base { +public: + virtual ~action_base() = default; + [[nodiscard]] virtual bool expects_parameter() const = 0; + virtual void invoke() const = 0; + virtual void invoke_with_parameter(std::string const& param) const = 0; + [[nodiscard]] virtual std::pair get_trait_hints() const = 0; + [[nodiscard]] virtual std::unique_ptr clone() const = 0; +}; +``` + +`action_base` is the polymorphic interface stored by registered arguments. +Application code normally uses `action_with_param`, `action_no_param`, or +the overloaded `helpers::make_action(...)` factories instead of implementing +this interface directly. + +- `expects_parameter()` reports whether the action consumes a value token. +- `invoke()` runs a no-value action. On `action_with_param`, it throws + `std::runtime_error`. +- `invoke_with_parameter(param)` runs a value action. On + `action_no_param`, the parameter is ignored. +- `get_trait_hints()` returns `{format_hint, purpose_hint}` for typed actions + when the parser trait exposes those members. +- `clone()` returns a heap-allocated copy used by `argument`. + +### `action_with_param` + +```cpp +template +class action_with_param : public action_base { +public: + using parameter_type = T; + + explicit action_with_param(std::function const& handler); + void invoke(T const& arg) const; + [[nodiscard]] bool expects_parameter() const override; + void invoke() const override; + void invoke_with_parameter(std::string const& param) const override; + [[nodiscard]] std::pair get_trait_hints() const override; + [[nodiscard]] std::unique_ptr clone() const override; +}; +``` + +Adapts a `std::function` into a parser action. During +`invoke_with_parameter`, the raw string is converted with +`parser_trait::parse(param)` and the parsed value is passed to the handler. +Parse failures are reported as `std::runtime_error` with trait-derived hints +when available. + +### `action_no_param` + +```cpp +class action_no_param : public action_base { +public: + explicit action_no_param(std::function const& handler); + + void invoke() const override; + [[nodiscard]] bool expects_parameter() const override; + void invoke_with_parameter(std::string const& param) const override; + [[nodiscard]] std::pair get_trait_hints() const override; + [[nodiscard]] std::unique_ptr clone() const override; +}; +``` + +Adapts a `std::function` into a flag-style action. +`expects_parameter()` returns `false`; `invoke_with_parameter(...)` ignores the +parameter and runs the no-value handler. + +### `argument_parser::helpers` + +```cpp +namespace argument_parser::helpers { + template + action_with_param + make_action(std::function const& function); + + action_no_param + make_action(std::function const& function); +} +``` + +Factory helpers for creating action objects used by `base_parser` and v2. + +### `argument` + +```cpp +class argument { +public: + argument(); + + template + argument(int id, std::string name, ActionType const& action); + + argument(argument const& other); + argument& operator=(argument const& other); + argument(argument&& other) noexcept = default; + argument& operator=(argument&& other) noexcept = default; + + [[nodiscard]] bool is_required() const; + [[nodiscard]] std::string get_name() const; + [[nodiscard]] bool is_invoked() const; + [[nodiscard]] bool expects_parameter() const; + [[nodiscard]] std::string get_help_text() const; + [[nodiscard]] bool is_positional() const; + [[nodiscard]] bool is_positional_accumulator() const; + [[nodiscard]] std::optional get_position_index() const; +}; +``` + +`argument` is the public descriptor returned by parser lookup operations. It is +copyable; copies clone their stored action. + +- `is_required()` reports whether parsing must see this argument. +- `get_name()` returns the registered display name. Named options are stored as + `"|"`; positional arguments use their positional name. +- `is_invoked()` reports whether the action ran during `handle_arguments(...)`. +- `expects_parameter()` mirrors the stored action. +- `get_help_text()` returns the registration help text. +- `is_positional()` identifies positional arguments. +- `is_positional_accumulator()` is true for positional accumulators. +- `get_position_index()` returns the explicit requested position, or + `std::nullopt`. + +### `base_parser` + +```cpp +class base_parser { +public: + template + void add_argument( + std::string const& short_arg, + std::string const& long_arg, + std::string const& help_text, + action_with_param const& action, + bool required); + + template + void add_argument( + std::string const& short_arg, + std::string const& long_arg, + std::string const& help_text, + bool required); + + void add_argument( + std::string const& short_arg, + std::string const& long_arg, + std::string const& help_text, + action_no_param const& action, + bool required); + + void add_argument( + std::string const& short_arg, + std::string const& long_arg, + std::string const& help_text, + bool required); + + template + void add_positional_argument( + std::string const& name, + std::string const& help_text, + action_with_param const& action, + bool required, + std::optional position = std::nullopt); + + template + void add_positional_argument( + std::string const& name, + std::string const& help_text, + bool required, + std::optional position = std::nullopt); + + template + void add_positional_accumulator( + std::string const& name, + std::string const& help_text, + action_with_param const& action, + bool required, + std::optional position = std::nullopt); + + void on_complete(std::function const& action); + + template + std::optional get_optional(std::string const& arg) const; + + [[nodiscard]] std::string build_help_text( + std::initializer_list convention_types) const; + + argument& get_argument(conventions::parsed_argument const& arg); + [[nodiscard]] std::optional find_argument_id(std::string const& arg) const; + void handle_arguments( + std::initializer_list convention_types); + void display_help( + std::initializer_list convention_types) const; + +protected: + base_parser() = default; +}; +``` + +`base_parser` owns registrations, parsed command-line tokens, stored values, +required-argument checks, and completion hooks. Its constructor is protected; +users normally instantiate a concrete parser from `` or a platform +header. Platform parser constructors populate `program_name` and +`parsed_arguments` from the native process arguments. + +`base_parser` is not thread-safe. `handle_arguments(...)` must be called on the +same thread that created the parser or it throws `std::runtime_error`. + +#### Named Arguments + +Use `add_argument(...)` for short and/or long options. The v1 API uses `"-"` as +the sentinel for an omitted short or long name. + +```cpp +argument_parser::parser parser; + +parser.add_argument( + "o", + "output", + "Output file.", + argument_parser::helpers::make_action( + [](std::string const& value) { + // use value + }), + true); + +parser.add_argument( + "v", + "verbose", + "Enable verbose output.", + false); +``` + +Registration fails with `std::runtime_error("The key already exists!")` if the +short or long key is already registered. Because duplicate checks also see the +`"-"` sentinel, avoid registering multiple v1 arguments that pass `"-"` for the +same side unless you have verified that behavior. + +The overloads behave as follows: + +- `add_argument(..., action_with_param const& action, bool required)` + parses a value and invokes the supplied typed action. +- `add_argument(..., bool required)` parses a value and stores it internally + for `get_optional(...)`. +- `add_argument(..., action_no_param const& action, bool required)` + invokes a no-value action. +- `add_argument(..., bool required)` stores `true` internally when the option is + present. + +#### Positional Arguments + +```cpp +parser.add_positional_argument( + "input", + "Input path.", + true, + 0); + +parser.add_positional_argument( + "count", + "Number of iterations.", + argument_parser::helpers::make_action( + [](int value) { + // use value + }), + false); +``` + +Positional arguments are matched against tokens that do not match any supplied +convention. You can provide an explicit zero-based `position`, or omit it to use +the next available positional slot. + +`add_positional_argument(..., bool required, ...)` stores the parsed value +internally under the positional name. The action overload invokes the supplied +typed action. + +`add_positional_accumulator(...)` registers a typed positional action that can +consume repeated positional tokens at its slot. Only one positional accumulator +is allowed, and it must be the last positional argument. Duplicate names, +negative explicit positions, or invalid accumulator placement throw +`std::runtime_error`. + +#### Parsing + +```cpp +parser.handle_arguments({ + &argument_parser::conventions::gnu_argument_convention, + &argument_parser::conventions::gnu_equal_argument_convention, + &argument_parser::conventions::windows_argument_convention, + &argument_parser::conventions::windows_equal_argument_convention, +}); +``` + +`handle_arguments(...)` invokes matching actions, records stored values, checks +required arguments, then runs completion hooks. For each token, conventions are +tried in order. If no convention matches, the token is consumed as the next +positional value when one is available. `--` forces all following tokens to be +treated as positional values. + +Unknown options, missing values, parse failures, unexpected positional values, +and action errors are reported with `std::runtime_error`. Missing required +arguments print diagnostics and help text, then call `std::exit(1)`. + +If a registered argument named `h` or `help` is found, the parser invokes that +help argument and returns from argument invocation early. Required checks and +completion hooks still run after invocation returns. + +#### Lookup And Help + +```cpp +if (auto output = parser.get_optional("output")) { + // *output is the stored value +} + +auto id = parser.find_argument_id("output"); +std::string help = parser.build_help_text({ + &argument_parser::conventions::gnu_argument_convention, +}); +parser.display_help({ + &argument_parser::conventions::gnu_argument_convention, +}); +``` + +`get_optional(arg)` returns a stored value for a registered short name, long +name, or positional name. It returns `std::nullopt` when no argument by that name +exists or when no value was stored. If a value exists but `T` does not match the +stored type, `std::any_cast` throws `std::bad_any_cast`. + +`find_argument_id(arg)` returns the internal integer id for a registered short +name, long name, or positional name. `get_argument(parsed_argument)` resolves a +parsed convention result to an `argument&` and throws when no match exists. + +`build_help_text(...)` returns formatted usage text. `display_help(...)` writes +that text to `std::cout`. + +#### Completion Hooks + +```cpp +parser.on_complete([](argument_parser::base_parser const& state) { + // inspect state.get_optional(...) +}); +``` + +`on_complete(...)` appends a callback that runs after successful argument +invocation and required-argument checks. + +## v2 API: `parser_v2.hpp` + +The v2 API is declared under `argument_parser::v2`. It provides a +map/initializer-list facade over the v1 `base_parser`, while still accepting the +same convention objects and action helpers. + +Typical use is through the platform alias from ``: + +```cpp +#include + +using namespace argument_parser::v2::flags; + +int main() { + argument_parser::v2::parser parser; + + parser.add_argument({ + {LongArgument, "count"}, + {HelpText, "Number of items to process."}, + {Required, true}, + }); + + parser.handle_arguments({ + &argument_parser::conventions::gnu_argument_convention, + &argument_parser::conventions::windows_argument_convention, + }); + + auto count = parser.get_optional("count"); +} +``` + +### `enum class add_argument_flags` + +These keys describe an argument passed to `v2::base_parser::add_argument`. + +| Flag | Value type | Meaning | +| --- | --- | --- | +| `ShortArgument` | `std::string` | Short option name, such as `"v"`. | +| `LongArgument` | `std::string` | Long option name, such as `"verbose"`. | +| `Positional` | `std::string` | Registers a positional argument. When present, the positional registration path is used. | +| `Position` | `int` | Optional zero-based position for a positional argument. | +| `HelpText` | `std::string` | Help text shown in generated help output. | +| `Action` | `action_with_param` or `action_no_param` | Callback invoked when the argument is found. | +| `Required` | `bool` | Marks the argument required when `true`. | +| `Reference` | `T*` | Stores a parsed typed value into an external object. Cannot be combined with `Action`. | +| `Accumulate` | `bool` or `T*` | Repeated-value mode. For `std::vector`, each parsed `U` is appended. For `int`, each occurrence increments the count. | + +The `argument_parser::v2::flags` namespace exposes inline constants with the +same names for concise initializer-list calls. + +```cpp +using namespace argument_parser::v2::flags; + +parser.add_argument({ + {ShortArgument, "q"}, + {HelpText, "Quiet mode."}, +}); +``` + +### `argument_parser::v2::deducers` + +```cpp +template +struct has_value_type; + +template +struct is_vector; + +template +constexpr bool is_vector_v = is_vector::test(); +``` + +These public type traits are used by v2 accumulator logic. `has_value_type` +detects `T::value_type`. `is_vector::test()` and `is_vector_v` are true +only when `T` is exactly a `std::vector` specialization. + +### `argument_parser::v2::base_parser` + +`v2::base_parser` is the v2 facade. Concrete platform parsers such as +`argument_parser::v2::linux_parser`, `macos_parser`, `windows_parser`, and the +portable `` alias `argument_parser::v2::parser` derive from it. + +The class privately inherits from the v1 `argument_parser::base_parser` and +re-exposes selected operations. + +```cpp +template +using typed_flag_value = + std::variant, bool, int, T*>; + +using non_typed_flag_value = + std::variant; + +template +using typed_argument_pair = + std::pair>; + +using non_typed_argument_pair = + std::pair; +``` + +Use typed aliases for arguments that parse a value of `T`, run an +`action_with_param`, store into a `T`, or accumulate values. Use non-typed +aliases for boolean flags and no-parameter actions. + +#### Typed `add_argument` + +```cpp +template +void add_argument( + std::unordered_map> const& argument_pairs); + +template +void add_argument(std::initializer_list> const& pairs); +``` + +Registers a typed option or positional argument. For non-positional options, +provide at least one of `ShortArgument` or `LongArgument`. If only one name is +supplied, the other is internally set to `"-"` and not registered. + +Without `Action`, `Reference`, or `Accumulate`, the parsed `T` is stored +internally and can be read with `get_optional()`. For positional arguments, +include `Positional`; `Position` can place the argument at a specific zero-based +slot. + +```cpp +int threshold = 0; + +parser.add_argument({ + {LongArgument, "threshold"}, + {Reference, &threshold}, + {HelpText, "Store the parsed threshold."}, +}); +``` + +#### Non-Typed `add_argument` + +```cpp +void add_argument( + std::unordered_map const& argument_pairs); + +void add_argument(std::initializer_list const& pairs); +``` + +Registers a non-typed option or positional argument. For options, provide at +least one of `ShortArgument` or `LongArgument`. Without `Action`, the argument +behaves as a boolean flag: when present, it stores `true` for +`get_optional()`. + +With `Action`, provide an `action_no_param`. Non-typed arguments cannot use +`Reference` or `Accumulate`. A non-typed positional argument is stored as +`std::string`. + +```cpp +parser.add_argument({ + {ShortArgument, "v"}, + {LongArgument, "verbose"}, + {HelpText, "Enable verbose output."}, +}); +``` + +#### Other Public Operations + +```cpp +argument_parser::base_parser& to_v1(); + +void handle_arguments( + std::initializer_list convention_types); + +template +std::optional get_optional(std::string const& arg); + +using argument_parser::base_parser::display_help; +using argument_parser::base_parser::on_complete; +``` + +`to_v1()` returns a mutable reference to the underlying v1 parser interface. +`handle_arguments(...)`, `get_optional(...)`, `display_help(...)`, and +`on_complete(...)` follow the v1 behavior described above. + +### Accumulation + +`Accumulate` has special typed behavior: + +- `T = std::vector` registers an argument that accepts repeated `U` values. +- `T = int` registers a counter-style option; each occurrence increments the + count. +- `Accumulate` set to `true` stores the accumulated result internally and makes + it available through `get_optional()`. +- `Accumulate` set to `T*`, or `Accumulate` combined with `Reference`, writes + directly to an external object. + +For vector accumulators, the public argument type is `std::vector`, but each +individual command-line occurrence is parsed as `U`. + +```cpp +parser.add_argument>({ + {LongArgument, "include-id"}, + {Accumulate, true}, + {HelpText, "May be repeated."}, +}); + +auto ids = parser.get_optional>("include-id"); +``` + +## Builder API: `argument_builder.hpp` + +The public builder API lives under `argument_parser::builder`. It provides a +staged fluent interface for registering arguments with +`argument_parser::v2::base_parser`. + +```cpp +#include +// or +#include + +using argument_parser::builder::new_argument; +``` + +### Public Types + +`non_type` is a marker type used before a value-producing mode has been +selected. Application code normally does not need to name it. + +`builder_mask` is a public namespace containing the compile-time machinery used +by `argument`. It exposes `mask_type`, `value_mode`, +`extra_capability`, capability constants such as `short_argument`, +`long_argument`, `positional`, `help_text`, `required`, `reference`, +`accumulate`, `count`, `store`, and `flag`, plus constexpr helpers such as +`bit`, `has`, `remove`, `replace`, `is_buildable`, and +`is_build_and_gettable`. These are mainly useful for type-level tests or +advanced wrappers. + +### `container` + +```cpp +template +class container; +``` + +Returned by `build_and_get(parser)`. It is populated after +`parser.handle_arguments(...)` completes. + +- `store_type get() const` returns the stored value. Call only after the + container is populated. +- `store_type& operator*()` dereferences the stored value. +- `store_type* operator->()` accesses members of the stored value. +- `operator bool()` reports whether a value is available. + +For builder-managed storage, `build_and_get()` registers an `on_complete` +callback that reads the parsed value by lookup key. For +`accumulate(existing_vector)`, the container references the provided vector +directly. + +### `argument` + +```cpp +template < + argument_parser::builder::builder_mask::mask_type mask = + argument_parser::builder::builder_mask::initial, + typename store_type = argument_parser::builder::non_type> +class argument; +``` + +The staged fluent builder. `mask` enables only valid next methods at compile +time. `store_type` is the type produced by the selected value mode. Most users +should create builders with `new_argument()`. + +### Entry Points + +```cpp +static auto argument<>::start() -> argument; +static inline auto new_argument(); +``` + +`new_argument()` is equivalent to `argument<>::start()`. + +```cpp +new_argument() + .long_argument("output") + .store() + .required() + .build(parser); +``` + +### Identifier Methods + +At least one identifier method must be used before `build(parser)` or +`build_and_get(parser)`. + +```cpp +auto short_argument(std::string short_name) const -> argument<..., store_type>; +auto long_argument(std::string long_name) const -> argument<..., store_type>; +auto positional(std::string positional_name) const -> argument<..., store_type>; +auto position(int index) const -> argument<..., store_type>; +``` + +`short_argument(...)` and `long_argument(...)` set named option identifiers and +can be combined with each other. `positional(...)` registers a positional +argument and is mutually exclusive with named identifiers. `position(...)` is +available only after `positional(...)` and only once. If no value mode is +selected for a positional argument, `build(parser)` registers a `std::string` +positional value. + +### Metadata Methods + +```cpp +auto help_text(std::string help) const -> argument<..., store_type>; +auto required(bool value = true) const -> argument<..., store_type>; +``` + +`help_text(...)` sets generated help text and is single-use. If omitted, v2 +generates default text, often using parser trait hints for typed value modes. + +`required(...)` marks the argument required when `value` is `true`. There is no +public `setRequired(...)` method in `argument_builder.hpp`; the public fluent API +is `required(...)`. + +### Terminal Value Modes + +Exactly one terminal value mode can be selected. Once selected, the other modes +are removed from the builder type. + +```cpp +template +auto store() const -> argument<..., T>; + +template +auto reference(T& value) const -> argument<..., T>; + +auto flag() const -> argument<..., bool>; + +template +auto accumulate() const -> argument<..., std::vector>; + +template +auto accumulate(T& value) const -> argument<..., T>; + +auto count() const -> argument<..., int>; +auto count(int& value) const -> argument<..., int>; + +template +auto action(Callable&& handler) const -> argument<..., non_type>; + +template +auto action(Callable&& handler) const -> argument<..., T>; +``` + +`store()` parses a value and stores it in the parser for later lookup. +`store()` is rejected at compile time. + +`reference(T&)` parses a value and assigns it directly to an object that must +outlive parsing. + +`flag()` registers a boolean presence flag. It is not available for positional +arguments. Named arguments also default to flag behavior when no terminal mode +is selected. + +`accumulate()` accepts repeated values and stores them as `std::vector`. +`accumulate(existing)` appends repeated values into an existing vector. The +existing object must be a `std::vector` and must outlive parsing. + +`count()` counts how many times a named argument appears. `count(int&)` writes +the count into an existing integer. `count()` is not available for positional +arguments. + +`action(handler)` registers a no-value action when `handler` is invocable as +`void()`. For positional arguments, the builder adapts it through a +`std::string` positional action and ignores the parsed positional text. + +`action(handler)` registers a typed action when `handler` is invocable as +`void(T const&)`. `action(...)` is rejected at compile time. + +### Build Methods + +```cpp +auto build(argument_parser::v2::base_parser& parser) const -> void; + +auto build_and_get(argument_parser::v2::base_parser& parser) const + -> container; +``` + +`build(parser)` registers the configured argument with the parser. It requires +an identifier, dispatches to the selected terminal mode, and defaults to a named +boolean flag or `std::string` positional value when no terminal mode was +selected. It may propagate validation errors from `v2::base_parser`. + +`build_and_get(parser)` registers the argument and returns a +`container` for storable modes such as `store()`, `flag()`, +default named flags, `accumulate()`, `accumulate(vector&)`, `count()`, and +`count(int&)`. It throws `std::logic_error` for unsupported terminal modes such +as actions. The returned container is normally empty until +`parser.handle_arguments(...)` completes. + +### Compile-Time Builder Rules + +Invalid fluent chains fail to compile rather than failing at runtime. + +- `build(...)` requires `short_argument(...)`, `long_argument(...)`, or + `positional(...)`. +- `short_argument(...)` and `long_argument(...)` can be combined. +- `positional(...)` is mutually exclusive with named identifiers. +- `position(...)` is available only after `positional(...)` and only once. +- `help_text(...)` and `required(...)` are single-use. +- Terminal value modes are mutually exclusive. +- `flag()` and `count()` are not available for positional arguments. +- `store()` and `action(...)` are rejected with `static_assert`. +- `accumulate(existing)` requires `existing` to be a `std::vector`. + +The header also exposes `argument_parser::builder::assertions`, a namespace of +small compile-time detection helpers and `static_assert` checks used to verify +these staged-builder rules. They are not needed for normal parser +configuration. + +### Complete Builder Example + +```cpp +#include +#include +#include +#include + +using argument_parser::builder::new_argument; + +int main() { + argument_parser::v2::parser parser(false); + + int threshold = 0; + std::vector ids; + + new_argument() + .long_argument("file") + .store() + .required() + .help_text("Input file.") + .build(parser); + + new_argument() + .long_argument("threshold") + .reference(threshold) + .build(parser); + + new_argument() + .long_argument("id") + .accumulate(ids) + .help_text("Repeatable numeric id.") + .build(parser); + + auto verbose = new_argument() + .short_argument("v") + .count() + .build_and_get(parser); + + parser.handle_arguments({ + &argument_parser::conventions::gnu_argument_convention, + &argument_parser::conventions::windows_argument_convention, + }); + + if (auto file = parser.get_optional("file")) { + std::cout << "file: " << *file << '\n'; + } + + if (verbose) { + std::cout << "verbosity: " << *verbose << '\n'; + } + + std::cout << "threshold: " << threshold << '\n'; + std::cout << "ids: " << ids.size() << '\n'; +} +``` + +## Caveats + +- The v1 API is retained as public compatibility surface, but the newer v2 and + builder APIs are generally more ergonomic. +- Parser registration and parsing are single-threaded; `handle_arguments(...)` + enforces the parser creation thread. +- `handle_arguments(...)` resets the current convention list after it returns + or throws. +- Namespaces named `argument_parser::internal::*` are implementation details + despite being present in public headers. diff --git a/README.md b/README.md index 3916e6f..e41d82e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # argument-parser -A lightweight, modern, and highly customizable C++17 argument parser with native platform argument collection, trait-driven typed parsing, pluggable option conventions, and a fluent `v2` builder API. +A lightweight, modern, and highly customizable C++17 argument parser with native platform argument collection, trait-driven typed parsing, repeatable argument accumulation, pluggable option conventions, and a fluent `v2` builder API. > `v1` is deprecated and mainly kept as implementation history. For new projects, use `argument_parser::v2` together with `argument_parser::builder`. @@ -8,11 +8,13 @@ A lightweight, modern, and highly customizable C++17 argument parser with native - Native platform parser alias: `argument_parser::v2::parser` resolves to the current platform parser and reads arguments directly from OS APIs. - Fluent builder API with compile-time builder constraints that prevent invalid combinations after a terminal/mutually exclusive mode has been selected. -- Type-safe parsing and extraction. Just extend `parser_trait` for your types and if just want to store use `get_optional()`! +- Type-safe parsing and extraction. Extend `parser_trait` for your own types and retrieve stored values with `get_optional()`. +- Repeatable value accumulation with `accumulate()`, `accumulate(vector&)`, `count()`, and `count(int&)`. +- `build_and_get(parser)` for storable builder modes, returning a small container that is populated after parsing completes. - Positional arguments with optional explicit ordering and support for `--` as a positional separator. - Trait-driven `format_hint` and `purpose_hint` metadata used in generated help text and parse errors. - Automatic help flag on `argument_parser::v2::parser` (`-h`, `--help`) with configurable exit behavior. -- Auto-formatted help output.. +- Auto-formatted help output. - Completion hooks via `parser.on_complete(...)`. - Pluggable conventions for GNU next-token, GNU equal-style, Windows next-token, and Windows inline `=` / `:` parsing, or bring your own! - Testing helper + pseudo command handler `argument_parser::v2::fake_parser`. @@ -28,15 +30,17 @@ A lightweight, modern, and highly customizable C++17 argument parser with native #include #include #include +#include -using argument = argument_parser::builder::argument<>; +using argument_parser::builder::new_argument; int main() { argument_parser::v2::parser parser(false); // --help prints without exiting immediately int threshold = 0; + std::vector ids; - argument::start() + new_argument() .short_argument("e") .long_argument("echo") .action([](std::string const& text) { @@ -44,26 +48,32 @@ int main() { }) .build(parser); - argument::start() + new_argument() .long_argument("file") .store() .required() .help_text("Input file to process.") .build(parser); - argument::start() + new_argument() .long_argument("threshold") .reference(threshold) + .help_text("Numeric threshold.") .build(parser); - argument::start() + auto verbose = new_argument() .short_argument("v") - .long_argument("verbose") - .flag() - .help_text("Enable verbose output.") + .help_text("Increase verbosity. Repeat for a higher level.") + .count() + .build_and_get(parser); + + new_argument() + .long_argument("id") + .help_text("Collect an id. May be repeated.") + .accumulate(ids) .build(parser); - argument::start() + new_argument() .positional("output") .position(0) .help_text("Output file.") @@ -89,6 +99,11 @@ int main() { } std::cout << "threshold: " << threshold << '\n'; + std::cout << "ids: " << ids.size() << '\n'; + + if (verbose) { + std::cout << "verbosity: " << *verbose << '\n'; + } } ``` @@ -129,7 +144,7 @@ struct argument_parser::parsing_traits::parser_trait { Then use the type directly from the builder: ```cpp -argument::start() +new_argument() .long_argument("point") .store() .build(parser); @@ -161,9 +176,15 @@ parser.display_help(conventions); Mix any of them in the same parser by passing the conventions you want to `handle_arguments()`. -## Builder Modes +## Builder API -`argument_parser::builder::argument<>` is a staged builder. `build(parser)` is the terminal call. +`argument_parser::builder::argument<>` is a staged builder. Prefer `argument_parser::builder::new_argument()` as the entry point: + +```cpp +using argument_parser::builder::new_argument; +``` + +`build(parser)` registers the argument and returns `void`. `build_and_get(parser)` is available for storable modes and returns a lightweight `builder::container`. The container is filled by an internal completion hook after `handle_arguments(...)` runs. Before `build(...)`, you compose an argument from three kinds of steps: @@ -173,13 +194,75 @@ Before `build(...)`, you compose an argument from three kinds of steps: - `store()` to parse and retain a value for later `get_optional()` - `flag()` to store a boolean presence flag - `reference(value)` to write the parsed result directly into an existing variable + - `accumulate()` to collect repeated values into a stored `std::vector` + - `accumulate(vector)` to collect repeated values into an existing `std::vector` + - `count()` to store how many times an option appears + - `count(value)` to write the occurrence count into an existing `int` - `action([] { ... })` for no-value callbacks - `action([](T const&) { ... })` for typed value callbacks -Once you select one value behavior, the other value behavior methods are disabled at compile time, so combinations like `store().action(...)` or `flag().reference(value)` are rejected by the type system. Also you cannot use the same method repeatedly as it is also disabled at compile time by the type system. +Once you select one value behavior, the other value behavior methods are disabled at compile time, so combinations like `store().action(...)` or `flag().reference(value)` are rejected by the type system. The same staged typing also prevents repeating one-shot methods such as `help_text(...)`, `position(...)`, or `store()` on the same builder chain. If you do not select a value behavior explicitly, `build(parser)` uses the default for the argument kind: named arguments become boolean flags, while positional arguments store a `std::string`. +## Accumulators and Counts + +Use `accumulate()` when the parser should accept the same value-bearing argument multiple times and store all parsed values: + +```cpp +auto values = new_argument() + .short_argument("n") + .long_argument("number") + .accumulate() + .build_and_get(parser); + +parser.handle_arguments(conventions); + +if (values) { + for (int value : *values) { + std::cout << value << '\n'; + } +} +``` + +Use `accumulate(target)` to append into a vector you own: + +```cpp +std::vector ids; + +new_argument() + .long_argument("id") + .accumulate(ids) + .build(parser); +``` + +Use `count()` for repeatable flags such as `-v -v -v`: + +```cpp +auto verbosity = new_argument() + .short_argument("v") + .count() + .build_and_get(parser); +``` + +The lower-level `v2::base_parser::add_argument` API exposes the same accumulator behavior through the `Accumulate` flag: + +```cpp +using namespace argument_parser::v2::flags; + +parser.add_argument>({ + {LongArgument, "id"}, + {HelpText, "Collect an id. May be repeated."}, + {Accumulate, true}, +}); + +std::vector captured_ids; +parser.add_argument>({ + {LongArgument, "captured-id"}, + {Accumulate, &captured_ids}, +}); +``` + ## Testing For unit tests or synthetic argument lists, use `argument_parser::v2::fake_parser` instead of the native platform parser: