Aot.ArgumentParser 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Aot.ArgumentParser --version 1.0.0
                    
NuGet\Install-Package Aot.ArgumentParser -Version 1.0.0
                    
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="Aot.ArgumentParser" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Aot.ArgumentParser" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Aot.ArgumentParser" />
                    
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 Aot.ArgumentParser --version 1.0.0
                    
#r "nuget: Aot.ArgumentParser, 1.0.0"
                    
#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.
#:package Aot.ArgumentParser@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Aot.ArgumentParser&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Aot.ArgumentParser&version=1.0.0
                    
Install as a Cake Tool

ArgumentParser

Licensed under the Apache License, Version 2.0

About

ArgumentParser provides functionality to parse commandline arguments without the use of Reflection. It is therefore compatible with AOT publishing.

It works by providing a roslyn analyzer, which will augment a user provided partial class with a static Parse(string[] args) method. This method will take the raw arguments from the commandline (in the form of a string[]) and return an instance of the partial class, and any accumulated errors as a tuple. The properties on the class are set to what was specified on the commandline.

The method is generated during compilation, which allows the generated code to be very simple and not rely on Reflection. By being very simple it will compile fine when publishing an AOT executable.

This is essentially an MVP at this point, so there no support (as of yet) for some nice to haves like:

  • Make arguments required via the required keyword
  • Specific types (like FileInfo)
  • Overloading the behaviour of the parser (how to handle certain errors for example)

Quickstart

For an example, check out the ExampleConsole project in this repository

First, add ArgumentParser to your project:

dotnet add package Aot.ArgumentParser

Next, create a public partial class and annotate it with the ParameterCollection Attribute. Inside of it, create public properties annotated with one of the property attributes (Flag/Option/Positional).

using ArgumentParser;

namespace ExampleConsole;

[ParameterCollection]
public partial class MyCommandLineArguments
{
	// flag attributes are designed as true/false values
	// if -v or --Verbose is specified on the commandline
	// this value will be true after parsing.
	// Flag cannot be used on other types than bool
	// Flags do not have a 'required' property (a required flag is just a true value)
	[Flag(shortName: "v", longName: "Verbose", description: "Enable verbose output")]
	public bool Verbose {get; set;}

	// Option attributes are designed to be passed as a sort of kv pair
	// Options may be passed either via their longName or shortName
	// the argument immediately succeeding this name will be interpreted
	// as the value of the option.

	// You don't need to set both longname and shortname, one is sufficient
	// The C# propertyname also does not need to align with the longName
	// Options may be set to required, triggering an exception if not passed
	// Option properties may have any supported type
	[Option(longName: "Target", required: true)]
	public string Output {get; set;}

	// Positional attributes are passed on the commandline without any
	// named identifier marking them. They are instead distinguished by the
	// ordinal position they take respective of eachother. They may appear at
	// any point in the commandline argument array, as long as they cannot
	// be mistaken for an option value it will parse fine. 

	// Example: '-v --Target myTarget 15' and '15 --Target myTarget -v'
	// will both produce the same output (15 is the 0th positional argument in both cases)
	// BUT
	// '--Target 15 myTarget -v' will give you an exception (myTarget will be parsed to int)

	// Positional arguments may have any supported type
	// Positional arguments may be set to required
	// Positional argument positions must form a sequence from 0..n-1
	[Positional(0, "the amount of times to repeat")]
	public int RepeatTimes {get; set;}
}

Now, simply build your project. A Parse method will appear on your class. Simply call it and feed it your args[] to get a parsed MyCommandLineArguments instance back, along with a list of errors (if any).

var (myArgs, err) = MyCommandLineArguments.Parse(args); // generated method
if (err.Any())
	// handle any errors
// use myArgs object

Supported types for argument properties

All types are parsed with their respective .Parse method from the BCL.

int
double
float
long
short
decimal
byte
sbyte
char
string
uint
ulong
ushort
bool
Guid
Uri
TimeSpan
DateTime

Inspecting the generated code

Simply add the following properties to your .csproj file: (The outputPath can be customized to your liking)

  <PropertyGroup>
    <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
    <CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)$(TargetFramework)/Generated</CompilerGeneratedFilesOutputPath>
  </PropertyGroup>

In the example case given above, something similar to the following code will be generated. Since this code is essentially peanuts and free of any using directives it should play nice with just about any target framework and user code.

ExampleConsole/obj/net9.0/Generated/ArgumentParser/ArgumentParser.Internal.ParserGenerator/MyCommandLineArguments_Parser.g.cs

// <auto-generated/>
namespace ExampleConsole
{
    public partial class MyCommandLineArguments
    {
        private static readonly ArgumentParser.OptionAttribute[] options = new ArgumentParser.OptionAttribute[] {
            new ArgumentParser.OptionAttribute("", "Target", ""),
        };
        private static readonly ArgumentParser.PositionalAttribute[] positionals = new ArgumentParser.PositionalAttribute[] {
            new ArgumentParser.PositionalAttribute(0, "the amount of times to repeat"),
        };
        private static readonly ArgumentParser.FlagAttribute[] flags = new ArgumentParser.FlagAttribute[] {
            new ArgumentParser.FlagAttribute("v", "Verbose", "Enable verbose output"),
        };
        private static readonly Dictionary<string,bool> requiredProperties = new Dictionary<string, bool>() {
            { "Output", false },
        };
        public static (MyCommandLineArguments result, List<ArgumentParser.ArgumentParserException> errors) Parse(string[] args)
        {
            var tokenizer = new ArgumentParser.ArgumentTokenizer();
            var (tokens, errors) = tokenizer.TokenizeArguments(args, options, positionals, flags);
            var instance = new MyCommandLineArguments();
            foreach (var token in tokens)
            {
                switch (token)
                {
                    case ArgumentParser.OptionToken optionToken:
                        if (optionToken.Name == "" || optionToken.Name == "Target")
                        {
                            instance.Output = optionToken.Value;
                            requiredProperties["Output"] = true;
                        }
                        break;
                    case ArgumentParser.PositionalToken positionalToken:
                        if (positionalToken.Position == 0)
                        {
                            instance.RepeatTimes = int.Parse(positionalToken.Value);
                        }
                        break;
                    case ArgumentParser.FlagToken flagToken:
                        if (flagToken.Name == "v" || flagToken.Name == "Verbose") { instance.Verbose = true; }
                        break;
                    default:
                        errors.Add(new ArgumentParser.InvalidTokenTypeException($"Unknown token type: {token.GetType().Name}"));
                        break;
                }
            }
            var missingRequired = requiredProperties
                .Where(kvp => !kvp.Value).Select(kvp => kvp.Key)
                .Select(k => new ArgumentParser.MissingRequiredArgumentException($"Missing required property: {k}"))
                .ToList();
            errors.AddRange(missingRequired);
            if (errors.Count > 0)
            {
                System.AggregateException ae = new System.AggregateException(message:"One or more required arguments missing", innerExceptions: missingRequired);
                throw ae;
            }
            return (instance, errors);
        }
    }
}

Default behaviour

The string[] parameter is tokenized and then the tokens are evaluated in a loop to determine which properties on the class to change. During tokenization, errors are collected when something doesn't tokenize correctly or when arguments are encountered that have no corresponding property in the class. These errors are nonfatal and will bubble up in the returned tuple, letting the user handle them as they see fit.

Parameters marked as required are tracked and checked off when found. At the end of the parse method, if any required properties are missing an AggregateException is thrown. The innerExceptions property is set to a list of Exceptions, one for each missing property.

Diagnostics and Errors

see AnalyzerRelease.Shipped.md

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.  net9.0 is compatible.  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.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.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.
  • .NETStandard 2.0

    • No dependencies.
  • net9.0

    • No dependencies.

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.3.0 286 6/10/2025
1.2.0 188 6/8/2025
1.1.1 103 6/1/2025
1.1.0 145 5/25/2025
1.0.1 221 5/15/2025
1.0.0 228 5/13/2025