From 105dc88ccd7b2a7985d1975c47ac900ac67eb281 Mon Sep 17 00:00:00 2001 From: Alexey Golub Date: Sun, 18 Aug 2019 17:54:14 +0300 Subject: [PATCH] Try to standardize built-in command options Also remove '-?' as a valid alias for help --- CliFx.Tests/CliApplicationTests.cs | 31 ------- CliFx/Models/CommandOptionSchema.cs | 16 +++- CliFx/Models/Extensions.cs | 125 ++++++++++++++-------------- CliFx/Services/HelpTextRenderer.cs | 31 ++++--- 4 files changed, 93 insertions(+), 110 deletions(-) diff --git a/CliFx.Tests/CliApplicationTests.cs b/CliFx.Tests/CliApplicationTests.cs index 0759b10..f06db9b 100644 --- a/CliFx.Tests/CliApplicationTests.cs +++ b/CliFx.Tests/CliApplicationTests.cs @@ -52,12 +52,6 @@ namespace CliFx.Tests new[] {"command", "-h"} ); - yield return new TestCaseData( - new[] {typeof(TestNamedCommand)}, - new[] {"command", "-?"} - ); - - // Default command is defined yield return new TestCaseData( @@ -80,11 +74,6 @@ namespace CliFx.Tests new[] {"-h"} ); - yield return new TestCaseData( - new[] {typeof(TestDefaultCommand)}, - new[] {"-?"} - ); - // Default command is not defined yield return new TestCaseData( @@ -107,11 +96,6 @@ namespace CliFx.Tests new[] {"-h"} ); - yield return new TestCaseData( - new[] {typeof(TestNamedCommand)}, - new[] {"-?"} - ); - // Specified a faulty command yield return new TestCaseData( @@ -123,11 +107,6 @@ namespace CliFx.Tests new[] {typeof(TestFaultyCommand)}, new[] {"faulty", "command", "-h"} ); - - yield return new TestCaseData( - new[] {typeof(TestFaultyCommand)}, - new[] {"faulty", "command", "-?"} - ); } private static IEnumerable GetTestCases_RunAsync_Negative() @@ -154,11 +133,6 @@ namespace CliFx.Tests new[] {"-h"} ); - yield return new TestCaseData( - new Type[0], - new[] {"-?"} - ); - yield return new TestCaseData( new Type[0], new[] {"command"} @@ -186,11 +160,6 @@ namespace CliFx.Tests new[] {"command", "-h"} ); - yield return new TestCaseData( - new[] {typeof(TestDefaultCommand)}, - new[] {"command", "-?"} - ); - // Specified a faulty command yield return new TestCaseData( diff --git a/CliFx/Models/CommandOptionSchema.cs b/CliFx/Models/CommandOptionSchema.cs index 46a03cc..ef4b0fb 100644 --- a/CliFx/Models/CommandOptionSchema.cs +++ b/CliFx/Models/CommandOptionSchema.cs @@ -7,7 +7,7 @@ namespace CliFx.Models /// /// Schema of a defined command option. /// - public class CommandOptionSchema + public partial class CommandOptionSchema { /// /// Underlying property. @@ -66,4 +66,18 @@ namespace CliFx.Models return buffer.ToString(); } } + + public partial class CommandOptionSchema + { + // Here we define some built-in options. + // This is probably a bit hacky but I couldn't come up with a better solution given this architecture. + // We define them here to serve as a single source of truth, because they are used... + // ...in CliApplication (when reading) and HelpTextRenderer (when writing). + + internal static CommandOptionSchema Help { get; } = + new CommandOptionSchema(null, "help", 'h', false, "Shows help text."); + + internal static CommandOptionSchema Version { get; } = + new CommandOptionSchema(null, "version", null, false, "Shows version information."); + } } \ No newline at end of file diff --git a/CliFx/Models/Extensions.cs b/CliFx/Models/Extensions.cs index e888c29..2e4f7e2 100644 --- a/CliFx/Models/Extensions.cs +++ b/CliFx/Models/Extensions.cs @@ -10,50 +10,6 @@ namespace CliFx.Models /// public static class Extensions { - /// - /// Gets whether a command was specified in the input. - /// - public static bool IsCommandSpecified(this CommandInput commandInput) - { - commandInput.GuardNotNull(nameof(commandInput)); - return !commandInput.CommandName.IsNullOrWhiteSpace(); - } - - /// - /// Gets whether help was requested in the input. - /// - public static bool IsHelpRequested(this CommandInput commandInput) - { - commandInput.GuardNotNull(nameof(commandInput)); - - var firstOptionAlias = commandInput.Options.FirstOrDefault()?.Alias; - - return string.Equals(firstOptionAlias, "help", StringComparison.OrdinalIgnoreCase) || - string.Equals(firstOptionAlias, "h", StringComparison.Ordinal) || - string.Equals(firstOptionAlias, "?", StringComparison.Ordinal); - } - - /// - /// Gets whether version information was requested in the input. - /// - public static bool IsVersionRequested(this CommandInput commandInput) - { - commandInput.GuardNotNull(nameof(commandInput)); - - var firstOptionAlias = commandInput.Options.FirstOrDefault()?.Alias; - - return string.Equals(firstOptionAlias, "version", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Gets whether this command is the default command, i.e. without a name. - /// - public static bool IsDefault(this CommandSchema commandSchema) - { - commandSchema.GuardNotNull(nameof(commandSchema)); - return commandSchema.Name.IsNullOrWhiteSpace(); - } - /// /// Finds a command that has specified name, or null if not found. /// @@ -89,6 +45,27 @@ namespace CliFx.Models return commandSchemas.FirstOrDefault(c => c.IsDefault()); } + /// + /// Determines whether an option schema matches specified alias. + /// + public static bool MatchesAlias(this CommandOptionSchema optionSchema, string alias) + { + optionSchema.GuardNotNull(nameof(optionSchema)); + alias.GuardNotNull(nameof(alias)); + + // Compare against name. Case is ignored. + var matchesByName = + !optionSchema.Name.IsNullOrWhiteSpace() && + string.Equals(optionSchema.Name, alias, StringComparison.OrdinalIgnoreCase); + + // Compare against short name. Case is NOT ignored. + var matchesByShortName = + optionSchema.ShortName != null && + alias.Length == 1 && alias[0] == optionSchema.ShortName; + + return matchesByName || matchesByShortName; + } + /// /// Finds an option that matches specified alias, or null if not found. /// @@ -97,23 +74,7 @@ namespace CliFx.Models optionSchemas.GuardNotNull(nameof(optionSchemas)); alias.GuardNotNull(nameof(alias)); - foreach (var optionSchema in optionSchemas) - { - // Compare against name. Case is ignored. - var matchesByName = - !optionSchema.Name.IsNullOrWhiteSpace() && - string.Equals(optionSchema.Name, alias, StringComparison.OrdinalIgnoreCase); - - // Compare against short name. Case is NOT ignored. - var matchesByShortName = - optionSchema.ShortName != null && - alias.Length == 1 && alias[0] == optionSchema.ShortName; - - if (matchesByName || matchesByShortName) - return optionSchema; - } - - return null; + return optionSchemas.FirstOrDefault(o => o.MatchesAlias(alias)); } /// @@ -131,5 +92,47 @@ namespace CliFx.Models return result; } + + /// + /// Gets whether a command was specified in the input. + /// + public static bool IsCommandSpecified(this CommandInput commandInput) + { + commandInput.GuardNotNull(nameof(commandInput)); + return !commandInput.CommandName.IsNullOrWhiteSpace(); + } + + /// + /// Gets whether help was requested in the input. + /// + public static bool IsHelpRequested(this CommandInput commandInput) + { + commandInput.GuardNotNull(nameof(commandInput)); + + var firstOption = commandInput.Options.FirstOrDefault(); + + return firstOption != null && CommandOptionSchema.Help.MatchesAlias(firstOption.Alias); + } + + /// + /// Gets whether version information was requested in the input. + /// + public static bool IsVersionRequested(this CommandInput commandInput) + { + commandInput.GuardNotNull(nameof(commandInput)); + + var firstOption = commandInput.Options.FirstOrDefault(); + + return firstOption != null && CommandOptionSchema.Version.MatchesAlias(firstOption.Alias); + } + + /// + /// Gets whether this command is the default command, i.e. without a name. + /// + public static bool IsDefault(this CommandSchema commandSchema) + { + commandSchema.GuardNotNull(nameof(commandSchema)); + return commandSchema.Name.IsNullOrWhiteSpace(); + } } } \ No newline at end of file diff --git a/CliFx/Services/HelpTextRenderer.cs b/CliFx/Services/HelpTextRenderer.cs index c2fbc53..89603e5 100644 --- a/CliFx/Services/HelpTextRenderer.cs +++ b/CliFx/Services/HelpTextRenderer.cs @@ -21,6 +21,11 @@ namespace CliFx.Services var column = 0; var row = 0; + // Get built-in option schemas (help and version) + var builtInOptionSchemas = new List { CommandOptionSchema.Help }; + if (source.TargetCommandSchema.IsDefault()) + builtInOptionSchemas.Add(CommandOptionSchema.Version); + // Get child command schemas var childCommandSchemas = source.AvailableCommandSchemas .Where(c => source.AvailableCommandSchemas.FindParent(c.Name) == source.TargetCommandSchema) @@ -158,14 +163,6 @@ namespace CliFx.Services void RenderOptions() { - var options = new List(); - options.AddRange(source.TargetCommandSchema.Options); - - options.Add(new CommandOptionSchema(null, "help", 'h', false, "Shows help text.")); - - if (source.TargetCommandSchema.IsDefault()) - options.Add(new CommandOptionSchema(null, "version", null, false, "Shows application version.")); - // Margin RenderMargin(); @@ -173,10 +170,10 @@ namespace CliFx.Services RenderHeader("Options"); // Options - foreach (var option in options) + foreach (var optionSchema in source.TargetCommandSchema.Options.Concat(builtInOptionSchemas)) { // Is required - if (option.IsRequired) + if (optionSchema.IsRequired) { RenderWithColor("* ", ConsoleColor.Red); } @@ -186,28 +183,28 @@ namespace CliFx.Services } // Short name - if (option.ShortName != null) + if (optionSchema.ShortName != null) { - RenderWithColor($"-{option.ShortName}", ConsoleColor.White); + RenderWithColor($"-{optionSchema.ShortName}", ConsoleColor.White); } // Delimiter - if (!option.Name.IsNullOrWhiteSpace() && option.ShortName != null) + if (!optionSchema.Name.IsNullOrWhiteSpace() && optionSchema.ShortName != null) { Render("|"); } // Name - if (!option.Name.IsNullOrWhiteSpace()) + if (!optionSchema.Name.IsNullOrWhiteSpace()) { - RenderWithColor($"--{option.Name}", ConsoleColor.White); + RenderWithColor($"--{optionSchema.Name}", ConsoleColor.White); } // Description - if (!option.Description.IsNullOrWhiteSpace()) + if (!optionSchema.Description.IsNullOrWhiteSpace()) { RenderColumnIndent(); - Render(option.Description); + Render(optionSchema.Description); } RenderNewLine();