bilal-fazlani / commanddotnet

A modern framework for building modern CLI apps
https://commanddotnet.bilal-fazlani.com
MIT License
575 stars 29 forks source link

Invoking 'help' in code #240

Closed asymetrixs closed 4 years ago

asymetrixs commented 4 years ago

Hi guys,

is there a way to invoke help in code? Let's say someone runs app com1 subcom2 and I am already in a method public void subcom2(string para1) then but a parameter is missing or does not fulfill certain requirements. How can I display the help from method subcom2 as if someone would have run app com1 subcom2 --help?

Thanks

drewburlingame commented 4 years ago

in v3, you can do this.

public void subcom2(CommandContext ctx, string para1)
{
    ...
    var cmd = ctx.ParseResult.TargetCommand;
    var helpText = ctx.AppConfig.HelpProvider.GetHelpText(cmd);
    ctx.Console.Out.WriteLine(helpText);
    ...
}

I've wondered about other ways to make this available. Were there any ways you looked for to do this? I'm wondering what would have been intuitive.

Some of the approaches I've toyed with...

  1. add ShowHelp property to CommandContext. Setting this to true would trigger the show help middleware. The downside is that a command method will have to reference CommandContext in the method. Not the end of the world.
  2. add ShowHelp() method to CommadContext. Calling this would immediately print the default help. It would basically wrap the solution I gave above.
  3. add an ex.ShowHelp() ext method that will add a key to ex.Data that the help middleware can check. Any thrown exception can trigger the help.
asymetrixs commented 4 years ago

Thanks, I was looking for exactly this, but yes, maybe a friendlier approach. I think I would prefer either solution 2 or 3, maybe 3 even with a throw new ShowHelpException();. For the 2nd solution I would have to hand the CommandContext down to sub-methods in order to call the Help from there (not sure if I would do that, but let's just assume I do), whereas for the throw new ShowHelpException() I could just throw it in the underlying methods and it would go all the way up to where it can be caught by the library.

drewburlingame commented 4 years ago

I can see ShowHelpException() or ShowHelpException(messageToShowBeforeHelp) being convenient. I've avoided it because it encourages the exceptions as control flow anti-pattern. But perhaps that's too strong of an opinion for the framework to enforce. It's certainly simple and sometimes a console app should just be simple.

I'd just like to make sure another simple option also exists at the same time so we're not pushing devs toward anti-patterns if that makes sense.

2 & 3 are easy enough. I can add those as well as ShowHelpException.

drewburlingame commented 4 years ago

I'm just feeling very dirty trying to implement the exception. I keep imagining my devs using it and it just doesn't fit with any patterns because we avoid that anti-pattern. I even mentioned the anti-pattern in the xml comments.

Can you give me more context in the types of underlying methods that would throw the exception? What are these methods doing? Do they contain validation logic? Are you looking for a general rule of, when there is a validation error, show the error and then show help? Perhaps there's a different config we need?

Another thing I can do is add the CommandContext to all exceptions that escape AppRunner and then you can get the context from the command and print help yourself. You could use any number of exceptions this way. Thoughts?

try
{
    appRunner.Run(args);
}
catch(ShowHelpException ex)
{
    ex.GetCommandContext()?.PrintHelp();
}
asymetrixs commented 4 years ago

The problem I have is that sometimes parameters of a method cannot be used alone or only alone. Let's say configtool network --list has to be alone but configtool network --nic ens192 --ipaddr W.X.Y.Z has to be used together. --nic alone or --ipaddr alone don't make any sense. This has to be checked and if the user uses wrong combinations, then I would like to print the help (maybe with a leading custom text to point out what the problem is). Or sometimes I have a command configtool network that without parameters starts a wizard, sometimes it does nothing (but still calls my method of course without giving me the option to print the help again to show the user how the command is supposed to be used).

So, in the method network I can make the checks (no need to perform them any deeper) so that a pure command to print the help is sufficient, actually. There is not really a need for the exception-based solutions.

drewburlingame commented 4 years ago

context.ShowHelpOnExit = true available in v3.6.0

release notes