WoofWare.Myriad.Plugins learns to parse args

This post is about WoofWare.Myriad.Plugins, a set of F# source generators (see it on NuGet). Go and read the README on GitHub if you’re interested.

They are particularly intended for PublishAot ahead-of-time compilation contexts, in which reflection is heavily restricted, but also for anyone who doesn’t want reflection for whatever reason (e.g. “to obtain the ability to step through code in a debugger”, or “for more predictable speed”).

Since my last post, I’ve implemented the following:

  • [<JsonSerialize>] (to stamp out toJsonNode : 'T -> JsonNode methods)
  • [<CreateCatamorphism>] (to build a non-stack-overflowing catamorphism for an algebraic data type)
  • [<ArgParser>] (to stamp out an argument parser).

ArgParser

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[<ArgParser>]
type Foo =
    {
        [<ArgumentHelpText "Enable the frobnicator">]
        SomeFlag : bool
        A : int option
        [<ArgumentDefaultFunction>]
        B : Choice<int, int>
        [<ArgumentDefaultEnvironmentVariable "MY_ENV_VAR">]
        BWithEnv : Choice<int, int>
        C : float list
        // optionally:
        [<PositionalArgs>]
        Rest : string list // or e.g. `int list` if you want them parsed into a type too
    }
    static member DefaultB () = 4

Features:

  • Optional arguments of type 'a option.
  • [<ArgumentDefaultFunction>] and [<ArgumentDefaultEnvironmentVariable>] to auto-populate default arguments which are not supplied. Default values are modelled as Choice<'a, 'a>, with Choice1Of2 meaning “the user gave me this”, and Choice2Of2 meaning “this was populated from a default source”.
  • Help text with [<ArgumentHelpText>], summoned with --help in any position where the parser is expecting an argument, and also summoned during certain failures to parse.
  • Detailed control over TimeSpan parsing with [<ParseExact>] (and [<InvariantCulture>] if desired).
  • Accumulation of arguments supplied repeatedly: for example, Path : FileInfo list is populated with --path /foo/bar --path /baz/quux.
  • Handling of --foo=bar and --foo bar equivalently.
  • Booleans have arity 1 or 0, whichever leads to a successful parse: --flag and --flag=true and --flag true are equivalent.
  • Positional arguments can appear anywhere, although if they start with a -- then it is best to put them after a trailing -- separator: --named-arg=blah -- --pos-arg1 pos-arg2 --pos-arg3.