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

Add MatchAsync to IParser<T>.IResult #187

Closed
giggio opened this issue Oct 17, 2023 · 2 comments
Closed

Add MatchAsync to IParser<T>.IResult #187

giggio opened this issue Oct 17, 2023 · 2 comments

Comments

@giggio
Copy link

giggio commented Oct 17, 2023

It is common that the executing code is async, and it is not easily doable with the current API.
We need a Task/ValueTask returning Func for the first argument, so async/await can be used.

This is my current workaround:

ProgramArguments programArguments;
var result = ProgramArguments.CreateParser()
.WithVersion("Naval Fate 2.0")
.Parse(args)
.Match(p => { programArguments = p; return -1; },
    result => { WriteLine(result.Help); return 0; },
    result => { WriteLine(result.Version); return 0; },
    result => { Error.WriteLine(result.Usage); return 1; });
if (result > -1) return result;
// continues with async/await...
return 0;
@atifaziz
Copy link
Collaborator

atifaziz commented Oct 18, 2023

You can do this today without the need for an async version of Match. Just return a Task<int> from each Match function argument instead of int, like this:

return await
    ProgramArguments.CreateParser()
                    .WithVersion("Naval Fate 2.0")
                    .Parse(args)
                    .Match(Main,
                           result => { WriteLine(result.Help); return Task.FromResult(0); },
                           result => { WriteLine(result.Version); return Task.FromResult(0); },
                           result => { Error.WriteLine(result.Usage); return Task.FromResult(1); });

static async Task<int> Main(ProgramArguments args)
{
    // ...

    return 0;
}

If you don't have a Main method, then you can also do this, where you unify ProgramArguments and int via object:

var result =
    ProgramArguments.CreateParser()
                    .WithVersion("Naval Fate 2.0")
                    .Parse(args)
                    .Match(args   => (object)args,
                           result => { WriteLine(result.Help); return 0; },
                           result => { WriteLine(result.Version); return 0; },
                           result => { Error.WriteLine(result.Usage); return 1; });

if (result is not ProgramArguments programArguments)
    return (int)result;
// continues with async/await...
return 0;

If you don't like the cast to object, you can use a generic discriminated union from OneOf as shown next:

var result =
    ProgramArguments.CreateParser()
                    .WithVersion("Naval Fate 2.0")
                    .Parse(args)
                    .Match(OneOf<ProgramArguments, int>.FromT0,
                           result => { WriteLine(result.Help); return OneOf<ProgramArguments, int>.FromT1(0); },
                           result => { WriteLine(result.Version); return OneOf<ProgramArguments, int>.FromT1(0); },
                           result => { Error.WriteLine(result.Usage); return OneOf<ProgramArguments, int>.FromT1(1); });
if (result.TryPickT1(out var exitCode, out var programArguments))
    return exitCode;
// continues with async/await...
return 0;

And if you still don't want to rely on another library, you can introduce your own union:

switch (
    ProgramArguments.CreateParser()
                    .WithVersion("Naval Fate 2.0")
                    .Parse(args)
                    .Match(args => (ProgramAction)new RunAction(args),
                           result => { WriteLine(result.Help); return new ExitAction(0); },
                           result => { WriteLine(result.Version); return new ExitAction(0); },
                           result => { Error.WriteLine(result.Usage); return new ExitAction(1); }))
{
    case ExitAction { ExitCode: var exitCode }:
        return exitCode;
    case RunAction { Arguments: var args }:
        // continues with async/await...
    return 0;
}

abstract record ProgramAction;
sealed record RunAction(ProgramArguments Arguments) : ProgramAction;
sealed record ExitAction(int ExitCode) : ProgramAction;

But then if you're going with a Match and a switch then you might as well go with just a switch as shown in the documentation:

var parser = Docopt.CreateParser(help).WithVersion("Naval Fate 2.0");

return parser.Parse(args) switch
{
    IArgumentsResult<IDictionary<string, ArgValue>> { Arguments: var arguments } => Run(arguments),
    IHelpResult => ShowHelp(help),
    IVersionResult { Version: var version } => ShowVersion(version),
    IInputErrorResult { Usage: var usage } => OnError(usage),
    var result => throw new System.Runtime.CompilerServices.SwitchExpressionException(result)
};

Anyway, as you can see, you have plenty of choices.

@atifaziz
Copy link
Collaborator

I'm going to close this as unnecessary, but happy to reconsider if the presented solutions don't address issue.

@atifaziz atifaziz closed this as not planned Won't fix, can't repro, duplicate, stale Oct 19, 2023
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