CommandLineInterface

Provides the functionality of parsing command line arguments, and then calling a command.

Description: The Modules template parameter is used directly with jaster.cli.binder.ArgBinder to provide the arg binding functionality. Please refer to ArgBinder's documentation if you are wanting to use custom made binder funcs.

Commands are detected by looking over every module in Modules, and within each module looking for types marked with @Command and matching their patterns to the given input.

Patterns: Patterns are pretty simple.

Example #1: The pattern "run" will match if the given command line args starts with "run".

Example #2: The pattern "run all" will match if the given command line args starts with "run all" (["run all"] won't work right now, only ["run", "all"] will)

Example #3: The pattern "r|run" will match if the given command line args starts with "r", or "run".

Longer patterns take higher priority than shorter ones.

Patterns with spaces are only allowed inside of @Command pattern UDAs. The @CommandNamedArg UDA is a bit more special.

For @CommandNamedArg, spaces are not allowed, since named arguments can't be split into spaces.

For @CommandNamedArg, patterns or subpatterns (When "|" is used to have multiple patterns) will be treated differently depending on their length. For patterns with only 1 character, they will be matched using short-hand argument form (See ArgPullParser's documentation). For pattern with more than 1 character, they will be matched using long-hand argument form.

Example #4: The pattern (for @CommandNamedArg) "v|verbose" will match when either "-v" or "--verbose" is used.

Internally, CommandResolver is used to perform command resolution, and a solution custom to CommandLineInterface is used for everything else regarding patterns.

Commands: A command is a struct or class that is marked with @Command.

A default command can be specified using @CommandDefault instead.

Commands have only one requirement - They have a function called onExecute.

The onExecute function is called whenever the command's pattern is matched with the command line arguments.

The onExecute function must be compatible with one of these signatures: void onExecute(); int onExecute();

The signature that returns an int is used to return a custom status code.

If a command has its pattern matched, then its arguments will be parsed before onExecute is called.

Arguments are either positional (@CommandPositionalArg) or named (@CommandNamedArg).

Constructors

this
this(ServiceProvider services)
Undocumented in source.
this
this(CommandLineSettings settings, ServiceProvider services)

Members

Aliases

ArgBinderInstance
alias ArgBinderInstance = ArgBinder!Modules
Undocumented in source.

Functions

parseAndExecute
int parseAndExecute(string[] args, IgnoreFirstArg ignoreFirst)
int parseAndExecute(ArgPullParser args)

Parses the given args, and then executes the appropriate command (if one was found).

Parameters

Modules

The modules that contain the commands and/or binder funcs to use.

Detailed Description

Dependency Injection

Whenever a command object is created, it is created using dependency injection (via the jioc library).

Each command is given its own service scope, even when a command calls another command.

Positional Arguments

A positional arg is an argument that appears in a certain 'position'. For example, imagine we had a command that we wanted to execute by using "myTool create SomeFile.txt \"This is some content\"".

The shell will pass ["create", "SomeFile.txt", "This is some content"] to our program. We will assume we already have a command that will match with "create". We are then left with the other two strings.

"SomeFile.txt" is in the 0th position, so its value will be binded to the field marked with @CommandPositionalArg(0).

"This is some content" is in the 1st position, so its value will be binded to the field marked with @CommandPositionalArg(1).

Named Arguments

A named arg is an argument that follows a name. Names are either in long-hand form ("--file") or short-hand form ("-f").

For example, imagine we execute a custom tool with "myTool create -f=SomeFile.txt --content \"This is some content\"".

The shell will pass ["create", "-f=SomeFile.txt", "--content", "This is some content"]. Notice how the '-f' uses an '=' sign, but '--content' doesn't. This is because the ArgPullParser supports various different forms of named arguments (e.g. ones that use '=', and ones that don't). Please refer to its documentation for more information.

Imagine we already have a command made that matches with "create". We are then left with the rest of the arguments.

"-f=SomeFile.txt" is parsed as an argument called "f" with the value "SomeFile.txt". Using the logic specified in the "Binding Arguments" section (below), we perform the binding of "SomeFile.txt" to whichever field marked with @CommandNamedArg matches with the name "f".

["--content", "This is some content"] is parsed as an argument called "content" with the value "This is some content". We apply the same logic as above.

Binding Arguments

Once we have matched a field marked with either @CommandPositionalArg or @CommandNamedArg with a position or name (respectively), then we need to bind the value to the field.

This is where the ArgBinder is used. First of all, please refer to its documentation as it's kind of important. Second of all, we esentially generate a call similar to: ArgBinderInstance.bind(myCommandInstance.myMatchedField, valueToBind)

So imagine we have this field inside a command - @CommandPositionalArg(0) int myIntField;

Now imagine we have the value "200" in the 0th position. This means it'll be matchd with myIntField.

This will esentially generate this call: ArgBinderInstance.bind(myCommandInstance.myIntField, "200")

From there, ArgBinder will do its thing of binding/converting the string "200" into the integer 200.

ArgBinder has support for user-defined binders (in fact, all of the built-in binders use this mechanism!). Please refer to its documentation for more information, or see example-04.

You can also specify validation for arguments, by attaching structs (that match the definition specified in ArgBinder's documentation) as UDAs onto your fields.

Beware you need to attach your validation struct as @Struct() (or with args) and not @Struct, notice the first one has parenthesis.

Boolean Binding

Bool arguments have special logic in place.

By only passing the name of a boolean argument (e.g. "--verbose"), this is treated as setting "verbose" to "true" using the ArgBinder.

By passing a value alongside a boolean argument that is either "true" or "false" (e.g. "--verbose true", "--verbose=false"), then the resulting value is passed to the ArgBinder as usual. In other words, "--verbose" is equivalent to "--verbose true".

By passing a value alongside a boolean argument that isn't one of the preapproved words then: The value will be treated as a positional argument; the boolean argument will be set to true.

For example, "--verbose" sets "verbose" to "true". Passing "--verbose=false/true" will set "verbose" to "false" or "true" respectively. Passing "--verbose push" would leave "push" as a positional argument, and then set "verbose" to "true".

These special rules are made so that boolean arguments can be given an explicit value, without them 'randomly' treating positional arguments as their value.

Optional And Required Arguments

By default, all arguments are required.

To make an optional argument, you must make it Nullable. For example, to have an optional int argument you'd use Nullable!int as the type.

Note that Nullable is publicly imported by this module, for ease of use.

Before a nullable argument is binded, it is first lowered down into its base type before being passed to the ArgBinder. In other words, a Nullable!int argument will be treated as a normal int by the ArgBinder.

If **any** required argument is not provided by the user, then an exception is thrown (which in turn ends up showing an error message). This does not occur with missing optional arguments.

Raw Arguments

For some applications, they may allow the ability for the user to provide a set of unparsed arguments. For example, dub allows the user to provide a set of arguments to the resulting output, when using the likes of dub run, e.g. dub run -- value1 value2 etc.

CommandLineInterface also provides this ability. You can use either the double dash like in dub ('--') or a triple dash (legacy reasons, '---').

After that, as long as your command contains a string[] field marked with @CommandRawListArg, then any args after the triple dash are treated as "raw args" - they won't be parsed, passed to the ArgBinder, etc. they'll just be passed into the variable as-is.

For example, you have the following member in a command @CommandRawListArg string[] rawList;, and you are given the following command - ["command", "value1", "--", "rawValue1", "rawValue2"], which will result in rawList's value becoming ["rawValue1", "rawValue2"]

Arguments Groups

Arguments can be grouped together so they are displayed in a more logical manner within your command's help text.

The recommended way to make an argument group, is to create an @CommandArgGroup UDA block:

@CommandArgGroup("Debug", "Flags relating the debugging.")
{
    @CommandNamedArg("trace|t", "Enable tracing") Nullable!bool trace;
    ...
}

While you *can* apply the UDA individually to each argument, there's one behaviour that you should be aware of - the group's description as displayed in the help text will use the description of the last found @CommandArgGroup UDA.

See Also

jaster.cli.infogen if you'd like to introspect information about commands yourself.

jaster.cli.commandparser if you only require the ability to parse commands.

Meta