Module dcli.program_options

Handles program options which are arguments passed with a leading - or -- and followed by a value

Features

  • Input validation
  • Customize seperators for associative array args
  • Supports environment variables
  • Supports default values
  • Supports custom types that have a constructor that is called with a string

  • Enhancements over std.getopt

  • getopt(args) is destructive on args.
  • You cannot create your getopts and the parse later, which in combination with try/catch leads to awkward code
  • getopt doesn't accept $ ./program -p 3. For short opts, you have to do $ ./program -p3.
  • getopt doesn't allow case-sensitive short name and a case-insensitive long name
  • getopt will initialize an array type with the default values AND what the program arg was.
  • You cannot assign values to bundled short args, they are only incrementable
  • There is no way to handle what happens with duplicate arguments

  • Example

    import std.file: thisExePath;
    import std.process: environment;
    
    environment["OPT_4"] = "22";
    
    auto args = [
        thisExePath,            // Program name should be ignored
        "program_name",         // Unknown argument, there's a handler for stray arguments
        "--opt-1", "value1",    // "--arg value" format
        "-b", "1",              // "-a 1" format, case sensitive short name by default
        "--Opt-3=2",            // "--arg=value" format, case insesitive long name by default
        // "--opt-4",           // Set by an envaronment variable
        "--OPT-5", "4",         // first value of an int array
        "--opt-5", "5",         // second value of an int array
        // "--opt-6",           // Not set, will be give default value of int array
        "--opt-7", "9",         // Also an array
        "--unknown", "ha",      // Unknown option and value
        "--opt-8=two",          // An enum vaue
        "-i", "-j", "--incremental", "--opt-9", // Option aliasing
        "--opt-10", "11",       // A validated, must be greater than 10
        "--opt-11", "3,4,5",    // Array format "--arg=v0,v1,v2"
        "--opt-12", "1=2::3=4::5=6", // Associative array with custom seperator (Default is ",")
        "--opt-13", "verbose",  // A custom parsed value - opt13 is an int
        "-xyz=-7",              // Setting multiple, bundleable options at once
        "--opt-1", "value2",    // Uses duplication policy to be ignored
        "--opt-14", "1,2",      // Parses to a Custom type
        "--opt-15",             // Boolean, no value
        "--",                   // Args after this are ignored
        "extra",
    ];
    
    enum Enum { one, two, }
    static struct Custom {
        int x;
        int y;
        this(int a, int b) {
            x = a;
            y = b;
        }
        this(string str) {
            import std.string: split;
            import std.conv: to;
            auto parts = str.split(",");
            x = parts[0].to!int;
            y = parts[1].to!int;
        }
    }
    
    auto options = ProgramOptions!(
        Option!("opt-1", string)
            .shortName!"a"
            .description!"This is the description for option 1"
            .duplicatePolicy!(OptionDuplicatePolicy.firstOneWins),
        Option!("opt-2", int)
            .shortName!"b"
            .description!"This is the description for option 2",
        Option!("opt-3", int)
            .shortName!"B"
            .description!(
    `There are three kinds of comments:
    1. Something rather sinister
    2. And something else that's not so sinister`
        ),
        Option!("opt-4", int)
            .defaultValue!3
            .environmentVar!"OPT_4"
            .description!"THis is one that takes an env var",
        Option!("opt-5", int[])
            .environmentVar!"OPT_5"
            .description!"THis is one that takes an env var as well",
        Option!("opt-6", int[])
            .defaultValue!([6, 7, 8]),
        Option!("opt-7", float[])
            .defaultValue!([1, 2]),
        Option!("opt-8", Enum),
        Option!("opt-9", int)
            .shortName!"i|j"
            .longName!"incremental|opt-9"
            .incremental!true
            .description!"sets some level incremental thingy",
        Option!("opt-10", int)
            .validator!(a => a > 10),
        Option!("opt-11", int[]),
        Option!("opt-12", int[int])
            .separator!"::",
        Option!("opt-13", int)
            .parser!((value) {
                if (value == "verbose") return 7;
                return -1;
            }),
        Option!("b0", int)
            .shortName!"x",
        Option!("b1", int)
            .shortName!"y",
        Option!("b2", int)
            .shortName!"z",
        Option!("opt-14", Custom),
        Option!("opt-15", bool),
        Option!("opt-16", bool)
            .longName!""
            .environmentVar!"OPT_16"
            .description!"THis one only takes and envornment variable and cant be set with any flags",
    )();
    
    string[] unknownArgs;
    options.unknownArgHandler = (string name) {
        unknownArgs ~= name;
        return false;
    };
    
    assert(options.parse(args) == ["extra"]);
    assert(unknownArgs == ["program_name", "--unknown", "ha"]);
    
    assert(options.opt1 == "value1");
    assert(options.opt2 == 1);
    assert(options.opt3 == 2);
    assert(options.opt4 == 22);
    assert(options.opt5 == [4, 5]);
    assert(options.opt6 == [6, 7, 8]);
    assert(options.opt7 == [9]);
    assert(options.opt8 == Enum.two);
    assert(options.opt9 == 4);
    assert(options.opt10 > 10);
    assert(options.opt11 == [3, 4, 5]);
    assert(options.opt12 == [1: 2, 3: 4, 5: 6]);
    assert(options.opt13 == 7);
    assert(options.b0 == -7);
    assert(options.b1 == -7);
    assert(options.b2 == -7);
    assert(options.opt14 == Custom(1, 2));
    assert(options.opt15 == true);
    
    assert(options.helpText ==
    `Options:
    -h  --help          Displays this help message
    -a  --opt-1         This is the description for option 1
    -b  --opt-2         This is the description for option 2
    -B  --opt-3         There are three kinds of comments:
                          1. Something rather sinister
                          2. And something else that's not so sinister
      --opt-4         THis is one that takes an env var
      --opt-5         THis is one that takes an env var as well
      --opt-6
      --opt-7
      --opt-8
    -i  --incremental   sets some level incremental thingy
      --opt-10
      --opt-11
      --opt-12
      --opt-13
    -x  --b0
    -y  --b1
    -z  --b2
      --opt-14
      --opt-15
    
    Environment Vars:
    OPT_4    See: --opt-4
    OPT_5    See: --opt-5
    OPT_16   THis one only takes and envornment variable and cant be set with any flags`
    );
    

    Classes

    NameDescription
    DuplicateProgramArgument Occurs if there's a duplicate argument
    InvalidProgramArgument Occurs when an argument does not validate
    MalformedProgramArgument Occurs when an argument is incorrectly formatted
    MissingProgramArgument Occurs when a program argument is missing
    ProgramOptionException Thrown when an error occurs in parsing

    Structs

    NameDescription
    ProgramOptions You can configure a ProgramOptions object with a number of Options and then use it to parse and array of command line arguments.

    Enums

    NameDescription
    OptionDuplicatePolicy The duplication policy can be passed to a ProgramOptions Option.duplicatePolicy

    Aliases

    NameTypeDescription
    Option OptionImpl!(name,T) Represents one program options. One of more of these can be given to a ProgramOptions object as template arguments.