Skip to content

Add CLI argument parser (rfl::cli::read)#613

Open
Centimo wants to merge 7 commits intogetml:mainfrom
Centimo:feature/cli-argument-parser
Open

Add CLI argument parser (rfl::cli::read)#613
Centimo wants to merge 7 commits intogetml:mainfrom
Centimo:feature/cli-argument-parser

Conversation

@Centimo
Copy link

@Centimo Centimo commented Feb 13, 2026

Summary

  • Add rfl::cli::read<T>(argc, argv) — parse command-line arguments into any reflectable struct using reflection
  • Automatic snake_casekebab-case field name conversion (host_name--host-name)
  • rfl::Positional<T> wrapper for bare positional arguments matched in declaration order
  • rfl::Short<"x", T> wrapper for single-character aliases (-p 8080, -v)
  • Smart bool-flag handling: -v somefile treats somefile as positional, not as value of -v
  • Comma-separated vector parsing (--tags=a,b,c), empty elements skipped
  • Three-stage architecture: parse_argvresolve_argsReader
  • Compile-time validation: nested wrappers (Positional<Short<...>>) and multi-char short names are static_assert errors

Parse command-line arguments (--key=value) into reflectable structs.
Supports nested structs, optional, vector, enum, Flatten, Rename.
Automatic snake_case to kebab-case conversion via SnakeCaseToKebabCase processor.
Positional<T> marks fields as positional CLI arguments (no -- prefix).
Short<_name, T> adds short aliases (e.g. -p for --port).
Both wrappers are transparent for non-CLI formats (JSON, YAML, etc.)
via Parser specializations that delegate to the inner type.

parse_argv now categorizes args into named/short/positional buckets.
resolve_args merges them into a flat map using compile-time metadata.
…ambiguity

- Fix std::forward<T> → std::move in move-assignment (Positional, Short)
- Fix move constructors copying via get() instead of moving value_
- Add static_assert: Short name must be exactly one character
- Add static_assert: disallow nested Positional<Short<...>> wrappers
- Fix split() skipping empty elements ("a,,b" → ["a", "b"])
- Add null-safety check in Reader::to_object()
- Reclaim non-boolean values from short bool flags (-v somefile)
- Add tests for bool-short-positional interaction and empty elements
- Add xml_content comment, blank line between transform functions
@gemini-code-assist
Copy link

Summary of Changes

Hello @Centimo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a comprehensive command-line interface (CLI) argument parsing module to the rfl library. It allows developers to easily define CLI configurations using standard C++ structs and automatically parse argc/argv into these structs. The new module handles various argument types, including positional, short aliases, and automatic case conversion, significantly simplifying CLI development by integrating seamlessly with the existing reflection capabilities.

Highlights

  • New CLI Argument Parser: Introduced rfl::cli::read<T>(argc, argv) for parsing command-line arguments into any reflectable C++ struct, leveraging reflection for automatic mapping.
  • Automatic Case Conversion: Implemented automatic conversion of snake_case field names in structs to kebab-case for CLI arguments (e.g., host_name maps to --host-name).
  • Positional Arguments Support: Added rfl::Positional<T> wrapper to designate fields as bare positional arguments, matched in declaration order, with support for -- separator to force subsequent tokens as positional.
  • Short Aliases: Provided rfl::Short<"x", T> wrapper for defining single-character short aliases (e.g., -p 8080, -v), with smart handling for boolean flags to prevent unintended consumption of subsequent tokens.
  • Compile-Time Validation: Ensured compile-time validation to prevent invalid configurations, such as nested Positional<Short<...>> wrappers or multi-character short names.
  • Modular Architecture: Designed the parser with a three-stage architecture: parse_argv for token categorization, resolve_args for mapping and conflict resolution using compile-time metadata, and Reader for integrating with reflect-cpp's parsing concept.
Changelog
  • docs/cli.md
    • Added new documentation detailing the usage, features, and architecture of the rfl::cli argument parser.
  • include/rfl.hpp
    • Included new headers for Positional.hpp, Short.hpp, and SnakeCaseToKebabCase.hpp to make these features available through the main rfl header.
  • include/rfl/Positional.hpp
    • Added Positional<T> struct to mark fields as positional command-line arguments, transparent for other serialization formats.
  • include/rfl/Short.hpp
    • Added Short<"x", T> struct to define single-character short aliases for command-line arguments, including a static assertion for single-character names.
  • include/rfl/SnakeCaseToKebabCase.hpp
    • Added SnakeCaseToKebabCase processor to automatically convert snake_case field names to kebab-case for CLI argument parsing.
  • include/rfl/cli.hpp
    • Added new aggregate header for the rfl::cli module, consolidating all CLI-related components.
  • include/rfl/cli/Parser.hpp
    • Added Parser type alias for rfl::cli to integrate with the generic parsing framework.
  • include/rfl/cli/Reader.hpp
    • Added Reader implementation for CLI arguments, handling basic type conversions, array splitting, and object path resolution.
  • include/rfl/cli/parse_argv.hpp
    • Added parse_argv function to categorize raw command-line tokens into named, short, and positional arguments, including support for the -- separator.
  • include/rfl/cli/read.hpp
    • Added the public read API for rfl::cli, orchestrating the argument parsing process and applying default processors like SnakeCaseToKebabCase.
  • include/rfl/cli/resolve_args.hpp
    • Added resolve_args function to map parsed arguments to struct fields using compile-time metadata, handle short alias resolution, and manage positional argument conflicts.
  • include/rfl/internal/is_positional.hpp
    • Added type trait is_positional to identify rfl::Positional types.
  • include/rfl/internal/is_short.hpp
    • Added type trait is_short to identify rfl::Short types.
  • include/rfl/internal/transform_case.hpp
    • Added transform_snake_to_kebab consteval function for converting snake_case to kebab-case.
  • include/rfl/parsing/Parser.hpp
    • Updated Parser.hpp to include new parser specializations for Positional and Short types.
  • include/rfl/parsing/Parser_positional.hpp
    • Added parser specialization for rfl::Positional<T> to ensure transparent parsing for positional arguments.
  • include/rfl/parsing/Parser_short.hpp
    • Added parser specialization for rfl::Short<"x", T> to ensure transparent parsing for short aliases.
  • include/rfl/parsing/is_required.hpp
    • Modified is_required to correctly handle Positional and Short wrappers when determining field requirements.
  • tests/CMakeLists.txt
    • Added the cli subdirectory to the test build system.
  • tests/cli/CMakeLists.txt
    • Added CMake configuration for the new CLI test suite.
  • tests/cli/test_basic.cpp
    • Added basic tests for parsing various data types (string, int, double, bool) from CLI arguments.
  • tests/cli/test_bool_short_positional.cpp
    • Added tests for specific interactions between short boolean flags and positional arguments, ensuring correct token consumption.
  • tests/cli/test_enum.cpp
    • Added tests for parsing enum types from CLI arguments.
  • tests/cli/test_errors.cpp
    • Added tests to verify error handling for invalid argument formats, type conversion failures, and duplicate keys.
  • tests/cli/test_flatten.cpp
    • Added tests for parsing flattened structs from CLI arguments.
  • tests/cli/test_kebab_case.cpp
    • Added tests to confirm automatic snake_case to kebab-case conversion for field names.
  • tests/cli/test_nested.cpp
    • Added tests for parsing nested structs using dot-separated CLI arguments.
  • tests/cli/test_optional.cpp
    • Added tests for parsing std::optional fields, covering both present and absent values.
  • tests/cli/test_positional.cpp
    • Added comprehensive tests for positional arguments, including named usage, -- separator, and error cases like too many arguments or conflicts.
  • tests/cli/test_positional_and_short.cpp
    • Added tests for combining positional and short arguments in a single configuration.
  • tests/cli/test_rename.cpp
    • Added tests for rfl::Rename to override default naming conventions for CLI arguments.
  • tests/cli/test_short.cpp
    • Added tests for short aliases, including different syntaxes (-x value, -x=value), and conflict detection.
  • tests/cli/test_vector.cpp
    • Added tests for parsing std::vector fields from comma-separated CLI argument values, including handling of empty elements.
  • tests/json/test_positional_and_short.cpp
    • Added tests to confirm that Positional and Short wrappers are transparent and do not affect JSON serialization/deserialization.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new command-line argument parser (rfl::cli::read) that leverages reflection to parse argc/argv into reflectable structs. Key features include automatic snake_case to kebab-case conversion for field names, support for positional arguments via rfl::Positional<T>, and single-character aliases using rfl::Short<"x", T>. The implementation includes robust handling for boolean flags, comma-separated vector parsing, and a three-stage parsing architecture (parse_argv -> resolve_args -> Reader). Compile-time validation prevents nested Positional and Short wrappers, ensuring type safety. Comprehensive test cases have been added to validate the new functionality across various scenarios, including error handling, nested structures, and interactions with other rfl features like Flatten and Rename. The documentation has been updated to reflect the new CLI module's usage and capabilities.

/// The underlying type.
using Type = T;

Positional() : value_(Type()) {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The default constructor Positional() : value_(Type()) {} does not have a requires std::is_default_constructible_v<Type> constraint. If Type is not default constructible, this will result in a compile-time error. It's good practice to explicitly state this requirement, similar to the Default constructor.

Suggested change
Positional() : value_(Type()) {}
Positional() requires std::is_default_constructible_v<Type> : value_(Type()) {}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Centimo you can do this if you want to but I personally don't care. Feel free to just ignore Gemini on this.


static_assert(_name.length == 1, "Short name must be exactly one character.");

Short() : value_(Type()) {}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Similar to Positional, the default constructor Short() : value_(Type()) {} lacks a requires std::is_default_constructible_v<Type> constraint. This could lead to compile errors if Type is not default constructible. Adding the constraint makes the requirements explicit.

Suggested change
Short() : value_(Type()) {}
Short() requires std::is_default_constructible_v<Type> : value_(Type()) {}

@liuzicheng1987
Copy link
Contributor

@Centimo, This is awesome stuff! I just read through the code a bit and I love it. I'm going to give it a try later but I like what I'm seeing so far.

@Centimo
Copy link
Author

Centimo commented Feb 14, 2026

@Centimo, This is awesome stuff! I just read through the code a bit and I love it. I'm going to give it a try later but I like what I'm seeing so far.

I'm planning on making a few more fixes, so you probably shouldn't rush into a full-blown review.

- Break string literal + std::to_string concatenation chain that triggers
  false -Warray-bounds in GCC 12 with -O3 -Werror
- Accept negative numbers as short option values (-p -42)
- Preserve positional argument order when reclaiming from bool short flags
- Add requires constraint on Positional/Short default constructors
- Add tests for negative values and multi-bool reclaim ordering
@liuzicheng1987
Copy link
Contributor

@Centimo , first of all, great job! Thank you so much.

There are a couple of minor review comments, which should take you like 10 minutes to resolve. Very minor stuff.

Also, could you update the README.md as well? This is a significant and very cool new functionality that should be highlighted in the README.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants