dotnet / command-line-api

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

[Proposal] Values API #2362

Open KathleenDollard opened 3 months ago

KathleenDollard commented 3 months ago

CLI authors will be able to retrieve values for symbols from the ParseResult or the ValueSubsystem. The ValueSubsystem is discussed in #2357, which focuses primarily on how default values are handled.

CLI authors that are using the core parser directly (no help, etc) can retrieve values from the ParseResult. When the pipeline is used, the values can be retrieved from the ValueSubsystem. The differences are:

Feature ParseResult ValueSubsystem
Specified default values No (*) Yes
Validated No Yes
Binding/Invocation No Yes

(*) The default value for the type is returned.

This is consistent with the core parser providing minimum functionality that is fixed over time, and subsystems that can evolve in the future to provide solutions for known default features like getting defaults from environment variables.

The API for the ParseResult and the ValueSubsystem will be the same:

public bool TryGetValue<T>(Symbol key, out T result);
public bool TryGetValue<T>(string key, out T result);
public T GetValue<T>(Symbol key);
public T GetValue<T>(string key);

The GetValue method throws if the key (usually a string) is not in the collection, and returns the types default value if the key is in the corresponding collection when the user entered the value.

Question: Does GetValue throw or return default on HasError? Probably preserve current behavior. Question: Does TryGetValue return false on HasError? Seems appropriate.

Both ParseResult and the ValueSubsystem also provide access to the underlying ValueResult object:

public Result TryGetValueResult(Symbol key, out T result);
public Result TryGetValueResult(string key, out T result);
public Result GetValueResult(Symbol key);
public Result GetValueResult(string key);

ValueResult is an immutable type that allows access to the Location, Symbol and other details about how the value was created. Note that while the Error reference is not changeable, errors can be added to the error collection.

public class ValueResult
{
    public CliSymbol ValueSymbol { get; }

    public T? GetValue<T>()
        => (T?)Value;

    public bool TryGetValue<T>(out T result);

    // This needs to be a collection because collection types have multiple tokens and they will not be simple offsets when response files are used
    // TODO: Consider more efficient ways to do this in the case where there is a single location
    public IEnumerable<Location> Locations { get; }

    public ValueResultOutcome Outcome { get; }

    public IList<CliError> Errors { get; }

    public override string ToString()

I greatly wish we had generic indexers. :-(

KalleOlaviNiemitalo commented 3 months ago

returns the types default value if the key is in the corresponding collection when the user entered the value.

That seems backwards. How does the application get the user-specified value rather than default?

public Result TryGetValueResult(Symbol key, out T result);
public Result TryGetValueResult(string key, out T result);
public Result GetValueResult(Symbol key);
public Result GetValueResult(string key);

Should those instead be

-public Result TryGetValueResult(Symbol key, out T result);
+public bool TryGetValueResult(Symbol key, out ValueResult result);
-public Result TryGetValueResult(string key, out T result);
+public bool TryGetValueResult(string key, out ValueResult result);
-public Result GetValueResult(Symbol key);
+public ValueResult GetValueResult(Symbol key);
-public Result GetValueResult(string key);
+public ValueResult GetValueResult(string key);

How do subsystems create instances of ValueResult? No constructors are listed but there are readonly properties.