From 5b898dcd48870e8ca51acede9b2f144c41703f98 Mon Sep 17 00:00:00 2001 From: rmcrackan Date: Mon, 4 May 2026 13:20:58 -0400 Subject: [PATCH] #1732 - fix: `--help`, `-h`, `/?`, `/h`, `/help` --- Source/LibationCli/Program.cs | 108 +++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 3 deletions(-) diff --git a/Source/LibationCli/Program.cs b/Source/LibationCli/Program.cs index 03ff3776..444d530c 100644 --- a/Source/LibationCli/Program.cs +++ b/Source/LibationCli/Program.cs @@ -7,6 +7,20 @@ using System.Threading.Tasks; namespace LibationCli; +file static class GlobalCliHelp +{ + internal static bool IsGlobalHelpToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) + return false; + return token.Equals("--help", StringComparison.OrdinalIgnoreCase) + || token.Equals("-h", StringComparison.OrdinalIgnoreCase) + || token.Equals("/?", StringComparison.OrdinalIgnoreCase) + || token.Equals("/h", StringComparison.OrdinalIgnoreCase) + || token.Equals("/help", StringComparison.OrdinalIgnoreCase); + } +} + public enum ExitCode { ProcessCompletedSuccessfully = 0, @@ -42,6 +56,11 @@ class Program var setBreakPointHere = args; #endif + if (TryPrintGlobalHelpOnly(args)) + return; + + args = NormalizeVerbShortHelpAliases(args); + var result = new Parser(ConfigureParser).ParseArguments(args, VerbTypes); if (result.Value is HelpVerb helper) @@ -67,9 +86,25 @@ class Program private static void HandleErrors(ParserResult result) { var errorsList = result.Errors.ToList(); - if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError))) + + if (errorsList.Any(e => e.Tag == ErrorType.HelpRequestedError)) { - Environment.ExitCode = (int)ExitCode.NonRunNonError; + Environment.ExitCode = (int)ExitCode.ProcessCompletedSuccessfully; + WriteVerbOptionsHelp(result); + return; + } + + if (errorsList.OfType().FirstOrDefault() is { } helpVerbErr) + { + Environment.ExitCode = (int)ExitCode.ProcessCompletedSuccessfully; + WriteHelpForVerbRequestedError(helpVerbErr); + return; + } + + if (errorsList.Any(e => e.Tag == ErrorType.VersionRequestedError)) + { + Environment.ExitCode = (int)ExitCode.ProcessCompletedSuccessfully; + Console.Error.WriteLine(HelpVerb.CreateHelpText().Heading); return; } @@ -100,10 +135,77 @@ class Program Console.Error.WriteLine(helpText); } + /// + /// Multi-verb parsing treats the first token as a verb name, so bare --help / -h must be handled here. + /// + private static bool TryPrintGlobalHelpOnly(string[] args) + { + if (args is not { Length: 1 } || !GlobalCliHelp.IsGlobalHelpToken(args[0])) + return false; + + WriteGlobalVerbListHelp(); + Environment.ExitCode = (int)ExitCode.ProcessCompletedSuccessfully; + return true; + } + + /// + /// CommandLineParser's implicit help is --help only; map the first -h after the verb (case-insensitive, so -H too) to --help. + /// + private static string[] NormalizeVerbShortHelpAliases(string[] args) + { + if (args.Length < 2) + return args; + + var copy = (string[])args.Clone(); + for (var i = 1; i < copy.Length; i++) + { + if (copy[i].Equals("-h", StringComparison.OrdinalIgnoreCase)) + { + copy[i] = "--help"; + break; + } + } + + return copy; + } + + private static void WriteGlobalVerbListHelp(string? preOptionsLine = null) + { + var helpText = HelpVerb.CreateHelpText(); + if (preOptionsLine is not null) + helpText.AddPreOptionsLine(preOptionsLine); + helpText.AddVerbs(VerbTypes); + Console.Error.WriteLine(helpText); + } + + private static void WriteVerbOptionsHelp(ParserResult result) + { + var helpText = HelpVerb.CreateHelpText(); + helpText.AddDashesToOption = true; + helpText.AutoHelp = true; + helpText.AddOptions(result); + Console.Error.WriteLine(helpText); + } + + private static void WriteHelpForVerbRequestedError(HelpVerbRequestedError helpVerbErr) + { + if (!helpVerbErr.Matched || helpVerbErr.Type is null || string.IsNullOrWhiteSpace(helpVerbErr.Verb)) + { + WriteGlobalVerbListHelp(); + return; + } + + var subResult = new Parser(ConfigureParser).ParseArguments(new[] { helpVerbErr.Verb }, VerbTypes); + if (subResult.TypeInfo.Current != typeof(NullInstance)) + WriteVerbOptionsHelp(subResult); + else + WriteGlobalVerbListHelp(); + } + private static void ConfigureParser(ParserSettings settings) { settings.AllowMultiInstance = true; settings.AutoVersion = false; - settings.AutoHelp = false; + settings.AutoHelp = true; } }