Getting started
argparse provides User-Defined Attributes (UDA) that can be used to annotate struct members that are part of command line interface.
Without User-Defined Attributes
Using UDAs is not required and if a struct has no UDAs then all data members are treated as named command line arguments:
import argparse;
// If struct has no UDA then all members are named arguments
struct Example
{
    // Basic data types are supported:
        // '--name' argument
        string name;
        // '--number' argument
        int number;
        // '--boolean' argument
        bool boolean;
    // Argument can have default value if it's not specified in command line
        // '--unused' argument
        string unused = "some default value";
    // Enums are supported
        enum Enum { unset, foo, boo }
        // '--choice' argument
        Enum choice;
    // Use array to store multiple values
        // '--array' argument
        int[] array;
    // Callback with no args (flag)
        // '--callback' argument
        void callback() {}
    // Callback with single value
        // '--callback1' argument
        void callback1(string value) { assert(value == "cb-value"); }
    // Callback with zero or more values
        // '--callback2' argument
        void callback2(string[] value) { assert(value == ["cb-v1","cb-v2"]); }
}
// This mixin defines standard main function that parses command line and calls the provided function:
mixin CLI!Example.main!((args)
{
    // 'args' has 'Example' type
    static assert(is(typeof(args) == Example));
    // do whatever you need
    import std.stdio: writeln;
    args.writeln;
    return 0;
});
Running the program above with -h argument will have the following output:

With User-Defined Attributes
Although UDA-less approach is useful as a starting point, it's not enough for real command line tool:
import argparse;
struct Example
{
    // Positional arguments are required by default
    @PositionalArgument(0)
    string name;
    // Named arguments can be attributed in bulk (parentheses can be omitted)
    @NamedArgument
    {
        // '--number' argument
        int number;
        // '--boolean' argument
        bool boolean;
        // Argument can have default value if it's not specified in command line
        // '--unused' argument
        string unused = "some default value";
    }
    // Enums are also supported
    enum Enum { unset, foo, boo }
    // '--choice' argument
    @NamedArgument
    Enum choice;
    // Named argument can have specific or multiple names
    @NamedArgument("apple","appl")
    int apple;
    @NamedArgument("b","banana","ban")
    int banana;
}
// This mixin defines standard main function that parses command line and calls the provided function:
mixin CLI!Example.main!((args)
{
    // 'args' has 'Example' type
    static assert(is(typeof(args) == Example));
    // do whatever you need
    import std.stdio: writeln;
    args.writeln;
    return 0;
});
Running the program above with -h argument will have the following output:

Last modified: 24 October 2025