Skip to content
This repository has been archived by the owner on Nov 16, 2022. It is now read-only.

HelpView should include the Suggestions #78

Open
rigofunc opened this issue Jul 10, 2017 · 9 comments
Open

HelpView should include the Suggestions #78

rigofunc opened this issue Jul 10, 2017 · 9 comments

Comments

@rigofunc
Copy link

If one command have options with suggenstions, the help view should output the suggestions. Or I lost something?

@jonsequitur
Copy link
Contributor

@xyting, would you mind giving an example?

@rigofunc
Copy link
Author

rigofunc commented Jul 11, 2017

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.

@jonsequitur
Copy link
Contributor

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.

@rigofunc
Copy link
Author

rigofunc commented Jul 12, 2017

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

@jonsequitur
Copy link
Contributor

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?

@jonsequitur
Copy link
Contributor

@rigofunc
Copy link
Author

rigofunc commented Jul 13, 2017

@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.

@seancpeters
Copy link
Contributor

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.

@kamranayub
Copy link

kamranayub commented Aug 15, 2017

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.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants