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

Argument-less aliases #2252

Closed
Reyhn3 opened this issue Jul 28, 2023 · 5 comments
Closed

Argument-less aliases #2252

Reyhn3 opened this issue Jul 28, 2023 · 5 comments

Comments

@Reyhn3
Copy link

Reyhn3 commented Jul 28, 2023

This is either a question I couldn't find and answer to, or a bug report.
I am trying to implement the suggested alias without argument described in The --verbosity option.

If you define aliases, use -v for --verbosity and make -v without an argument an alias for --verbosity Diagnostic. Use -q for --verbosity Quiet.

If I have the aliases "--verbosity", "-v" and "-q", how can I tell them apart in my handlers? When I'm looking through the object inheritance when debugging in Visual Studio, I am unable to find the "-v" string from the command line. And the Tokens array is empty.

This is my code example:

void Main()
{
	var rootCommand = new RootCommand("Option Alias Demo");

	var verbosityOption = new Option<VerbosityLevel>(
		new[]
			{
				"--verbosity",
				"-v",
				"-q"
			},
		result =>
			{
//TODO: Identify "-v" and "-q" and return VerbosityLevel.Verbose / VerbosityLevel.Quiet respectively
				result.Tokens.Dump("This should have '-v'"); // This is always empty
				return VerbosityLevel.Normal;
			},
		true,
		"Set logging level")
	{
		Arity = ArgumentArity.ZeroOrOne
	};
		
	rootCommand.AddGlobalOption(verbosityOption);
	rootCommand.SetHandler(verbosity =>
		{
			Console.WriteLine("Using verbosity level '{0}'", verbosity);
		}, verbosityOption);
	
//	rootCommand.Invoke("--verbosity Minimal"); // Works
	rootCommand.Invoke("-v"); // Does NOT work
}

public enum VerbosityLevel
{
	Quiet,
	Minimal,
	Normal,
	Detailed,
	Verbose
}
@KalleOlaviNiemitalo
Copy link

It should be available from OptionResult.Token rather than SymbolResult.Tokens.

However, if you make -v and -q aliases of --verbosity but parse them differently, then --help won't automatically explain this to the user, and completions might need extra work too. So it might be better to make them separate Option<T> instances that just affect the same setting.

@Reyhn3
Copy link
Author

Reyhn3 commented Jul 28, 2023

Thanks for your answer that set me on the correct path!

To achieve a single-option that also handles no values for the aliases, I can replace the ParseArgument-delegate with this:

result =>
	{
		if (result.Parent is OptionResult option)
		{
			if (Equals(option.Token, null))
				return VerbosityLevel.Normal;

			if (string.Equals("-q", option.Token.Value, StringComparison.InvariantCultureIgnoreCase))
				return VerbosityLevel.Quiet;
					
			if (string.Equals("-v", option.Token.Value, StringComparison.InvariantCultureIgnoreCase))
				return VerbosityLevel.Verbose;

			var value = result.Tokens.SingleOrDefault()?.Value;
			if (Enum.TryParse<VerbosityLevel>(value, true, out var level))
				return level;
		}

		return VerbosityLevel.Normal;
	}

Unfortunately, you're also right that the help text is misleading. To change it seems overly difficult.
As you propose, a completely different solution is to have three separate options:

void Main()
{
	var rootCommand = new RootCommand("Option Alias Demo");

	var verbosityOption =
		new Option<VerbosityLevel>("--verbosity", () => VerbosityLevel.Normal, "Sets level of feedback")
			{
				Arity = ArgumentArity.ExactlyOne
			};

	var loudOption = new Option<bool>("-v", "Enable verbose logging");
	var quietOption = new Option<bool>("-q", "Disable logging");

	rootCommand.AddGlobalOption(verbosityOption);
	rootCommand.AddGlobalOption(loudOption);
	rootCommand.AddGlobalOption(quietOption);

	rootCommand.SetHandler((verbosity, loud, quiet) =>
		{
			Console.WriteLine("Verbosity: '{0}', Loud: {1}, Quiet: {2}", verbosity, loud, quiet);
		}, verbosityOption, loudOption, quietOption);
	
	rootCommand.Invoke("-v -q --verbosity minimal");
}

public enum VerbosityLevel
{
	Quiet,
	Minimal,
	Normal,
	Detailed,
	Verbose
}

The only drawback to this solution is that there are three options that are set and can be contradictory. But, the user-experience is better, and that's what counts.

@Reyhn3
Copy link
Author

Reyhn3 commented Jul 28, 2023

A third option is to add a middleware to the multi-option alternative above.
That way the three options can be intercepted and handled separately without the need for the actual command handler to care about them. This may not always be optimal, but in this particular case, to set the verbosity level on a logger, it works perfectly.

void Main()
{
	var rootCommand = new RootCommand("Option Alias Demo");

	var verbosityOption =
		new Option<VerbosityLevel>("--verbosity", () => VerbosityLevel.Normal, "Sets level of feedback")
		{
			Arity = ArgumentArity.ExactlyOne
		};

	var loudOption = new Option<bool>("-v", "Enable verbose logging");
	var quietOption = new Option<bool>("-q", "Disable logging");

	rootCommand.AddGlobalOption(verbosityOption);
	rootCommand.AddGlobalOption(loudOption);
	rootCommand.AddGlobalOption(quietOption);

//TODO: This is for demonstration purposes only - use it differently!
	var sharedVerbosityLevel = VerbosityLevel.Normal;

	InvocationMiddleware verbosityMiddleware =
		async (context, next) =>
			{
				if (context.ParseResult.FindResultFor(loudOption) != null)
					sharedVerbosityLevel = VerbosityLevel.Verbose;
				else if (context.ParseResult.FindResultFor(quietOption) != null)
					sharedVerbosityLevel = VerbosityLevel.Quiet;
				else
					sharedVerbosityLevel = context.ParseResult.GetValueForOption(verbosityOption);

				await next(context);
			};

	rootCommand.SetHandler(_ =>
		{
			Console.WriteLine("Verbosity: '{0}'", sharedVerbosityLevel);
		});

	var cli = new CommandLineBuilder(rootCommand)
		.UseDefaults()
		.AddMiddleware(verbosityMiddleware)
		.Build();

	cli.Invoke("--help");
	cli.Invoke("-v -q --verbosity minimal");
}

public enum VerbosityLevel
{
	Quiet,
	Minimal,
	Normal,
	Detailed,
	Verbose
}

@Reyhn3
Copy link
Author

Reyhn3 commented Jul 28, 2023

I'm closing this ticket because I got an answer to my question, and I don't see a need for any change or feature request. The library works well enough.

@Reyhn3 Reyhn3 closed this as completed Jul 28, 2023
@elgonzo
Copy link
Contributor

elgonzo commented Jul 28, 2023

Just an FYI: Before you go and choose middlewares as the prefered approach to achive your desired solution, take a look at the current System.CommandLiine developments, and check out recent daily builds of System.CommandLine (the nuget feed with daily builds is mentioned in the readme.md).

The libary is undergoing significant changes since the last version has been published on the official nuget.org feed (2.0.0-beta4.22272.1 from a year ago). Amongst those changes is the library moving from the old middleware pipeline to a different approach dubbed "CliActions". (Some of the related discussions: #2071, #2162; and there are more discussions and tidbits about that to be found here in the github issue tracker.)

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

3 participants