|
| 1 | +using static Spectre.Console.Cli.CommandTreeTokenizer; |
| 2 | + |
1 | 3 | namespace Spectre.Console.Cli; |
2 | 4 |
|
3 | 5 | internal sealed class CommandExecutor |
@@ -101,34 +103,99 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> |
101 | 103 | } |
102 | 104 | } |
103 | 105 |
|
| 106 | + [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Improves code readability by grouping together related statements into a block")] |
104 | 107 | private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args) |
105 | 108 | { |
106 | | - var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments); |
| 109 | + CommandTreeParserResult? parsedResult = null; |
| 110 | + CommandTreeTokenizerResult tokenizerResult; |
107 | 111 |
|
108 | | - var parserContext = new CommandTreeParserContext(args, settings.ParsingMode); |
109 | | - var tokenizerResult = CommandTreeTokenizer.Tokenize(args); |
110 | | - var parsedResult = parser.Parse(parserContext, tokenizerResult); |
| 112 | + try |
| 113 | + { |
| 114 | + (parsedResult, tokenizerResult) = InternalParseCommandLineArguments(model, settings, args); |
111 | 115 |
|
112 | | - var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand(); |
113 | | - var lastParsedCommand = lastParsedLeaf?.Command; |
114 | | - if (lastParsedLeaf != null && lastParsedCommand != null && |
| 116 | + var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand(); |
| 117 | + var lastParsedCommand = lastParsedLeaf?.Command; |
| 118 | + |
| 119 | + if (lastParsedLeaf != null && lastParsedCommand != null && |
115 | 120 | lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp && |
116 | 121 | lastParsedCommand.DefaultCommand != null) |
| 122 | + { |
| 123 | + // Adjust for any parsed remaining arguments by |
| 124 | + // inserting the the default command ahead of them. |
| 125 | + var position = tokenizerResult.Tokens.Position; |
| 126 | + foreach (var parsedRemaining in parsedResult.Remaining.Parsed) |
| 127 | + { |
| 128 | + position--; |
| 129 | + position -= parsedRemaining.Count(value => value != null); |
| 130 | + } |
| 131 | + position = position < 0 ? 0 : position; |
| 132 | + |
| 133 | + // Insert this branch's default command into the command line |
| 134 | + // arguments and try again to see if it will parse. |
| 135 | + var argsWithDefaultCommand = new List<string>(args); |
| 136 | + argsWithDefaultCommand.Insert(position, lastParsedCommand.DefaultCommand.Name); |
| 137 | + |
| 138 | + (parsedResult, tokenizerResult) = InternalParseCommandLineArguments(model, settings, argsWithDefaultCommand); |
| 139 | + } |
| 140 | + } |
| 141 | + catch (CommandParseException) when (parsedResult == null && settings.ParsingMode == ParsingMode.Strict) |
117 | 142 | { |
118 | | - // Insert this branch's default command into the command line |
119 | | - // arguments and try again to see if it will parse. |
120 | | - var argsWithDefaultCommand = new List<string>(args); |
| 143 | + // The parsing exception might be resolved by adding in the default command, |
| 144 | + // but we can't know for sure. Take a brute force approach and try this for |
| 145 | + // every position between the arguments. |
| 146 | + for (int i = 0; i < args.Count; i++) |
| 147 | + { |
| 148 | + var argsWithDefaultCommand = new List<string>(args); |
| 149 | + argsWithDefaultCommand.Insert(args.Count - i, "__default_command"); |
| 150 | + |
| 151 | + try |
| 152 | + { |
| 153 | + (parsedResult, tokenizerResult) = InternalParseCommandLineArguments(model, settings, argsWithDefaultCommand); |
121 | 154 |
|
122 | | - argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name); |
| 155 | + break; |
| 156 | + } |
| 157 | + catch (CommandParseException) |
| 158 | + { |
| 159 | + // Continue. |
| 160 | + } |
| 161 | + } |
123 | 162 |
|
124 | | - parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode); |
125 | | - tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand); |
126 | | - parsedResult = parser.Parse(parserContext, tokenizerResult); |
| 163 | + if (parsedResult == null) |
| 164 | + { |
| 165 | + // Failed to parse having inserted the default command between each argument. |
| 166 | + // Repeat the parsing of the original arguments to throw the correct exception. |
| 167 | + InternalParseCommandLineArguments(model, settings, args); |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + if (parsedResult == null) |
| 172 | + { |
| 173 | + // The arguments failed to parse despite everything we tried above. |
| 174 | + // Exceptions should be thrown above before ever getting this far, |
| 175 | + // however the following is the ulimately backstop and avoids |
| 176 | + // the compiler from complaining about returning null. |
| 177 | + throw CommandParseException.UnknownParsingError(); |
127 | 178 | } |
128 | 179 |
|
129 | 180 | return parsedResult; |
130 | 181 | } |
131 | 182 |
|
| 183 | + /// <summary> |
| 184 | + /// Parse the command line arguments using the specified <see cref="CommandModel"/> and <see cref="CommandAppSettings"/>, |
| 185 | + /// returning the parser and tokenizer results. |
| 186 | + /// </summary> |
| 187 | + /// <returns>The parser and tokenizer results as a tuple.</returns> |
| 188 | + private (CommandTreeParserResult ParserResult, CommandTreeTokenizerResult TokenizerResult) InternalParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args) |
| 189 | + { |
| 190 | + var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments); |
| 191 | + |
| 192 | + var parserContext = new CommandTreeParserContext(args, settings.ParsingMode); |
| 193 | + var tokenizerResult = CommandTreeTokenizer.Tokenize(args); |
| 194 | + var parsedResult = parser.Parse(parserContext, tokenizerResult); |
| 195 | + |
| 196 | + return (parsedResult, tokenizerResult); |
| 197 | + } |
| 198 | + |
132 | 199 | private static async Task<int> Execute( |
133 | 200 | CommandTree leaf, |
134 | 201 | CommandTree tree, |
|
0 commit comments