diff --git a/src/docfx/Extensions/DocfxHelpProvider.cs b/src/docfx/Extensions/DocfxHelpProvider.cs new file mode 100644 index 00000000000..5064bcf3727 --- /dev/null +++ b/src/docfx/Extensions/DocfxHelpProvider.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Spectre.Console; +using Spectre.Console.Cli; +using Spectre.Console.Cli.Help; +using Spectre.Console.Rendering; + +#nullable enable + +namespace Docfx; + +internal sealed class DocfxHelpProvider : HelpProvider +{ + private readonly UsageStyle? _usageStyle; + + public DocfxHelpProvider(ICommandAppSettings settings) + : base(settings) + { + _usageStyle = settings.HelpProviderStyles?.Usage; + } + + public override IEnumerable GetUsage(ICommandModel model, ICommandInfo? command) + { + if (command is not { IsDefaultCommand: true, Parent: null } + || !model.Commands.Any(command => !command.IsHidden && !command.IsDefaultCommand)) + { + return base.GetUsage(model, command); + } + + var usage = new Paragraph() + .Append("USAGE:", _usageStyle?.Header) + .Append("\n ") + .Append(model.ApplicationName) + .Append(" [COMMAND]", _usageStyle?.Command); + + foreach (var argument in command.Parameters.OfType().Where(argument => argument.IsRequired).OrderBy(argument => argument.Position)) + { + usage.Append($" <{argument.Value}>", _usageStyle?.RequiredArgument); + } + + foreach (var argument in command.Parameters.OfType().Where(argument => !argument.IsRequired).OrderBy(argument => argument.Position)) + { + usage.Append($" [{argument.Value}]", _usageStyle?.OptionalArgument); + } + + usage.Append(" [OPTIONS]\n", _usageStyle?.Options); + return [usage]; + } +} diff --git a/src/docfx/Program.cs b/src/docfx/Program.cs index b71bb354f17..5b457ff1276 100644 --- a/src/docfx/Program.cs +++ b/src/docfx/Program.cs @@ -12,6 +12,11 @@ namespace Docfx; internal class Program { internal static int Main(string[] args) + { + return Run(args); + } + + internal static int Run(string[] args, IAnsiConsole? console = null) { var app = new CommandApp(); @@ -19,9 +24,13 @@ internal static int Main(string[] args) app.Configure(config => { config.SetApplicationName("docfx"); + config.SetHelpProvider(new DocfxHelpProvider(config.Settings)); config.UseStrictParsing(); config.SetExceptionHandler(OnException); + if (console is not null) + config.ConfigureConsole(console); + config.AddCommand("init"); config.AddCommand("build"); config.AddCommand("metadata"); diff --git a/test/docfx.Tests/CommandLineTest.cs b/test/docfx.Tests/CommandLineTest.cs index 66abb6b1288..2e30793d605 100644 --- a/test/docfx.Tests/CommandLineTest.cs +++ b/test/docfx.Tests/CommandLineTest.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using Docfx.Common; +using Spectre.Console; namespace Docfx.Tests; @@ -30,6 +31,24 @@ public static void PrintsHelp() Assert.Equal(0, Program.Main(["template", "--help"])); } + [Fact] + public static void PrintsRootUsageInCommandFirstOrder() + { + using var writer = new StringWriter(); + var console = AnsiConsole.Create(new() + { + Ansi = AnsiSupport.No, + ColorSystem = ColorSystemSupport.NoColors, + Out = new AnsiConsoleOutput(writer), + }); + + Assert.Equal(0, Program.Run(["-?"], console)); + + var output = writer.ToString(); + Assert.Contains("docfx [COMMAND] [config] [OPTIONS]", output); + Assert.DoesNotContain("docfx [config] [OPTIONS] [COMMAND]", output); + } + [Fact] public static void FailForUnknownArgs() {