Allow custom help providers (#1259)

Allow custom help providers

* Version option will show in help even with a default command

* Reserve `-v` and `--version` as special Spectre.Console command line arguments (nb. breaking change for Spectre.Console users who have a default command with a settings class that uses either of these switches).

* Help writer correctly determines if trailing commands exist and whether to display them as optional or mandatory in the usage statement.

* Ability to control the number of indirect commands to display in the help text when the command itself doesn't have any examples of its own. Defaults to 5 (for backward compatibility) but can be set to any integer or zero to disable completely.

* Significant increase in unit test coverage for the help writer.

* Minor grammatical improvements to website documentation.
This commit is contained in:
Frank Ray
2023-09-08 08:51:33 +01:00
committed by GitHub
parent 813a53cdfa
commit 131b37fff8
70 changed files with 1646 additions and 330 deletions

View File

@ -0,0 +1,34 @@
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli.Tests.Data.Help;
internal class CustomHelpProvider : HelpProvider
{
private readonly string version;
public CustomHelpProvider(ICommandAppSettings settings, string version)
: base(settings)
{
this.version = version;
}
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo command)
{
return new IRenderable[]
{
new Text("--------------------------------------"), Text.NewLine,
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
new Text("--------------------------------------"), Text.NewLine,
Text.NewLine,
};
}
public override IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo command)
{
return new IRenderable[]
{
Text.NewLine,
new Text($"Version {version}"),
};
}
}

View File

@ -0,0 +1,21 @@
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli.Tests.Data.Help;
internal class RedirectHelpProvider : IHelpProvider
{
public virtual IEnumerable<IRenderable> Write(ICommandModel model)
{
return Write(model, null);
}
#nullable enable
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
#nullable disable
{
return new[]
{
new Text("Help has moved online. Please see: http://www.example.com"),
Text.NewLine,
};
}
}

View File

@ -1,5 +0,0 @@
namespace Spectre.Console.Tests.Data;
public sealed class EmptySettings : CommandSettings
{
}

View File

@ -15,7 +15,7 @@ public sealed class OptionalArgumentWithPropertyInitializerSettings : CommandSet
[CommandOption("-c")]
public int Count { get; set; } = 1;
[CommandOption("-v")]
[CommandOption("--value")]
public int Value { get; set; } = 0;
}

View File

@ -9,4 +9,5 @@ ARGUMENTS:
[QUX]
OPTIONS:
-h, --help Prints help information
-h, --help Prints help information
-v, --version Prints version information

View File

@ -0,0 +1,18 @@
DESCRIPTION:
Contains settings for a cat.
USAGE:
myapp cat [LEGS] [OPTIONS] <COMMAND>
ARGUMENTS:
[LEGS] The number of legs
OPTIONS:
DEFAULT
-h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
COMMANDS:
lion <TEETH> The lion command

View File

@ -0,0 +1,11 @@
USAGE:
myapp branch [GREETING] [OPTIONS] [COMMAND]
ARGUMENTS:
[GREETING]
OPTIONS:
-h, --help Prints help information
COMMANDS:
greeter

View File

@ -0,0 +1,30 @@
DESCRIPTION:
The animal command.
USAGE:
myapp animal [LEGS] [OPTIONS] <COMMAND>
EXAMPLES:
myapp animal dog --name Rufus --age 12 --good-boy
myapp animal dog --name Luna
myapp animal dog --name Charlie
myapp animal dog --name Bella
myapp animal dog --name Daisy
myapp animal dog --name Milo
myapp animal horse --name Brutus
myapp animal horse --name Sugar --IsAlive false
myapp animal horse --name Cash
myapp animal horse --name Dakota
myapp animal horse --name Cisco
myapp animal horse --name Spirit
ARGUMENTS:
[LEGS] The number of legs
OPTIONS:
-h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive
COMMANDS:
dog <AGE> The dog command
horse The horse command

View File

@ -1,19 +0,0 @@
DESCRIPTION:
The animal command.
USAGE:
myapp animal [LEGS] [OPTIONS] <COMMAND>
EXAMPLES:
myapp animal --help
ARGUMENTS:
[LEGS] The number of legs
OPTIONS:
-h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive
COMMANDS:
dog <AGE> The dog command
horse The horse command

View File

@ -0,0 +1,15 @@
--------------------------------------
--- CUSTOM HELP PROVIDER ---
--------------------------------------
USAGE:
myapp [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
Version 1.0

View File

@ -0,0 +1 @@
Help has moved online. Please see: http://www.example.com

View File

@ -0,0 +1,15 @@
--------------------------------------
--- CUSTOM HELP PROVIDER ---
--------------------------------------
USAGE:
myapp [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
Version 1.0

View File

@ -0,0 +1 @@
Help has moved online. Please see: http://www.example.com

View File

@ -1,4 +1,4 @@
DESCRIPTION:
DESCRIPTION:
The lion command.
USAGE:
@ -10,7 +10,8 @@ ARGUMENTS:
OPTIONS:
DEFAULT
-h, --help Prints help information
-h, --help Prints help information
-v, --version Prints version information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100

View File

@ -1,21 +0,0 @@
DESCRIPTION:
The lion command.
USAGE:
myapp <TEETH> [LEGS] [OPTIONS]
EXAMPLES:
myapp 12 -c 3
ARGUMENTS:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPTIONS:
DEFAULT
-h, --help Prints help information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting

View File

@ -0,0 +1,15 @@
--------------------------------------
--- CUSTOM HELP PROVIDER ---
--------------------------------------
USAGE:
myapp [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
Version 1.0

View File

@ -0,0 +1,24 @@
DESCRIPTION:
The dog command.
USAGE:
myapp <AGE> [LEGS] [OPTIONS]
EXAMPLES:
myapp --name Rufus --age 12 --good-boy
myapp --name Luna
myapp --name Charlie
myapp --name Bella
myapp --name Daisy
myapp --name Milo
ARGUMENTS:
<AGE>
[LEGS] The number of legs
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
-g, --good-boy

View File

@ -1,4 +1,4 @@
DESCRIPTION:
DESCRIPTION:
The lion command.
USAGE:
@ -10,7 +10,8 @@ ARGUMENTS:
OPTIONS:
DEFAULT
-h, --help Prints help information
-h, --help Prints help information
-v, --version Prints version information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100

View File

@ -1,8 +1,8 @@
DESCRIPTION:
DESCRIPTION:
The lion command.
USAGE:
myapp <TEETH> [LEGS] [OPTIONS]
myapp <TEETH> [LEGS] [OPTIONS] [COMMAND]
ARGUMENTS:
<TEETH> The number of teeth the lion has
@ -10,7 +10,8 @@ ARGUMENTS:
OPTIONS:
DEFAULT
-h, --help Prints help information
-h, --help Prints help information
-v, --version Prints version information
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100

View File

@ -5,5 +5,6 @@ ARGUMENTS:
<FOO> Dummy argument FOO
OPTIONS:
-h, --help Prints help information
--baz Dummy option BAZ
-h, --help Prints help information
-v, --version Prints version information
--baz Dummy option BAZ

View File

@ -0,0 +1,24 @@
USAGE:
myapp [OPTIONS] <COMMAND>
EXAMPLES:
myapp dog --name Rufus --age 12 --good-boy
myapp dog --name Luna
myapp dog --name Charlie
myapp dog --name Bella
myapp dog --name Daisy
myapp dog --name Milo
myapp horse --name Brutus
myapp horse --name Sugar --IsAlive false
myapp horse --name Cash
myapp horse --name Dakota
myapp horse --name Cisco
myapp horse --name Spirit
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
horse The horse command

View File

@ -3,7 +3,10 @@ USAGE:
EXAMPLES:
myapp dog --name Rufus --age 12 --good-boy
myapp horse --name Brutus
myapp dog --name Luna
myapp dog --name Charlie
myapp dog --name Bella
myapp dog --name Daisy
OPTIONS:
-h, --help Prints help information

View File

@ -0,0 +1,20 @@
USAGE:
myapp [OPTIONS] <COMMAND>
EXAMPLES:
myapp dog --name Rufus --age 12 --good-boy
myapp dog --name Luna
myapp dog --name Charlie
myapp dog --name Bella
myapp dog --name Daisy
myapp dog --name Milo
myapp horse --name Brutus
myapp horse --name Sugar --IsAlive false
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
horse The horse command

View File

@ -1,14 +1,10 @@
USAGE:
myapp [OPTIONS] <COMMAND>
EXAMPLES:
myapp dog --name Rufus --age 12 --good-boy
myapp horse --name Brutus
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
USAGE:
myapp [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
horse The horse command

View File

@ -0,0 +1,24 @@
USAGE:
myapp [OPTIONS] <COMMAND>
EXAMPLES:
myapp dog --name Rufus --age 12 --good-boy
myapp dog --name Luna
myapp dog --name Charlie
myapp dog --name Bella
myapp dog --name Daisy
myapp dog --name Milo
myapp horse --name Brutus
myapp horse --name Sugar --IsAlive false
myapp horse --name Cash
myapp horse --name Dakota
myapp horse --name Cisco
myapp horse --name Spirit
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
dog <AGE> The dog command
horse The horse command

View File

@ -3,7 +3,10 @@ USAGE:
EXAMPLES:
myapp animal dog --name Rufus --age 12 --good-boy
myapp animal horse --name Brutus
myapp animal dog --name Luna
myapp animal dog --name Charlie
myapp animal dog --name Bella
myapp animal dog --name Daisy
OPTIONS:
-h, --help Prints help information

View File

@ -0,0 +1,19 @@
USAGE:
myapp [OPTIONS] <COMMAND>
EXAMPLES:
myapp animal dog --name Rufus --age 12 --good-boy
myapp animal dog --name Luna
myapp animal dog --name Charlie
myapp animal dog --name Bella
myapp animal dog --name Daisy
myapp animal dog --name Milo
myapp animal horse --name Brutus
myapp animal horse --name Sugar --IsAlive false
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
animal The animal command

View File

@ -0,0 +1,9 @@
USAGE:
myapp [OPTIONS] <COMMAND>
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
animal The animal command

View File

@ -0,0 +1,23 @@
USAGE:
myapp [OPTIONS] <COMMAND>
EXAMPLES:
myapp animal dog --name Rufus --age 12 --good-boy
myapp animal dog --name Luna
myapp animal dog --name Charlie
myapp animal dog --name Bella
myapp animal dog --name Daisy
myapp animal dog --name Milo
myapp animal horse --name Brutus
myapp animal horse --name Sugar --IsAlive false
myapp animal horse --name Cash
myapp animal horse --name Dakota
myapp animal horse --name Cisco
myapp animal horse --name Spirit
OPTIONS:
-h, --help Prints help information
-v, --version Prints version information
COMMANDS:
animal The animal command

View File

@ -7,7 +7,8 @@ global using System.Linq;
global using System.Runtime.CompilerServices;
global using System.Threading.Tasks;
global using Shouldly;
global using Spectre.Console.Cli;
global using Spectre.Console.Cli;
global using Spectre.Console.Cli.Help;
global using Spectre.Console.Cli.Unsafe;
global using Spectre.Console.Testing;
global using Spectre.Console.Tests.Data;

View File

@ -34,7 +34,7 @@
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
<DependentUpon>%(ParentFile).cs</DependentUpon>
</None>
<None Update="Expectations\Help\Greeter_Default.Output.verified.txt">
<None Update="Expectations\Help\Default_Greeter.Output.verified.txt">
<ParentFile>$([System.String]::Copy('%(FileName)').Split('.')[0])</ParentFile>
<DependentUpon>%(ParentFile).cs</DependentUpon>
</None>

View File

@ -1,3 +1,5 @@
using Spectre.Console.Cli.Tests.Data.Help;
namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
@ -75,8 +77,8 @@ public sealed partial class CommandAppTests
}
[Fact]
[Expectation("Command")]
public Task Should_Output_Command_Correctly()
[Expectation("Branch")]
public Task Should_Output_Branch_Correctly()
{
// Given
var fixture = new CommandAppTester();
@ -91,7 +93,53 @@ public sealed partial class CommandAppTests
});
// When
var result = fixture.Run("cat", "--help");
var result = fixture.Run("cat", "--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Branch_Called_Without_Help")]
public Task Should_Output_Branch_When_Called_Without_Help_Option()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<CatSettings>("cat", animal =>
{
animal.SetDescription("Contains settings for a cat.");
animal.AddCommand<LionCommand>("lion");
});
});
// When
var result = fixture.Run("cat");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Branch_Default_Greeter")]
public Task Should_Output_Branch_With_Default_Correctly()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<OptionalArgumentWithDefaultValueSettings>("branch", animal =>
{
animal.SetDefaultCommand<GreeterCommand>();
animal.AddCommand<GreeterCommand>("greeter");
});
});
// When
var result = fixture.Run("branch", "--help");
// Then
return Verifier.Verify(result.Output);
@ -138,7 +186,7 @@ public sealed partial class CommandAppTests
});
// When
var result = fixture.Run("cat", "lion", "--help");
var result = fixture.Run("cat", "lion", "--help");
// Then
return Verifier.Verify(result.Output);
@ -203,7 +251,7 @@ public sealed partial class CommandAppTests
}
[Fact]
[Expectation("Greeter_Default")]
[Expectation("Default_Greeter")]
public Task Should_Not_Output_Default_Command_When_Command_Has_No_Required_Parameters_And_Is_Called_Without_Args()
{
// Given
@ -219,19 +267,131 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Custom_Help_Registered_By_Instance")]
public Task Should_Output_Custom_Help_When_Registered_By_Instance()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Create the custom help provider
var helpProvider = new CustomHelpProvider(configurator.Settings, "1.0");
// Register the custom help provider instance
registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Custom_Help_Registered_By_Type")]
public Task Should_Output_Custom_Help_When_Registered_By_Type()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Register the custom help provider type
registrar.Register(typeof(IHelpProvider), typeof(RedirectHelpProvider));
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Custom_Help_Configured_By_Instance")]
public Task Should_Output_Custom_Help_When_Configured_By_Instance()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Configure the custom help provider instance
configurator.SetHelpProvider(new CustomHelpProvider(configurator.Settings, "1.0"));
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Custom_Help_Configured_By_Type")]
public Task Should_Output_Custom_Help_When_Configured_By_Type()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Configure the custom help provider type
configurator.SetHelpProvider<RedirectHelpProvider>();
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("RootExamples")]
public Task Should_Output_Root_Examples_Defined_On_Root()
[Expectation("Root_Examples")]
public Task Should_Output_Examples_Defined_On_Root()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("horse", "--name", "Brutus");
// All root examples should be shown
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("dog", "--name", "Luna");
configurator.AddExample("dog", "--name", "Charlie");
configurator.AddExample("dog", "--name", "Bella");
configurator.AddExample("dog", "--name", "Daisy");
configurator.AddExample("dog", "--name", "Milo");
configurator.AddExample("horse", "--name", "Brutus");
configurator.AddExample("horse", "--name", "Sugar", "--IsAlive", "false");
configurator.AddExample("horse", "--name", "Cash");
configurator.AddExample("horse", "--name", "Dakota");
configurator.AddExample("horse", "--name", "Cisco");
configurator.AddExample("horse", "--name", "Spirit");
configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse");
});
@ -241,21 +401,147 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("RootExamples_Children")]
public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples()
[Expectation("Root_Examples_Children")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")]
public Task Should_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.SetApplicationName("myapp");
// It should be capped to the first 5 examples by default
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus");
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_Eight")]
public Task Should_Output_Eight_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Show the first 8 examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 8;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_Twelve")]
public Task Should_Output_All_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Show all examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_None")]
public Task Should_Not_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Do not show examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 0;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
@ -266,8 +552,9 @@ public sealed partial class CommandAppTests
}
[Fact]
[Expectation("RootExamples_Leafs")]
public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
[Expectation("Root_Examples_Leafs")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")]
public Task Should_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
var fixture = new CommandAppTester();
@ -276,11 +563,148 @@ public sealed partial class CommandAppTests
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
animal.SetDescription("The animal command.");
// It should be capped to the first 5 examples by default
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus");
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Leafs_Eight")]
public Task Should_Output_Eight_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// Show the first 8 examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 8;
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Leafs_Twelve")]
public Task Should_Output_All_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// Show all examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Leafs_None")]
public Task Should_Not_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// Do not show examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 0;
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
@ -292,18 +716,31 @@ public sealed partial class CommandAppTests
}
[Fact]
[Expectation("CommandExamples")]
public Task Should_Only_Output_Command_Examples_Defined_On_Command()
[Expectation("Branch_Examples")]
public Task Should_Output_Examples_Defined_On_Branch()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
animal.AddExample(new[] { "animal", "--help" });
animal.SetDescription("The animal command.");
// All branch examples should be shown
animal.AddExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
animal.AddExample("animal", "dog", "--name", "Luna");
animal.AddExample("animal", "dog", "--name", "Charlie");
animal.AddExample("animal", "dog", "--name", "Bella");
animal.AddExample("animal", "dog", "--name", "Daisy");
animal.AddExample("animal", "dog", "--name", "Milo");
animal.AddExample("animal", "horse", "--name", "Brutus");
animal.AddExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false");
animal.AddExample("animal", "horse", "--name", "Cash");
animal.AddExample("animal", "horse", "--name", "Dakota");
animal.AddExample("animal", "horse", "--name", "Cisco");
animal.AddExample("animal", "horse", "--name", "Spirit");
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
@ -317,19 +754,26 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("DefaultExamples")]
public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified()
[Expectation("Default_Examples")]
public Task Should_Output_Examples_Defined_On_Root_If_Default_Command_Is_Specified()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<LionCommand>();
fixture.SetDefaultCommand<DogCommand>();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.AddExample("12", "-c", "3");
// All root examples should be shown
configurator.AddExample("--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("--name", "Luna");
configurator.AddExample("--name", "Charlie");
configurator.AddExample("--name", "Bella");
configurator.AddExample("--name", "Daisy");
configurator.AddExample("--name", "Milo");
});
// When

View File

@ -5,27 +5,92 @@ public sealed partial class CommandAppTests
public sealed class Version
{
[Fact]
public void Should_Output_The_Version_To_The_Console()
public void Should_Output_CLI_Version_To_The_Console()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddBranch<MammalSettings>("mammal", mammal =>
{
mammal.AddCommand<DogCommand>("dog");
mammal.AddCommand<HorseCommand>("horse");
});
});
});
// When
var result = fixture.Run(Constants.VersionCommand);
// Then
result.Output.ShouldStartWith("Spectre.Cli version ");
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_No_Command()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
});
// When
var result = fixture.Run("--version");
// Then
result.Output.ShouldBe("1.0");
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_Command()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
configurator.AddCommand<EmptyCommand>("empty");
});
// When
var result = fixture.Run("empty", "--version");
// Then
result.Output.ShouldBe("1.0");
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_Default_Command()
{
// Given
var fixture = new CommandAppTester();
fixture.SetDefaultCommand<EmptyCommand>();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
});
// When
var result = fixture.Run("--version");
// Then
result.Output.ShouldBe("1.0");
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_Branch_Default_Command()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
configurator.AddBranch<EmptyCommandSettings>("branch", branch =>
{
branch.SetDefaultCommand<EmptyCommand>();
});
});
// When
var result = fixture.Run("--version");
// Then
result.Output.ShouldBe("1.0");
}
}
}

View File

@ -362,7 +362,7 @@ public sealed partial class CommandAppTests
});
// When
var result = app.Run("-c", "0", "-v", "50", "ABBA", "Herreys");
var result = app.Run("-c", "0", "--value", "50", "ABBA", "Herreys");
// Then
result.ExitCode.ShouldBe(0);