private TokenParseResult TryParseNamedArgument(string argument, string argumentPrefix, NamedArgumentType namedArgType, out IReadOnlyList <ArgumentAndValue> parsedArgs) { var prefixLength = argumentPrefix.Length; Debug.Assert(argument.Length >= prefixLength); // Figure out where the argument name ends. var endIndex = argument.IndexOfAny(_argumentSet.Attribute.ArgumentValueSeparators, prefixLength); // Special case: check for '+' and '-' for booleans. if (endIndex < 0 && argument.Length >= 2) { var lastArgumentChar = argument[argument.Length - 1]; if (ArgumentNameTerminators.Any(t => lastArgumentChar.Equals(t))) { endIndex = argument.Length - 1; } } // If we don't have a separator or terminator, then consume the full string. if (endIndex < 0) { endIndex = argument.Length; } // Extract the argument name(s), separate from the prefix // or optional argument value. var options = argument.Substring(prefixLength, endIndex - prefixLength); // Extract the option argument (a.k.a. value), if there is one. string optionArgument = null; if (argument.Length > prefixLength + options.Length) { // If there's an argument value separator, then extract the value after the separator. if (_argumentSet.Attribute.ArgumentValueSeparators.Any(sep => argument[prefixLength + options.Length] == sep)) { optionArgument = argument.Substring(prefixLength + options.Length + 1); } // Otherwise, it might be a terminator; extract the rest of the string. else { optionArgument = argument.Substring(prefixLength + options.Length); } } // Now try to figure out how many names are present. if (namedArgType == NamedArgumentType.ShortName && (_argumentSet.Attribute.AllowMultipleShortNamesInOneToken || _argumentSet.Attribute.AllowElidingSeparatorAfterShortName)) { Debug.Assert(_argumentSet.Attribute.ShortNamesAreOneCharacterLong); // Since short names are one character long, we parse them one at a // time, preparing for multiple arguments in this one token. var args = new List <ArgumentAndValue>(); for (var index = 0; index < options.Length; ++index) { // Try parsing it as a short name; bail immediately if we find an invalid // one. var possibleShortName = new string(options[index], 1); if (!_argumentSet.TryGetNamedArgument(possibleShortName, out ArgumentDefinition arg)) { parsedArgs = null; return(TokenParseResult.UnknownNamedArgument(namedArgType, possibleShortName)); } // If this parsed as a short name that takes a required option argument, // and we didn't see an option argument, and we allow mushing together // short names and their option arguments, then try parsing the rest of // this token as an option argument. var lastChar = index == options.Length - 1; if (arg.RequiresOptionArgument && _argumentSet.Attribute.AllowElidingSeparatorAfterShortName && optionArgument == null && !lastChar) { optionArgument = options.Substring(index + 1); index = options.Length - 1; lastChar = true; } if (!_argumentSet.Attribute.AllowMultipleShortNamesInOneToken && args.Count > 0) { parsedArgs = null; return(TokenParseResult.UnknownNamedArgument()); } args.Add(new ArgumentAndValue { Arg = arg, Value = lastChar ? optionArgument : null }); } parsedArgs = args; } else { // Try to look up the argument by name. if (!_argumentSet.TryGetNamedArgument(options, out ArgumentDefinition arg)) { parsedArgs = null; return(TokenParseResult.UnknownNamedArgument(namedArgType, options)); } parsedArgs = new[] { new ArgumentAndValue { Arg = arg, Value = optionArgument } }; } // If the last named argument we saw in this token required an // option argument to go with it, then yield that information // so it can be used by the caller (e.g. in completion generation). var lastArg = parsedArgs.GetLastOrDefault(); if (lastArg != null && lastArg.Arg.RequiresOptionArgument && string.IsNullOrEmpty(lastArg.Value)) { return(TokenParseResult.RequiresOptionArgument(lastArg.Arg)); } return(TokenParseResult.Ready); }
private TokenParseResult TryParseNextNamedArgument(IReadOnlyList <string> args, int index, string longNameArgumentPrefix, string shortNameArgumentPrefix, object destination, out int argsConsumed) { argsConsumed = 1; var argument = args[index]; var result = TokenParseResult.UnknownNamedArgument(); IReadOnlyList <ArgumentAndValue> parsedArgs = null; if (result.IsUnknown && longNameArgumentPrefix != null) { result = TryParseNamedArgument(argument, longNameArgumentPrefix, NamedArgumentType.LongName, out parsedArgs); } if (result.IsUnknown && shortNameArgumentPrefix != null) { result = TryParseNamedArgument(argument, shortNameArgumentPrefix, NamedArgumentType.ShortName, out parsedArgs); } // If our policy allows a named argument's value to be placed // in the following token, and if we're missing a required // value, and if there's at least one more token, then try // to parse the next token as the current argument's value. if (result.State == TokenParseResultState.RequiresOptionArgument && _argumentSet.Attribute.AllowNamedArgumentValueAsSucceedingToken && index + 1 < args.Count) { var lastParsedArg = parsedArgs.GetLast(); Debug.Assert(lastParsedArg.Arg.RequiresOptionArgument); Debug.Assert(!lastParsedArg.Arg.TakesRestOfLine); Debug.Assert(string.IsNullOrEmpty(parsedArgs.GetLast().Value)); ++index; ++argsConsumed; lastParsedArg.Value = args[index]; result = TokenParseResult.Ready; } if (!result.IsReady) { ReportUnrecognizedArgument(result, argument); return(result); } foreach (var parsedArg in parsedArgs) { // TODO: Obviate the need to use string.Empty here. var argValue = parsedArg.Value ?? string.Empty; if (parsedArg.Arg.TakesRestOfLine) { if (!parsedArg.Arg.TrySetRestOfLine(argValue, args.Skip(index + 1), destination)) { result = TokenParseResult.FailedParsing; continue; } argsConsumed = args.Count - index; // skip the rest of the line } else { if (!TryParseAndStore(parsedArg.Arg, argValue, destination)) { result = TokenParseResult.FailedParsing; continue; } } } return(result); }