Refactor a bit

This commit is contained in:
Alexey Golub
2020-05-05 22:23:27 +03:00
parent c58629e999
commit cbb72b16ae
7 changed files with 82 additions and 23 deletions

View File

@@ -3,11 +3,16 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests namespace CliFx.Tests
{ {
public partial class ErrorReportingSpecs public partial class ErrorReportingSpecs
{ {
private readonly ITestOutputHelper _output;
public ErrorReportingSpecs(ITestOutputHelper output) => _output = output;
[Fact] [Fact]
public async Task Command_may_throw_a_generic_exception_which_exits_and_prints_error_message_and_stack_trace() public async Task Command_may_throw_a_generic_exception_which_exits_and_prints_error_message_and_stack_trace()
{ {
@@ -33,6 +38,8 @@ namespace CliFx.Tests
"System.Exception:", "System.Exception:",
"Kaput", "at", "Kaput", "at",
"CliFx.Tests"); "CliFx.Tests");
_output.WriteLine(stdErrData);
} }
[Fact] [Fact]
@@ -57,6 +64,8 @@ namespace CliFx.Tests
// Assert // Assert
exitCode.Should().Be(69); exitCode.Should().Be(69);
stdErrData.Should().Be("Kaput"); stdErrData.Should().Be("Kaput");
_output.WriteLine(stdErrData);
} }
[Fact] [Fact]
@@ -81,6 +90,8 @@ namespace CliFx.Tests
// Assert // Assert
exitCode.Should().NotBe(0); exitCode.Should().NotBe(0);
stdErrData.Should().NotBeEmpty(); stdErrData.Should().NotBeEmpty();
_output.WriteLine(stdErrData);
} }
[Fact] [Fact]
@@ -99,7 +110,7 @@ namespace CliFx.Tests
.Build(); .Build();
// Act // Act
await application.RunAsync(new[] { "exc" }); await application.RunAsync(new[] {"exc"});
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd(); var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
var stdErrData = console.Output.Encoding.GetString(stdErr.ToArray()).TrimEnd(); var stdErrData = console.Output.Encoding.GetString(stdErr.ToArray()).TrimEnd();
@@ -114,6 +125,9 @@ namespace CliFx.Tests
"sub", "sub",
"You can run", "to show help on a specific command." "You can run", "to show help on a specific command."
); );
_output.WriteLine(stdOutData);
_output.WriteLine(stdErrData);
} }
[Fact] [Fact]
@@ -131,7 +145,7 @@ namespace CliFx.Tests
.Build(); .Build();
// Act // Act
await application.RunAsync(new[] { "exc" }); await application.RunAsync(new[] {"exc"});
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd(); var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd();
var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd(); var stdOutData = console.Output.Encoding.GetString(stdOut.ToArray()).TrimEnd();
@@ -146,6 +160,9 @@ namespace CliFx.Tests
"sub", "sub",
"You can run", "to show help on a specific command." "You can run", "to show help on a specific command."
); );
_output.WriteLine(stdOutData);
_output.WriteLine(stdErrData);
} }
[Fact] [Fact]
@@ -162,7 +179,7 @@ namespace CliFx.Tests
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] { "exc", "-m", "Kaput" }, new[] {"exc", "-m", "Kaput"},
new Dictionary<string, string>()); new Dictionary<string, string>());
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd(); var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd();
@@ -173,6 +190,8 @@ namespace CliFx.Tests
"System.Exception:", "System.Exception:",
"Kaput", "at", "Kaput", "at",
"CliFx.Tests"); "CliFx.Tests");
_output.WriteLine(stdErrData);
} }
[Fact] [Fact]
@@ -190,7 +209,7 @@ namespace CliFx.Tests
// Act // Act
var exitCode = await application.RunAsync( var exitCode = await application.RunAsync(
new[] { "not-a-valid-command", "-r", "foo" }, new[] {"not-a-valid-command", "-r", "foo"},
new Dictionary<string, string>()); new Dictionary<string, string>());
var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd(); var stdErrData = console.Error.Encoding.GetString(stdErr.ToArray()).TrimEnd();
@@ -200,7 +219,8 @@ namespace CliFx.Tests
exitCode.Should().NotBe(0); exitCode.Should().NotBe(0);
stdErrData.Should().ContainAll( stdErrData.Should().ContainAll(
"Can't find a command that matches the following arguments:", "Can't find a command that matches the following arguments:",
"not-a-valid-command"); "not-a-valid-command"
);
stdOutData.Should().ContainAll( stdOutData.Should().ContainAll(
"Usage", "Usage",
"[command]", "[command]",
@@ -208,7 +228,11 @@ namespace CliFx.Tests
"-h|--help", "Shows help text.", "-h|--help", "Shows help text.",
"Commands", "Commands",
"inv", "inv",
"You can run", "to show help on a specific command."); "You can run", "to show help on a specific command."
);
_output.WriteLine(stdOutData);
_output.WriteLine(stdErrData);
} }
} }
} }

View File

@@ -2,11 +2,16 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
using Xunit.Abstractions;
namespace CliFx.Tests namespace CliFx.Tests
{ {
public partial class HelpTextSpecs public partial class HelpTextSpecs
{ {
private readonly ITestOutputHelper _output;
public HelpTextSpecs(ITestOutputHelper output) => _output = output;
[Fact] [Fact]
public async Task Version_information_can_be_requested_by_providing_the_version_option_without_other_arguments() public async Task Version_information_can_be_requested_by_providing_the_version_option_without_other_arguments()
{ {
@@ -29,6 +34,8 @@ namespace CliFx.Tests
// Assert // Assert
exitCode.Should().Be(0); exitCode.Should().Be(0);
stdOutData.Should().Be("v6.9"); stdOutData.Should().Be("v6.9");
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -68,6 +75,8 @@ namespace CliFx.Tests
"cmd", "NamedCommand description.", "cmd", "NamedCommand description.",
"You can run", "to show help on a specific command." "You can run", "to show help on a specific command."
); );
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -104,6 +113,8 @@ namespace CliFx.Tests
"sub", "SubCommand description.", "sub", "SubCommand description.",
"You can run", "to show help on a specific command." "You can run", "to show help on a specific command."
); );
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -137,6 +148,8 @@ namespace CliFx.Tests
"-e|--option-e", "OptionE description.", "-e|--option-e", "OptionE description.",
"-h|--help", "Shows help text." "-h|--help", "Shows help text."
); );
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -167,6 +180,8 @@ namespace CliFx.Tests
"cmd", "NamedCommand description.", "cmd", "NamedCommand description.",
"You can run", "to show help on a specific command." "You can run", "to show help on a specific command."
); );
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -190,6 +205,8 @@ namespace CliFx.Tests
"Usage", "Usage",
"cmd-with-params", "<first>", "<parameterb>", "<third list...>", "[options]" "cmd-with-params", "<first>", "<parameterb>", "<third list...>", "[options]"
); );
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -217,6 +234,8 @@ namespace CliFx.Tests
"* -g|--option-g", "* -g|--option-g",
"-h|--option-h" "-h|--option-h"
); );
_output.WriteLine(stdOutData);
} }
[Fact] [Fact]
@@ -241,6 +260,8 @@ namespace CliFx.Tests
"* -a|--option-a", "Environment variable:", "ENV_OPT_A", "* -a|--option-a", "Environment variable:", "ENV_OPT_A",
"-b|--option-b", "Environment variable:", "ENV_OPT_B" "-b|--option-b", "Environment variable:", "ENV_OPT_B"
); );
_output.WriteLine(stdOutData);
} }
} }
} }

View File

@@ -51,7 +51,6 @@
<ProjectReference Include="..\CliFx.Analyzers\CliFx.Analyzers.csproj" ReferenceOutputAssembly="true" IncludeAssets="CliFx.Analyzers.dll" /> <ProjectReference Include="..\CliFx.Analyzers\CliFx.Analyzers.csproj" ReferenceOutputAssembly="true" IncludeAssets="CliFx.Analyzers.dll" />
</ItemGroup> </ItemGroup>
<Target Name="CopyAnalyzerToPackage"> <Target Name="CopyAnalyzerToPackage">
<ItemGroup> <ItemGroup>
<TfmSpecificPackageFile Include="$(OutDir)/CliFx.Analyzers.dll" PackagePath="analyzers/dotnet/cs" BuildAction="none" /> <TfmSpecificPackageFile Include="$(OutDir)/CliFx.Analyzers.dll" PackagePath="analyzers/dotnet/cs" BuildAction="none" />

View File

@@ -39,10 +39,14 @@ namespace CliFx.Domain
!string.IsNullOrWhiteSpace(Name) && !string.IsNullOrWhiteSpace(Name) &&
string.Equals(Name, name, StringComparison.OrdinalIgnoreCase); string.Equals(Name, name, StringComparison.OrdinalIgnoreCase);
public bool MatchesShortName(char shortName) => public bool MatchesShortName(char? shortName) =>
ShortName != null && ShortName != null &&
ShortName == shortName; ShortName == shortName;
public bool MatchesNameOrShortName(string? name, char? shortName) =>
MatchesName(name) ||
MatchesShortName(shortName);
public bool MatchesNameOrShortName(string alias) => public bool MatchesNameOrShortName(string alias) =>
MatchesName(alias) || MatchesName(alias) ||
alias.Length == 1 && MatchesShortName(alias.Single()); alias.Length == 1 && MatchesShortName(alias.Single());

View File

@@ -40,6 +40,21 @@ namespace CliFx.Domain
public bool MatchesName(string? name) => string.Equals(name, Name, StringComparison.OrdinalIgnoreCase); public bool MatchesName(string? name) => string.Equals(name, Name, StringComparison.OrdinalIgnoreCase);
public IReadOnlyList<CommandOptionSchema> GetBuiltInOptions()
{
var result = new List<CommandOptionSchema>(2);
var helpOption = CommandOptionSchema.HelpOption;
var versionOption = CommandOptionSchema.VersionOption;
result.Add(helpOption);
if (IsDefault)
result.Add(versionOption);
return result;
}
private void InjectParameters(ICommand command, IReadOnlyList<CommandUnboundArgumentInput> parameterInputs) private void InjectParameters(ICommand command, IReadOnlyList<CommandUnboundArgumentInput> parameterInputs)
{ {
// All inputs must be bound // All inputs must be bound

View File

@@ -212,12 +212,8 @@ namespace CliFx.Domain
var options = command.Options var options = command.Options
.OrderByDescending(o => o.IsRequired) .OrderByDescending(o => o.IsRequired)
.ToList(); .Concat(command.GetBuiltInOptions())
.ToArray();
// Add built-in options
options.Add(CommandOptionSchema.HelpOption);
if (command.IsDefault)
options.Add(CommandOptionSchema.VersionOption);
foreach (var option in options) foreach (var option in options)
{ {

View File

@@ -217,7 +217,7 @@ If that's not possible, consider converting the parameter into an option, to avo
{ {
var message = $@" var message = $@"
Command '{command.Type.FullName}' is invalid because it contains one or more options without a name: Command '{command.Type.FullName}' is invalid because it contains one or more options without a name:
{string.Join(Environment.NewLine, invalidOptions.Select(p => p.Property.Name))} {string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
Options must have either a name or a short name or both, because that's what identifies them. Options must have either a name or a short name or both, because that's what identifies them.
@@ -232,7 +232,7 @@ To fix this, ensure all options have their names or short names set to some valu
{ {
var message = $@" var message = $@"
Command '{command.Type.FullName}' is invalid because it contains one or more options whose names are too short: Command '{command.Type.FullName}' is invalid because it contains one or more options whose names are too short:
{string.Join(Environment.NewLine, invalidOptions.Select(p => $"{p.Property.Name} ('{p.DisplayName}')"))} {string.Join(Environment.NewLine, invalidOptions.Select(o => $"{o.Property.Name} ('{o.DisplayName}')"))}
Option names must be at least 2 characters long to avoid confusion with short names. Option names must be at least 2 characters long to avoid confusion with short names.
If you intended to set the short name instead, use the corresponding attribute overload. If you intended to set the short name instead, use the corresponding attribute overload.
@@ -249,7 +249,7 @@ To fix this, ensure all option names are at least 2 characters long.";
{ {
var message = $@" var message = $@"
Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same name ('{name}'): Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same name ('{name}'):
{string.Join(Environment.NewLine, invalidOptions.Select(p => p.Property.Name))} {string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
Options must have unique names, because that's what identifies them. Options must have unique names, because that's what identifies them.
Names are not case-sensitive. Names are not case-sensitive.
@@ -266,7 +266,7 @@ To fix this, ensure that all options have different names.";
{ {
var message = $@" var message = $@"
Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same short name ('{shortName}'): Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same short name ('{shortName}'):
{string.Join(Environment.NewLine, invalidOptions.Select(p => p.Property.Name))} {string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
Options must have unique short names, because that's what identifies them. Options must have unique short names, because that's what identifies them.
Short names are case-sensitive (i.e. 'a' and 'A' are different short names). Short names are case-sensitive (i.e. 'a' and 'A' are different short names).
@@ -283,7 +283,7 @@ To fix this, ensure that all options have different short names.";
{ {
var message = $@" var message = $@"
Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same fallback environment variable name ('{environmentVariableName}'): Command '{command.Type.FullName}' is invalid because it contains {invalidOptions.Count} options with the same fallback environment variable name ('{environmentVariableName}'):
{string.Join(Environment.NewLine, invalidOptions.Select(p => p.Property.Name))} {string.Join(Environment.NewLine, invalidOptions.Select(o => o.Property.Name))}
Options cannot share the same environment variable as a fallback. Options cannot share the same environment variable as a fallback.
Environment variable names are not case-sensitive. Environment variable names are not case-sensitive.