mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 21:38:16 +08:00
Move Spectre.Console.Cli to it's own package
This commit is contained in:

committed by
Patrik Svensson

parent
b600832e00
commit
36ca22ffac
@ -0,0 +1,88 @@
|
||||
namespace Spectre.Console.Tests.Unit.Cli.Annotations;
|
||||
|
||||
[ExpectationPath("Arguments")]
|
||||
public sealed partial class CommandArgumentAttributeTests
|
||||
{
|
||||
[UsesVerify]
|
||||
public sealed class ArgumentCannotContainOptions
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "--foo <BAR>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("ArgumentCannotContainOptions")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class MultipleValuesAreNotSupported
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<FOO> <BAR>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("MultipleValuesAreNotSupported")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class ValuesMustHaveName
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("ValuesMustHaveName")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
return Verifier.Verify(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Fixture
|
||||
{
|
||||
public static string Run<TSettings>(params string[] args)
|
||||
where TSettings : CommandSettings
|
||||
{
|
||||
using (var writer = new TestConsole())
|
||||
{
|
||||
var app = new CommandApp();
|
||||
app.Configure(c => c.ConfigureConsole(writer));
|
||||
app.Configure(c => c.AddCommand<GenericCommand<TSettings>>("foo"));
|
||||
app.Run(args);
|
||||
|
||||
return writer.Output
|
||||
.NormalizeLineEndings()
|
||||
.TrimLines()
|
||||
.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
namespace Spectre.Console.Tests.Unit.Cli.Annotations;
|
||||
|
||||
public sealed partial class CommandArgumentAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Not_Contain_Options()
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => new CommandArgumentAttribute(0, "--foo <BAR>"));
|
||||
|
||||
// Then
|
||||
result.ShouldNotBe(null);
|
||||
result.ShouldBeOfType<CommandTemplateException>().And(exception =>
|
||||
exception.Message.ShouldBe("Arguments can not contain options."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("<FOO> <BAR>")]
|
||||
[InlineData("[FOO] [BAR]")]
|
||||
[InlineData("[FOO] <BAR>")]
|
||||
[InlineData("<FOO> [BAR]")]
|
||||
public void Should_Not_Contain_Multiple_Value_Names(string template)
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => new CommandArgumentAttribute(0, template));
|
||||
|
||||
// Then
|
||||
result.ShouldNotBe(null);
|
||||
result.ShouldBeOfType<CommandTemplateException>().And(exception =>
|
||||
exception.Message.ShouldBe("Multiple values are not supported."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("<>")]
|
||||
[InlineData("[]")]
|
||||
public void Should_Not_Contain_Empty_Value_Name(string template)
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => new CommandArgumentAttribute(0, template));
|
||||
|
||||
// Then
|
||||
result.ShouldNotBe(null);
|
||||
result.ShouldBeOfType<CommandTemplateException>().And(exception =>
|
||||
exception.Message.ShouldBe("Values without name are not allowed."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("<FOO>", true)]
|
||||
[InlineData("[FOO]", false)]
|
||||
public void Should_Parse_Valid_Options(string template, bool required)
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandArgumentAttribute(0, template);
|
||||
|
||||
// Then
|
||||
result.ValueName.ShouldBe("FOO");
|
||||
result.IsRequired.ShouldBe(required);
|
||||
}
|
||||
}
|
@ -0,0 +1,240 @@
|
||||
namespace Spectre.Console.Tests.Unit.Cli.Annotations;
|
||||
|
||||
[ExpectationPath("Arguments")]
|
||||
public sealed partial class CommandOptionAttributeTests
|
||||
{
|
||||
[UsesVerify]
|
||||
public sealed class UnexpectedCharacter
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("<FOO> $ <BAR>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("UnexpectedCharacter")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Encountered unexpected character '$'.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class UnterminatedValueName
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--foo|-f <BAR")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("UnterminatedValueName")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Encountered unterminated value name 'BAR'.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class OptionsMustHaveName
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--foo|-")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("OptionsMustHaveName")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Options without name are not allowed.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class OptionNamesCannotStartWithDigit
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--1foo")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("OptionNamesCannotStartWithDigit")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Option names cannot start with a digit.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class InvalidCharacterInOptionName
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--f$oo")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("InvalidCharacterInOptionName")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Encountered invalid character '$' in option name.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class LongOptionMustHaveMoreThanOneCharacter
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--f")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("LongOptionMustHaveMoreThanOneCharacter")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Long option names must consist of more than one character.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class ShortOptionMustOnlyBeOneCharacter
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("--foo|-bar")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("ShortOptionMustOnlyBeOneCharacter")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Short option names can not be longer than one character.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class MultipleOptionValuesAreNotSupported
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("-f|--foo <FOO> <BAR>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("MultipleOptionValuesAreNotSupported")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Multiple option values are not supported.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class InvalidCharacterInValueName
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("-f|--foo <F$OO>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("InvalidCharacterInValueName")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("Encountered invalid character '$' in value name.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
[UsesVerify]
|
||||
public sealed class MissingLongAndShortName
|
||||
{
|
||||
public sealed class Settings : CommandSettings
|
||||
{
|
||||
[CommandOption("<FOO>")]
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Expectation("MissingLongAndShortName")]
|
||||
public Task Should_Return_Correct_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = Fixture.Run<Settings>();
|
||||
|
||||
// Then
|
||||
result.Exception.Message.ShouldBe("No long or short name for option has been specified.");
|
||||
return Verifier.Verify(result.Output);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Fixture
|
||||
{
|
||||
public static CommandAppFailure Run<TSettings>(params string[] args)
|
||||
where TSettings : CommandSettings
|
||||
{
|
||||
var app = new CommandAppTester();
|
||||
app.Configure(c =>
|
||||
{
|
||||
c.AddCommand<GenericCommand<TSettings>>("foo");
|
||||
});
|
||||
|
||||
return app.RunAndCatch<CommandTemplateException>(args);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
namespace Spectre.Console.Tests.Unit.Cli.Annotations;
|
||||
|
||||
public sealed partial class CommandOptionAttributeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Should_Parse_Short_Name_Correctly()
|
||||
{
|
||||
// Given, When
|
||||
var option = new CommandOptionAttribute("-o|--option <VALUE>");
|
||||
|
||||
// Then
|
||||
option.ShortNames.ShouldContain("o");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Long_Name_Correctly()
|
||||
{
|
||||
// Given, When
|
||||
var option = new CommandOptionAttribute("-o|--option <VALUE>");
|
||||
|
||||
// Then
|
||||
option.LongNames.ShouldContain("option");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("<VALUE>")]
|
||||
public void Should_Parse_Value_Correctly(string value)
|
||||
{
|
||||
// Given, When
|
||||
var option = new CommandOptionAttribute($"-o|--option {value}");
|
||||
|
||||
// Then
|
||||
option.ValueName.ShouldBe("VALUE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Only_Short_Name()
|
||||
{
|
||||
// Given, When
|
||||
var option = new CommandOptionAttribute("-o");
|
||||
|
||||
// Then
|
||||
option.ShortNames.ShouldContain("o");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Should_Parse_Only_Long_Name()
|
||||
{
|
||||
// Given, When
|
||||
var option = new CommandOptionAttribute("--option");
|
||||
|
||||
// Then
|
||||
option.LongNames.ShouldContain("option");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("<VALUE>")]
|
||||
public void Should_Throw_If_Template_Is_Empty(string value)
|
||||
{
|
||||
// Given, When
|
||||
var option = Record.Exception(() => new CommandOptionAttribute(value));
|
||||
|
||||
// Then
|
||||
option.ShouldBeOfType<CommandTemplateException>().And(e =>
|
||||
e.Message.ShouldBe("No long or short name for option has been specified."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("--bar|-foo")]
|
||||
[InlineData("--bar|-f-b")]
|
||||
public void Should_Throw_If_Short_Name_Is_Invalid(string value)
|
||||
{
|
||||
// Given, When
|
||||
var option = Record.Exception(() => new CommandOptionAttribute(value));
|
||||
|
||||
// Then
|
||||
option.ShouldBeOfType<CommandTemplateException>().And(e =>
|
||||
e.Message.ShouldBe("Short option names can not be longer than one character."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("--o")]
|
||||
public void Should_Throw_If_Long_Name_Is_Invalid(string value)
|
||||
{
|
||||
// Given, When
|
||||
var option = Record.Exception(() => new CommandOptionAttribute(value));
|
||||
|
||||
// Then
|
||||
option.ShouldBeOfType<CommandTemplateException>().And(e =>
|
||||
e.Message.ShouldBe("Long option names must consist of more than one character."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-")]
|
||||
[InlineData("--")]
|
||||
public void Should_Throw_If_Option_Have_No_Name(string template)
|
||||
{
|
||||
// Given, When
|
||||
var option = Record.Exception(() => new CommandOptionAttribute(template));
|
||||
|
||||
// Then
|
||||
option.ShouldBeOfType<CommandTemplateException>().And(e =>
|
||||
e.Message.ShouldBe("Options without name are not allowed."));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("--foo|-foo[b", '[')]
|
||||
[InlineData("--foo|-f€b", '€')]
|
||||
[InlineData("--foo|-foo@b", '@')]
|
||||
public void Should_Throw_If_Option_Contains_Invalid_Name(string template, char invalid)
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => new CommandOptionAttribute(template));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<CommandTemplateException>().And(e =>
|
||||
{
|
||||
e.Message.ShouldBe($"Encountered invalid character '{invalid}' in option name.");
|
||||
e.Template.ShouldBe(template);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("--foo <HELLO-WORLD>", "HELLO-WORLD")]
|
||||
[InlineData("--foo <HELLO_WORLD>", "HELLO_WORLD")]
|
||||
public void Should_Accept_Dash_And_Underscore_In_Value_Name(string template, string name)
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandOptionAttribute(template);
|
||||
|
||||
// Then
|
||||
result.ValueName.ShouldBe(name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("--foo|-1")]
|
||||
public void Should_Throw_If_First_Letter_Of_An_Option_Name_Is_A_Digit(string template)
|
||||
{
|
||||
// Given, When
|
||||
var result = Record.Exception(() => new CommandOptionAttribute(template));
|
||||
|
||||
// Then
|
||||
result.ShouldBeOfType<CommandTemplateException>().And(e =>
|
||||
{
|
||||
e.Message.ShouldBe("Option names cannot start with a digit.");
|
||||
e.Template.ShouldBe(template);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Multiple_Short_Options_Are_Supported()
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandOptionAttribute("-f|-b");
|
||||
|
||||
// Then
|
||||
result.ShortNames.Count.ShouldBe(2);
|
||||
result.ShortNames.ShouldContain("f");
|
||||
result.ShortNames.ShouldContain("b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Multiple_Long_Options_Are_Supported()
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandOptionAttribute("--foo|--bar");
|
||||
|
||||
// Then
|
||||
result.LongNames.Count.ShouldBe(2);
|
||||
result.LongNames.ShouldContain("foo");
|
||||
result.LongNames.ShouldContain("bar");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("-f|--foo <BAR>")]
|
||||
[InlineData("--foo|-f <BAR>")]
|
||||
[InlineData("<BAR> --foo|-f")]
|
||||
[InlineData("<BAR> -f|--foo")]
|
||||
[InlineData("-f <BAR> --foo")]
|
||||
[InlineData("--foo <BAR> -f")]
|
||||
public void Template_Parts_Can_Appear_In_Any_Order(string template)
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandOptionAttribute(template);
|
||||
|
||||
// Then
|
||||
result.LongNames.ShouldContain("foo");
|
||||
result.ShortNames.ShouldContain("f");
|
||||
result.ValueName.ShouldBe("BAR");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Is_Not_Hidden_From_Help_By_Default()
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandOptionAttribute("--foo");
|
||||
|
||||
// Then
|
||||
result.IsHidden.ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Can_Indicate_That_It_Must_Be_Hidden_From_Help_Text()
|
||||
{
|
||||
// Given, When
|
||||
var result = new CommandOptionAttribute("--foo") { IsHidden = true };
|
||||
|
||||
// Then
|
||||
result.IsHidden.ShouldBeTrue();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user