Show help for default command (#953)

Shows help if the default command was called without any arguments but has required arguments.
This way it does not hinder execution of a default command without any required arguments.

When called without args:
- Default command with required params -> Show help
- Default command with required params and additional commands -> Show help including additional commands
- Default command without required params -> Execute command
This commit is contained in:
krisrok 2022-12-14 18:19:12 +01:00 committed by GitHub
parent b793482ebb
commit 4a8a4ab048
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 449 additions and 319 deletions

View File

@ -66,6 +66,14 @@ internal sealed class CommandExecutor
return leaf.ShowHelp ? 0 : 1; return leaf.ShowHelp ? 0 : 1;
} }
// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command));
return 1;
}
// Register the arguments with the container. // Register the arguments with the container.
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining); _registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);

View File

@ -0,0 +1,17 @@
using Spectre.Console;
public class GreeterCommand : Command<OptionalArgumentWithDefaultValueSettings>
{
private readonly IAnsiConsole _console;
public GreeterCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute([NotNull] CommandContext context, [NotNull] OptionalArgumentWithDefaultValueSettings settings)
{
_console.WriteLine(settings.Greeting);
return 0;
}
}

View File

@ -0,0 +1,16 @@
DESCRIPTION:
The lion command.
USAGE:
myapp <TEETH> [LEGS] [OPTIONS]
ARGUMENTS:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPTIONS:
-h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> The agility between 0 and 100
-c <CHILDREN> The number of children the lion has

View File

@ -0,0 +1,19 @@
DESCRIPTION:
The lion command.
USAGE:
myapp <TEETH> [LEGS] [OPTIONS]
ARGUMENTS:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPTIONS:
-h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
COMMANDS:
giraffe <LENGTH> The giraffe command

View File

@ -29,4 +29,15 @@
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" /> <ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Update="Expectations\Help\Default_Without_Args_Additional.Output.verified.txt">
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
<DependentUpon>%(ParentFile).cs</DependentUpon>
</None>
<None Update="Expectations\Help\Greeter_Default.Output.verified.txt">
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
<DependentUpon>%(ParentFile).cs</DependentUpon>
</None>
</ItemGroup>
</Project> </Project>

View File

@ -1,319 +1,377 @@
using Spectre.Console.Cli; using Spectre.Console.Cli;
namespace Spectre.Console.Tests.Unit.Cli; namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests public sealed partial class CommandAppTests
{ {
[UsesVerify] [UsesVerify]
[ExpectationPath("Help")] [ExpectationPath("Help")]
public class Help public class Help
{ {
[Fact] [Fact]
[Expectation("Root")] [Expectation("Root")]
public Task Should_Output_Root_Correctly() public Task Should_Output_Root_Correctly()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog"); configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse"); configurator.AddCommand<HorseCommand>("horse");
configurator.AddCommand<GiraffeCommand>("giraffe"); configurator.AddCommand<GiraffeCommand>("giraffe");
}); });
// When // When
var result = fixture.Run("--help"); var result = fixture.Run("--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("Hidden_Commands")] [Expectation("Hidden_Commands")]
public Task Should_Skip_Hidden_Commands() public Task Should_Skip_Hidden_Commands()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog"); configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse"); configurator.AddCommand<HorseCommand>("horse");
configurator.AddCommand<GiraffeCommand>("giraffe") configurator.AddCommand<GiraffeCommand>("giraffe")
.WithExample(new[] { "giraffe", "123" }) .WithExample(new[] { "giraffe", "123" })
.IsHidden(); .IsHidden();
}); });
// When // When
var result = fixture.Run("--help"); var result = fixture.Run("--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("Description_No_Trailing_Period")] [Expectation("Description_No_Trailing_Period")]
public Task Should_Not_Trim_Description_Trailing_Period() public Task Should_Not_Trim_Description_Trailing_Period()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog"); configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse"); configurator.AddCommand<HorseCommand>("horse");
configurator.AddCommand<GiraffeCommand>("giraffe") configurator.AddCommand<GiraffeCommand>("giraffe")
.WithExample(new[] { "giraffe", "123" }) .WithExample(new[] { "giraffe", "123" })
.IsHidden(); .IsHidden();
configurator.TrimTrailingPeriods(false); configurator.TrimTrailingPeriods(false);
}); });
// When // When
var result = fixture.Run("--help"); var result = fixture.Run("--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("Command")] [Expectation("Command")]
public Task Should_Output_Command_Correctly() public Task Should_Output_Command_Correctly()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
configurator.AddBranch<CatSettings>("cat", animal => configurator.AddBranch<CatSettings>("cat", animal =>
{ {
animal.SetDescription("Contains settings for a cat."); animal.SetDescription("Contains settings for a cat.");
animal.AddCommand<LionCommand>("lion"); animal.AddCommand<LionCommand>("lion");
}); });
}); });
// When // When
var result = fixture.Run("cat", "--help"); var result = fixture.Run("cat", "--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("Leaf")] [Expectation("Leaf")]
public Task Should_Output_Leaf_Correctly() public Task Should_Output_Leaf_Correctly()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
configurator.AddBranch<CatSettings>("cat", animal => configurator.AddBranch<CatSettings>("cat", animal =>
{ {
animal.SetDescription("Contains settings for a cat."); animal.SetDescription("Contains settings for a cat.");
animal.AddCommand<LionCommand>("lion"); animal.AddCommand<LionCommand>("lion");
}); });
}); });
// When // When
var result = fixture.Run("cat", "lion", "--help"); var result = fixture.Run("cat", "lion", "--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("Default")] [Expectation("Default")]
public Task Should_Output_Default_Command_Correctly() public Task Should_Output_Default_Command_Correctly()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.SetDefaultCommand<LionCommand>(); fixture.SetDefaultCommand<LionCommand>();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
}); });
// When // When
var result = fixture.Run("--help"); var result = fixture.Run("--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("RootExamples")] [Expectation("Default_Without_Args")]
public Task Should_Output_Root_Examples_Defined_On_Root() public Task Should_Output_Default_Command_When_Command_Has_Required_Parameters_And_Is_Called_Without_Args()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.Configure(configurator => fixture.SetDefaultCommand<LionCommand>();
{ fixture.Configure(configurator =>
configurator.SetApplicationName("myapp"); {
configurator.AddExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); configurator.SetApplicationName("myapp");
configurator.AddExample(new[] { "horse", "--name", "Brutus" }); });
configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse"); // When
}); var result = fixture.Run();
// When // Then
var result = fixture.Run("--help"); return Verifier.Verify(result.Output);
}
// Then
return Verifier.Verify(result.Output); [Fact]
} [Expectation("Default_Without_Args_Additional")]
public Task Should_Output_Default_Command_And_Additional_Commands_When_Default_Command_Has_Required_Parameters_And_Is_Called_Without_Args()
[Fact] {
[Expectation("RootExamples_Children")] // Given
public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples() var fixture = new CommandAppTester();
{ fixture.SetDefaultCommand<LionCommand>();
// Given fixture.Configure(configurator =>
var fixture = new CommandAppTester(); {
fixture.Configure(configurator => configurator.SetApplicationName("myapp");
{ configurator.AddCommand<GiraffeCommand>("giraffe");
configurator.SetApplicationName("myapp"); });
configurator.AddCommand<DogCommand>("dog")
.WithExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); // When
configurator.AddCommand<HorseCommand>("horse") var result = fixture.Run();
.WithExample(new[] { "horse", "--name", "Brutus" });
}); // Then
return Verifier.Verify(result.Output);
// When }
var result = fixture.Run("--help");
[Fact]
// Then [Expectation("Greeter_Default")]
return Verifier.Verify(result.Output); public Task Should_Not_Output_Default_Command_When_Command_Has_No_Required_Parameters_And_Is_Called_Without_Args()
} {
// Given
[Fact] var fixture = new CommandAppTester();
[Expectation("RootExamples_Leafs")] fixture.SetDefaultCommand<GreeterCommand>();
public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() fixture.Configure(configurator =>
{ {
// Given configurator.SetApplicationName("myapp");
var fixture = new CommandAppTester(); });
fixture.Configure(configurator =>
{ // When
configurator.SetApplicationName("myapp"); var result = fixture.Run();
configurator.AddBranch<AnimalSettings>("animal", animal =>
{ // Then
animal.SetDescription("The animal command."); return Verifier.Verify(result.Output);
animal.AddCommand<DogCommand>("dog") }
.WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
animal.AddCommand<HorseCommand>("horse") [Fact]
.WithExample(new[] { "animal", "horse", "--name", "Brutus" }); [Expectation("RootExamples")]
}); public Task Should_Output_Root_Examples_Defined_On_Root()
}); {
// Given
// When var fixture = new CommandAppTester();
var result = fixture.Run("--help"); fixture.Configure(configurator =>
{
// Then configurator.SetApplicationName("myapp");
return Verifier.Verify(result.Output); configurator.AddExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
} configurator.AddExample(new[] { "horse", "--name", "Brutus" });
configurator.AddCommand<DogCommand>("dog");
[Fact] configurator.AddCommand<HorseCommand>("horse");
[Expectation("CommandExamples")] });
public Task Should_Only_Output_Command_Examples_Defined_On_Command()
{ // When
// Given var result = fixture.Run("--help");
var fixture = new CommandAppTester();
fixture.Configure(configurator => // Then
{ return Verifier.Verify(result.Output);
configurator.SetApplicationName("myapp"); }
configurator.AddBranch<AnimalSettings>("animal", animal =>
{ [Fact]
animal.SetDescription("The animal command."); [Expectation("RootExamples_Children")]
animal.AddExample(new[] { "animal", "--help" }); public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples()
{
animal.AddCommand<DogCommand>("dog") // Given
.WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); var fixture = new CommandAppTester();
animal.AddCommand<HorseCommand>("horse") fixture.Configure(configurator =>
.WithExample(new[] { "animal", "horse", "--name", "Brutus" }); {
}); configurator.SetApplicationName("myapp");
}); configurator.AddCommand<DogCommand>("dog")
.WithExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
// When configurator.AddCommand<HorseCommand>("horse")
var result = fixture.Run("animal", "--help"); .WithExample(new[] { "horse", "--name", "Brutus" });
});
// Then
return Verifier.Verify(result.Output); // When
} var result = fixture.Run("--help");
[Fact] // Then
[Expectation("DefaultExamples")] return Verifier.Verify(result.Output);
public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified() }
{
// Given [Fact]
var fixture = new CommandAppTester(); [Expectation("RootExamples_Leafs")]
fixture.SetDefaultCommand<LionCommand>(); public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
fixture.Configure(configurator => {
{ // Given
configurator.SetApplicationName("myapp"); var fixture = new CommandAppTester();
configurator.AddExample(new[] { "12", "-c", "3" }); fixture.Configure(configurator =>
}); {
configurator.SetApplicationName("myapp");
// When configurator.AddBranch<AnimalSettings>("animal", animal =>
var result = fixture.Run("--help"); {
animal.SetDescription("The animal command.");
// Then animal.AddCommand<DogCommand>("dog")
return Verifier.Verify(result.Output); .WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
} animal.AddCommand<HorseCommand>("horse")
.WithExample(new[] { "animal", "horse", "--name", "Brutus" });
[Fact] });
[Expectation("NoDescription")] });
public Task Should_Not_Show_Truncated_Command_Table_If_Commands_Are_Missing_Description()
{ // When
// Given var result = fixture.Run("--help");
var fixture = new CommandAppTester();
fixture.Configure(configurator => // Then
{ return Verifier.Verify(result.Output);
configurator.SetApplicationName("myapp"); }
configurator.AddCommand<NoDescriptionCommand>("bar");
}); [Fact]
[Expectation("CommandExamples")]
// When public Task Should_Only_Output_Command_Examples_Defined_On_Command()
var result = fixture.Run("--help"); {
// Given
// Then var fixture = new CommandAppTester();
return Verifier.Verify(result.Output); fixture.Configure(configurator =>
} {
configurator.SetApplicationName("myapp");
[Fact] configurator.AddBranch<AnimalSettings>("animal", animal =>
[Expectation("ArgumentOrder")] {
public Task Should_List_Arguments_In_Correct_Order() animal.SetDescription("The animal command.");
{ animal.AddExample(new[] { "animal", "--help" });
// Given
var fixture = new CommandAppTester(); animal.AddCommand<DogCommand>("dog")
fixture.SetDefaultCommand<GenericCommand<ArgumentOrderSettings>>(); .WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
fixture.Configure(configurator => animal.AddCommand<HorseCommand>("horse")
{ .WithExample(new[] { "animal", "horse", "--name", "Brutus" });
configurator.SetApplicationName("myapp"); });
}); });
// When // When
var result = fixture.Run("--help"); var result = fixture.Run("animal", "--help");
// Then // Then
return Verifier.Verify(result.Output); return Verifier.Verify(result.Output);
} }
[Fact] [Fact]
[Expectation("Hidden_Command_Options")] [Expectation("DefaultExamples")]
public Task Should_Not_Show_Hidden_Command_Options() public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified()
{ {
// Given // Given
var fixture = new CommandAppTester(); var fixture = new CommandAppTester();
fixture.SetDefaultCommand<GenericCommand<HiddenOptionSettings>>(); fixture.SetDefaultCommand<LionCommand>();
fixture.Configure(configurator => fixture.Configure(configurator =>
{ {
configurator.SetApplicationName("myapp"); configurator.SetApplicationName("myapp");
}); configurator.AddExample(new[] { "12", "-c", "3" });
});
// When
var result = fixture.Run("--help"); // When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output); // Then
} return Verifier.Verify(result.Output);
} }
}
[Fact]
[Expectation("NoDescription")]
public Task Should_Not_Show_Truncated_Command_Table_If_Commands_Are_Missing_Description()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddCommand<NoDescriptionCommand>("bar");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("ArgumentOrder")]
public Task Should_List_Arguments_In_Correct_Order()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<GenericCommand<ArgumentOrderSettings>>();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Hidden_Command_Options")]
public Task Should_Not_Show_Hidden_Command_Options()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<GenericCommand<HiddenOptionSettings>>();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
}
}