diff --git a/src/Spectre.Cli.Tests/Unit/Internal/CommandTreeBinderTests.cs b/src/Spectre.Cli.Tests/Unit/Internal/CommandTreeBinderTests.cs index 156a60b..025a709 100644 --- a/src/Spectre.Cli.Tests/Unit/Internal/CommandTreeBinderTests.cs +++ b/src/Spectre.Cli.Tests/Unit/Internal/CommandTreeBinderTests.cs @@ -114,11 +114,11 @@ public static T Bind(IEnumerable args, Action action) // Parse command tree. var parser = new CommandTreeParser(CommandModelBuilder.Build(configurator)); - var (tree, _) = parser.Parse(args); + var result = parser.Parse(args); // Bind the settings to the tree. CommandSettings settings = new T(); - CommandBinder.Bind(tree, ref settings, new TypeResolverAdapter(null)); + CommandBinder.Bind(result.Tree, ref settings, new TypeResolverAdapter(null)); // Return the settings. return (T)settings; diff --git a/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeParserTests.cs b/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeParserTests.cs index 154d386..5c5e0b3 100644 --- a/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeParserTests.cs +++ b/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeParserTests.cs @@ -9,6 +9,7 @@ using Spectre.Cli.Tests.Data; using Spectre.Cli.Tests.Data.Settings; using Xunit; +using static Spectre.Cli.Internal.Parsing.CommandTreeParser; namespace Spectre.Cli.Tests.Unit.Internal.Parsing { @@ -18,17 +19,17 @@ public sealed class CommandTreeParserTests public void Should_Capture_Remaining_Arguments() { // Given, When - var (tree, remaining) = new Fixture().Parse(new[] { "dog", "--", "--foo", "-bar", "\"baz\"", "qux" }, config => + var result = new Fixture().Parse(new[] { "dog", "--", "--foo", "-bar", "\"baz\"", "qux" }, config => { config.AddCommand("dog"); }); // Then - remaining.Raw.Count.ShouldBe(4); - remaining.Raw[0].ShouldBe("--foo"); - remaining.Raw[1].ShouldBe("-bar"); - remaining.Raw[2].ShouldBe("\"baz\""); - remaining.Raw[3].ShouldBe("qux"); + result.Remaining.Raw.Count.ShouldBe(4); + result.Remaining.Raw[0].ShouldBe("--foo"); + result.Remaining.Raw[1].ShouldBe("-bar"); + result.Remaining.Raw[2].ShouldBe("\"baz\""); + result.Remaining.Raw[3].ShouldBe("qux"); } /// @@ -145,7 +146,7 @@ public void Should_Use_Default_Command_If_No_Command_Was_Specified(string expect public void Should_Not_Use_Default_Command_If_Command_Was_Specified() { // Given, When - var (tree, _) = new Fixture() + var result = new Fixture() .WithDefaultCommand() .Parse(new[] { "cat" }, config => { @@ -153,8 +154,8 @@ public void Should_Not_Use_Default_Command_If_Command_Was_Specified() }); // Then - tree.Command.CommandType.ShouldBe(); - tree.Command.SettingsType.ShouldBe(); + result.Tree.Command.CommandType.ShouldBe(); + result.Tree.Command.SettingsType.ShouldBe(); } private sealed class Fixture @@ -173,7 +174,7 @@ public Fixture WithDefaultCommand() return this; } - public (CommandTree, IRemainingArguments remaining) Parse(IEnumerable args, Action func) + public CommandTreeParserResult Parse(IEnumerable args, Action func) { func(_configurator); @@ -183,7 +184,7 @@ public Fixture WithDefaultCommand() public string Serialize(IEnumerable args, Action func) { - var (tree, _) = Parse(args, func); + var result = Parse(args, func); var settings = new XmlWriterSettings { @@ -196,7 +197,7 @@ public string Serialize(IEnumerable args, Action func) using (var buffer = new StringWriter()) using (var xmlWriter = XmlWriter.Create(buffer, settings)) { - CommandTreeSerializer.Serialize(tree).WriteTo(xmlWriter); + CommandTreeSerializer.Serialize(result.Tree).WriteTo(xmlWriter); xmlWriter.Flush(); return buffer.GetStringBuilder().ToString().NormalizeLineEndings(); } diff --git a/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeSerializer.cs b/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeSerializer.cs index e7c5dce..df4b5db 100644 --- a/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeSerializer.cs +++ b/src/Spectre.Cli.Tests/Unit/Internal/Parsing/CommandTreeSerializer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Xml; using Spectre.Cli.Internal.Modelling; @@ -57,22 +58,22 @@ private static XmlNode Serialize(XmlDocument doc, CommandTree tree) return node; } - private static IEnumerable CreateMappedParameterNodes(XmlDocument document, List<(CommandParameter param, string value)> parameters) + private static IEnumerable CreateMappedParameterNodes(XmlDocument document, List parameters) { - foreach (var (param, value) in parameters) + foreach (var parameter in parameters) { - if (param is CommandOption option) + if (parameter.Parameter is CommandOption option) { var node = document.CreateElement("option"); node.SetNullableAttribute("name", option.GetOptionName()); - node.SetNullableAttribute("assigned", value); + node.SetNullableAttribute("assigned", parameter.Value); yield return node; } - else if (param is CommandArgument argument) + else if (parameter.Parameter is CommandArgument argument) { var node = document.CreateElement("argument"); node.SetNullableAttribute("name", argument.Value); - node.SetNullableAttribute("assigned", value); + node.SetNullableAttribute("assigned", parameter.Value); yield return node; } } diff --git a/src/Spectre.Cli.Tests/Unit/Internal/Parsing/Tokenization/CommandTreeTokenizerTests.cs b/src/Spectre.Cli.Tests/Unit/Internal/Parsing/Tokenization/CommandTreeTokenizerTests.cs index d2a4454..cb137fb 100644 --- a/src/Spectre.Cli.Tests/Unit/Internal/Parsing/Tokenization/CommandTreeTokenizerTests.cs +++ b/src/Spectre.Cli.Tests/Unit/Internal/Parsing/Tokenization/CommandTreeTokenizerTests.cs @@ -1,4 +1,4 @@ -using Shouldly; +using Shouldly; using Spectre.Cli.Internal.Parsing; using Xunit; @@ -11,12 +11,12 @@ public sealed class CommandTreeTokenizerTests public void Should_Parse_String_Correctly(params string[] args) { // Given, When - var (result, remaining) = CommandTreeTokenizer.Tokenize(args); + var result = CommandTreeTokenizer.Tokenize(args); // Then - result.Count.ShouldBe(1); - result[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); - result[0].Value.ShouldBe("foo"); + result.Tokens.Count.ShouldBe(1); + result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); + result.Tokens[0].Value.ShouldBe("foo"); } [Theory] @@ -24,12 +24,12 @@ public void Should_Parse_String_Correctly(params string[] args) public void Should_Parse_Quoted_String_Correctly(params string[] args) { // Given, When - var (result, remaining) = CommandTreeTokenizer.Tokenize(args); + var result = CommandTreeTokenizer.Tokenize(args); // Then - result.Count.ShouldBe(1); - result[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); - result[0].Value.ShouldBe("foo"); + result.Tokens.Count.ShouldBe(1); + result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); + result.Tokens[0].Value.ShouldBe("foo"); } [Theory] @@ -41,14 +41,14 @@ public void Should_Parse_Quoted_String_Correctly(params string[] args) public void Should_Parse_Short_Option_Correctly(params string[] args) { // Given, When - var (result, remaining) = CommandTreeTokenizer.Tokenize(args); + var result = CommandTreeTokenizer.Tokenize(args); // Then - result.Count.ShouldBe(2); - result[0].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); - result[0].Value.ShouldBe("f"); - result[1].TokenKind.ShouldBe(CommandTreeToken.Kind.String); - result[1].Value.ShouldBe("bar"); + result.Tokens.Count.ShouldBe(2); + result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); + result.Tokens[0].Value.ShouldBe("f"); + result.Tokens[1].TokenKind.ShouldBe(CommandTreeToken.Kind.String); + result.Tokens[1].Value.ShouldBe("bar"); } [Theory] @@ -60,47 +60,48 @@ public void Should_Parse_Short_Option_Correctly(params string[] args) public void Should_Parse_Long_Option_Correctly(params string[] args) { // Given, When - var (result, remaining) = CommandTreeTokenizer.Tokenize(args); + var result = CommandTreeTokenizer.Tokenize(args); // Then - result.Count.ShouldBe(2); - result[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); - result[0].Value.ShouldBe("foo"); - result[1].TokenKind.ShouldBe(CommandTreeToken.Kind.String); - result[1].Value.ShouldBe("bar"); + result.Tokens.Count.ShouldBe(2); + result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); + result.Tokens[0].Value.ShouldBe("foo"); + result.Tokens[1].TokenKind.ShouldBe(CommandTreeToken.Kind.String); + result.Tokens[1].Value.ShouldBe("bar"); } [Fact] public void Should_Parse_Remaining_Parameters() { // Given, When - var (result, remaining) = CommandTreeTokenizer.Tokenize(new[] { "--foo", "--", "--bar", "-qux", "\"lol\"", "w00t" }); + var result = CommandTreeTokenizer.Tokenize( + new[] { "--foo", "--", "--bar", "-qux", "\"lol\"", "w00t" }); // Then - result.Count.ShouldBe(8); + result.Tokens.Count.ShouldBe(8); - remaining.Count.ShouldBe(4); - remaining[0].ShouldBe("--bar"); - remaining[1].ShouldBe("-qux"); - remaining[2].ShouldBe("\"lol\""); - remaining[3].ShouldBe("w00t"); + result.Remaining.Count.ShouldBe(4); + result.Remaining[0].ShouldBe("--bar"); + result.Remaining[1].ShouldBe("-qux"); + result.Remaining[2].ShouldBe("\"lol\""); + result.Remaining[3].ShouldBe("w00t"); - result[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); - result[0].Value.ShouldBe("foo"); - result[1].TokenKind.ShouldBe(CommandTreeToken.Kind.Remaining); - result[1].Value.ShouldBe("--"); - result[2].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); - result[2].Value.ShouldBe("bar"); - result[3].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); - result[3].Value.ShouldBe("q"); - result[4].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); - result[4].Value.ShouldBe("u"); - result[5].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); - result[5].Value.ShouldBe("x"); - result[6].TokenKind.ShouldBe(CommandTreeToken.Kind.String); - result[6].Value.ShouldBe("lol"); - result[7].TokenKind.ShouldBe(CommandTreeToken.Kind.String); - result[7].Value.ShouldBe("w00t"); + result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); + result.Tokens[0].Value.ShouldBe("foo"); + result.Tokens[1].TokenKind.ShouldBe(CommandTreeToken.Kind.Remaining); + result.Tokens[1].Value.ShouldBe("--"); + result.Tokens[2].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); + result.Tokens[2].Value.ShouldBe("bar"); + result.Tokens[3].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); + result.Tokens[3].Value.ShouldBe("q"); + result.Tokens[4].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); + result.Tokens[4].Value.ShouldBe("u"); + result.Tokens[5].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); + result.Tokens[5].Value.ShouldBe("x"); + result.Tokens[6].TokenKind.ShouldBe(CommandTreeToken.Kind.String); + result.Tokens[6].Value.ShouldBe("lol"); + result.Tokens[7].TokenKind.ShouldBe(CommandTreeToken.Kind.String); + result.Tokens[7].Value.ShouldBe("w00t"); } } } diff --git a/src/Spectre.Cli/Internal/CommandBinder.cs b/src/Spectre.Cli/Internal/CommandBinder.cs index a9818b4..8c2e805 100644 --- a/src/Spectre.Cli/Internal/CommandBinder.cs +++ b/src/Spectre.Cli/Internal/CommandBinder.cs @@ -25,11 +25,11 @@ TypeConverter GetConverter(CommandParameter parameter) while (tree != null) { // Process mapped parameters. - foreach (var (parameter, value) in tree.Mapped) + foreach (var mapped in tree.Mapped) { - var converter = GetConverter(parameter); - parameter.Assign(settings, converter.ConvertFromInvariantString(value)); - ValidateParameter(parameter, settings); + var converter = GetConverter(mapped.Parameter); + mapped.Parameter.Assign(settings, converter.ConvertFromInvariantString(mapped.Value)); + ValidateParameter(mapped.Parameter, settings); } // Process unmapped parameters. diff --git a/src/Spectre.Cli/Internal/CommandExecutor.cs b/src/Spectre.Cli/Internal/CommandExecutor.cs index dbb7ca6..855cb3c 100644 --- a/src/Spectre.Cli/Internal/CommandExecutor.cs +++ b/src/Spectre.Cli/Internal/CommandExecutor.cs @@ -30,10 +30,10 @@ public Task Execute(IConfiguration configuration, IEnumerable args) // Parse and map the model against the arguments. var parser = new CommandTreeParser(model); - var (tree, remaining) = parser.Parse(args); + var parsedResult = parser.Parse(args); // Currently the root? - if (tree == null) + if (parsedResult.Tree == null) { // Display help. ConsoleRenderer.Render(HelpWriter.Write(model)); @@ -41,7 +41,7 @@ public Task Execute(IConfiguration configuration, IEnumerable args) } // Get the command to execute. - var leaf = tree.GetLeafCommand(); + var leaf = parsedResult.Tree.GetLeafCommand(); if (leaf.Command.IsBranch || leaf.ShowHelp) { // Branches can't be executed. Show help. @@ -50,14 +50,14 @@ public Task Execute(IConfiguration configuration, IEnumerable args) } // Register the arguments with the container. - _registrar?.RegisterInstance(typeof(IRemainingArguments), remaining); + _registrar?.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining); // Create the resolver and the context. var resolver = new TypeResolverAdapter(_registrar?.Build()); - var context = new CommandContext(remaining); + var context = new CommandContext(parsedResult.Remaining); // Execute the command tree. - return Execute(leaf, tree, context, resolver); + return Execute(leaf, parsedResult.Tree, context, resolver); } private static Task Execute( diff --git a/src/Spectre.Cli/Internal/HelpWriter.cs b/src/Spectre.Cli/Internal/HelpWriter.cs index 7b67ff2..c04aa58 100644 --- a/src/Spectre.Cli/Internal/HelpWriter.cs +++ b/src/Spectre.Cli/Internal/HelpWriter.cs @@ -11,6 +11,38 @@ namespace Spectre.Cli.Internal { internal static class HelpWriter { + // Consider removing this in favor for value tuples at some point. + private sealed class HelpArgument + { + public string Name { get; } + public bool Required { get; } + public string Description { get; } + + public HelpArgument(string name, bool required, string description) + { + Name = name; + Required = required; + Description = description; + } + } + + // Consider removing this in favor for value tuples at some point. + private sealed class HelpOption + { + public string Short { get; } + public string Long { get; } + public string Value { get; } + public string Description { get; } + + public HelpOption(string @short, string @long, string @value, string @description) + { + Short = @short; + Long = @long; + Value = value; + Description = description; + } + } + public static IRenderable Write(CommandModel model) { return WriteCommand(model, null); @@ -170,10 +202,10 @@ private static void WriteExamples(RenderableComposer composer, CommandModel mode private static void WriteArguments(RenderableComposer composer, CommandInfo command) { - var arguments = new List<(string name, bool required, string description)>(); + var arguments = new List(); arguments.AddRange(command?.Parameters?.OfType()?.Select( - x => (x.Value, x.Required, x.Description)) - ?? Array.Empty<(string, bool, string)>()); + x => new HelpArgument(x.Value, x.Required, x.Description)) + ?? Array.Empty()); if (arguments.Count == 0) { @@ -182,20 +214,20 @@ private static void WriteArguments(RenderableComposer composer, CommandInfo comm composer.Color(ConsoleColor.Yellow, c => c.Text("ARGUMENTS:")).LineBreak(); - var maxLength = arguments.Max(x => x.name.Length); + var maxLength = arguments.Max(arg => arg.Name.Length); - foreach (var (name, required, description) in arguments) + foreach (var argument in arguments) { composer.Tab(); // Argument name. - composer.Condition(required, - @true: c1 => c1.Color(ConsoleColor.Gray, c => c.Text($"<{name}>")), - @false: c1 => c1.Color(ConsoleColor.Gray, c => c.Text($"[{name}]"))); + composer.Condition(argument.Required, + @true: c1 => c1.Color(ConsoleColor.Gray, c => c.Text($"<{argument.Name}>")), + @false: c1 => c1.Color(ConsoleColor.Gray, c => c.Text($"[{argument.Name}]"))); // Description - composer.Spaces(maxLength - name.Length); - composer.Tab().Text(description?.TrimEnd('.')?.Trim()); + composer.Spaces(maxLength - argument.Name.Length); + composer.Tab().Text(argument.Description?.TrimEnd('.')?.Trim()); composer.LineBreak(); } @@ -206,14 +238,14 @@ private static void WriteArguments(RenderableComposer composer, CommandInfo comm private static void WriteOptions(RenderableComposer composer, CommandInfo command) { // Collect all options into a single structure. - var parameters = new List<(string @short, string @long, string value, string description)> + var parameters = new List { - ("h", "help", null, "Prints help information") + new HelpOption("h", "help", (string)null, "Prints help information") }; parameters.AddRange(command?.Parameters?.OfType()?.Select(o => - (o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(), o.ValueName, o.Description)) - ?? Array.Empty<(string, string, string, string)>()); + new HelpOption(o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(), o.ValueName, o.Description)) + ?? Array.Empty()); var options = parameters.ToArray(); if (options.Length > 0) @@ -221,17 +253,17 @@ private static void WriteOptions(RenderableComposer composer, CommandInfo comman composer.Color(ConsoleColor.Yellow, c => c.Text("OPTIONS:")).LineBreak(); // Start with composing a list of lines. - var result = new List<(string description, BlockElement element)>(); - foreach (var (@short, @long, value, description) in options) + var result = new List>(); + foreach (var option in options) { var item = new BlockElement(); item.Append(new TabElement()); // Short - if (@short != null) + if (option.Short != null) { - item.Append(new TextElement($"-{@short}")); - if (@long != null) + item.Append(new TextElement($"-{option.Short}")); + if (option.Long != null) { item.Append(new TextElement(",")); } @@ -242,26 +274,29 @@ private static void WriteOptions(RenderableComposer composer, CommandInfo comman } // Long - if (@long != null) + if (option.Long != null) { item.Append(new TextElement(" ")); - item.Append(new TextElement($"--{@long}")); + item.Append(new TextElement($"--{option.Long}")); } // Value - if (value != null) + if (option.Value != null) { item.Append(new TextElement(" ")); - item.Append(new ColorElement(ConsoleColor.Gray, new TextElement($"<{value}>"))); + item.Append(new ColorElement(ConsoleColor.Gray, new TextElement($"<{option.Value}>"))); } - result.Add((description, item)); + result.Add(Tuple.Create(option.Description, item)); } // Now add the descriptions to all lines. - var maxLength = result.Max(x => x.element.Length); - foreach (var (description, element) in result) + var maxLength = result.Max(x => x.Item2.Length); + foreach (var item in result) { + var description = item.Item1; + var element = item.Item2; + if (!string.IsNullOrWhiteSpace(description)) { var neededSpaces = maxLength - element.Length; @@ -276,7 +311,7 @@ private static void WriteOptions(RenderableComposer composer, CommandInfo comman } // Append the items. - composer.Append(result.Select(x => x.element)); + composer.Append(result.Select(x => x.Item2)); composer.LineBreak(); } } diff --git a/src/Spectre.Cli/Internal/Modelling/CommandModelBuilder.cs b/src/Spectre.Cli/Internal/Modelling/CommandModelBuilder.cs index 9cd4775..416b246 100644 --- a/src/Spectre.Cli/Internal/Modelling/CommandModelBuilder.cs +++ b/src/Spectre.Cli/Internal/Modelling/CommandModelBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; @@ -8,6 +9,21 @@ namespace Spectre.Cli.Internal.Modelling { internal static class CommandModelBuilder { + // Consider removing this in favor for value tuples at some point. + private sealed class OrderedProperties + { + public int Level { get; } + public int SortOrder { get; } + public PropertyInfo[] Properties { get; } + + public OrderedProperties(int level, int sortOrder, PropertyInfo[] properties) + { + Level = level; + SortOrder = sortOrder; + Properties = properties; + } + } + public static CommandModel Build(IConfiguration configuration) { var result = new List(); @@ -62,14 +78,14 @@ private static IEnumerable GetParameters(CommandInfo command) // We need to get parameters in order of the class where they were defined. // We assign each inheritance level a value that is used to properly sort the // arguments when iterating over them. - IEnumerable<(int level, int sortOrder, PropertyInfo[] properties)> GetPropertiesInOrder() + IEnumerable GetPropertiesInOrder() { var current = command.SettingsType; var level = 0; var sortOrder = 0; while (current.BaseType != null) { - yield return (level, sortOrder, current.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)); + yield return new OrderedProperties(level, sortOrder, current.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public)); current = current.BaseType; // Things get a little bit complicated now. @@ -92,11 +108,11 @@ private static IEnumerable GetParameters(CommandInfo command) } var groups = GetPropertiesInOrder(); - foreach (var (_, _, properties) in groups.OrderBy(x => x.level).ThenBy(x => x.sortOrder)) + foreach (var group in groups.OrderBy(x => x.Level).ThenBy(x => x.SortOrder)) { var parameters = new List(); - foreach (var property in properties) + foreach (var property in group.Properties) { if (property.IsDefined(typeof(CommandOptionAttribute))) { diff --git a/src/Spectre.Cli/Internal/Parsing/CommandTree.cs b/src/Spectre.Cli/Internal/Parsing/CommandTree.cs index d979b31..e67a26e 100644 --- a/src/Spectre.Cli/Internal/Parsing/CommandTree.cs +++ b/src/Spectre.Cli/Internal/Parsing/CommandTree.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Spectre.Cli.Internal.Exceptions; using Spectre.Cli.Internal.Modelling; @@ -7,7 +8,7 @@ namespace Spectre.Cli.Internal.Parsing internal sealed class CommandTree { public CommandInfo Command { get; } - public List<(CommandParameter, string)> Mapped { get; } + public List Mapped { get; } public List Unmapped { get; } public CommandTree Parent { get; } public CommandTree Next { get; set; } @@ -17,7 +18,7 @@ public CommandTree(CommandTree parent, CommandInfo command) { Parent = parent; Command = command; - Mapped = new List<(CommandParameter, string)>(); + Mapped = new List(); Unmapped = new List(); } diff --git a/src/Spectre.Cli/Internal/Parsing/CommandTreeExtensions.cs b/src/Spectre.Cli/Internal/Parsing/CommandTreeExtensions.cs index f7bd666..f0f28a2 100644 --- a/src/Spectre.Cli/Internal/Parsing/CommandTreeExtensions.cs +++ b/src/Spectre.Cli/Internal/Parsing/CommandTreeExtensions.cs @@ -57,7 +57,7 @@ public static bool IsOptionMappedWithParent(this CommandTree tree, string name, if (option != null) { - return node.Mapped.Any(p => p.Item1 == option); + return node.Mapped.Any(p => p.Parameter == option); } node = node.Parent; diff --git a/src/Spectre.Cli/Internal/Parsing/CommandTreeParser.cs b/src/Spectre.Cli/Internal/Parsing/CommandTreeParser.cs index c109a7b..d24a196 100644 --- a/src/Spectre.Cli/Internal/Parsing/CommandTreeParser.cs +++ b/src/Spectre.Cli/Internal/Parsing/CommandTreeParser.cs @@ -17,16 +17,32 @@ public enum State Remaining = 1 } + // // Consider removing this in favor for value tuples at some point. + public sealed class CommandTreeParserResult + { + public CommandTree Tree { get; } + public IRemainingArguments Remaining { get; } + + public CommandTreeParserResult(CommandTree tree, IRemainingArguments remaining) + { + Tree = tree; + Remaining = remaining; + } + } + public CommandTreeParser(CommandModel configuration) { _configuration = configuration; _help = new CommandOptionAttribute("-h|--help"); } - public (CommandTree tree, IRemainingArguments remaining) Parse(IEnumerable args) + public CommandTreeParserResult Parse(IEnumerable args) { var context = new CommandTreeParserContext(args, _configuration.ParsingMode); - var (tokens, rawRemaining) = CommandTreeTokenizer.Tokenize(context.Arguments); + + var tokenizerResult = CommandTreeTokenizer.Tokenize(context.Arguments); + var tokens = tokenizerResult.Tokens; + var rawRemaining = tokenizerResult.Remaining; var result = (CommandTree)null; if (tokens.Count > 0) @@ -39,13 +55,15 @@ public CommandTreeParser(CommandModel configuration) if (_configuration.DefaultCommand != null) { result = ParseCommandParameters(context, _configuration.DefaultCommand, null, tokens); - return (result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); + return new CommandTreeParserResult( + result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); } // Show help? if (_help?.IsMatch(token.Value) == true) { - return (null, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); + return new CommandTreeParserResult( + null, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); } // Unexpected option. @@ -59,7 +77,8 @@ public CommandTreeParser(CommandModel configuration) if (_configuration.DefaultCommand != null) { result = ParseCommandParameters(context, _configuration.DefaultCommand, null, tokens); - return (result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); + return new CommandTreeParserResult( + result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); } } @@ -75,7 +94,8 @@ public CommandTreeParser(CommandModel configuration) } } - return (result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); + return new CommandTreeParserResult( + result, new RemainingArguments(context.GetRemainingArguments(), rawRemaining)); } private CommandTree ParseCommand( @@ -134,7 +154,7 @@ public CommandTreeParser(CommandModel configuration) // Add unmapped parameters. foreach (var parameter in node.Command.Parameters) { - if (node.Mapped.All(m => m.Item1 != parameter)) + if (node.Mapped.All(m => m.Parameter != parameter)) { node.Unmapped.Add(parameter); } @@ -188,7 +208,7 @@ public CommandTreeParser(CommandModel configuration) // Yes, this was an argument. var value = stream.Consume(CommandTreeToken.Kind.String).Value; - node.Mapped.Add((parameter, value)); + node.Mapped.Add(new MappedCommandParameter(parameter, value)); context.IncreaseArgumentPosition(); } @@ -208,7 +228,9 @@ public CommandTreeParser(CommandModel configuration) var option = node.FindOption(token.Value, isLongOption); if (option != null) { - node.Mapped.Add((option, ParseOptionValue(context, stream, token, node, option))); + node.Mapped.Add(new MappedCommandParameter( + option, ParseOptionValue(context, stream, token, node, option))); + return; } diff --git a/src/Spectre.Cli/Internal/Parsing/CommandTreeTokenizer.cs b/src/Spectre.Cli/Internal/Parsing/CommandTreeTokenizer.cs index 3d69807..967ca12 100644 --- a/src/Spectre.Cli/Internal/Parsing/CommandTreeTokenizer.cs +++ b/src/Spectre.Cli/Internal/Parsing/CommandTreeTokenizer.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -14,7 +15,20 @@ public enum Mode Remaining = 1 } - public static (CommandTreeTokenStream, IReadOnlyList) Tokenize(IEnumerable args) + // Consider removing this in favor for value tuples at some point. + public sealed class CommandTreeTokenizerResult + { + public CommandTreeTokenStream Tokens { get; } + public IReadOnlyList Remaining { get; } + + public CommandTreeTokenizerResult(CommandTreeTokenStream tokens, IReadOnlyList remaining) + { + Tokens = tokens; + Remaining = remaining; + } + } + + public static CommandTreeTokenizerResult Tokenize(IEnumerable args) { var tokens = new List(); var position = 0; @@ -33,7 +47,9 @@ public static (CommandTreeTokenStream, IReadOnlyList) Tokenize(IEnumerab previousReader = reader; } - return (new CommandTreeTokenStream(tokens), context.Remaining); + return new CommandTreeTokenizerResult( + new CommandTreeTokenStream(tokens), + context.Remaining); } private static int ParseToken(CommandTreeTokenizerContext context, TextBuffer reader, int position, int start, List tokens) diff --git a/src/Spectre.Cli/Internal/Parsing/MappedCommandParameter.cs b/src/Spectre.Cli/Internal/Parsing/MappedCommandParameter.cs new file mode 100644 index 0000000..15a6227 --- /dev/null +++ b/src/Spectre.Cli/Internal/Parsing/MappedCommandParameter.cs @@ -0,0 +1,17 @@ +using Spectre.Cli.Internal.Modelling; + +namespace Spectre.Cli.Internal.Parsing +{ + // Consider removing this in favor for value tuples at some point. + internal sealed class MappedCommandParameter + { + public CommandParameter Parameter { get; } + public string Value { get; } + + public MappedCommandParameter(CommandParameter parameter, string value) + { + Parameter = parameter; + Value = value; + } + } +} \ No newline at end of file diff --git a/src/Spectre.Cli/Spectre.Cli.csproj b/src/Spectre.Cli/Spectre.Cli.csproj index 5e73e47..cfa4d29 100644 --- a/src/Spectre.Cli/Spectre.Cli.csproj +++ b/src/Spectre.Cli/Spectre.Cli.csproj @@ -1,6 +1,6 @@ - + - net461;netstandard2.0 + netstandard2.0 IOperation 7.2 An extremly opinionated command line parser. @@ -22,11 +22,6 @@ - - - - - ../Spectre.ruleset