MinimalCommandLine 0.0.3

dotnet add package MinimalCommandLine --version 0.0.3
NuGet\Install-Package MinimalCommandLine -Version 0.0.3
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="MinimalCommandLine" Version="0.0.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add MinimalCommandLine --version 0.0.3
#r "nuget: MinimalCommandLine, 0.0.3"
#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 MinimalCommandLine as a Cake Addin
#addin nuget:?package=MinimalCommandLine&version=0.0.3

// Install MinimalCommandLine as a Cake Tool
#tool nuget:?package=MinimalCommandLine&version=0.0.3

System.CommandLine.Minimal

A set of minimal builders that sits on top of the System.CommandLine namespace to give an experience similar to the ASP.Net Core minimal API builders.

Primary Goal:

The primary goal of this library design is to give the developer the option to use one of the following approaches:

Hello World:

using System.CommandLine.Minimal;

var app = new MinimalCommandLineBuilder()
    .Build();

app.AddRootDescription("A simple demo app for the command line.")
    .AddRootArgument<string>("Message")
    .AddRootOption<string>("--first-option", opt => opt.AddAlias("-o1"))
    .AddRootOption<string>("--second-option")
    .SetRootHandler(
        (string message, string option1, string option2) =>
        {
            Console.WriteLine($"Hello World!  {message}");
            Console.WriteLine($"  Option 1:{option1}, Option2 {option2}");
        }
    );

app.Execute(args);

Getting Started

git clone https://github.com/dotnetKyle/MinimalCommandLine.git

Using Visual Studio:

Set DemoApp as the startup project.

Check the Properties/launchSettings.json file, ensure that the commandLineArgs property is set to -h

Using the dotnet CLI:

dotnet build DemoApp.csproj -c Debug

cd \bin\Debug\net6.0\

DemoApp.exe -h

Simple Examples:

Inline Approach:

The API and the application logic are together. Uses an Action<Task> directly in the Program.cs.

var app = new MinimalCommandLineBuilder()
  .Build();

app.AddRootDescription("Create X509Certificates.");

// generate a root CA certificate
app.AddCommand("rootCA"
  cmdOptions => 
  {
    cmdOptions
      .AddCommandDescription("Create a self-signed root certificate authority.")
      .AddArgument<string>("CommonName", argument =>
        argument.AddHelpName("Common Name")
          .AddDescription("Add a common name to the certificate's subject name.")
      )
        .AddOption<string[]>("-ou", option =>
          option.AddAlias("--organizational-unit")
            .AddDescription(
              "Add one or more Organizational Units (OUs) to the certificate's subject name."
            )
          )
        .AddOption<DateOnly>("-na", option =>
          option.AddAlias("--not-after")
            .AddDescription("Add a date that the certificate cannot be used after.")
            .AddDefaultValue(DateOnly.FromDateTime(DateTime.UtcNow.AddYears(10)))
          )
        // Bind the application logic here
        .SetHandler(async (string commonName, string[] OUs, DateOnly notAfter) =>
        {
          var notAfterDate = notAfter.ToDateTime(TimeOnly.MinValue, DateTimeKind.Utc);

          if (OUs is null)
            OUs = Array.Empty<string>();

          var filePath = Path.Combine(Environment.CurrentDirectory, "rootCA.pfx");

          var subjectName = $"CN={commonName}";

          foreach (var ou in OUs)
            subjectName += $", OU={ou}";

          subjectName += $", O=Your Org Name Here, C=USA";

          using (var rsa = RSA.Create(2048))
          {
            var req = new CertificateRequest(
              subjectName,
              rsa,
              HashAlgorithmName.SHA256,
              RSASignaturePadding.Pkcs1);

            req.CertificateExtensions.Add(
              new X509BasicConstraintsExtension(true, false, 0, true)
            );

            using (var cert = req.CreateSelfSigned(DateTime.UtcNow,notAfterDate))
            {
              var pfx = cert.Export(X509ContentType.Pfx);

              await File.WriteAllBytesAsync(filePath, pfx);

              Console.WriteLine(filePath);
            }
          }
        })
  });

Separate Approach (static class):

Same logic as above but inside a static method allows for the parameters to have optional values (which are automatically to the API help convention).

var app = new MinimalCommandLineBuilder()
  .Build();

app.AddRootDescription("Create X509Certificates.");

// generate a rootCA certificate
app.AddCommand("rootCA"
  cmdOptions => 
  {
    cmdOptions
      .AddCommandDescription("Create a self-signed root certificate authority.")
      .AddArgument<string>("CommonName", argument =>
        argument.AddHelpName("Common Name")
          .AddDescription("Add a common name to the certificate's subject name.")
      )
        .AddOption<string[]>("-ou", option =>
          option.AddAlias("--organizational-unit")
            .AddDescription("Add one or more OUs to the certificate's subject name.")
          )
        .AddOption<string>("-o", option =>
          option.AddAlias("--organization")
            .AddDescription("Override the default organization name.")
          )
        // Use a static method for the application logic
        .SetHandler(RootCaGenerator.GenerateSelfSigned);
    });

public static class RootCaGenerator
{
  public static async Task GenerateSelfSigned(
      string commonName, 
      string[] OUs, 
      string organization = "Your Org Here")
  {
    // Truncated for brevity
  }
}

Separate Approach (instance class with dependency injection):

Uses a class instance and gets dependencies from DI.

// add the command and it's dependencies to DI
var app = new MinimalCommandLineBuilder()
  .AddTransient<ISerialNumberProvider, FileSystemSerialNumberProvider>()
  .AddTransient<IntermediateCaGenerator>()
  .Build();

app.AddRootDescription("Create X509Certificates.");

// generate a intermediateCA certificate
app.MapCommand<IntermediateCaGenerator>("intermediateCA", 
  // this parameter is a binder to map the command to the instance method containing the application logic
  handler => handler.GenerateCaAsync,
  cmdOptions => 
  {
    cmdOptions
      .AddCommandDescription("Create a intermediate certificate authority.")
      .AddArgument<string>("CommonName", argument =>
        argument.AddHelpName("Common Name")
          .AddDescription("Add a common name to the certificate's subject name.")
      )
      .AddArgument<string>("IssuerCertificate", argument =>
        argument.AddHelpName("Issuer Certificate")
          .AddDescription("Add an issuer certificate with its private key.")
      )
      .AddOption<string[]>("-ou", option =>
        // truncated for brevity
    });

public class IntermediateCaGenerator
{
  ISerialNumberProvider _serialNumberProvider;
  public IntermediateCaGenerator(ISerialNumberProvider serialNumberProvider)
  {
    _serialNumberProvider = serialNumberProvider;
  }

  public async Task GenerateCaAsync(string commonName, string issuerFilePath)
  {
    var certificateSerialNumber = _serialNumberProvider.NextSerialNumber();
    // Truncated for brevity
  }
}
public class FileSystemSerialNumberProvider : IFileSystemSerialNumberProvider
{
  // Truncated for brevity
}
Product 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. 
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
0.0.3 605 9/14/2022
0.0.3-alpha 167 9/14/2022
0.0.2 177 9/14/2022
0.0.2-alpha 170 9/14/2022

Added missing support for the Root Command.