Skip to content

ParserBuilder #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Oct 20, 2024
Merged

ParserBuilder #37

merged 21 commits into from
Oct 20, 2024

Conversation

rsore
Copy link
Owner

@rsore rsore commented Oct 20, 2024

Description

In this development branch I have implemented a ParserBuilder class, as proposed in #35.

The previous approach of passing all configuration options as template parameters directly to the Parser class led to complex, unreadable code, especially as the number of options and flags grew. By introducing ParserBuilder, the user can now build the parser step-by-step, resulting in cleaner and more maintainable code. This change simplifies parser creation without sacrificing compile-time safety and flexibility. The builder pattern not only makes the code more readable but also allows users to conditionally or incrementally add flags and options, making it more adaptable to dynamic configurations.

An example use-case can be seen below:

CLArgs::Parser parser = CLArgs::ParserBuilder{}
                                .add_program_description<"Program description">()
                                .add_flag<VerboseFlag>()
                                .add_option<ConfigOption>()
                                .build();

The ParserBuilder uses compile-time template instantiation to incrementally assemble the parser's configuration. Each add_flag, add_option, and add_program_description call modifies the parser type at compile time, ensuring that type safety is maintained throughout the process. This approach avoids runtime overhead while making the parser-building process more flexible.

The Parser type that is returned is the same as before, and the same API applies. As you can see, I have also added the notion of a "program description," which is an optional string the user can provide. If added, the generated help message will include this description.

A key advantage of this new builder pattern is the ability to conditionally add flags or options based on the target environment. For example, users can add platform-specific options or flags, such as enabling specific flags only for Windows builds, as shown below:

auto builder = CLArgs::ParserBuilder{}
                   .add_flag<VerboseFlag>()
                   .add_option<ConfigOption>();
#ifdef _WIN32
    builder = builder.add_flag<WindowsSpecificFlag>();
#endif
CLArgs::Parser parser = builder.build();

Related Issues

Closes #35.

How Has This Been Tested?

I have adapted existing tests to build with the new parser API, and they all pass. I have also introduced new unit tests, specific for the builder. Finally, manual and visual testing has been performed throughout development.

Further considerations

Though this new builder API does make the user-side parser definition more readable, I have a growing concern with regards to the Parser class definition. Consider the simple program() method:

template <CLArgs::CmdFlag... Flags, CLArgs::CmdOption... Options, CLArgs::StringLiteral ProgramDescription>
std::string_view
CLArgs::Parser<CLArgs::CmdFlagList<Flags...>, CLArgs::CmdOptionList<Options...>, ProgramDescription>::program() const noexcept
{
    return program_;
}

The current growth of template parameters is becoming unwieldy, particularly when considering future features such as positional arguments (#8), sub-commands, and validators (#4). To manage this complexity, I propose encapsulating the configuration into a ParserConfig struct. This would store all parameters as static members, simplifying the function signatures and improving readability. While there is a risk that the complexity could simply be shifted to this struct, the benefits of a centralized configuration object could outweigh that, especially as we introduce more features.

rsore added 21 commits October 16, 2024 22:51
…ith IsPartOf subclass of std::disjunction. Added is_part_of_v helper variable.
…e form of tuple types. Allows for more extensive customization later
…n a singleton API, users can easily implement it themselves.
@rsore rsore added feature New feature improvement Improvement to existing feature labels Oct 20, 2024
@rsore rsore linked an issue Oct 20, 2024 that may be closed by this pull request
Copy link

clang-tidy review says "All clean, LGTM! 👍"

@rsore rsore merged commit 0ed92d1 into main Oct 20, 2024
5 checks passed
@rsore rsore deleted the 35-parser-builder branch October 20, 2024 21:44
rsore added a commit that referenced this pull request Oct 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature improvement Improvement to existing feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement a compile-time parser builder class
1 participant