vertical-cli 1.0.0-dev.20240617.11

Prefix Reserved
This is a prerelease version of vertical-cli.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package vertical-cli --version 1.0.0-dev.20240617.11                
NuGet\Install-Package vertical-cli -Version 1.0.0-dev.20240617.11                
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="vertical-cli" Version="1.0.0-dev.20240617.11" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add vertical-cli --version 1.0.0-dev.20240617.11                
#r "nuget: vertical-cli, 1.0.0-dev.20240617.11"                
#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 vertical-cli as a Cake Addin
#addin nuget:?package=vertical-cli&version=1.0.0-dev.20240617.11&prerelease

// Install vertical-cli as a Cake Tool
#tool nuget:?package=vertical-cli&version=1.0.0-dev.20240617.11&prerelease                

vertical-cli

Minimal command line arguments parser.

Quick start

Install

dotnet add package vertical-cli --prerelease

Configure and run

using System;
using System.IO;
using Vertical.Cli.Configuration;

// Define a model that will receive the arguments
record Arguments(FileInfo Source, FileInfo Destination, bool Overwrite);

// Define the command
var command = new RootCommand<Arguments, Task>("fcopy");
command
    .AddArgument(x => x.Source)
    .AddArgument(x => x.Destination)
    .AddSwitch(x => x.Overwrite, ["-o", "--overwrite"])
    .SetHandler(async (model, cancellationToken) => 
    {
        if (model.Overwrite || model.Source.Exists())
        {
            Console.WriteLine("Desintation file exists and will not be overwritten.");
        }
        await using var source = model.Source.OpenRead();
        await using var dest = model.Destination.OpenWrite();
        await source.CopyToAsync(dest, cancellationToken);        
    });

await command.InvokeAsync(arguments);

Features

  • Binds command line arguments to strongly typed models
  • Configure positional arguments. options and switches using posix and GNU notation
  • Configure contextual parameter validation
  • Define a hierarchy of commands each with distinct or inherited models
  • Invoke synchronous and asynchronous workloads
  • Uses a source generator to bind models removing the need for reflection (AOT friendly)
  • Generates basic help content (better if descriptions are given)

Symbol types

The library will parse the following:

  • Positional arguments with no identifier when they are located before or after options.
  • Posix style identifiers beginning with a single dash, e.g. -a, or -abc (same as -a, -b, and -c`).
  • GNU style options beginning with a double dash, e.g. --long-option.
  • Operands that follow option identifiers as a separate argument, or joined by : or =, e.g. --user=admin.
  • Command names, e.g. git commit.

Arity

Arguments and options can specify an arity that determines how many times they can be referenced on the command line. Common constants include:

  • Arity.ZeroOrOne
  • Arity.ZeroOrMany
  • Arity.One
  • Arity.OneOrMany

Setup

Commands

Commands represent a control path in the application. At a minimum, the configuration requires a root command. Commands may define subcommands, and this is how a hierarchical structure is formed. An example of a hierarchical command path is in the .net CLI tooling:

$ dotnet nuget push

Models

Each command requires a model to bind arguments to. Models must have a public constructor. The binder will bind arguments to constructor arguments and public writeable properties. Bindings are configured in each argument, option, or switch, and use strongly typed expressions. Models of subcommands must be a subtype of the model of the parent command. This constraint supports the notion of symbol scopes, and is discussed shortly.

Result Type

All commands in an application must return the same result type from their handlers. The result type may be any value type, reference type, Task, or Task<T>. The only type forbidden is void.

Handlers

Handlers a functions that receive the model, perform the application's logic, and return a result.

Symbols

Symbols represent a way to associate command line arguments with their respective bindings in the model. Symbols are either positional arguments, options, and switches. Symbols have the following characteristics:

  • A value type that is inferred by the model binding expression.
  • An arity that describes the minimum and maximum number of values that can be bound.
  • In the case of options or switches, one or more names the symbol can be referred to in command line arguments.
  • A scope that determines if the symbol applies to the command where it is defined, sub commands, or both.
  • A function that provides a default value in case an argument is not provided (optional).
  • A description that can be displayed in help content (optional).
  • A set of rules that can be used to validate input values (optional).

Example

This example simulates the setup of two .net CLI tools.

public enum Verbosity { Verbose, Normal, Minimal }

public abstract class BaseModel
{
    public Verbosity Verbosity { get; set; }
}

public class BuildModel : BaseModel
{
    public FileInfo ProjectPath { get; set; } = default!;
    public string Configuration { get; set; } = default!;
    public DirectoryInfo OutputDirectory { get; set; } = default!;
    public bool Force { get; set; }
    public bool NoRestore { get; set; }
}

var rootCommand = new RootCommand<BaseModel, Task>("dotnet");
rootCommand
    .AddOption(x => x.Verbosity, ["--verbosity"],
        description: "Output verbosity (Verbose, Normal, Minimal)")
    .AddHelpSwitch(Task.CompletedTask);

var buildCommand = new SubCommand<BuildModel, Task>("build");
buildCommand
    .AddArgument(x => x.ProjectPath, Arity.One,
        description: "Path to the project file",
        validation: v => v.Exists())
    .AddOption(x => x.Configuration, ["-c", "--configuration"],
        defaultProvider: () => "Debug",
        description: "Configuration name to build")
    .AddOption(x => x.OutputDirectory, ["-o", "--output"],
        defaultProvider: () => new DirectoryInfo(Directory.GetCurrentDirectory()),
        description: "Directory where the built artifacts will be written")
    .AddSwitch(x => x.Force, ["-f", "--force"],
        description: "Forces all dependencies to be resolved")
    .AddSwitch(x => x.NoRestore, ["--no-restore"],
        description: "Don't execute implicit restore")
    .SetHandler(async (model, cancellation) => {
        // Build the project        
    })
    .AddHelpSwitch(Task.CompletedTask);

rootCommand.AddSubCommand(buildCommand);

try
{
    await rootCommand.InvokeAsync(args, CancellationToken.None);
}
catch (CommandLineException exception)
{
    Console.WriteLine(exception.Message);
}
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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.