dotnet / command-line-api

Command line parsing, invocation, and rendering of terminal output.
https://github.com/dotnet/command-line-api/wiki
MIT License
3.41k stars 382 forks source link

Feature Proposal: Source Generation for Command and Options #2323

Closed carstencodes closed 9 months ago

carstencodes commented 9 months ago

Hello,

as I am a huge fan of Don't Repeat Yourself.

If I am using the NamingConventionBinder, I do something like this:

var command = new CliCommand("foo", "Do the bar thing") {
   new CliOption<int>( new [] { "--number" }, getDefaultValue: () => 42 ) { Arity = ArgumentArity.ExactlyOne };
};
command.Handler = CommandHandler.Create<FooOptions>(o => { Console.WriteLine(o.Number) });

with

class FooOptions 
{
   public int Number { get; private set; }
}

In order to minimize shattering, I would suggest doing the following:

[HandleCommand("foo", Description="Do the bar thing")]
public partial class FooOptions
{
    [Option("--number")]
    [System.ComponentModel.DefaultValue(42)]
    [System.ComponentModel.DataAnnotations.Range(0, int.MaxValue, MaximumIsInclusive = true)]
    public int Number { get; init; }
}

using

var command = CommandFactory.Create<FooOptions>();
command.Handler = CommandHandler.Create<FooOptions>(o => {  });

At that point, I would like to have something like:

interface ISupportsCommandCreation
{
     static CliCommand CreateCommand();
}

where a source generator will create an implementation for

partial class FooOptions: ISupportsCommandCreation
{
      static CliCommand ISupportsCommandCreations.CreateCommand()
      {
             // Implementation goes here
      }
}

What do you think? Would this be a good feature to add to this project?

If not, feel free to close this issue.

KalleOlaviNiemitalo commented 9 months ago

If a developer has written [HandleCommand("foo", Description="Do the bar thing")] and then needs to make the description localisable, how would they change the code?

carstencodes commented 9 months ago

We're currently using CommandLineParser in most of our Projects. They solve it as follows:

Adding the HelpText will use the Text supplied. If ResourceType is set, than it will be used as the HelpText Resource Provider:

[HandleCommand("foo", Description=nameof(Resources.SR_FooCommandDescription), DescriptionResource=typeof(Resources))]

I would also prefer using System.Component.DescriptionAttribute, but as far as I am concerned, it is not localizable either, is it?

What do you think?

carstencodes commented 9 months ago

Most equivalent work is also present in System.ComponentModel and System.ComponentModel.DataAnnotations. It would be good to support it this way.

carstencodes commented 9 months ago

Please note, that I am also willing to contribute the implementation for this, but I won't create an implementation and add much effort into it, if the PR will be close because this is not wanted or even added to the scope by the maintainers.

KalleOlaviNiemitalo commented 9 months ago

DescriptionAttribute itself is not localizable, but derived classes can load the description from resources, like here: https://github.com/dotnet/runtime/blob/bf5e279d9239bfef5bb1b8d6212f1b971c434606/src/libraries/System.ComponentModel.TypeConverter/src/System/Timers/TimersDescriptionAttribute.cs#L24

I suppose a source generator could support that by translating [CustomDescription("ResourceKey")] to new CustomDescriptionAttribute("ResourceKey").Description if it detects that CustomDescriptionAttribute is based on DescriptionAttribute.

elgonzo commented 9 months ago

Just as an FYI, there is already a 3rd-party project that is trying to do what you seek: https://github.com/dotnet/command-line-api/discussions/2310 (based on the System.CommandLine version available on the public nuget.org and not on the newer System.CommandLine code base as present in the repository here, as it seems). It supports code generation but it seems to lack localization features as of now.

carstencodes commented 9 months ago

Awesome!