Console.Routing
3.1.0
See the version list below for details.
dotnet add package Console.Routing --version 3.1.0
NuGet\Install-Package Console.Routing -Version 3.1.0
<PackageReference Include="Console.Routing" Version="3.1.0" />
paket add Console.Routing --version 3.1.0
#r "nuget: Console.Routing, 3.1.0"
// Install Console.Routing as a Cake Addin #addin nuget:?package=Console.Routing&version=3.1.0 // Install Console.Routing as a Cake Tool #tool nuget:?package=Console.Routing&version=3.1.0
Console.Routing
Console.Routing is a framework that makes it easy to build command line tools. Proper command line parsing can be tricky and time consuming. This framework hopes to take that away. With this library You can implement a command line tool with almost no overhead.
Console.Routing works with a router that somewhat similar to ASP.NET. It routes commands and parameters from the command line a specific C# method and fills in all method parameters with the matching command line arguments. If no route is found, a clear user friendly error is given.
Documentation
Setup
By adding one line of code to the Program.Main
you enable route discovery and handling of arguments.
using ConsoleRouting;
Routing.Handle(args);
The examples below describe commands for a fictitious tool named tool
.
Discovery
You can expose any method to command line argument with the [Command]
attribute. To avoid accidental exposure,
the declaring class must be marked with [Module]
. This example is the bare minimum to get your first command up-and-running.
[Module]
public class MyTool
{
[Command]
public void Hello()
{
Console.WriteLine("Hello world!");
}
}
Which can be executed on the command line with:
> tool hello
Parameter types
String parameters
To interpret a command line parameter as a string, you can add a string parameter to your command method.
> tool hello John
[Command]
public void Hello(string name)
{
Console.Writeline("Hello " + name);
}
Flag parameters
Flags are used to set a setting to true. Each of the following three command lines statements will route to this method:
> tool hello John
> tool hello John --upper
[Command]
public void Hello(string name, Flag upper)
{
if (upper) name = name.ToUpper();
Console.Writeline("Hello " + name);
}
The Flag has a default cast to bool.
Alternatively, you can also use a bool parameter. The following method has the same command line signature as above.
[Command]
public void Hello(string name, bool upper)
{
if (upper) name = name.ToUpper();
Console.Writeline("Hello " + name);
}
Aligning with linux style arguments, you can abbreviate flags by using a single dash, and the first letter.
> tool hello John -u
You can group multiple letter flags:
> tool feed -cdr
[Command]
public void Feed(Flag cat, Flag dog, Flag rabbit)
{
}
Flag parameters with a value
In some tools it's common to add an argument that comes with a flag. A good exmple of this is the equivalent of Git's commit message.
> tool log --message "First commit"
> tool log -m "First commit"
You can create this behaviour with a Flag<string>
parameter.
[Command]
public void Log(Flag<string> message)
{
Logger.Log(message);
}
Notice that the Flag<T>
has an implicit cast to T
.
The Flag<T>
implementation currently also supports enums and int
.
> tool loop --count 5
[Command]
public void Loop(Flag<int> count)
{ ... }
Assignment parameters
You can provide key value pairs (assignments) as a parameter as well:
> tool login user=john password=secret
[Command]
public void Login(Assignment user, Assignment password)
{ ... }
Integer parameters
Integers are also regognised as parameter types. If the user provides anything else than an integer, in this case, the routing will not be match this method
> tool count 5
[Command]
public void Count(int max)
{
for (int i = 0; i < count; i++) Console.WriteLine(i);
}
More on parameters
Optional paramters
To make a parameter optional, you can add an [Optional]
attribute to it. The value will be set to null or default if it is not provided.
> tool greet John
[Command]
public void Greet([Optional]string name)
{
if (name is null)
{
Console.WriteLine("Hello");
}
else
{
Console.Writeline("Hello " + name);
}
}
This attribute is not necessary on any type of flag or assignment parameter since they are optional by design.
Parameter aliases
You can create an parameter alias with the [Alt]
attribute:
> tool debug --no-color
[Command]
public void Debug([Alt("no-color")] Flag nocolor)
{
}
More on commands
Command Overloading
You can overload your commands. So if you provide two commands with the same name, but different parameter types, the proper command route will be found:
> tool count
> tool count 3
> tool count Count
Each of the above inputs, will route to a different method below:
[Command]
public void Count()
{ ... }
[Command]
public void Count(int number)
{ ... }
[Command]
public void Count(string name)
{ ... }
Command name aliases
As an opposite of overloading, Console.Routing allows a single command to have multiple aliases.
> tool greet Anna
> tool greeting Anna
Just like the default command names, matching is case insensitive. Bare in mind, that if you add an alias, you should also provide the original name in the list, if you want to make that work as well.
[Command("greet", "greeting")]
public void Greet(string name)
{
Console.Writeline("Hello " + name);
}
Aliases also allow you to use commands that C# syntax do not allow. If you must you can parse a flag as a command, but keep in mind that it will just be treated as a literal, so if you need the abbreviation too, you have to add it yourself.
[Command("info", "give-info", "--info", "-i")]
public void Info(string name)
{
Console.Writeline("Hello " + name);
}
Hidden commands
If you want a command to be usable, but not showing up in the help, you can use the [Hidden] attribute:
[Command, Hidden]
public void Secret()
{ ... }
The Default command
You should always provide a command that respons when the user has given no input at all. This command can also be used for root flags: if no command or sub command has been given.
> tool --help
> tool --info
[Command, Default]
public void Info(Flag help, Flag version)
{
if (help.Set) ShowHelp();
if (version.Set) ShowVersion();
}
Nested Commands
You can create nested commands, or command groups, by marking a Module class as a command in itself:
> tool database update
> tool database drop
This example (borrowed from Entity Frameowrk command line tool), can be constructed like this:
[Module, Command]
public class Database
{
[Command]
public void Update() { }
[Command]
public void Drop() { }
}
You can create deeper nested commands by creating sub classes.
[Module, Command]
public class Main
{
[Command]
public class Sub
{
[Command]
public void SubSub()
{
}
}
}
Help text
A command can have an additional [Help]
attribute for descriptions in automatic documentation.
A mature command line tool, should have a help line for each command.
[Command, Help("This greeting greets any provided name")]
public void Greet(string name)
{
Console.WriteLine($"Hello {name}")
}
You can invoke the help documentation by calling PrintHelp
:
[Command, Help("Prints this help text")]
public void Help(Flag version)
{
Routing.PrintHelp();
}
[Command, Help("Says hello to name")]
public void Greet(string name)
{ ... }
The produced help text looks like this:
My tool:
Help --version | Prints this help text
Greet <name> | Says hello to name
The router gives you access to all route data, if you want to write your own implementation.
Documentation
By default ConsoleRouting provides a help command, that lists all the available visible commands. But it also allows for more indepth documentation by writing
> tool help <command>
This can optionally be followed bu subcommands. The help can print out four segments:
- The route, enabled by default
- The description, provided by the
[Help( ... )]
attribute. - The parameter list. This one can be enriched by providing C# XML inline documentation in your code.
- The extendeddocumentation, also provided by the
///<summary>
field in the C# XML documentation A fully documentation enriched command will look something like this:
/// <summary>Says hello to the person in question</summary>
/// <param name="name"> The name of the person that will be greeted. You can use any name in the known universe </param>
/// <param name="uppercase">Transforms the name to all caps</param>
/// <param name="repeats">How many times the greeting should be repeated</param>
[Command, Help("Says hello to the given name")]
public void Greet([Optional]string name, bool uppercase, Flag<int> repeats)
{
if (uppercase) name = name.ToUpper();
for(int i = 1; i < (repeats.HasValue ? repeats.Value : 1); i++)
{
Console.WriteLine($"Hello {name}!");
}
}
This will produce:
> tool help greet
Command:
Greet (<name>) --uppercase --repeats <value>
Description:
Says hello to the given name
Parameters:
(<name>) The name that will be greeted. You can use any name in the known universe
--uppercase Transforms the name to all caps
--repeats <value> How many times the greeting should be repeated
Documentation:
Says hello to the name in question
In order to enable xml documentation to be published with your tool - necessary for the help enrichment to work, you have to enable
it either your build settings (Visual Studio / Project / properties / Build / Output / Xml Documentation file) or directly in the .csproj
file of the app or dll where your commands reside:
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
Global Settings
You can mark any static class as a global settings class. This allows you to use the same parameters on all commands in your tool.
Only properties (not fields) will be set when a matching name is found. And currently only bool
is supported (a flag on the command line)
But it's the plan to add string
and int
(valued flags) later.
For this, use the [Global]
flag.
[Global]
public static class Settings
{
public static bool Debug { get; set; }
public static bool Verbose { get; set; }
}
You can have multple static classes marked as global.
Working with Multiple Projects or Commands not in your Startup Project.
By using the default Routing.Handle(args)
all modules and commands in your startup project will
be discovered as a route candidate. If you have commands in a different library (dll) or in multiple libraries,
you can use the RouteBuilder class instead.
The router builder allows you to add assemblies to the discovery list, either by providing the assembly or a type in the assembly. The example below has some duplicate functionality but it shows you the options:
var router = new RouterBuilder()
.Add(Assembly.GetExecutingAssembly())
.AddAssemblyOf<Program>()
.Add(AppDomain.CurrentDomain.GetAssemblies())
.Buid();
router.Handle(args);
Adding services through dependency injection
You can add services available for dependency injection through the RouterBuilder.
To add a service, use the .AddService()
method.
var router = new RouterBuilder()
.AddAssemblyOf<Program>()
.AddService<SomeService>()
.Buid();
Capturing
If you want a certain parameter to capture the route, you can add a Capture
.
If the capture parameter is found anywhere in the argument list, the execution will go
to that command, without further resolving other routes.
To have a capture, add a Capture
attribute to a command method like this:
[Command, Capture("--help", "-h")]
public void Help(Args args)
{
Console.WriteLine($"Help for these arguments: {args}");
}
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- microsoft.extensions.hosting (>= 5.0.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
3.3.0-beta-6 | 184 | 8/16/2023 |
3.3.0-beta-5 | 131 | 4/23/2023 |
3.3.0-beta-4 | 135 | 4/23/2023 |
3.3.0-beta-2 | 161 | 6/12/2022 |
3.3.0-beta-1 | 161 | 6/5/2022 |
3.2.2 | 763 | 4/7/2022 |
3.2.0 | 431 | 11/1/2021 |
3.1.2 | 322 | 10/14/2021 |
3.1.1 | 318 | 10/14/2021 |
3.1.0 | 354 | 10/13/2021 |
3.1.0-beta7 | 241 | 9/3/2021 |
3.1.0-beta6 | 236 | 9/3/2021 |
3.1.0-beta5 | 252 | 3/17/2021 |
3.1.0-beta4 | 255 | 3/15/2021 |
3.0.0 | 434 | 9/8/2020 |
3.0.0-beta9 | 294 | 7/16/2020 |
3.0.0-beta8 | 334 | 7/9/2020 |
3.0.0-beta7 | 355 | 7/9/2020 |
3.0.0-beta6 | 368 | 7/9/2020 |
3.0.0-beta5 | 320 | 7/7/2020 |
3.0.0-beta4 | 416 | 7/5/2020 |
3.0.0-beta3 | 368 | 7/3/2020 |
3.0.0-beta2 | 291 | 7/2/2020 |
Added C# inline XML documentation for commands and parameters