Guider Upgrade with System.CommandLine - Implementing Help and Version Options

Guider Upgrade with System.CommandLine - Implementing Help and Version Options

Another post outlines how to package a console project into a .NET CLI tool, using an example project called Guider. This tool can be invoked from the terminal as guidgen to generate a GUID and copy it to the clipboard for convenient access.

This post extends Guider into a proper CLI tool, meaning it now supports the --help and --version options.

Before: (Options are ignored)

> guidgen
341f4f0a-4f2f-4b1c-9a57-bc06e6c8b68f
> guidgen --version
363dbd30-6339-4580-9950-11c0f958fb84
> guidgen --help
d3ef5e7e-4caa-4eb3-9fd1-7cdb3cb5fdff

After: (A proper CLI tool)

> guidgen
e1690e6f-2fe5-4e51-bb2d-25dfbbd05c00
> guidgen --version
1.1.0
> guidgen --help
Description:
  Generate a GUID and copy it to the clipboard

Usage:
  guidgen [options]

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

The project is available on GitHub here. All changes described in this post are part of this pull request.

Introducing System.CommandLine

One possible way to handle command-line arguments is by using a specialised library. System.CommandLine is one such option.

The first commit introduces this library into the project. It extracts the GUID generation logic into the GenerateGuid method and sets up a single command: rootCommand. The --help and --version options are automatically generated by the library.

using TextCopy;
using System.CommandLine;

RootCommand rootCommand = new("Generate a GUID and copy it to the clipboard");
rootCommand.SetHandler(GenerateGuid);
return await rootCommand.InvokeAsync(args);

void GenerateGuid()
{
    Guid guid = Guid.NewGuid();
    Console.WriteLine(guid);
    ClipboardService.SetText(guid.ToString());
}

Here is how it works:

> dotnet run
4e483f10-c83c-4675-a18d-ed1ba41d0667
> dotnet run -- --version
1.1.0+87e7ea8528b51bc603693fe622df85e5b2e8c701
> dotnet run -- --help
Description:
  Generate a GUID and copy it to the clipboard

Usage:
  Guider.Console [options]

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

There are two issues with the implementation. One, the version output includes an appended commit hash, which is unnecessary. Two, the help output references the project name (Guider.Console) instead of the tool name (guidgen).

The following sections introduce failing tests for these issues and their respective fixes.

Adding Tests

The implementation issues necessitate testing the tool as a whole. The project can be executed with dotnet run -- {args}. This process can be replicated and automated in tests. The second commit implements a CliRunner class, adapted from this resource, and adds the first integration test:

[Test]
public void OutputsValidGuid()
{
	// Act
	var (exitCode, output) = CliRunner.Run("");
	bool valid = Guid.TryParse(output, out Guid guid);
	
	// Assert
	valid.Should().Be(true);
	exitCode.Should().Be(0);
}

The test executable is located in ./Guider.Tests/bin/Debug/net8.0/, requiring the path to the console project (./Guider.Console) to be specified as ../../../../Guider.Console.

The third commit resolves the versioning issue by disabling IncludeSourceRevisionInInformationalVersion in the project file. Details about this setting are available here.

[Test]
public void UsesMajorMinorPatchVersioning()
{
	// Act
	var (exitCode, output) = CliRunner.Run("--version");
	bool valid = Regex.IsMatch(output, @"^\d+\.\d+\.\d+$");
	
	// Assert
	valid.Should().Be(true);
	exitCode.Should().Be(0);
}

The fourth commit corrects the tool name displayed in the help output by renaming the root command.

[Test]
public void UsesCustomToolName()
{
	// Act
	var (exitCode, output) = CliRunner.Run("--help");
	
	// Assert
	output.Should().NotContain("Guider.Console");
	exitCode.Should().Be(0);
}

Each of these tests takes over a second to execute, which is significantly longer than the typical time taken by unit tests (milliseconds). With just three tests, it is not yet an issue.

Sharing the Tool

With these changes, Guider is ready to use. It can first be packed from the ./Guider.Console directory:

dotnet pack

... and then installed if it does not yet exist:

dotnet tool install Guider --global --add-source ./packages

... or updated to the latest version if it does:

dotnet tool update Guider --global --add-source ./packages

Next Steps

Guider now supports versioning, includes a help page, and has all commands and options covered by tests. It has become a proper CLI tool.

Potential next steps include additional functionality or distributing it via NuGet.org.

Read more