Tutorial
Introduction
The library is based on the simple observation that configuration parameters can be naturally described using discriminated unions. For instance:
type Arguments =
| Working_Directory of path: string
| Listener of host: string * port: int
| Log_Level of level: int
| Detach
Argu takes such discriminated unions and generates a corresponding argument parsing scheme. For example, a parser generated from the above template would take the following command line input
|
and parse it into the list
[ Working_Directory "/var/run" ; Listener("localhost", 8080) ; Detach ]
Argu is also capable of reading the AppSettings
section
of an application's configuration file:
|
Both XML configuration and command line arguments can be parsed at the same time. By default, command line parameters override their corresponding XML configuration.
Basic Usage
A minimal parser based on the above example can be created as follows:
open Argu
type CliArguments =
| Working_Directory of path: string
| Listener of host: string * port: int
| Data of base64: byte[]
| Port of tcp_port: int
| Log_Level of level: int
| Detach
interface IArgParserTemplate with
member s.Usage =
match s with
| Working_Directory _ -> "specify a working directory."
| Listener _ -> "specify a listener (hostname : port)."
| Data _ -> "binary data in base64 encoding."
| Port _ -> "specify a primary port."
| Log_Level _ -> "set the log level."
| Detach -> "detach daemon from console."
We extract the argument parser from the template using the following command:
let parser = ArgumentParser.Create<CliArguments>(programName = "gadget.exe")
We can get the automatically generated usage string by typing
let usage = parser.PrintUsage()
giving
|
To parse a command line input:
let results = parser.Parse [| "--detach"; "--listener"; "localhost" ; "8080" |]
which gives
let all = results.GetAllResults() // [ Detach; Listener ("localhost", 8080) ]
Querying Parameters
While getting a single list of all parsed results might be useful for some cases, it is more likely that you need to query the results for specific parameters:
let detach = results.Contains Detach
let listener = results.GetResults Listener
The following methods return the last observed result for given argument case
let dataOpt = results.TryGetResult Data
let logLevel = results.GetResult(Log_Level, defaultValue = 0)
Querying using quotations enables a simple and type safe way to deconstruct parse results into their constituent values.
Customization
The parsing behaviour of the configuration parameters can be customized by fixing attributes to the union cases:
type Argument =
| [<Mandatory>] Cache_Path of path: string
| [<NoCommandLine>] Connection_String of conn: string
| [<Unique>] Listener of host: string * port: int
| [<EqualsAssignment>] Assignment of value: string
| [<EqualsAssignmentOrSpaced>] AssignmentOrSpace of value: string
| [<AltCommandLine("-p")>] Primary_Port of tcp_port: int
In this case,
Mandatory
: parser will fail if no configuration for this parameter is given.NoCommandLine
: restricts this parameter to the AppSettings section.AltCommandLine
: specifies an alternative command line switch.EqualsAssignment
: enforces--assignment=value
and--assignment key=value
CLI syntax.EqualsAssignmentOrSpaced
: enforces--assignment=value
and--assignment value
CLI syntax.Unique
: parser will fail if CLI provides this argument more than once.
Many more attributes are also available, such as
First
: Argument can only be placed at the beginning of the command line.Hidden
: do not display in the help usage string.CustomAppSettings
: sets a custom key name for AppSettings.CustomAssignment
: works like EqualsAssignment but with a custom separator string.
Please see the API Reference for a complete list of all attributes provided by Argu.
Supported Primitives
Arguments can specify the following primitives as parameters:
bool
,byte
andsbyte
.int
,int16
andint64
.uint
,uint16
anduint64
.char
,string
andguid
.float
,double
anddecimal
.System.Numerics.BigInt
.byte[]
, which accepts base64 representations.
Optional and List parameters
Additionally, it is possible to specify argument parameters that are either optional or lists:
type VariadicParameters =
| [<EqualsAssignment>] Enable_Logging of path: string option
| Tcp_Ports of port: int list
which results in the following syntax:
|
Note that arguments that use optional or list must have precisely one parameter.
Enumeration parameters
Argu can also accept enumerations as parameters:
type MyEnum =
| First = 1
| Second = 2
| Third = 3
type EnumArguments =
| Get_Enum of MyEnum
which results in the syntax
|
Note that it is possible to specify F# unions instead of enumerations in this context, provided that these do not specify any parameters in any of their cases.
Main commands
Arguments carrying the MainCommand attribute can be used to specify the main set of arguments for the CLI. These arguments can be passed without the need to specify a switch identifier.
type WGetArguments =
| Quiet
| No_Check_Certificate
| [<MainCommand; ExactlyOnce; Last>] Urls of url: string list
which generates the syntax
|
SubCommands
As of Argu 3.0, it is possible to provide nested, contextual parsing. For example, consider this mock git CLI syntax:
[<CliPrefix(CliPrefix.Dash)>]
type CleanArgs =
| D
| F
| X
interface IArgParserTemplate with
member this.Usage =
match this with
| D -> "Remove untracked directories in addition to untracked files"
| F -> "Git clean will refuse to delete files or directories unless given -f."
| X -> "Remove only files ignored by Git."
and CommitArgs =
| Amend
| [<AltCommandLine("-p")>] Patch
| [<AltCommandLine("-m")>] Message of msg: string
interface IArgParserTemplate with
member this.Usage =
match this with
| Amend -> "Replace the tip of the current branch by creating a new commit."
| Patch -> "Use the interactive patch selection interface to chose which changes to commit."
| Message _ -> "Use the given <msg> as the commit message. "
and GitArgs =
| Version
| [<AltCommandLine("-v")>] Verbose
| [<CliPrefix(CliPrefix.None)>] Clean of ParseResults<CleanArgs>
| [<CliPrefix(CliPrefix.None)>] Commit of ParseResults<CommitArgs>
interface IArgParserTemplate with
member this.Usage =
match this with
| Version -> "Prints the Git suite version that the git program came from."
| Verbose -> "Print a lot of output to stdout."
| Clean _ -> "Remove untracked files from the working tree."
| Commit _ -> "Record changes to the repository."
and the following console app entrypoint
//[<EntryPoint>]
let main argv =
try
parser.ParseCommandLine(inputs = argv, raiseOnUsage = true) |> ignore
0
with :? ArguParseException as e -> eprintfn "%s" e.Message; 1
which generates the following syntax on corresponding command and subcommand help requests:
|
and for the subcommand:
|
This allows specifying parameters that are particular to a subcommand context.
For instance, git clean -fdx
parses correctly to [Clean [F; D; X]]
, however
git -f
or git commit -f
will both result in a parse error:
|
Inheriting parent arguments
Switches specified in the parent argument union do not automatically make it to the syntax of the child subcommand. For example the command
|
will result in parse error since Version
is not a part of the subcommand syntax,
but one of its parent syntax. It is possible to parent options visible inside subcommands
by attaching the InheritAttribute
to switches.
type GitArgs =
| [<Inherit>] Version
which would make the aforementioned syntax valid.
Post Processing
It should be noted here that arbitrary unions are not supported by the parser. Union cases can only contain fields of primitive types. This means that user-defined parsers are not supported. For configuration inputs that are non-trivial, a post-process facility is provided
NOTE prior to version 6.2.0, these methods were called PostProcessResult, PostProcessResults, TryPostProcessResult
let parsePort p =
if p < 0 || p > int UInt16.MaxValue then
failwith "invalid port number."
else p
let ports = results.GetResults(Port, parsePort)
This construct is useful since error handling is delegated to the mechanisms of Argu.
Unparsing Support
Argu is convenient when it comes to automated process spawning:
open System.Diagnostics
let arguments = parser.PrintCommandLineArgumentsFlat [ Port 42; Working_Directory "temp" ]
Process.Start("foo.exe", arguments)
It can also be used to auto-generate a suitable AppSettings
configuration file:
let xml = parser.PrintAppSettingsArguments [ Port 42; Working_Directory "/tmp" ]
which would yield the following:
|
More Examples
Check out the samples folder for CLI implementations that use Argu.
val string: value: 'T -> string
--------------------
type string = String
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
namespace System.Data
--------------------
namespace Microsoft.FSharp.Data
val byte: value: 'T -> byte (requires member op_Explicit)
--------------------
type byte = Byte
--------------------
type byte<'Measure> = byte
<summary> Interface that must be implemented by all Argu template types </summary>
union case CliArguments.Data: base64: byte array -> CliArguments
--------------------
namespace System.Data
--------------------
namespace Microsoft.FSharp.Data
type ArgumentParser = abstract Accept: visitor: IArgumentParserVisitor<'R> -> 'R member GetArgumentCases: unit -> ArgumentCaseInfo list member GetSubCommandParsers: unit -> ArgumentParser list member PrintCommandLineSyntax: ?programName: string * ?usageStringCharacterWidth: int -> string member PrintUsage: ?message: string * ?programName: string * ?hideSyntax: bool * ?usageStringCharacterWidth: int -> string static member Create: ?programName: string * ?helpTextMessage: string * ?usageStringCharacterWidth: int * ?errorHandler: IExiter * ?checkStructure: bool -> ArgumentParser<#IArgParserTemplate> member ErrorHandler: IExiter member HelpDescription: string member HelpFlags: string list member HelpTextMessage: string option ...
<summary> The Argu type generates an argument parser given a type argument that is an F# discriminated union. It can then be used to parse command line arguments or XML configuration. </summary>
--------------------
type ArgumentParser<'Template (requires 'Template :> IArgParserTemplate)> = inherit ArgumentParser new: ?programName: string * ?helpTextMessage: string * ?usageStringCharacterWidth: int * ?errorHandler: IExiter * ?checkStructure: bool -> ArgumentParser<'Template> override Accept: visitor: IArgumentParserVisitor<'a1> -> 'a1 member GetArgumentCaseInfo: value: 'Template -> ArgumentCaseInfo + 1 overload member GetSubCommandParser: [<ReflectedDefinition>] expr: Expr<(ParseResults<'SubTemplate> -> 'Template)> -> ArgumentParser<'SubTemplate> (requires 'SubTemplate :> IArgParserTemplate) member GetTag: value: 'Template -> int member Parse: ?inputs: string array * ?configurationReader: IConfigurationReader * ?ignoreMissing: bool * ?ignoreUnrecognized: bool * ?raiseOnUsage: bool -> ParseResults<'Template> member ParseCommandLine: ?inputs: string array * ?ignoreMissing: bool * ?ignoreUnrecognized: bool * ?raiseOnUsage: bool -> ParseResults<'Template> member ParseConfiguration: configurationReader: IConfigurationReader * ?ignoreMissing: bool -> ParseResults<'Template> member PrintAppSettingsArguments: args: 'Template list * ?printComments: bool -> string ...
<summary> The Argu type generates an argument parser given a type argument that is an F# discriminated union. It can then be used to parse command line arguments or XML configuration. </summary>
--------------------
new: ?programName: string * ?helpTextMessage: string * ?usageStringCharacterWidth: int * ?errorHandler: IExiter * ?checkStructure: bool -> ArgumentParser<'Template>
member ParseResults.Contains: [<ReflectedDefinition>] expr: Quotations.Expr<'Template> * ?source: ParseSource -> bool
member ParseResults.GetResults: [<ReflectedDefinition>] expr: Quotations.Expr<'Template> * ?source: ParseSource -> 'Template list
member ParseResults.GetResults: [<ReflectedDefinition>] expr: Quotations.Expr<('Field -> 'Template)> * parser: ('Field -> 'R) * ?source: ParseSource -> 'R list
member ParseResults.TryGetResult: [<ReflectedDefinition>] expr: Quotations.Expr<'Template> * ?source: ParseSource -> 'Template option
member ParseResults.TryGetResult: [<ReflectedDefinition>] expr: Quotations.Expr<('Field -> 'Template)> * parser: ('Field -> 'R) * ?source: ParseSource -> 'R option
member ParseResults.GetResult: [<ReflectedDefinition>] expr: Quotations.Expr<('Field -> 'Template)> * ?defaultValue: 'Field * ?source: ParseSource -> 'Field
member ParseResults.GetResult: [<ReflectedDefinition>] expr: Quotations.Expr<'Template> * ?defaultValue: 'Template * ?source: ParseSource -> 'Template
member ParseResults.GetResult: [<ReflectedDefinition>] expr: Quotations.Expr<('Field -> 'Template)> * defThunk: (unit -> 'Field) * ?source: ParseSource * ?errorCode: ErrorCode * ?showUsage: bool -> 'Field
member ParseResults.GetResult: [<ReflectedDefinition>] expr: Quotations.Expr<('Field -> 'Template)> * defaultValue: 'Field * parser: ('Field -> 'R) * ?source: ParseSource * ?errorCode: ErrorCode * ?showUsage: bool -> 'R
member ParseResults.GetResult: [<ReflectedDefinition>] expr: Quotations.Expr<('Field -> 'Template)> * defThunk: (unit -> 'Field) * parser: ('Field -> 'R) * ?source: ParseSource * ?errorCode: ErrorCode * ?showUsage: bool -> 'R
type MandatoryAttribute = inherit Attribute new: unit -> MandatoryAttribute
<summary> Demands at least one parsed result for this argument; a parse exception is raised otherwise. </summary>
--------------------
new: unit -> MandatoryAttribute
type NoCommandLineAttribute = inherit Attribute new: unit -> NoCommandLineAttribute
<summary> Disable CLI parsing for this argument. Use for AppSettings parsing only. </summary>
--------------------
new: unit -> NoCommandLineAttribute
type UniqueAttribute = inherit Attribute new: unit -> UniqueAttribute
<summary> Demands that the argument should be specified at most once; a parse exception is raised otherwise. </summary>
--------------------
new: unit -> UniqueAttribute
type EqualsAssignmentAttribute = inherit CustomAssignmentAttribute new: unit -> EqualsAssignmentAttribute
<summary> Use '--param=arg' or '--param key=value' assignment syntax in CLI. Requires that the argument should have parameters of arity 1 or 2 only. </summary>
--------------------
new: unit -> EqualsAssignmentAttribute
type EqualsAssignmentOrSpacedAttribute = inherit CustomAssignmentOrSpacedAttribute new: unit -> EqualsAssignmentOrSpacedAttribute
<summary> Use '--param=arg' assignment syntax in CLI. Parameters can also be assigned using space as separator e.g. '--param arg' Requires that the argument should have parameters of arity 1 only. </summary>
--------------------
new: unit -> EqualsAssignmentOrSpacedAttribute
type AltCommandLineAttribute = inherit Attribute new: [<ParamArray>] names: string array -> AltCommandLineAttribute member Names: string array
<summary> Declares a set of secondary CLI identifiers for the current parameter. Does not replace the default identifier which is either auto-generated or specified by the CustomCommandLine attribute. </summary>
--------------------
new: [<ParamArray>] names: string array -> AltCommandLineAttribute
type FirstAttribute = inherit CliPositionAttribute new: unit -> FirstAttribute
<summary> Declares that argument can only be placed at the beginning of the CLI syntax. A parse exception will be raised if that is not the case. </summary>
--------------------
new: unit -> FirstAttribute
type MainCommandAttribute = inherit Attribute new: argumentName: string -> MainCommandAttribute + 1 overload member ArgumentName: string
<summary> Declares that argument is the main command of the CLI syntax. Arguments are specified without requiring a switch. </summary>
--------------------
new: unit -> MainCommandAttribute
new: argumentName: string -> MainCommandAttribute
type ExactlyOnceAttribute = inherit Attribute new: unit -> ExactlyOnceAttribute
<summary> Demands that the argument should be specified exactly once; a parse exception is raised otherwise. Equivalent to attaching both the Mandatory and Unique attribute on the parameter. </summary>
--------------------
new: unit -> ExactlyOnceAttribute
type LastAttribute = inherit CliPositionAttribute new: unit -> LastAttribute
<summary> Declares that argument can only be placed at the end of the CLI syntax. A parse exception will be raised if that is not the case. </summary>
--------------------
new: unit -> LastAttribute
module CliPrefix from Argu
<summary> Predefined CLI prefixes to be added </summary>
--------------------
type CliPrefixAttribute = inherit Attribute new: prefix: string -> CliPrefixAttribute member Prefix: string
<summary> Specifies a custom prefix for auto-generated CLI names. This defaults to double dash ('--'). </summary>
--------------------
new: prefix: string -> CliPrefixAttribute
<summary> Single Dash prefix '-' </summary>
type Version = interface ICloneable interface IComparable interface IComparable<Version> interface IEquatable<Version> interface ISpanFormattable interface IFormattable new: unit -> unit + 4 overloads member Clone: unit -> obj member CompareTo: version: obj -> int + 1 overload member Equals: obj: obj -> bool + 1 overload ...
<summary>Represents the version number of an assembly, operating system, or the common language runtime. This class cannot be inherited.</summary>
--------------------
Version() : Version
Version(version: string) : Version
Version(major: int, minor: int) : Version
Version(major: int, minor: int, build: int) : Version
Version(major: int, minor: int, build: int, revision: int) : Version
<summary> No Cli Prefix </summary>
<summary> Argument parsing result holder. </summary>
union case GitArgs.Version: GitArgs
--------------------
type Version = interface ICloneable interface IComparable interface IComparable<Version> interface IEquatable<Version> interface ISpanFormattable interface IFormattable new: unit -> unit + 4 overloads member Clone: unit -> obj member CompareTo: version: obj -> int + 1 overload member Equals: obj: obj -> bool + 1 overload ...
<summary>Represents the version number of an assembly, operating system, or the common language runtime. This class cannot be inherited.</summary>
--------------------
Version() : Version
Version(version: string) : Version
Version(major: int, minor: int) : Version
Version(major: int, minor: int, build: int) : Version
Version(major: int, minor: int, build: int, revision: int) : Version
<summary> Parse exception raised by Argu </summary>
<summary>Represents a 16-bit unsigned integer.</summary>
type Process = inherit Component interface IDisposable new: unit -> unit member BeginErrorReadLine: unit -> unit member BeginOutputReadLine: unit -> unit member CancelErrorRead: unit -> unit member CancelOutputRead: unit -> unit member Close: unit -> unit member CloseMainWindow: unit -> bool member Kill: unit -> unit + 1 overload ...
<summary>Provides access to local and remote processes and enables you to start and stop local system processes.</summary>
--------------------
Process() : Process
Process.Start(startInfo: ProcessStartInfo) : Process
Process.Start(fileName: string, arguments: Collections.Generic.IEnumerable<string>) : Process
Process.Start(fileName: string, arguments: string) : Process
Process.Start(fileName: string, userName: string, password: Security.SecureString, domain: string) : Process
Process.Start(fileName: string, arguments: string, userName: string, password: Security.SecureString, domain: string) : Process