SnapCLI 1.0.0-pre

This is a prerelease version of SnapCLI.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package SnapCLI --version 1.0.0-pre                
NuGet\Install-Package SnapCLI -Version 1.0.0-pre                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SnapCLI" Version="1.0.0-pre" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SnapCLI --version 1.0.0-pre                
#r "nuget: SnapCLI, 1.0.0-pre"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install SnapCLI as a Cake Addin
#addin nuget:?package=SnapCLI&version=1.0.0-pre&prerelease

// Install SnapCLI as a Cake Tool
#tool nuget:?package=SnapCLI&version=1.0.0-pre&prerelease                

The SnapCLI library provides a simple Command Line Interface (CLI) API in C#, built on top of System.CommandLine.

NuGet package

The library is available in a NuGet package:

Motivation

The goal of this project is to provide a simple and effective way to handle command-line commands and parameters, allowing developers to create POSIX-like CLI applications with minimal hassle in parsing the command line and enabling them to focus on application logic. Additionally, it facilitates providing all necessary information for the application's help system, making it easily accessible to end users. The DragonFruit project was a step in this direction, but is very limited in abilities it provides.

Command line syntax

Since this project is based on the System.CommandLine package, the parsing rules are exactly the same as those for that package. The Microsoft documentation provides detailed explanations of the command-line syntax recognized by System.CommandLine. We will include more links to this documentation throughout the text below.

API Paradigm

The API paradigm of this project is to use attributes to declare and describe CLI commands, options, and arguments.

Any public static method can be declared as a CLI command handler using the [Command] attribute, and effectively represent an entry point to the CLI application for that command. Any parameter of command handler method automatically becomes a command option. See examples below for details.

Where are the classes?

There are multiple frameworks that require separate class implementation for each command. In my opinion, creating a per-command classes adds unnecessary bloat to the code with little to no benefit. To provide additional information such as descriptions and aliases, attributes are anyway required on top of the class declaration. Since the goal is to simplify things as much as possible, I decided not to use classes at all in my approach.

Usage

Commands

A command in command-line input is a token that specifies an action or defines a group of related actions.

Any public static method can be declared as a CLI command handler using the [Command] attribute.

[Command]
static public void Hello() 
{
    Console.WriteLine("Hello World!");
}

Additional information can be provided in attribute parameters to enhance command-line parsing and the help system, such as the command's explicit name, aliases, description, and whether the command is hidden

[Command(name:"hello", aliases:["hi"], description:"Hello example", hidden:false)]
static public void Hello() 
{
    Console.WriteLine("Hello World!");
}
Command name convention
  • If a program has only one command handler method declared with [Command] attribute and the command name is not explicitly specified in the name parameter of the attribute, this command is automatically treated as root command (see more below).
  • If the command name not specified in the attribute then the method name, converted to lower case, is implicitly used as the command name. For example method Hello() will handle Hello command.
  • If method name is used implicitly and contains an underscore (_), it declares a subcommand. For example, a method named "list_orders()" will define a subcommand orders under list command.
  • If name is specified explicitly and contains spaces, it declares a subcommand. For example, name:"list orders" declares orders as a subcommand of the list command.

More information on subcommands is provided below.

Options

An option is a named parameter that can be passed to a command.

Any parameter of command handler method automatically becomes a command option. In the next example name becomes option for command hello:

[Command(name:"hello", aliases:["hi"], description:"Hello example", hidden:false)]
static public void Hello(string name = "World") 
{
    Console.WriteLine($"Hello {name}!");
}

Of course we can provide additional information about option with attribute [Options] such as explicit name, aliases, description, and whatever option is required.

[Command(name:"hello", aliases:["hi"], description:"Hello example", hidden:false)]
static public void Hello(
    [Option(name:"name", description:"The name we should use for the greeting")]
    string name = "World"
) 
{
    Console.WriteLine($"Hello {name}!");
}

Required options must be specified on the command line; otherwise, the program will show an error and display the command help. Method parameters that have default values (as in the examples above) are, by default, translated into options that are not reuired, while those without default values are always translated into required options.

Option name convention

  • If option name is not explicitly specified in the attribute, or attribute is ommitted, the name of the parameter will be implicitly used.
  • The option name automatically prepended with one dash - (if name consists of a single letter) or two dashes --, unless it is already starting with dash.

What do we have so far?

> sample hello -?
Description:
  Hello example

Usage:
  sample hello [options]

Options:
  --name <name>   The name we should use for the greeting [default: World]
  -?, -h, --help  Show help and usage information

> sample hello
Hello World!

> sample hello --name Michael
Hello Michael!

Arguments

An argument is a value passed to an option or command without specifying an option name; it is also referred to as a positional argument.

You can declare that parameter is argument with an [Argument] attribute. Lets change our example a little bit:

[Command(name:"hello", aliases:["hi"], description:"Hello example", hidden:false)]
static public void Hello(
    [Argument(name:"name", description:"The name we should use for the greeting")]
    string name = "World"
) 
{
    Console.WriteLine($"Hello {name}!");
}

Now we don't need to specify --name option name. Also, note how the help message has changed:

> sample hello -?
Description:
  Hello example

Usage:
  sample hello [name] [options]

Arguments:
  [name]  The name we should use for the greeting [default: World]

Options:
  -?, -h, --help  Show help and usage information

> sample hello Michael
Hello Michael!

Argument name convention

  • Argument name is used only for help, it cannot be specified on command line.
  • If argument name is not explicitly specified in the attribute, or attribute is ommitted, the name of the parameter will be implicitly used.

You can provide options before arguments or arguments before options on the command line. See documentation for details.

Arity

The arity of an option or command's argument is the number of values that can be passed if that option or command is specified. Arity is expressed with a minimum value and a maximum value.

[Command(name: "print", description: "Arity example")]
static public void Print(
    [Argument(arityMin:1, arityMax:2, name:"numbers", description:"Takes 1 or 2 numbers")]
    int[] nums
)
{
    Console.WriteLine($"Numbers are: {string.Join(",", nums)}!");
}

Output:

> sample print -?
Description:
  Arity example

Usage:
  sample print [numbers]... [options]

Arguments:
  [numbers]  Takes 1 or 2 numbers

Options:
  -?, -h, --help  Show help and usage information

> sample print 12
Numbers are: 12!

> sample print 12 76
Numbers are: 12,76!

Subcommands

As mentioned earlier, if command name has spaces (or name not specified and method name has underscores) it describes a subcommand. Any command may have multiple subcommands.

In the following example we have a subcommand world of the command hello:

[Command(name:"hello world", description:"This command greets the world!")]
static public void Hello() 
{
    Console.WriteLine("Hello World!");
}

Or equivalent using method name:

[Command(description:"This command greets the world!")]
static public void hello_world() 
{
    Console.WriteLine("Hello World!");
}

The usage output will be as follows:

> sample -?
Description:

Usage:
  sample [command] [options]

Options:
  --version       Show version information
  -?, -h, --help  Show help and usage information

Commands:
  hello

> sample hello -?
Description:

Usage:
  sample hello [command] [options]

Options:
  -?, -h, --help  Show help and usage information

Commands:
  world

> sample hello world -?
Description:
  This command greets the world!

Usage:
  sample hello world [options]

Options:
  -?, -h, --help  Show help and usage information


> sample hello world
Hello World!

In example above we have description for hello world command, but not for hello. What if we don't need a handler for that command at all? In this case we may use [assembly: ParentCommand()] attribute at the top of the source file:

[assembly: ParentCommand(name: "hello", description: "This command greets someone", aliases: ["hi"])]

In a similar way we may provide description to the root command, i.e. to the program itself, using [assembly: Program()] attribute:

[assembly: Program(description: "This is sample program")]

Alternatively, we may use standard [assembly: AssemblyDescription] attribute:

[assembly: AssemblyDescription(description: "This is sample program")]

Root command

When we have multiple commands in a CLI program and execute the program without parameters it will show help message. If instead of help we want to perform some actions, we need to assign a handler for the root command. For that we use [RootCommand] attribute and it's usage is similar to one of [Command] except you cannot specify the command name.

[RootCommand(description: "This command greets the world!")]
static public void Hello()
{
    Console.WriteLine("Hello World!");
}

Note: There can be only one method declared with [RootCommand] attribute.

Global options

Any public static propety or field can be declared as global option with [Option] attribute.

[Option(name:"config", description:"Configuration file name", aliases: ["c","cfg"])]
public static string ConfigFile = "config.ini";

Aliases

Both commands and options may have aliases.

Case sensitivity

Command and option names and aliases are case-sensitive. If you want your CLI to be case insensitive, define aliases for the various casing alternatives.

.Net support

Currently implemeted for .Net 8.0 with plans to support for .Net Standard

License

This project is licensed under the MIT license. Parts of this project (src/build/) borrowed with some modifications from DragonFruit under the MIT license.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on SnapCLI:

Package Downloads
SnapCLI.DataAnnotations

This extension for SnapCLI library enables validation of command-line arguments based on data annotation attributes, ensuring that input meets specified criteria before executing the command.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
2.0.1-pre 37 11/13/2024
2.0.0-pre 47 11/13/2024
1.1.0-pre 45 10/11/2024
1.0.5-pre 45 9/26/2024
1.0.4-pre 45 9/23/2024
1.0.3-pre 86 9/21/2024
1.0.2-pre 64 9/14/2024
1.0.1-pre 47 9/14/2024
1.0.0-pre 49 9/13/2024