CliCommandLineParser
CliCommandLineParser copied to clipboard
HelpView should include the Suggestions
If one command have options with suggenstions, the help view should output the suggestions. Or I lost something?
@xyting, would you mind giving an example?
I use following code:
public static class ToolsParser
{
public static Parser Instance = new Parser(
options: ToolsCommand());
private static Command ToolsCommand() =>
Command("codegen",
"Generate EF/EF Core/Dapper code from database",
Option("-c|--connectionstring",
"Specify which database reverse engineering.",
ExactlyOneArgument()
.With(name: "CONNECTIONSTRING")),
Option("-t|--target",
"Specify which type code to generate. Allowed vaues are ef, efcore, dapper",
ExactlyOneArgument()
.With(defaultValue: () => "ef")
.WithSuggestionsFrom("ef",
"efcore",
"dapper")
.With(name: "TARGET")),
Option("-o|--output",
"Output directory in which to place the generated code.",
ExactlyOneArgument()
.With(name: "OUTPUT_DIR")),
HelpOption());
private static Option HelpOption() =>
Option("-h|--help",
"Show help information",
NoArguments());
}
class Program
{
static int Main(string[] args)
{
var parseResult = ToolsParser.Instance.Parse(args);
#if DEBUG
Console.WriteLine(parseResult.Diagram());
#endif
var appliedOptions = parseResult["codegen"];
if (appliedOptions.HasOption("help"))
{
Console.WriteLine(parseResult.Command().HelpView());
return 0;
}
}
}
The help View show:
Usage: codegen [options]
Options:
-c, --connectionstring <CONNECTIONSTRING> Specify which database reverse engineering.
-t, --target <TARGET> Specify which type code to generate. Allowed vaues are ef, efcore, dapper
-o, --output <OUTPUT_DIR> Output directory in which to place the generated code.
-h, --help Show help information
But I want the help view to show the -t
suggestion, rather than I hardcode the Allowed values are ef, ef core, dapper
.
This is currently supported, but not via WithSuggestionsFrom
. If you change this:
Option("-t|--target",
"Specify which type code to generate. Allowed vaues are ef, efcore, dapper",
ExactlyOneArgument()
.With(defaultValue: () => "ef")
.WithSuggestionsFrom("ef",
"efcore",
"dapper")
to this:
Option("-t|--target",
"Specify which type code to generate. Allowed vaues are ef, efcore, dapper",
AnyOneOf("ef",
"efcore",
"dapper")
.With(defaultValue: () => "ef")
then the help output will be:
Usage: codegen [options]
Options:
-c, --connectionstring <CONNECTIONSTRING> Specify which database reverse engineering.
-t, --target <TARGET> Specify which type code to generate. Allowed vaues are ef, efcore, dapper
-o, --output <OUTPUT_DIR> Output directory in which to place the generated code.
-h, --help Show help information
WithSuggestionsFrom
is just for completions, with no resulting validation error if a different value is provided, which is why it isn't fed back to the help output. This may be worth changing, although for unbounded completion lists (e.g. NuGet packages) it wouldn't be supportable.
I had change to your suggested code, but the help output have not any changed. I want the AnyOneOf("ef", "efcore", "dapper")
the values ef
, efcore
, dapper
be generated in the help output, rather then I hardcode the help
message with Allowed values are ef, ef core, dapper.
I want the help will be:
Usage: codegen [options]
Options:
-c, --connectionstring <CONNECTIONSTRING> Specify which database reverse engineering.
-t, --target <TARGET> Specify which type code to generate. The allowed vaues are ef, efcore, dapper (ef, efcore, dapper are generated from AnyOneOf)
-o, --output <OUTPUT_DIR> Output directory in which to place the generated code.
-h, --help Show help information
Apologies, I confused the validation error messages (which do show the allowed values when you use Accept.AnyOneOf
) with the help output (which does not).
As for what the help output should look like, something like this would be easier to make consistent and more localization-friendly:
Usage: codegen [options]
Options:
-c, --connectionstring <CONNECTIONSTRING> Specify which database reverse engineering.
-t, --target <ef|efcore|dapper> Specify which type code to generate.
-o, --output <OUTPUT_DIR> Output directory in which to place the generated code.
-h, --help Show help information
As I mentioned above, this would show up for Accept.AnyOneOf
or Accept.ZeroOrMoreOf
but not for Accept.SuggestionsFrom
.
Thoughts?
@richlander @livarcocc @seancpeters
@jonsequitur Thanks. I see, the output look like following will be better.
Usage: codegen [options]
Options:
-c, --connectionstring <CONNECTIONSTRING> Specify which database to reverse engineering.
-t, --target <ef|efcore|dapper> Specify which kind code to generate.
-o, --output <OUTPUT_DIR> Output directory in which to place the generated code.
-h, --help Show help information
But, the options maybe more than 3/4...10 items, so, the <ef|efcore|dapper>
style maybe not a good choice.
I like the compact display of the options. For when there are a lot of options, which would make the page wide, perhaps a display like we're using in dotnet/templating would be desirable - like for the -au|--auth
option below:
>dotnet new3 mvc -h
Template Instantiation Commands for .NET Core CLI
Usage: new3 [options]
Options:
-h, --help Displays help for this command.
-l, --list Lists templates containing the specified name. If no name is specified, lists all templates.
-n, --name The name for the output being created. If no name is specified, the name of the current directory is used.
-o, --output Location to place the generated output.
-i, --install Installs a source or a template pack.
-u, --uninstall Uninstalls a source or a template pack.
--type Filters templates based on available types. Predefined values are "project", "item" or "other".
--force Forces content to be generated even if it would change existing files.
-lang, --language Specifies the language of the template to create.
ASP.NET Core Web App (Model-View-Controller) (C#)
Author: Microsoft
Description: A project template for creating an ASP.NET Core application with example ASP.NET Core MVC Views and Controllers. This template can also be used for RESTful HTTP services.
This template contains technologies from parties other than Microsoft, see https://aka.ms/template-3pn for details.
Options:
-au|--auth The type of authentication to use
None - No authentication
Individual - Individual authentication
IndividualB2C - Individual authentication with Azure AD B2C
SingleOrg - Organizational authentication for a single tenant
MultiOrg - Organizational authentication for multiple tenants
Windows - Windows authentication
Default: None
--aad-b2c-instance The Azure Active Directory B2C instance to connect to (use with IndividualB2C auth type).
string - Optional
Default: https://login.microsoftonline.com/tfp/
But we have a lot of highly customized output display going on there, so this might be overkill for the general case.
I've implemented an extension to this where it'll automatically include short and long options based on an enum.
Options:
-?, -h, --help Show help information
-v, --verbosity Set the verbosity level of the command. Allowed values are: q[uiet], i[nfo], v[erbose], d[ebug], t[race]
I have a few helper methods:
public static class EnumOption
{
internal static Option Create<TEnum>(string alias, string help, bool allowFirstChar = true)
{
var values = GetEnumOptionValues(typeof(TEnum), allowFirstChar).ToArray();
var enumHelp = GetEnumHelpText(typeof(TEnum), allowFirstChar);
return Option(alias, help + enumHelp, AnyOneOf(values));
}
internal static IEnumerable<string> GetEnumOptionValues(Type enumType, bool allowFirstChar)
{
var names = Enum.GetNames(enumType).Select(n => n.ToLowerInvariant());
var letters = new Dictionary<char, bool>();
foreach (var name in names)
{
yield return name;
if (allowFirstChar)
{
var letter = name[0];
if (!letters.ContainsKey(letter))
{
letters.Add(letter, true);
yield return letter.ToString();
}
else
{
throw new ArgumentException(
"Cannot create enum-based option with allowFirstChar unless " +
"all enum names start with a unique character.",
nameof(allowFirstChar));
}
}
}
}
internal static string GetEnumHelpText(Type enumType, bool allowFirstChar)
{
var names = Enum.GetNames(enumType).Select(n => n.ToLowerInvariant());
return " Allowed values are: " + String.Join(", ",
names.Select(n => allowFirstChar
? n.Insert(1, "[").Insert(n.Length + 1, "]")
: n));
}
internal static TEnum Parse<TEnum>(AppliedOption option)
{
var names = Enum.GetNames(typeof(TEnum));
var value = option.Value<string>();
if (value != null && value.Length == 1)
{
// first char
var name = names.FirstOrDefault(n => n.ToLowerInvariant().StartsWith(value));
if (name != null)
{
return (TEnum) Enum.Parse(typeof(TEnum), name);
}
}
else if (value != null && value.Length > 1)
{
var name = names.FirstOrDefault(n => n.ToLowerInvariant() == value);
if (name != null)
{
return (TEnum) Enum.Parse(typeof(TEnum), name);
}
}
throw new ArgumentException(
$"Could not parse enum '{typeof(TEnum)}' " +
$"for option '{option.Name}' with value '{value}'",
nameof(option));
}
}
You can use it like this:
public static Option VerbosityOption =>
EnumOption.Create<VerbosityLevel>("-v|--verbosity", "Set the verbosity level of the command.")
// accessing value
if (command.HasOption(CommonOptions.VerbosityOption.Name))
{
var level = EnumOption.Parse<VerbosityLevel>(
command[CommonOptions.VerbosityOption.Name]);
}
@jonsequitur @seancpeters your ideas would work great, the main thing I'd like is Enum support which I had to add myself and the shortened name support.
So with the help text as an argument:
Options:
-?, -h, --help Show help information
-v, --verbosity <q[uiet]|i[nfo]|v[erbose]|d[ebug]|t[race]> Set the verbosity level of the command.