Skip to content

New Config proposal #2101

Closed
Closed
@adamsitnik

Description

@adamsitnik

One of our goals is simplyfing the configuration, making it easier to discover and use.

This can be achieved by making the configuration mutable (no need for a dedicated builder) and making it an optional argument for Command.Parse (this has already happened).

My current idea is following:

public class CliConfiguration
{
    public CliConfiguration(Command command)
    {
        Command = command;
        Directives = new()
        {
            new SuggestDirective()
            // I am not sure whether we should enable other Directives by default
            // new EnvironmentVariablesDirective()
            // new ParseDirective()
        };
    }

    public Command Command { get; }

    public bool EnablePosixBundling { get; set; } = true;

    public bool EnableDefaultExceptionHandler {  get; set; } = true;

    public bool EnableTypoCorrections { get; set; } = false;

    public TimeSpan? ProcessTerminationTimeout { get; set; } = TimeSpan.FromSeconds(2);

    public int ParseErrorExitCode { get; set; } = 1;

    public TryReplaceToken? ResponseFileTokenReplacer { get; set; } = StringExtensions.TryReadResponseFile;

    public List<Directive> Directives { get; }
}

How to disable signaling and handling of process termination via a CancellationToken? Set ProcessTerminationTimeout to null.

How to disable default response file token replacer? Set ResponseFileTokenReplacer to null.

How to customize exception handler? Set EnableDefaultExceptionHandler to false and catch the exceptions on your own:

CliConfiguration config = new(command) { EnableDefaultExceptionHandler = false };
ParseResult parseResult = command.Parse(args, config);

try
{
    return parseResult.Invoke();
}
catch (Exception ex)
{
    // custom exception handler
}

The alternative for customizing the ParseErrorExitCode is following:

ParseResult parseResult = command.Parse(args);

if (parseResult.Errors.Any())
{
    return $customValue;
}

The most tricky part is how HelpOption and VersionOption should be enabled and disabled. Currently, the config builder just adds them to the provided Command options list:

private static void OverwriteOrAdd<T>(Command command, T option) where T : Option
{
if (command.HasOptions)
{
for (int i = 0; i < command.Options.Count; i++)
{
if (command.Options[i] is T)
{
command.Options[i] = option;
return;
}
}
}
command.Options.Add(option);
}

I don't like it, as it's a side effect. But on the other hand, I don't have an ideal alternative.

Possible solutions:

Expect the users to add them in explicit way.

command.Options.Add(new HelpOption());
command.Options.Add(new VersionOption());

Advantage(s): no magic, everything is crystal clear, perf.
Disadvantage(s): help would be not enabled by default.

Create every command with Help and Version options added by default

public Command(string name, bool addDefaultOptions = true)
{
    if (addDefaultOptions)
    {
        Options.Add(new HelpOption());
        Options.Add(new VersionOption());
    }
}

Advantage(s): help enabled by default.
Disadvantage(s): Hard to customize help (find, cast & customize or replace) I expect that Version makes sense only for the root command, it would pollute subcommands.

Perhaps it would make more sense for RootCommand?
Maybe the argument should not be optional, so everyone who creates a Command would need to think about it?

Expose the options as part of Config type, add them the root Command when parsing

public class CliConfiguration
{
    public HelpOption? HelpOption { get; set; } = new ();
    
    public VersionOption? VersionOption { get; set; } = new ();
}

Advantage(s): quite easy to discover and configure, enabled by default.
Disadvantage(s): side effect of adding them to the root command (but so far nobody complained about it beside me?)

Do we really need VersionOption enabled by default?

Add a list of default Options to Config, similarly to Directives

public class CliConfiguration
{
    public List<Option> Options { get; }
}

Advantage(s): quite easy to discover, consistent with Directives
Disadvantage(s): hard to configure a specific option (find HelpOption, cast it and then set the builder), side effect of adding them to the root command

@jonsequitur @KathleenDollard @Keboo @KalleOlaviNiemitalo please provide feedback

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions