- C++ 100%
| examples | ||
| include | ||
| .gitignore | ||
| LICENCE.md | ||
| README.md | ||
| uncrustify-cfg | ||
CLIARGS
Cliargs (pronounced: CLEE-argz) is a header-only, C++26, constexpr ("constant expression"), functor-based parser library for command-line arguments. This library and any projects using it are licensed under the GNU Affero General Public Licence v3.0 or any later, compatible licence. See licence file for more information.
Its primary, stand-out feature is using member types and variadic parameter packs for everything instead of hashmaps of variants for options and vectors for arguments and the like. Every piece of data collected along the context of a function is given as a reference to their exact type limiting internal invariants. In theory, this allows a program compiled with Cliargs to avoid performing name comparisons in a hashmap, binary tree, or otherwise input storage or wasting memory with collections of variants but the difference here in human-noticeable operation time and memory usage would be negligible. In these ways, it fills a very similar niche to the clap Rust crate ("Command Line Argument Parser for Rust") but is deprived of the attributes conventions that Rust allows for with its pre-processors.
Usage
All tokens found within cliargs namespace.
Of most note are the parsers in cliargs/parsers.hpp such as cliargs::normalised_parser_from_base and cliargs::natural_integer_parser and the kernels and subkernels in cliargs/kernels.hpp such as cliargs::basic_kernel and its subkernels.
Examples
In this project's examples/ directory, there is a number of potential implementations for how the Cliargs templates can be used.
Whilst most of this structure can be changed with kernelised template overloads (i.e. defining the same template on a struct subtype of an existing kernel), the general form of a Cliargs command follows as such:
struct some_command {
// optional, is what is executed for this command scope.
template <typename Kernel_>
static auto operator()(
Kernel_&, // will be reference to the kernel passed to `execute()`
auto&& context, // generated from
auto&&... args // number of values here corresponds to length of `arguments` + `optional_arguments`, with the arguments in that order
);
// argument definitions
using arguments = std::tuple<some_argument_a, some_argument_b, some_argument_c>;
using optional_arguments = std::tuple<some_optional_argument_d, some_optional_argument_e>;
// optional, defines options for the command scope
template <typename Kernel_>
struct options {
// used for `parser_member`, the shape of parser functor types is described in `cliargs/parsers.hpp`
using parser_t = some_parser;
constexpr inline options(
Kernel_& kernel // reference to a given kernel, could be anything
) :
some_string_member(
Kernel_::template make_string_option<Kernel_>(
kernel,
// literals, at least one is required, either format is acceptable
"string-long-option", // long option literal, used like `--string-long-option <string value>` in the program
'S' // short option literal, used like `-S <string value>` in the program
)
),
some_bool_member(
Kernel_::template make_bool_option<Kernel_>(
kernel,
// literals, at least one is required, either format is acceptable
"bool-long-option", // long option literal, used like `--bool-long-option` in the program
'B' // short option literal, used like `-B` in the program
)
),
some_parsed_member(
Kernel_::template make_parsed_option<Kernel_, parser_t>(
kernel,
// literals, at least one is required, either format is acceptable
"parsed-long-option", // long option literal, used like `--parsed-long-option <parseable string value>` in the program
'P' // short option literal, used like `-P <parseable string value>` in the program
)
)
{}
// members must be defined with the kernelised option types
typename Kernel_::template string_option_tt<Kernel_> some_string_member;
typename Kernel_::template bool_option_tt<Kernel_> some_bool_member;
typename Kernel_::template parsed_option_tt<Kernel_, parser_t> some_parsed_member;
// `members` type defined with pointers-to-members constants
using members = typename Kernel_::template option_members_tt<
Kernel_,
&options::some_string_member,
&options::some_bool_member,
&options::some_parsed_member
>;
};
};
Arguments are structs that must define a public parser type member and a NAME string-view-like, constant member. Optional arguments are the same and can define a DEFAULT_VALUE constant member if empty optionals are not preferred on non-specification.
Modifying behaviour - kernels
Kernels are highly internally templated structures. Overriding the behaviour of one function in a kernel is as simple as using a kernel subclass of the original kernel class. For example,
struct kernel_a {
// a collection type template
template <typename T_>
using collection_tt = std::vector<T_>;
template <typename Kernel_, typename T_>
static constexpr inline decltype(auto) make_empty_collection() {
using collection_t = typename Kernel_::template collection_t<T_>;
return collection_t{};
}
};
// inherits from `kernel_a`
struct kernel_b : kernel_a {
template <typename T_>
using collection_tt = std::list<T_>;
};
int main() {
auto collection = kernel_b::template make_empty_collection<kernel_b, int>(); // `collection` is of type `std::list<int>`
return 0;
}
// Aside: In this example, `collection_tt` does not accept a `Kernel_` type parameter nor does the `make_empty_collection` member function accept a reference to the kernel as all of the type template kernel members throughout Cliargs do but this is to showcase the utility with a minimal example.
allows the redefining of one member to change the behaviour of other members. Another advantage of the kernelisation approach is that they create their dependency chains at the point that one of their members is specialised external to the kernel itself which avoids the issue of getting the "xyz type is undefined" error when it is defined later in the struct. This allows the use of "subkernels" (i.e. incomplete kernels, members of which usually use members of the kernel template parameter type that are defined within a different subkernel) as every individual member of a kernel can be defined in a separate header file independently if so chosen.
Dependencies
As a rule, this header-only library does not add independent, versioned submodules. The reasoning for this is manifold:
-
this can lead to situations where large projects employ the same header-only library more than once but at different versions of it,
-
there is no guarantee that updates required to keep a header-only library functional and/or safe are going to remain header-only, and
-
this library does not define any specifics on how it must be used in a compiled program.
Dependent submodules (i.e. submodules that explicitly exist only to serve Cliargs) would be allowed but are unexpected to be of use.
Zpauk
Version: v0.1.1
Git SHA: 4841cd858b9df985b71b369f3ae8e5d316eb0306588ccbfb98f83599cbcd1ffb
Available at: git.mnpx.dev
Zpauk's include directory must be in include path.
Credits
- MnPX, designer and developer of Cliargs.