Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Usage of noun and verb with further parameters #932

Open
ImfeldC opened this issue Jul 22, 2024 · 1 comment
Open

Usage of noun and verb with further parameters #932

ImfeldC opened this issue Jul 22, 2024 · 1 comment

Comments

@ImfeldC
Copy link

ImfeldC commented Jul 22, 2024

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?

@piotrwcislo
Copy link

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:

  1. 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.
  2. 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

.WithParsed<TpmInitArgs>( ... )
.WithParsed<TpmShowArgs>(... )

(kinda resembling how routes are declared in controllers in ASP.NET WebApi)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants