spectreconsole / spectre.console

A .NET library that makes it easier to create beautiful console applications.
https://spectreconsole.net
MIT License
8.9k stars 454 forks source link

Add public API to display help manually by user code #1555

Open psulek opened 1 month ago

psulek commented 1 month ago

Is your feature request related to a problem? Please describe. When there is need to manually display help, there is no public API to do so, even when its there in private/internal code.

I have situation when 1 command option is required to be entered by user. Lets say arg --source is required, and user runs app with app.exe --test where there is not defined option --test.

So program only writes Error: Missing required argument 'source'. By now i want to manually display help by calling some public API.

Describe the solution you'd like Maybe HelpProvider can have some static method Render(App app) where app will be my instance of my Cli app. For example:

var app = new CommandApp<MyCmd>();
var exitCode = app.Run(args);
if (exitCode != 0) {
   HelpProvider.Render(app);
}

or maybe App class directly: if (app.Run(args) !=0 ) { app.RenderHelp(); }

Describe alternatives you've considered I have found alternative way via reflection, which is ugly and not production ready:

public void RenderHelp(Spectre.Console.Cli.ICommandApp app)
{
        var _app = app.GetType().InvokeMember("_app", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, app, []);
        var configurator = _app.GetType().InvokeMember("_configurator", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField, null, _app, []);
        var cfgSettings = configurator.GetType().InvokeMember("Settings",
            BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty, null, configurator, []);

        var cmdModelBuilderType = Type.GetType("Spectre.Console.Cli.CommandModelBuilder,Spectre.Console.Cli");
        var cmdModel = cmdModelBuilderType.InvokeMember("Build", BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod, null, null,
            [configurator]);

        var defCommand = cmdModel.GetType().InvokeMember("DefaultCommand", BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty, null, cmdModel, []);

        var helpProvider = new HelpProvider(cfgSettings as ICommandAppSettings);
        var helpLines = helpProvider.Write(cmdModel as ICommandModel, defCommand as ICommandInfo);

        foreach (var line in helpLines)
        {
            AnsiConsole.Write(line);
        }
}

Additional context


Please upvote :+1: this issue if you are interested in it.

psulek commented 1 month ago

And now when i testing this reflection solution i got wrong help text when using branches :-(