From e7e47b1c9d13dad4caca6d940f1a20556ca39dc6 Mon Sep 17 00:00:00 2001 From: Alexey Golub Date: Tue, 30 Jul 2019 20:11:59 +0300 Subject: [PATCH] Quick and dirty but working support for subcommands in help --- CliFx/CliApplication.cs | 13 +- CliFx/Internal/Extensions.cs | 7 + CliFx/Services/CommandHelpTextRenderer.cs | 366 +++++++++++---------- CliFx/Services/ICommandHelpTextRenderer.cs | 2 +- 4 files changed, 216 insertions(+), 172 deletions(-) diff --git a/CliFx/CliApplication.cs b/CliFx/CliApplication.cs index b52abbb..84e0494 100644 --- a/CliFx/CliApplication.cs +++ b/CliFx/CliApplication.cs @@ -92,9 +92,18 @@ namespace CliFx c => c.Error.WriteLine($"Specified command [{commandInput.CommandName}] is not defined.")); } + // Get default command schema + var defaultCommandSchema = availableCommandSchemas.FirstOrDefault(c => c.IsDefault()); + + // Use a stub if default command schema is not defined + if (defaultCommandSchema == null) + { + defaultCommandSchema = _commandSchemaResolver.GetCommandSchema(typeof(StubDefaultCommand)); + availableCommandSchemas = availableCommandSchemas.Concat(new[] {defaultCommandSchema}).ToArray(); + } + // Show help - var stubDefaultCommandSchema = _commandSchemaResolver.GetCommandSchema(typeof(StubDefaultCommand)); - _commandHelpTextRenderer.RenderHelpText(_applicationMetadata, availableCommandSchemas, stubDefaultCommandSchema); + _commandHelpTextRenderer.RenderHelpText(_applicationMetadata, availableCommandSchemas, defaultCommandSchema); return commandInput.IsCommandSpecified() ? -1 : 0; } diff --git a/CliFx/Internal/Extensions.cs b/CliFx/Internal/Extensions.cs index 90cd612..ccb5767 100644 --- a/CliFx/Internal/Extensions.cs +++ b/CliFx/Internal/Extensions.cs @@ -25,6 +25,13 @@ namespace CliFx.Internal return builder; } + public static string SubstringUntilLast(this string s, string sub, + StringComparison comparison = StringComparison.Ordinal) + { + var index = s.LastIndexOf(sub, comparison); + return index < 0 ? s : s.Substring(0, index); + } + public static TValue GetValueOrDefault(this IReadOnlyDictionary dic, TKey key) => dic.TryGetValue(key, out var result) ? result : default; diff --git a/CliFx/Services/CommandHelpTextRenderer.cs b/CliFx/Services/CommandHelpTextRenderer.cs index a87ba31..a3b5626 100644 --- a/CliFx/Services/CommandHelpTextRenderer.cs +++ b/CliFx/Services/CommandHelpTextRenderer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using CliFx.Internal; using CliFx.Models; @@ -20,191 +21,218 @@ namespace CliFx.Services { } + private void RenderDescription(CommandSchema commandSchema) + { + if (commandSchema.Description.IsNullOrWhiteSpace()) + return; + + _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkGray, c => + { + c.Output.WriteLine("Description"); + }); + + _console.Output.Write(" "); + _console.Output.Write(commandSchema.Description); + _console.Output.WriteLine(); + + _console.Output.WriteLine(); + } + + private void RenderUsage(ApplicationMetadata applicationMetadata, CommandSchema commandSchema, bool hasChildCommands) + { + _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkGray, c => + { + c.Output.WriteLine("Usage"); + }); + + _console.Output.Write(" "); + + _console.Output.Write(applicationMetadata.ExecutableName); + _console.Output.Write(' '); + + if (!commandSchema.IsDefault()) + { + _console.Output.Write(commandSchema.Name); + _console.Output.Write(' '); + } + + if (hasChildCommands) + { + _console.Output.Write("[command]"); + _console.Output.Write(' '); + } + + _console.Output.Write("[options]"); + _console.Output.WriteLine(); + _console.Output.WriteLine(); + } + + private void RenderOptions(CommandSchema commandSchema) + { + var options = new List(); + options.AddRange(commandSchema.Options); + + options.Add(new CommandOptionSchema(null, "help", 'h', null, false, "Shows help text.")); + + if (commandSchema.IsDefault()) + options.Add(new CommandOptionSchema(null, "version", 'v', null, false, "Shows application version.")); + + _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkGray, c => + { + c.Output.WriteLine("Options"); + }); + + foreach (var option in options) + { + _console.Output.Write(" "); + + if (!option.Name.IsNullOrWhiteSpace()) + { + _console.WithColor(option.IsRequired ? ConsoleColor.White : ConsoleColor.Gray, c => + { + c.Output.Write("--"); + c.Output.Write(option.Name); + }); + } + + if (!option.Name.IsNullOrWhiteSpace() && option.ShortName != null) + { + _console.Output.Write('|'); + } + + if (option.ShortName != null) + { + _console.WithColor(option.IsRequired ? ConsoleColor.White: ConsoleColor.Gray, c => + { + c.Output.Write('-'); + c.Output.Write(option.ShortName); + }); + } + + if (!option.Description.IsNullOrWhiteSpace()) + { + _console.Output.Write(" "); + _console.Output.Write(option.Description); + } + + _console.Output.WriteLine(); + } + + _console.Output.WriteLine(); + } + + private void RenderChildCommands(ApplicationMetadata applicationMetadata, CommandSchema commandSchema, + IReadOnlyList childCommandSchemas) + { + if (!childCommandSchemas.Any()) + return; + + _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkGray, c => + { + c.Output.WriteLine("Commands"); + }); + + foreach (var childCommandSchema in childCommandSchemas) + { + _console.Output.Write(" "); + + _console.Output.Write(GetRelativeCommandName(childCommandSchema, commandSchema)); + + if (!childCommandSchema.Description.IsNullOrWhiteSpace()) + { + _console.Output.Write(" "); + _console.Output.Write(childCommandSchema.Description); + } + + _console.Output.WriteLine(); + } + + _console.Output.WriteLine(); + + // Child command help tip + + _console.Output.Write("You can run `"); + + _console.Output.Write(applicationMetadata.ExecutableName); + _console.Output.Write(' '); + + if (!commandSchema.IsDefault()) + { + _console.Output.Write(commandSchema.Name); + _console.Output.Write(' '); + } + + _console.Output.Write("[command] --help` to show help on a specific command."); + + _console.Output.WriteLine(); + } + public void RenderHelpText(ApplicationMetadata applicationMetadata, - IReadOnlyList availableCommandSchemas, CommandSchema matchingCommandSchema = null) => - new RenderHelpTextImpl(_console, applicationMetadata, availableCommandSchemas, matchingCommandSchema).Render(); + IReadOnlyList availableCommandSchemas, CommandSchema matchingCommandSchema) + { + var childCommandSchemas = GetChildCommandSchemas(availableCommandSchemas, matchingCommandSchema); + + // Render application info + if (matchingCommandSchema.IsDefault()) + { + _console.Output.Write(applicationMetadata.Title); + _console.Output.Write(" v"); + _console.Output.Write(applicationMetadata.VersionText); + _console.Output.WriteLine(); + _console.Output.WriteLine(); + } + + RenderDescription(matchingCommandSchema); + RenderUsage(applicationMetadata, matchingCommandSchema, childCommandSchemas.Any()); + RenderOptions(matchingCommandSchema); + RenderChildCommands(applicationMetadata, matchingCommandSchema, childCommandSchemas); + } } public partial class CommandHelpTextRenderer { - private class RenderHelpTextImpl + private static CommandSchema GetParentOrNull(CommandSchema commandSchema, IReadOnlyList availableCommandSchemas) { - private readonly IConsole _console; - private readonly ApplicationMetadata _applicationMetadata; - private readonly IReadOnlyList _availableCommandSchemas; - private readonly CommandSchema _matchingCommandSchema; + if (commandSchema.IsDefault()) + return null; - private readonly IReadOnlyList _childCommandSchemas; - - public RenderHelpTextImpl(IConsole console, ApplicationMetadata applicationMetadata, - IReadOnlyList availableCommandSchemas, CommandSchema matchingCommandSchema) + var nameBuffer = commandSchema.Name; + while (nameBuffer.Contains(' ')) { - _console = console; - _applicationMetadata = applicationMetadata; - _availableCommandSchemas = availableCommandSchemas; - _matchingCommandSchema = matchingCommandSchema; + nameBuffer = nameBuffer.SubstringUntilLast(" "); + var parent = availableCommandSchemas.FirstOrDefault(c => + string.Equals(c.Name, nameBuffer, StringComparison.OrdinalIgnoreCase)); - _childCommandSchemas = GetChildCommandSchemas(); + if (parent != null) + return parent; } - private IReadOnlyList GetChildCommandSchemas() - { - // TODO: - var prefix = _matchingCommandSchema == null || _matchingCommandSchema.Name.IsNullOrWhiteSpace() - ? "" - : _matchingCommandSchema.Name + " "; + return availableCommandSchemas.FirstOrDefault(c => c.IsDefault()); + } - return new CommandSchema[0]; + private static IReadOnlyList GetChildCommandSchemas(IReadOnlyList availableCommandSchemas, + CommandSchema parentCommandSchema) + { + var result = new List(); + + foreach (var commandSchema in availableCommandSchemas) + { + if (commandSchema == parentCommandSchema) + continue; + + if (GetParentOrNull(commandSchema, availableCommandSchemas) == parentCommandSchema) + result.Add(commandSchema); } - private void RenderAppInfo() - { - if (_matchingCommandSchema != null && !_matchingCommandSchema.Name.IsNullOrWhiteSpace()) - return; + return result; + } - _console.Output.Write(_applicationMetadata.Title); - _console.Output.Write(" v"); - _console.Output.Write(_applicationMetadata.VersionText); - _console.Output.WriteLine(); - _console.Output.WriteLine(); - } + private static string GetRelativeCommandName(CommandSchema commandSchema, CommandSchema parentCommandSchema) + { + if (parentCommandSchema.Name.IsNullOrWhiteSpace()) + return commandSchema.Name; - private void RenderDescription() - { - if (_matchingCommandSchema == null || _matchingCommandSchema.Description.IsNullOrWhiteSpace()) - return; - - _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkCyan, c => - { - c.Output.WriteLine("Description"); - }); - - _console.Output.Write(" "); - _console.Output.Write(_matchingCommandSchema.Description); - _console.Output.WriteLine(); - - _console.Output.WriteLine(); - } - - private void RenderUsage() - { - var hasChildCommands = _childCommandSchemas.Any(); - - _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkCyan, c => - { - c.Output.WriteLine("Usage"); - }); - - _console.Output.Write(" "); - - _console.Output.Write(_applicationMetadata.ExecutableName); - _console.Output.Write(' '); - - if (_matchingCommandSchema != null && !_matchingCommandSchema.Name.IsNullOrWhiteSpace()) - { - _console.Output.Write(_matchingCommandSchema.Name); - _console.Output.Write(' '); - } - - if (hasChildCommands) - { - _console.Output.Write("[command]"); - _console.Output.Write(' '); - } - - _console.Output.Write("[options]"); - _console.Output.WriteLine(); - _console.Output.WriteLine(); - } - - private void RenderOptions() - { - var options = new List(); - options.AddRange(_matchingCommandSchema?.Options ?? Enumerable.Empty()); - options.Add(new CommandOptionSchema(null, "help", 'h', null, false, "Shows help text.")); - - if (_matchingCommandSchema == null || _matchingCommandSchema.Name.IsNullOrWhiteSpace()) - options.Add(new CommandOptionSchema(null, "version", 'v', null, false, "Shows application version.")); - - _console.WithColor(ConsoleColor.Black, ConsoleColor.DarkCyan, c => - { - c.Output.WriteLine("Options"); - }); - - foreach (var option in options) - { - _console.Output.Write(" "); - - if (!option.Name.IsNullOrWhiteSpace()) - { - _console.WithColor(option.IsRequired ? ConsoleColor.Yellow : ConsoleColor.White, c => - { - c.Output.Write("--"); - c.Output.Write(option.Name); - }); - } - - if (!option.Name.IsNullOrWhiteSpace() && option.ShortName != null) - { - _console.Output.Write('|'); - } - - if (option.ShortName != null) - { - _console.WithColor(option.IsRequired ? ConsoleColor.Yellow : ConsoleColor.White, c => - { - c.Output.Write('-'); - c.Output.Write(option.ShortName); - }); - } - - if (!option.Description.IsNullOrWhiteSpace()) - { - _console.Output.Write(" "); - _console.Output.Write(option.Description); - } - - _console.Output.WriteLine(); - } - - _console.Output.WriteLine(); - } - - private void RenderChildCommands() - { - // TODO - } - - private void RenderSubCommandHelpTip() - { - if (!_childCommandSchemas.Any()) - return; - - _console.Output.Write("You can run `"); - - _console.Output.Write(_applicationMetadata.ExecutableName); - _console.Output.Write(' '); - - if (_matchingCommandSchema != null && !_matchingCommandSchema.Name.IsNullOrWhiteSpace()) - { - _console.Output.Write(_matchingCommandSchema.Name); - _console.Output.Write(' '); - } - - _console.Output.Write("[command] --help` to show help on a specific command."); - - _console.Output.WriteLine(); - } - - public void Render() - { - RenderAppInfo(); - RenderDescription(); - RenderUsage(); - RenderOptions(); - RenderChildCommands(); - RenderSubCommandHelpTip(); - } + return commandSchema.Name.Substring(parentCommandSchema.Name.Length + 1); } } } \ No newline at end of file diff --git a/CliFx/Services/ICommandHelpTextRenderer.cs b/CliFx/Services/ICommandHelpTextRenderer.cs index ef223ab..305451d 100644 --- a/CliFx/Services/ICommandHelpTextRenderer.cs +++ b/CliFx/Services/ICommandHelpTextRenderer.cs @@ -6,6 +6,6 @@ namespace CliFx.Services public interface ICommandHelpTextRenderer { void RenderHelpText(ApplicationMetadata applicationMetadata, - IReadOnlyList availableCommandSchemas, CommandSchema matchingCommandSchema = null); + IReadOnlyList availableCommandSchemas, CommandSchema matchingCommandSchema); } } \ No newline at end of file