| name | Clap.Net |
| nuget | https://www.nuget.org/packages/Clap.Net/ |
| link | https://github.com/simon-curtis/Clap.Net |
| author | Simon Curtis |
Command line arguments parsing
This is how you can use Clap.Net .
The code that you start with is
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)\GX</CompilerGeneratedFilesOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Clap.Net" Version="0.2.39" />
</ItemGroup>
</Project>
The code that you will use is
using ConsoleDemo; // --help // -f 10 -s 20 var cmd = CmdForSum.Parse(args); cmd.MySum();
using Clap.Net;
namespace ConsoleDemo;
[Command(Name = "MakeSum")]
public partial class CmdForSum
{
[Arg(Short = 'f', Long = "firstarg", Help = "first argument")]
public int x { get; set; } = 0;
[Arg(Short = 's', Long = "secondarg", Help = "second argument")]
public int y { get; set; } = 0;
public void MySum()
{
Console.WriteLine($"Hello, {x+y}!");
}
}
The code that is generated is
/*
* CAUTION! This code has been generated by the Clap.Net source generator and should not be edited.
*
* Name: MakeSum
* About:
* Long About:
*/
#nullable enable
namespace ConsoleDemo;
public partial class CmdForSum
{
private const string HelpMessage =
"""
Usage: {{EXECUTABLE_NAME}} [OPTIONS]
Options:
-f, --firstarg first argument
-s, --secondarg second argument
-h, --help Shows this help message
""";
public class CmdForSumParseResult
{
private enum ResultType { Success, Help, Version, Error }
private readonly ResultType _type;
private readonly ConsoleDemo.CmdForSum? _command;
private readonly Clap.Net.Models.ShowHelp? _help;
private readonly Clap.Net.Models.ShowVersion? _version;
private readonly Clap.Net.Models.ParseError? _error;
private CmdForSumParseResult(ConsoleDemo.CmdForSum command)
{
_type = ResultType.Success;
_command = command;
}
private CmdForSumParseResult(Clap.Net.Models.ShowHelp help)
{
_type = ResultType.Help;
_help = help;
}
private CmdForSumParseResult(Clap.Net.Models.ShowVersion version)
{
_type = ResultType.Version;
_version = version;
}
private CmdForSumParseResult(Clap.Net.Models.ParseError error)
{
_type = ResultType.Error;
_error = error;
}
public bool IsSuccess => _type == ResultType.Success;
public bool IsHelp => _type == ResultType.Help;
public bool IsVersion => _type == ResultType.Version;
public bool IsError => _type == ResultType.Error;
public ConsoleDemo.CmdForSum Command => _command ?? throw new System.InvalidOperationException("Result is not Success");
public Clap.Net.Models.ShowHelp Help => _help ?? throw new System.InvalidOperationException("Result is not Help");
public Clap.Net.Models.ShowVersion Version => _version ?? throw new System.InvalidOperationException("Result is not Version");
public Clap.Net.Models.ParseError Error => _error ?? throw new System.InvalidOperationException("Result is not Error");
public TNewParseResult ChangeType<TNewParseResult>() where TNewParseResult : class
{
object? value = _type switch
{
ResultType.Success => _command,
ResultType.Help => _help,
ResultType.Version => _version,
ResultType.Error => _error,
_ => throw new System.InvalidOperationException("Unknown result type")
};
if (value is TNewParseResult result)
return result;
throw new System.InvalidCastException(
$"Cannot cast {value?.GetType().FullName ?? "null"} to {typeof(TNewParseResult).FullName}");
}
public static implicit operator CmdForSumParseResult(ConsoleDemo.CmdForSum value) => new(value);
public static implicit operator CmdForSumParseResult(Clap.Net.Models.ShowHelp value) => new(value);
public static implicit operator CmdForSumParseResult(Clap.Net.Models.ShowVersion value) => new(value);
public static implicit operator CmdForSumParseResult(Clap.Net.Models.ParseError value) => new(value);
}
public static ConsoleDemo.CmdForSum Parse(System.ReadOnlySpan<string> args)
{
// Protect against DoS attacks with excessive argument counts
const int MaxTotalArguments = 50000;
if (args.Length > MaxTotalArguments)
{
throw new System.ArgumentException(
$"Total argument count ({args.Length}) exceeds maximum of {MaxTotalArguments}");
}
var tokens = Clap.Net.ArgsLexer.Lex(args);
return Parse(tokens);
}
public static ConsoleDemo.CmdForSum Parse(System.ReadOnlySpan<Clap.Net.IToken> tokens)
{
var parseResult = TryParse(tokens);
if (parseResult.IsSuccess)
return parseResult.Command;
if (parseResult.IsVersion)
{
System.Console.WriteLine(parseResult.Version.Version);
System.Environment.Exit(0);
}
if (parseResult.IsError)
{
DisplayError(parseResult.Error.Message, parseResult.Error.HelpMessage);
System.Environment.Exit(0);
}
PrintHelpMessage(parseResult.Help.HelpMessage);
System.Environment.Exit(0);
// Unreachable: all control paths above call Environment.Exit(0)
return default!;
}
public static CmdForSumParseResult TryParse(System.ReadOnlySpan<string> args)
{
var tokens = Clap.Net.ArgsLexer.Lex(args);
return TryParse(tokens);
}
public static CmdForSumParseResult TryParse(System.ReadOnlySpan<Clap.Net.IToken> tokens)
{
if (tokens.Length > 0 && tokens[0] is Clap.Net.ShortFlag('h') or Clap.Net.LongFlag("help"))
{
return new Clap.Net.Models.ShowHelp(GetFormattedHelpMessage());
}
if (tokens.Length > 0 && tokens[0] is Clap.Net.ShortFlag('v') or Clap.Net.LongFlag("version"))
{
return new Clap.Net.Models.ShowVersion("1.0.0.0");
}
// Argument 'x' is a named argument
Clap.Net.Models.FieldValue<System.Int32> @__clapgen_x = (System.Int32)0;
// Argument 'y' is a named argument
Clap.Net.Models.FieldValue<System.Int32> @__clapgen_y = (System.Int32)0;
var index = 0;
while (index < tokens.Length)
{
switch (tokens[index])
{
// Handling CompoundFlag
case Clap.Net.CompoundFlag(var chars):
{
foreach (var c in chars)
{
switch (c)
{
case 'f':
{
@__clapgen_x = @__clapgen_x.Value + 1;
break;
}
case 's':
{
@__clapgen_y = @__clapgen_y.Value + 1;
break;
}
default:
{
return new Clap.Net.Models.ParseError($"Unexpected flag supplied in compound flags '{c}'", GetFormattedHelpMessage());
}
}
}
index++;
break;
}
// Setting named argument 'ConsoleDemo.CmdForSum.x'
// action 'Set'
case Clap.Net.ShortFlag('f') or Clap.Net.LongFlag("firstarg"):
{
index++;
if (index >= tokens.Length || tokens[index] is not Clap.Net.ValueLiteral(var value))
return new Clap.Net.Models.ParseError("Expected value to follow named arg 'x'", GetFormattedHelpMessage());
@__clapgen_x = TryParseOrThrow<int>(value, int.TryParse, "integer");
index++;
break;
}
// Setting named argument 'ConsoleDemo.CmdForSum.y'
// action 'Set'
case Clap.Net.ShortFlag('s') or Clap.Net.LongFlag("secondarg"):
{
index++;
if (index >= tokens.Length || tokens[index] is not Clap.Net.ValueLiteral(var value))
return new Clap.Net.Models.ParseError("Expected value to follow named arg 'y'", GetFormattedHelpMessage());
@__clapgen_y = TryParseOrThrow<int>(value, int.TryParse, "integer");
index++;
break;
}
case var arg:
{
return new Clap.Net.Models.ParseError($"Unknown argument '{Clap.Net.TokenExtensions.Format(arg)}'", GetFormattedHelpMessage());
}
}
}
// No required fields
return new ConsoleDemo.CmdForSum
{
x = @__clapgen_x.Value,
y = @__clapgen_y.Value,
};
}
public static void DisplayError(string message, string helpMessage)
{
var previousColour = System.Console.ForegroundColor;
System.Console.ForegroundColor = System.ConsoleColor.Red;
System.Console.WriteLine(message);
System.Console.ForegroundColor = previousColour;
System.Console.WriteLine(helpMessage);
}
public static void PrintHelpMessage(string helpMessage)
{
System.Console.WriteLine(helpMessage);
}
private static string GetFormattedHelpMessage()
{
var executableName = System.IO.Path.GetFileNameWithoutExtension(System.Environment.GetCommandLineArgs()[0]);
if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows))
executableName += ".exe";
return HelpMessage.Replace("{{EXECUTABLE_NAME}}", executableName);
}
private delegate bool TryParseDelegate<T>(string input, out T result);
private static T TryParseOrThrow<T>(string input, TryParseDelegate<T> tryParse, string typeName)
{
if (tryParse(input, out var result))
return result;
throw new System.FormatException($"Failed to parse '{input}' as {typeName}");
}
private static T TryParseWithCustomParser<T>(string input, System.Func<string, T> parser)
{
try
{
return parser(input);
}
catch (System.Exception ex)
{
throw new System.FormatException($"Custom parser failed to parse '{input}': {ex.Message}", ex);
}
}
private static object TryConvertOrThrow(string input, System.Type targetType, string typeName)
{
try
{
return System.Convert.ChangeType(input, targetType);
}
catch (System.Exception ex)
{
throw new System.FormatException($"Failed to convert '{input}' to {typeName}: {ex.Message}", ex);
}
}
}
Code and pdf at
https://ignatandrei.github.io/RSCG_Examples/v2/docs/Clap.Net