command-line-api
command-line-api copied to clipboard
Customize --version output
I'd like to have --version output some additional information, like in these:
PS C:\> MSBuild.exe /version
Microsoft (R) Build Engine version 15.9.21+g9802d43bc3 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
15.9.21.664
$ gcc --version
gcc.exe (x86_64-posix-sjlj, built by strawberryperl.com project) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
That is however somewhat cumbersome to implement on top of System.Commandline, because then the application:
- Cannot call CommandLineBuilderExtensions.UseDefaults, which would add another
--versionoption and cause an exception. Must instead call UseEnvironmentVariableDirective etc. one by one. - Must hardcode the value of MiddlewareOrderInternal.VersionOption = -1200.
- Must reimplement the VersionOptionCannotBeCombinedWithOtherArguments validation.
Can we have API like this:
namespace System.CommandLine.Builder
{
partial class CommandLineBuilderExtensions
{
public static CommandLineBuilder UseVersionOption(this CommandLineBuilder builder);
public static CommandLineBuilder UseVersionOption(this CommandLineBuilder builder, params string[] aliases);
+ public static CommandLineBuilder UseVersionOption(this CommandLineBuilder builder, Action<InvocationContext> handler);
+ public static CommandLineBuilder UseVersionOption(this CommandLineBuilder builder, Action<InvocationContext> handler, params string[] aliases);
}
}
The provided handler would then be responsible of all output, and could set InvocationContext.ExitCode if desired.
Overloads with Func<InvocationContext, Task> could be added later if needed, but I don't think they would be needed.
The middleware would call handler(context) instead of doing this write: https://github.com/dotnet/command-line-api/blob/209b724a3c843253d3071e8348c353b297b0b8b5/src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs#L625
If the handler wanted to display AssemblyInformationalVersionAttribute.InformationalVersion, it would have to locate the attribute on its own.
Another difficulty here is that, because CommandLineBuilder.LocalizationResources is not public, an external definition of VersionOption cannot so easily get the localized VersionOptionDescription: https://github.com/dotnet/command-line-api/blob/209b724a3c843253d3071e8348c353b297b0b8b5/src/System.CommandLine/Help/VersionOption.cs#L56-L60
It is possible to call the public API _builder.Build().Configuration.LocalizationResources.VersionOptionDescription(), though.
I hope https://github.com/dotnet/command-line-api/pull/1969 won't make an external implementation more difficult.
We're planning to move many features out of middleware in order to make things more flexible while also reducing the performance footprint of the built-in features that happen to be implemented as middleware today. Making it easier to bring your own external implementations of --help and --version is a core goal.
With System.CommandLine 2.0.0-beta4.23178.3, I can apparently do this:
VersionOption versionOption = rootCommand.Options.OfType<VersionOption>().Single();
versionOption.Action = new AugmentedVersionOptionAction(versionOption.Action);
and then:
private class AugmentedVersionOptionAction : CliAction
{
private readonly CliAction inner;
public AugmentedVersionOptionAction(CliAction inner)
{
this.inner = inner;
}
public override int Invoke(ParseResult parseResult)
{
int exitCode = this.inner.Invoke(parseResult);
this.OutputExtraText(parseResult);
return exitCode;
}
public override async Task<int> InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
{
int exitCode = await this.inner.InvokeAsync(parseResult, cancellationToken).ConfigureAwait(false);
this.OutputExtraText(parseResult);
return exitCode;
}
private void OutputExtraText(ParseResult parseResult)
{
parseResult.Configuration.Output.WriteLine("Company Confidential");
}
}
It is unfortunate that this has to override both Invoke and InvokeAsync, though.
What is the state of easier ways to extend / replace default help and version commands?
Is this the best option we have, or is there something in the works?
Also found that calling Invoke on ParseResult does not call help or version, and reports --help and --version as errors. This is inconsistent and means I can't check if Help or Version will be invoked as they seem to be hardcoded outside of Parse.
Also found that calling
InvokeonParseResultdoes not call help or version, and reports--helpand--versionas errors. This is inconsistent and means I can't check if Help or Version will be invoked as they seem to be hardcoded outside of Parse.
Which version are you using?
Which version are you using?
Latest released on nuget, beta 4.
This (Copilot GPT5 provided) code seems to work with the latest RC:
void Main(string[] args)
{
var root = new RootCommand("Demo with custom --version");
// Replace the Action on the built-in VersionOption
var versionOpt = root.Options.OfType<VersionOption>().Single();
versionOpt.Action = new CustomVersionAction();
// set up other Options...
var exitCode = root.Parse(args ?? new string[0]).Invoke();
}
// A synchronous action that runs when --version is present.
// Actions are terminating in the invocation pipeline by default, so this will short-circuit normal execution.
sealed class CustomVersionAction : SynchronousCommandLineAction
{
public override int Invoke(ParseResult parseResult)
{
parseResult.InvocationConfiguration.Output.WriteLine("MyVersion");
return 0;
}
public override bool ClearsParseErrors => true;
}
@Gerboa, CustomVersionAction should also override ClearsParseErrors like the built-in action does here: https://github.com/dotnet/command-line-api/blob/e292617c0f829bfe777c7ad51467c6a509a9aff8/src/System.CommandLine/VersionOption.cs#L72
I'll consider this fixed in v2.0.0-rc.1.25451.107, even though the API is not what I proposed.
Updated my code example above with suggested ClearsParseErrors override.