Appearance
TIP
It is perfectly legal to use the same input class across multiple commands
Oakton commands consist of two parts:
- A concrete input class that holds all the argument and flag data inputs
- A concrete class that inherits from
OaktonCommand<T>
orOaktonAsyncCommand<T>
where the "T" is the input class in the first bullet point
Looking again at the NameCommand
from the getting started topic:
cs
[Description("Print somebody's name")]
public class NameCommand : OaktonCommand<NameInput>
{
public NameCommand()
{
// The usage pattern definition here is completely
// optional
Usage("Default Color").Arguments(x => x.Name);
Usage("Print name with specified color").Arguments(x => x.Name, x => x.Color);
}
public override bool Execute(NameInput input)
{
var text = input.Name;
if (!string.IsNullOrEmpty(input.TitleFlag))
{
text = input.TitleFlag + " " + text;
}
AnsiConsole.MarkupLine($"[{input.Color}]{text}[/]");
// Just telling the OS that the command
// finished up okay
return true;
}
}
There's only a couple things to note about a command class:
- The only entry point is the
Execute()
method - The boolean return from the
Execute()
method simply denotes whether or not the command completed successfully. This will be important for any kind of console application that you'll want to use in automated builds to prevent false positive results - The
Usages
syntax in the constructor is explained in a section below - The
[Description]
attribute on the class is strictly for the purpose of providing help text and is not mandatory
If you want to make use of async/await
, you can inherit from OaktonAsyncCommand<T>
instead. The only difference is signature of the Execute()
method:
cs
public class DoNameThingsCommand : OaktonAsyncCommand<NameInput>
{
public override async Task<bool> Execute(NameInput input)
{
AnsiConsole.MarkupLine($"[{input.Color}]Starting...[/]");
await Task.Delay(TimeSpan.FromSeconds(3));
AnsiConsole.MarkupLine($"[{input.Color}]Done! Hello {input.Name}[/]");
return true;
}
}
Argument Usages
As shown in the NameCommand
in the section above, you can specify the valid combinations of arguments and the order in which they should follow in the command line usage by modifying the Usages
property in the constructor function of a command:
cs
public OtherNameCommand()
{
// You can specify multiple usages
Usage("describe what is different about this usage")
// Specify which arguments are part of this usage
// and in what order they should be expressed
// by the user
.Arguments(x => x.Name, x => x.Color)
// Optionally, you can provide a white list of valid
// flags in this usage
.ValidFlags(x => x.TitleFlag);
}
If you do not explicitly specify usages, Oakton will assume that all arguments are mandatory and in the order in which they appear within the input class.
Specifying Command Names
By default, Oakton determines the command name for a command class by taking the class name, removing the "Command" suffix, and then using the all lower case remainder of the string. For an example, a command class called CleanCommand
would have the command name clean. To override that behavior, you can use the Alias
property on Oakton's [Description]
attribute as shown below:
cs
[Description("Say my name differently", Name = "different-name")]
public class AliasedCommand : OaktonCommand<NameInput>
Asynchronous Commands
Oakton also supports the ability to write asynchronous commands that take advantage of the ability to use asynchronous Program.Main()
method signatures in recent versions of .Net.
To write an asynchronous command, use the OaktonAsyncCommand<T>
type as your base class for your command as shown below:
cs
[Description("Say my name", Name = "say-async-name")]
public class AsyncSayNameCommand : OaktonAsyncCommand<SayName>
{
public AsyncSayNameCommand()
{
Usage("Capture the users name").Arguments(x => x.FirstName, x => x.LastName);
}
public override async Task<bool> Execute(SayName input)
{
await Console.Out.WriteLineAsync($"{input.FirstName} {input.LastName}");
return true;
}
}
Likewise, to execute asynchronously from Program.Main()
, there are new overloads on CommandExecutor
for async:
cs
static Task<int> Main(string[] args)
{
var executor = CommandExecutor.For(_ =>
{
// Find and apply all command classes discovered
// in this assembly
_.RegisterCommands(typeof(Program).GetTypeInfo().Assembly);
});
return executor.ExecuteAsync(args);
}