RSCG – Clap.Net

 
 

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&#91;index&#93;)
            {
                // 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


Posted

in

,

by

Tags: