PRI.CommandLineExtensions 1.0.4

dotnet add package PRI.CommandLineExtensions --version 1.0.4
                    
NuGet\Install-Package PRI.CommandLineExtensions -Version 1.0.4
                    
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="PRI.CommandLineExtensions" Version="1.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PRI.CommandLineExtensions" Version="1.0.4" />
                    
Directory.Packages.props
<PackageReference Include="PRI.CommandLineExtensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PRI.CommandLineExtensions --version 1.0.4
                    
#r "nuget: PRI.CommandLineExtensions, 1.0.4"
                    
#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.
#addin nuget:?package=PRI.CommandLineExtensions&version=1.0.4
                    
Install PRI.CommandLineExtensions as a Cake Addin
#tool nuget:?package=PRI.CommandLineExtensions&version=1.0.4
                    
Install PRI.CommandLineExtensions as a Cake Tool

System.CommandLine Extensions

CommandLineExtensions helps make command line argument parsing simple while extending and leveraging Microsoft-supported System.CommandLine.

Mission

To leverage ConsoleApplicationBuilder to create a simple way to define and process command-line parameters with a fluent interface.

Designed with ConsoleApplicationBuilder in mind.

ConsoleApplicationBuilder helps create .NET console apps that support configuration, logging, Dependency Injection (DI), lifetime, and other things that .NET developers have come to expect.

var builder = ConsoleApplication.CreateBuilder(args); // 👈🏽 ConsoleApplicationBuilder
builder.AddCommand()                                  // 👈🏽 CommandLineExtensions start here, adding a root command-line command
  .WithDescription("File processor")                  // 👈🏽 with a description of "𝘍𝘪𝘭𝘦 𝘱𝘳𝘰𝘤𝘦𝘴𝘴𝘰𝘳"
  .WithArgument("file", "file path to process")       // 👈🏽 with a required argument named "𝘧𝘪𝘭𝘦" and described "𝘧𝘪𝘭𝘦 𝘱𝘢𝘵𝘩 𝘵𝘰 𝘱𝘳𝘰𝘤𝘦𝘴𝘴"
  .WithHandler((file)=>{});                           // 👈🏽 with a handler to invoke when executed

return builder.Build<RootCommand>().Invoke(args);

Benefits

  • Simplicity.
  • Fluent Interface.
  • Explicitness.
  • Reduced gotchas.

More Examples

Required Named Options

Required options can be added with the WithRequiredOption method. For example:

var builder = ConsoleApplication.CreateBuilder(args);
builder.Services.AddCommand<ProcessFileCommand>()
	.WithRequiredOption<FileInfo>("--file", "file path to process") // 👈🏽 required option
	.WithHandler(fileInfo =>
	{
		Console.WriteLine($"File {fileInfo.Name} is {fileInfo.Length} bytes in size and was created on {fileInfo.CreationTime}.");
	});
return builder.Build<ProcessFileCommand>().Invoke(args);

The command-line for this example might be:

app --file file.txt

The help for the above example is:

Description:
  File processor

Usage:
  app [options]

Options:
  --file <file> (REQUIRED)  file path to process
  --version                 Show version information
  -?, -h, --help            Show help and usage information

Injection

System.CommandLine does not inherently support .NET dependency injection. CommandLineExtensions enables dependency injection via ConsoleApplicationBuilder. Commands added through the AddCommand extension method automatically leverage dependency injection and any types that are instantiated by CommandLineExtensions will have dependencies resolved. For example:

var builder = ConsoleApplication.CreateBuilder(args);
builder.Services.AddCommand<ProcessFileCommand>() // 👈🏽 ProcessFileCommand depends on ILogger<T> injected via the constructor.
	.WithArgument<FileInfo>("file", "The filename to process.")
	.WithHandler<ProcessFileCommandHandler>();

return builder.Build<ProcessFileCommand>().Invoke(args);

// logger will be injected when ProcessFileCommand is built 👇🏽
public class ProcessFileCommandHandler(ILogger<ProcessFileCommandHandler> logger)
 : ICommandHandler<FileInfo>
{
	public int Execute(FileInfo fileInfo)
	{
		logger.LogInformation("Executed called.");
		Console.WriteLine($"Got parameter '{fileInfo.FullName}");
		return 0;
	}
}

public class ProcessFileCommand() : RootCommand("File processor");

Enum Options

An option with a fixed, finite set of values can easily be supported with an enum. Verbosity, for example:

var builder = ConsoleApplication.CreateBuilder(args);
builder.Services.AddCommand<ProcessFileCommand>()
	.WithOption<Verbosities>("--verbosity", "verbosity") // 👈🏽 Verbosities enum
	.WithHandler(v =>
	{
		switch (v)
		{
			case Verbosities.Information:
				StdOut.WriteLine("Something happened.");
				break;
			case Verbosities.Warning:
				StdOut.WriteLine("Something bad happened.");
				break;
			case Verbosities.Error:
				StdOut.WriteLine("Error happened.");
				break;
			case Verbosities.Fatal:
				StdOut.WriteLine("Something fatal happened!");
				break;
			case Verbosities.Quiet:
			default:
				break;
		}
		actualVerbosity = v;
	});
var command = builder.Build<ProcessFileCommand>();

return command.Invoke(["--help"]);

public enum Verbosities
{
	Quiet,
	Information,
	Warning,
	Error,
	Fatal
}

And will produce help output similar to:

Description:
	File processor

Usage:
	app [options]

Options:
	--verbosity <Error|Fatal|Information|Quiet|Warning>  verbosity
	--version                                            Show version information
	-?, -h, --help                                       Show help and usage information

Option Aliases

Options require a name used to specify the option on the command line. Additional names (aliases) can be configured for a single option. For example, an option with the name --option can have an alias named -o:

var builder = ConsoleApplication.CreateBuilder(args);
builder.Services.AddCommand<ProcessFileCommand>()
	.WithArgument<FileInfo>("file", "file path to process")
	.WithOption<FileInfo?>("--output", "output location")
	.AddAlias("-o") // 👈🏽 add alias '-o' for '--output'
	.WithHandler((inputFileInfo, outputFileInfo) =>
	{
		var outputText =
			$"File {inputFileInfo.Name} is {inputFileInfo.Length} bytes in size and was created on {inputFileInfo.CreationTime}";
		if (outputFileInfo != null)
		{
			using var writer = outputFileInfo.CreateText();
			writer.WriteLine(outputText);
		}
		else
		{
			Console.WriteLine(outputText);
		}
	});
return builder.Build<ProcessFileCommand>().Invoke(args);

Subcommands

System.CommandLine commands can have multiple subcommands so that when the application is executed the command line arguments define which subcommand to execute. For example, an application can have two subcommands, one to read a file and one to write a file:

var builder = ConsoleApplication.CreateBuilder(args);
builder.Services.AddCommand()
	.WithSubcommand<ReadCommand>() // 👈🏽 add read command
		.WithArgument<FileInfo>("file", "file path to process")
	 	.WithSubcommandHandler(file => Console.WriteLine($"read <file> argument = {file.Name}"))
	.WithSubcommand<WriteCommand>() // 👈🏽 add write command
		.WithArgument<FileInfo>("file", "file path to process")
		.WithArgument<string>("text", "text to write to file, quoted.")
		.WithSubcommandHandler((file, text) => Console.WriteLine($"write <file> argument = {file.Name} with text '{text}'."))
	.WithHandler(() => Console.WriteLine("Please choose read or write subcommand."));
return builder.Build<RootCommand>().Invoke(args, Console);

public class ReadCommand() : Command("read", "read subcommand");
public class WriteCommand() : Command("write", "write subcommand");

Subcommand Aliases

Like options, subcommands may have aliases. For example, the above read/write subcommands example could have aliases r and w for read and write:

var builder = ConsoleApplication.CreateBuilder(args);
builder.Services.AddCommand()
	.WithSubcommand<ReadCommand>()
		.AddAlias("r") // 👈🏽 add alias "r" for read command
		.WithArgument<FileInfo>("file", "file path to process")
	 	.WithSubcommandHandler(file => Console.WriteLine($"read <file> argument = {file.Name}"))
	.WithSubcommand<WriteCommand>()
		.AddAlias("w")  // 👈🏽 add alias "w" for write command
		.WithArgument<FileInfo>("file", "file path to process")
		.WithArgument<string>("text", "text to write to file, quoted.")
		.WithSubcommandHandler((file, text) => Console.WriteLine($"write <file> argument = {file.Name} with text '{text}'."))
	.WithHandler(() => Console.WriteLine("Please choose read or write subcommand."));
return builder.Build<RootCommand>().Invoke(args, Console);

Typed command handlers

One of the intents of CommandLineExtensions is to help make complex command-line applications simpler. Lambdas make for self-contained, easy-to-read examples; but they're not indicative of real-world, non-trivial applications. Complex applications often use tried-and-true object-oriented techniques to address complexity. Techniques like type encapsulation are used to encapsulate specific data and logic from other concerns of the application. A command handler, for example, may be encapsulated in a class declaration. CommandLineExtensions supports types that implement built-in interface Pri.CommandLineExtensions.ICommandHandler.

Limitations and known issues

This is the first, MVP release of CommandLineExtensions. It has some limitations and doesn't support all the features of System.CommandLine. CommandLineExtensions is intended to be simpler than System.CommandLine extensions and tries to address the most common command-line arguments/commands needs and is unlikely to ever have parity with System.CommandLine. Here are some of the limitations and known issues with CommandLineExtensions:

  • FromAmong isn't directly supported, use the enum support instead.
  • Parameters (arguments, options) are limited to two. Support for more parameters is planned for the future.
  • There is no support for Option<T>-derived types. CommandLineExtensions focuses on a fluent interface, but support for this may be added in the future.
  • Default value factories are not supported, only compile-time constants or config-time values. Support for default value factories in System.CommandLine is more complex than it's worth (requiring custom help messages, etc.)
  • Global options are not yet supported.
  • Custom help is not supported.
  • Unmatched tokens is not supported.

Support

For support, please see the GitHub Issues section of ConsoleApplicationBuilder.

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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.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.

Version Downloads Last updated
1.0.4 185 4/9/2025
1.0.1 131 3/22/2025
1.0.0 125 3/22/2025