Closed giggio closed 1 year ago
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.
I'm going to close this as unnecessary, but happy to reconsider if the presented solutions don't address issue.
It is common that the executing code is async, and it is not easily doable with the current API. We need a
Task
/ValueTask
returningFunc
for the first argument, soasync
/await
can be used.This is my current workaround: