diff --git a/src/Spectre.Console.Tests/Data/Settings/ArgumentOrderSettings.cs b/src/Spectre.Console.Tests/Data/Settings/ArgumentOrderSettings.cs new file mode 100644 index 0000000..829f87f --- /dev/null +++ b/src/Spectre.Console.Tests/Data/Settings/ArgumentOrderSettings.cs @@ -0,0 +1,22 @@ +using Spectre.Console.Cli; + +namespace Spectre.Console.Tests.Data +{ + public sealed class ArgumentOrderSettings : CommandSettings + { + [CommandArgument(0, "[QUX]")] + public int Qux { get; set; } + + [CommandArgument(3, "")] + public int Corgi { get; set; } + + [CommandArgument(1, "")] + public int Bar { get; set; } + + [CommandArgument(2, "")] + public int Baz { get; set; } + + [CommandArgument(0, "")] + public int Foo { get; set; } + } +} diff --git a/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_List_Arguments_In_Correct_Order.verified.txt b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_List_Arguments_In_Correct_Order.verified.txt new file mode 100644 index 0000000..31c0da3 --- /dev/null +++ b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_List_Arguments_In_Correct_Order.verified.txt @@ -0,0 +1,12 @@ +USAGE: + myapp [QUX] [OPTIONS] + +ARGUMENTS: + + + + + [QUX] + +OPTIONS: + -h, --help Prints help information \ No newline at end of file diff --git a/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Default_Command_Correctly.verified.txt b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Default_Command_Correctly.verified.txt index dae4ce6..f34969b 100644 --- a/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Default_Command_Correctly.verified.txt +++ b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Default_Command_Correctly.verified.txt @@ -2,8 +2,8 @@ USAGE: myapp [LEGS] [OPTIONS] ARGUMENTS: - [LEGS] The number of legs The number of teeth the lion has + [LEGS] The number of legs OPTIONS: -h, --help Prints help information diff --git a/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Root_Examples_If_Default_Command_Is_Specified.verified.txt b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Root_Examples_If_Default_Command_Is_Specified.verified.txt index 6725d51..725c159 100644 --- a/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Root_Examples_If_Default_Command_Is_Specified.verified.txt +++ b/src/Spectre.Console.Tests/Expectations/CommandAppTests.Help.Help.Should_Output_Root_Examples_If_Default_Command_Is_Specified.verified.txt @@ -5,8 +5,8 @@ EXAMPLES: myapp 12 -c 3 ARGUMENTS: - [LEGS] The number of legs The number of teeth the lion has + [LEGS] The number of legs OPTIONS: -h, --help Prints help information diff --git a/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs b/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs index 2bf5269..351ffde 100644 --- a/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs +++ b/src/Spectre.Console.Tests/Unit/Cli/CommandAppTests.Help.cs @@ -244,6 +244,24 @@ namespace Spectre.Console.Tests.Unit.Cli // Then return Verifier.Verify(output); } + + [Fact] + public Task Should_List_Arguments_In_Correct_Order() + { + // Given + var fixture = new CommandAppFixture(); + fixture.WithDefaultCommand>(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var (_, output, _, _) = fixture.Run("--help"); + + // Then + return Verifier.Verify(output); + } } } } diff --git a/src/Spectre.Console/Cli/Internal/HelpWriter.cs b/src/Spectre.Console/Cli/Internal/HelpWriter.cs index 72b2f30..f3f5f23 100644 --- a/src/Spectre.Console/Cli/Internal/HelpWriter.cs +++ b/src/Spectre.Console/Cli/Internal/HelpWriter.cs @@ -11,12 +11,14 @@ namespace Spectre.Console.Cli.Internal private sealed class HelpArgument { public string Name { get; } + public int Position { get; set; } public bool Required { get; } public string? Description { get; } - public HelpArgument(string name, bool required, string? description) + public HelpArgument(string name, int position, bool required, string? description) { Name = name; + Position = position; Required = required; Description = description; } @@ -25,7 +27,7 @@ namespace Spectre.Console.Cli.Internal { var arguments = new List(); arguments.AddRange(command?.Parameters?.OfType()?.Select( - x => new HelpArgument(x.Value, x.Required, x.Description)) + x => new HelpArgument(x.Value, x.Position, x.Required, x.Description)) ?? Array.Empty()); return arguments; } @@ -94,63 +96,61 @@ namespace Spectre.Console.Cli.Internal composer.Style("yellow", "USAGE:").LineBreak(); composer.Tab().Text(model.GetApplicationName()); - var parameters = new Stack(); + var parameters = new List(); if (command == null) { - parameters.Push("[aqua][/]"); - parameters.Push("[grey][[OPTIONS]][/]"); + parameters.Add("[grey][[OPTIONS]][/]"); + parameters.Add("[aqua][/]"); } else { - var current = command; - if (command.IsBranch) - { - parameters.Push("[aqua][/]"); - } - - while (current != null) + foreach (var current in command.Flatten()) { var isCurrent = current == command; - if (isCurrent) - { - parameters.Push("[grey][[OPTIONS]][/]"); - } - - if (current.Parameters.OfType().Any()) - { - var optionalArguments = current.Parameters.OfType().Where(x => !x.Required).ToArray(); - if (optionalArguments.Length > 0 || !isCurrent) - { - foreach (var optionalArgument in optionalArguments) - { - parameters.Push($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]"); - } - } - - if (isCurrent) - { - foreach (var argument in current.Parameters.OfType() - .Where(a => a.Required).OrderBy(a => a.Position).ToArray()) - { - parameters.Push($"[aqua]<{argument.Value.EscapeMarkup()}>[/]"); - } - } - } if (!current.IsDefaultCommand) { if (isCurrent) { - parameters.Push($"[underline]{current.Name.EscapeMarkup()}[/]"); + parameters.Add($"[underline]{current.Name.EscapeMarkup()}[/]"); } else { - parameters.Push($"{current.Name.EscapeMarkup()}"); + parameters.Add($"{current.Name.EscapeMarkup()}"); } } - current = current.Parent; + if (current.Parameters.OfType().Any()) + { + if (isCurrent) + { + foreach (var argument in current.Parameters.OfType() + .Where(a => a.Required).OrderBy(a => a.Position).ToArray()) + { + parameters.Add($"[aqua]<{argument.Value.EscapeMarkup()}>[/]"); + } + } + + var optionalArguments = current.Parameters.OfType().Where(x => !x.Required).ToArray(); + if (optionalArguments.Length > 0 || !isCurrent) + { + foreach (var optionalArgument in optionalArguments) + { + parameters.Add($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]"); + } + } + } + + if (isCurrent) + { + parameters.Add("[grey][[OPTIONS]][/]"); + } + } + + if (command.IsBranch) + { + parameters.Add("[aqua][/]"); } } @@ -238,20 +238,18 @@ namespace Spectre.Console.Cli.Internal grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true }); grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) }); - foreach (var argument in arguments) + foreach (var argument in arguments.Where(x => x.Required).OrderBy(x => x.Position)) { - if (argument.Required) - { - grid.AddRow( - $"[silver]<{argument.Name.EscapeMarkup()}>[/]", - argument.Description?.TrimEnd('.') ?? " "); - } - else - { - grid.AddRow( - $"[grey][[{argument.Name.EscapeMarkup()}]][/]", - argument.Description?.TrimEnd('.') ?? " "); - } + grid.AddRow( + $"[silver]<{argument.Name.EscapeMarkup()}>[/]", + argument.Description?.TrimEnd('.') ?? " "); + } + + foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position)) + { + grid.AddRow( + $"[grey][[{argument.Name.EscapeMarkup()}]][/]", + argument.Description?.TrimEnd('.') ?? " "); } grid.AddEmptyRow(); diff --git a/src/Spectre.Console/Cli/Internal/Modelling/CommandInfo.cs b/src/Spectre.Console/Cli/Internal/Modelling/CommandInfo.cs index 2ea39d0..ad5160d 100644 --- a/src/Spectre.Console/Cli/Internal/Modelling/CommandInfo.cs +++ b/src/Spectre.Console/Cli/Internal/Modelling/CommandInfo.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; namespace Spectre.Console.Cli.Internal @@ -51,5 +52,19 @@ namespace Spectre.Console.Cli.Internal } } } + + public List Flatten() + { + var result = new Stack(); + + var current = this; + while (current != null) + { + result.Push(current); + current = current.Parent; + } + + return result.ToList(); + } } }