Skip to content

Command line option parser

cannorin edited this page Apr 19, 2019 · 12 revisions

Module: FSharp.CommandLine.Options

Source: src/FSharp.CommandLine/options.fs


FSharp.CommandLine.Options provides a declarative and highly reusable command line option parser.

Capable of inputs like -f a.txt, --file=a.txt, -Wall, --flag+, and /dosstyle. Also treats -- ("bare double dash") correctly.

Can be either used

  1. with CommandOption.parse: CommandOption<'a> -> string list -> ('a option * string list) function.

  2. with | OptionParse <option> <result_deconstructor> active pattern.

  3. inside command {} computation expression.

You should not create your own --help option; this library handles it by itself.

Types and Modules

  • type CommandOption<'a> ... represents command options.

  • module CommandOption

    • val getRemainingOptions: (argv:string list) -> (found_options:string list) ... finds occurrences of options.
    • val parse: (opt: CommandOption<'a>) -> (argv: string list) -> (result:'a option * remaining_args:string list)
    • val parseMany: (opt: CommandOption<'a>) -> (argv: string list) -> (results:option list * remaining_args:string list) ... parses all the occurrences of specified option.
  • the rest will be explained later.

Computation Expression Syntax

commandOption can be used to define an option which takes an argument.

commandOption {
  names ["t"; "txt"] // no hyphens or slashes, please!
  description "Speficy text file."
  takes (format("%s").withNames["file"])
  suggests (fun _ -> [ CommandSuggestion.Files (Some "*.txt") ])
}

commandFlag can also be used to define a flag.

commandFlag {
  names ["f"; "force"]
  description "Use the --force, Luke!"
  style SingleHyphenStyle.SingleLong
}

Here are a few notes about several operators.

defaultValue operator

Specifies the value to use when the option is given without argument (e.g. used as a flag).

Can only be used inside commandOption {}.

Example:

let backupOption =
  commandOption {
    names ["b"]
    description "Copy the original file to <file>."
    takes (format("%s").map(fun file _ -> file))
    defaultValue (fun s -> sprintf "%s.bak" s)
  }

takes operator

takes (<parser>[<modifier>])

Source: src/FSharp.CommandLine/optionValues.fs

Specifies how to handle the argument. Can only be used inside commandOption {}.

Consists of two parts: the parser and the modifier.

Parsers

Specify how to parse the argument - the input string.

  1. format parser

Uses type-safe scanf format.

The types of captured variables will be inferred from the format string.

  1. regex parser

Uses .NET regex. Very similar to the famous regex active pattern.

Captures variables as string list; no type inference here.

Modifiers

Constructs another type of object from the captured variables, and/or name the captures for --help.

  • .map(fun .. -> ..) modifier

An obvious function to create higher constructs.

The names of arguments are important here and will be respected when shown in --help; you don't have to use the .withNames modifier alongside this.

  • .withNames[..] / |> withNames[..] modifier

A function to specify the names of the captures, used especially when .map is not needed but you want to make --help look better.

Comparison:

takes (format("%s:%i"))
//   -f, --file=<STRING_VALUE>:<INT_VALUE>
takes (format("%s:%i").withNames ["filename"; "index"]) 
//   -f, --file=<filename>:<index>
  • .asConst(..) / |> asConst .. modifier

When there are no captures, you can use this to avoid writing .map(fun _ -> ..).

Examples:

let fileOption =
  commandOption {
    names ["f"; "file"]
    description "Name of a file to use (Default index: 0)"
    takes (format("%s:%i").withNames ["filename"; "index"])
    takes (format("%s").map (fun filename -> (filename, 0)))
    suggests (fun _ -> [CommandSuggestion.Files None])
  }

type Verbosity = Quiet | Normal | Full | Custom of int

let verbosityOption =
  commandOption {
    names ["v"; "verbosity"]
    description "Display this amount of information in the log."
    takes (regex @"q(uiet)?$" |> asConst Quiet)
    takes (format "n" |> asConst Quiet)
    takes (format "normal" |> asConst Quiet)
    takes (regex @"f(ull)?$" |> asConst Full)
    takes (format("custom:%i").map (fun level -> Custom level))
    takes (format("c:%i").map (fun level -> Custom level))
  }

suggests operator

Specifies the shell suggestions it generates. Can only be used inside commandOption {}.

See Suggestion Generation System.

style operator

Specifies how to handle inputs like -abcd.

  • SingleHyphenStyle.SingleLong ... treat -abcd as --abcd.
  • SingleHyphenStyle.SingleShort ... treat -abcd as -a bcd.
  • SingleHyphenStyle.MergedShort ... treat -abcd as -a -b -c -d.

When not specified, MergedShort will be used (it's the UNIX tradition).

If you want to be like gcc (-Wall), SingleShort is what you want.

Short-hand definition

You can also use these methods to define new options/flags with fewer keystrokes:

  • Command.option(_names, _format, ?_descr, ?defVal, ?_style)

  • Command.flag(_names, ?_descr, ?_style)

Option augmentation

When you use command options within command {} computation expression, you may want to

  • restrict the number of occurrences to be only one
  • allow multiple occurrences and treat the result as a 'a list, not 'a option
  • assign a default value to the bound variable, if missing

All of the above can be achieved by option augmentation.

interface ICommandOption<'a>

When bound with opt .. in .. operator it assigns a value of type 'a.

Note that CommandOption<'b> implements ICommandOption<'a option>, since it assigns an optional value when bound with that operator.

type AugmentedCommandOption<'a, 'b>

Represents the 'augmented' command options that preserve specs of the original option but modify the parser's behaviour.

When bound with opt .. in .. operator it assigns a value of type 'b, because it implements ICommandOption<'b>.

module CommandOption

  • val map: (f: 'a -> 'b) -> (c: ICommandOption<'a>) -> AugmentedCommandOption<'a, 'b> ... map the parsed result.

  • val zeroOrMore (co: ICommandOption<'a option>) -> AugmentedCommandOption<'a option, 'a list> ... apply parser until no matches found, and return the result as a list.

  • val zeroOrExactlyOne (co: ICommandOption<'a option>) -> AugmentedCommandOption<'a list, 'a option> ... apply parser once and immediately fail if there are one or more occurrences left.

  • val whenMissingUse: (v: 'a) -> (co: ICommandOption<'a option>) -> AugmentedCommandOption<'a option, 'a> ... return a specified default value if there are no occurrences.

Example

let forceOption descr =
  commandFlag {
    names ["f"; "force"]
    description descr
  }
// val forceOption: ICommandOption<bool option>

let mainCommand =
  command {
    opt force1 in forceOption // val force1: bool option
    opt force2 in forceOption |> CommandOption.zeroOrExactlyOne
                              |> CommandOption.whenMissingUse false
                              // val force2: bool
    if force2 then
      ..
    else
      ..
  }

let forceOption2 descr =
  commandFlag {
    names ["f"; "force"]
    description descr
  } |> CommandOption.zeroOrExactlyOne
    |> CommandOption.whenMissingUse false
// val forceOption: ICommandOption<bool>