This commit is contained in:
Tyrrrz
2022-01-04 22:31:50 +02:00
parent f6ef6cd4c0
commit 33ec2eb3a0
6 changed files with 80 additions and 61 deletions

View File

@@ -223,56 +223,6 @@ public class NamedChildCommand : ICommand
stdErr.Should().NotBeNullOrWhiteSpace();
}
// Regression test for #117
[Fact]
public async Task Help_text_lists_parameters_in_specified_order()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
public abstract class CommandBase : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public abstract ValueTask ExecuteAsync(IConsole console);
}
[Command]
public class Command : CommandBase
{
[CommandParameter(1)]
public string Bar { get; set; }
[CommandParameter(2)]
public IReadOnlyList<string> Baz { get; set; }
public override ValueTask ExecuteAsync(IConsole console) => default;
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] { "--help" },
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ContainAllInOrder(
"USAGE",
"<foo>", "<bar>", "<baz...>"
);
}
[Fact]
public async Task Help_text_shows_application_metadata()
{
@@ -421,6 +371,57 @@ public class Command : ICommand
);
}
// https://github.com/Tyrrrz/CliFx/issues/117
[Fact]
public async Task Help_text_shows_usage_format_which_lists_all_parameters_in_specified_order()
{
// Arrange
var commandType = DynamicCommandBuilder.Compile(
// language=cs
@"
// Base members appear last in reflection order
public abstract class CommandBase : ICommand
{
[CommandParameter(0)]
public string Foo { get; set; }
public abstract ValueTask ExecuteAsync(IConsole console);
}
[Command]
public class Command : CommandBase
{
[CommandParameter(2)]
public IReadOnlyList<string> Baz { get; set; }
[CommandParameter(1)]
public string Bar { get; set; }
public override ValueTask ExecuteAsync(IConsole console) => default;
}
");
var application = new CliApplicationBuilder()
.AddCommand(commandType)
.UseConsole(FakeConsole)
.Build();
// Act
var exitCode = await application.RunAsync(
new[] { "--help" },
new Dictionary<string, string>()
);
var stdOut = FakeConsole.ReadOutputString();
// Assert
exitCode.Should().Be(0);
stdOut.Should().ContainAllInOrder(
"USAGE",
"<foo>", "<bar>", "<baz...>"
);
}
[Fact]
public async Task Help_text_shows_usage_format_which_lists_all_required_options()
{

View File

@@ -29,7 +29,7 @@ public abstract class BindingValidator<T> : IBindingValidator
/// You can use the utility methods <see cref="Ok"/> and <see cref="Error"/> to
/// create an appropriate result.
/// </remarks>
public abstract BindingValidationError? Validate(T value);
public abstract BindingValidationError? Validate(T? value);
BindingValidationError? IBindingValidator.Validate(object? value) => Validate((T) value!);
BindingValidationError? IBindingValidator.Validate(object? value) => Validate((T?) value);
}

View File

@@ -72,7 +72,7 @@ internal class HelpConsoleFormatter : ConsoleFormatter
Write(' ');
// Parameters
foreach (var parameter in _context.CommandSchema.Parameters)
foreach (var parameter in _context.CommandSchema.Parameters.OrderBy(p => p.Order))
{
Write(ConsoleColor.DarkCyan, parameter.Property.IsScalar()
? $"<{parameter.Name}>"

View File

@@ -16,8 +16,8 @@ internal partial class ApplicationSchema
public IReadOnlyList<string> GetCommandNames() => Commands
.Select(c => c.Name)
.Where(n => !string.IsNullOrWhiteSpace(n))
.ToArray()!;
.WhereNotNullOrWhiteSpace()
.ToArray();
public CommandSchema? TryFindDefaultCommand() =>
Commands.FirstOrDefault(c => c.IsDefault);

View File

@@ -89,13 +89,12 @@ internal partial class CommandSchema
var parameterSchemas = type.GetProperties()
.Select(ParameterSchema.TryResolve)
.Where(p => p is not null)
.OrderBy(p => p!.Order)
.WhereNotNull()
.ToArray();
var optionSchemas = type.GetProperties()
.Select(OptionSchema.TryResolve)
.Where(o => o is not null)
.WhereNotNull()
.Concat(implicitOptionSchemas)
.ToArray();
@@ -103,8 +102,8 @@ internal partial class CommandSchema
type,
name,
description,
parameterSchemas!,
optionSchemas!
parameterSchemas,
optionSchemas
);
}

View File

@@ -6,6 +6,25 @@ namespace CliFx.Utils.Extensions;
internal static class CollectionExtensions
{
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source)
where T : class
{
foreach (var i in source)
{
if (i is not null)
yield return i;
}
}
public static IEnumerable<string> WhereNotNullOrWhiteSpace(this IEnumerable<string?> source)
{
foreach (var i in source)
{
if (!string.IsNullOrWhiteSpace(i))
yield return i;
}
}
public static void RemoveRange<T>(this ICollection<T> source, IEnumerable<T> items)
{
foreach (var item in items)
@@ -17,5 +36,5 @@ internal static class CollectionExtensions
IEqualityComparer<TKey> comparer) =>
dictionary
.Cast<DictionaryEntry>()
.ToDictionary(entry => (TKey) entry.Key, entry => (TValue) entry.Value, comparer)!;
.ToDictionary(entry => (TKey) entry.Key, entry => (TValue) entry.Value, comparer);
}