Usage of noun and verb with further parameters
Hi, Is it possible to use CommandLineParser with noun and verb syntax? I would like to setup a command line tool, which supports different nouns (in CommandLineParser terminology, this would be a verb)
As example:
- Myapp.exe tpm init -v ....
- Myapp.exe tpm show -v -l ....
- MyApp.exe dongle show -l .... In the example above I have two levels of verbs, in the first level the noun and in the second level the verb.
Is this possible out-of-the-box? If not, are there ways to implement this?
hey @ImfeldC I have the same problem and haven't found an easy way to do this. One solution might be to add a custom attribute like this:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = true)]
public class NounAttribute : Attribute
{
public readonly string Name;
public NounAttribute(string nounName)
{
Name = nounName;
}
}
[Noun("tpm")]
[Verb("init")]
public class TpmInitArgs
{
// ...
}
[Noun("tpm")]
[Verb("show")]
public class TpmShowArgs
{
// ...
}
[Noun("dongle")]
[Verb("show")]
public class DongleShowArgs
{
// ...
}
And add an extension method that handles the attribute:
public static class ParserExtensions
{
public static ParserResult<object> ParseArgumentsWithNoun<T1, T2, T3>(this Parser parser, string[] args)
{
if (args.Length < 1)
{
// just return whatever as it will fail; there is no way to create NotParsed as it's sealed and ctor is internal
return parser.ParseArguments(args, []);
}
var types = new[] { typeof(T1), typeof(T2), typeof(T3) };
var nounTypesLookup = types
.SelectMany(t => t.GetTypeInfo().GetCustomAttributes<NounAttribute>().Select(attr => new {Type = t, Attribute = attr}))
.ToLookup(x => x.Attribute.Name, x => new {x.Type, x.Attribute});
if (!nounTypesLookup.Contains(args[0]))
{
// just return whatever as it will fail; there is no way to create NotParsed as it's sealed and ctor is internal
return parser.ParseArguments(args.Skip(1), types);
}
return parser.ParseArguments(args.Skip(1).ToArray(), nounTypesLookup[args[0]].Select(x => x.Type).ToArray());
}
}
public static class Program
{
public static void Main(string[] args)
{
Parser.Default.ParseArgumentsWithNoun<TpmInitArgs, TpmShowArgs, DongleShowArgs>(args)
.WithParsed<TpmInitArgs>(arg => { Console.WriteLine("TpmInitArgs"); })
.WithParsed<TpmShowArgs>(arg => { Console.WriteLine("TpmShowArgs"); })
.WithParsed<DongleShowArgs>(arg => { Console.WriteLine("DongleShowArgs"); });
}
}
Although this solution would be a bit problematic:
- you will have to add an extension method accepting the same number of generic parameters as there are possible ways to run the application (all noun-verb pairs). It will quickly turn out that there will be actually a lot of those actions and parameter types to parse.
- the built-in Program Usage display does not work
What would be really amazing is a way to declare a Noun attribute on a class, and have the Verb attribute declared per field/property of a class, like so:
[Noun("tpm")]
public class TpmArgs
{
[Verb("init")]
public TpmInitArgs TpmInitArgs;
[Verb("show")
public TpmShowArgs TpmShowArgs;
}
which can be declared as simply as
Parser.Default.ParseArguments<TpmArgs>(...)
and would eventually map to particular args type in WithParsed<T>
.WithParsed<TpmInitArgs>( ... )
.WithParsed<TpmShowArgs>(... )
(kinda resembling how routes are declared in controllers in ASP.NET WebApi)