dotnet / command-line-api

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

Question: How to show default/fallback value in Commandline help when using multiple configuration providers #1279

Open BobSilent opened 3 years ago

BobSilent commented 3 years ago

I am using commandline in hosting and binding the configuration values, from a json configuration file and some values from commandline - multiple configuration providers.

I have a Configuration:

    internal class AaaCommandOptions : CommandOptionsBase
    {
        public static readonly ReportOutputFormat DefaultOutputFormat      = ReportOutputFormat.PDF;
        public static readonly string DefaultOutputDirector                = @"%TEMP%\ABC";
        public static readonly string DefaultCssFile                       = @"Default.css";

        [Required]
        public string ReportName { get; set; }

        public string OutputDirectory { get; set; }
        public string CssFile { get; set; } = DefaultCssFile;
        public ReportOutputFormat OutputFormat { get; set; } = DefaultOutputFormat;
    }

and i am Binding the values like that:

host.ConfigureServices((ctx, services) =>
                                       {
                                           services.AddOptions<AaaCommandOptions>()
                                                   .Bind(ctx.Configuration)
                                                   .BindCommandLine()
                                                   .ValidateDataAnnotations();

What I want to achieve is: having a possibility to describe a default (fall back value) which can be overwritten in either the config file (via environment variable) or at latest via commandline (if a option exists)

A common practice is to add the Command-line configuration provider last in a series of providers to allow command-line arguments to override configuration set by the other providers.

What i also want to have is to show the default value in the command line help.

If i use the Func<T> getDefaultValue:

                command.AddOption(new Option<string>(new[]
                                                     {
                                                         $"--{nameof(AaaCommandOptions.OutputDirectory)}", "-o"
                                                     },
                                                     () => AaaCommandOptions.DefaultOutputDirectory)
                                  {
                                      Description = "Storage path",
                                  });

This shows nicely the default value in the help output, but it also sets the value if the option is not provided on command line. So the Fallback value is always set if no other value is provided via command line.

As workaround I am doing today something like that

                command.AddOption(new Option<string>(new[]
                                                     {
                                                         $"--{nameof(AaaCommandOptions.OutputDirectory)}", "-o"
                                                     })
                                  {
                                      Description = $"Storage path. Default: {AaaCommandOptions.DefaultOutputDirectory}",
                                  });

What would be a recommended way?

jonsequitur commented 3 years ago

Do you want help to show, as the default, a value potentially coming from a different configuration provider? In other words, by "fallback" do you mean the application-defined default that would be used in the absence of any user-created configurations?

BobSilent commented 3 years ago

yes exactly, i updated my question with an example of the AaaCommandOptions class, you see i set some defaults in the class when it gets created I want to show these default values in the commandline help

jonsequitur commented 3 years ago

We've made some changes recently to allow the HelpBuilder to be more customizable for when you want to communicate this kind of detail to your users. Here's an example: https://github.com/dotnet/command-line-api/blob/d4d31f7291da5e06b573bf5f3096542b02fe5553/src/System.CommandLine.Tests/UseHelpTests.cs#L216-L237

BobSilent commented 3 years ago

This looks helpful, but in my case I want to provide a default Customization although I cannot provide an DefaultValue Function.

https://github.com/dotnet/command-line-api/blob/d4d31f7291da5e06b573bf5f3096542b02fe5553/src/System.CommandLine/Help/HelpBuilder.cs#L158-L161

https://github.com/dotnet/command-line-api/blob/d4d31f7291da5e06b573bf5f3096542b02fe5553/src/System.CommandLine/Help/HelpBuilder.cs#L445-L456 the customization gets only applied if it has a default value: it checks for .HasDefaultValue.

@jonsequitur is it really required to check if a default value exists, or in case of Customization it can be applied anyway, as this is what I want, if I explicitly specify a customization.

jonsequitur commented 3 years ago

The idea would be to completely replace the description for the argument so that you could include whatever description of the defaults and configurations are appropriate.

BobSilent commented 3 years ago

yes, more or less like that.

So for me it would be sufficient to replace the .HasDefaultValue to something like HasDefaultValueOrCustomization