private void ReportUnrecognizedArgument(ArgumentSetParseResult result, string argument) { switch (result.State) { case ArgumentSetParseResultType.UnknownNamedArgument: ReportLine(Strings.UnrecognizedArgument, argument); if (!string.IsNullOrEmpty(result.NamedArg)) { var possibleArgs = GetSimilarNamedArguments(result.NamedArgType, result.NamedArg).ToList(); if (possibleArgs.Any()) { ReportLine( " " + (possibleArgs.Count == 1 ? Strings.PossibleIntendedNamedArgument : Strings.PossibleIntendedNamedArguments), string.Join(", ", possibleArgs.Select(a => "'" + a + "'"))); } } break; case ArgumentSetParseResultType.UnknownPositionalArgument: ReportLine(Strings.UnrecognizedArgument, argument); break; case ArgumentSetParseResultType.RequiresOptionArgument: ReportLine(Strings.MissingRequiredOptionArgument, argument); break; } }
/// <summary> /// Tries to finalize parsing to the given output object. /// </summary> /// <param name="destination">Output object.</param> /// <param name="parseResult">Parse result.</param> /// <returns>Parse result.</returns> public ArgumentSetParseResult Finalize(object destination, ArgumentSetParseResult parseResult) { // Finalize all arguments: named args first, then positional default args. var result = parseResult; foreach (var arg in ArgumentSet.NamedArguments.Concat(ArgumentSet.PositionalArguments)) { var argState = GetStateForArgument(arg, destination); if (!argState.TryFinalize(_options.FileSystemReader)) { result = ArgumentSetParseResult.FailedFinalizing; } } return(result); }
/// <summary> /// Parses an argument list into an object. /// </summary> /// <param name="args">String arguments to parse.</param> /// <param name="destination">Output arguments object.</param> /// <returns>Parse result.</returns> public ArgumentSetParseResult ParseTokens(IEnumerable <string> args, object destination) { var result = ArgumentSetParseResult.Ready(null); IReadOnlyList <string> argsList = args.ToList(); for (var index = 0; index < argsList.Count;) { var currentResult = TryParseNextToken(argsList, index, destination, out int argsConsumed); if (!currentResult.IsReady) { result = currentResult; } else if (result.IsReady) { result = currentResult; } index += argsConsumed; } return(result); }
private ArgumentSetParseResult TryParseNextPositionalArgument(IReadOnlyList <string> args, int index, object destination, out int argsConsumed) { var argument = args[index]; argsConsumed = 1; if (!ArgumentSet.TryGetPositionalArgument(NextPositionalArgIndexToParse, out ArgumentDefinition positionalArg)) { var result = ArgumentSetParseResult.UnknownPositionalArgument; ReportUnrecognizedArgument(result, argument); return(result); } if (!positionalArg.AllowMultiple) { NextPositionalArgIndexToParse += 1; } var argState = GetStateForArgument(positionalArg, destination); if (positionalArg.TakesRestOfLine) { if (!argState.TrySetRestOfLine(args.Skip(index))) { return(ArgumentSetParseResult.FailedParsing); } argsConsumed = args.Count - index; // skip the rest of the line return(ArgumentSetParseResult.Ready(positionalArg)); } else { Debug.Assert(argument != null); return(TryParseAndStore(argState, argument) ? ArgumentSetParseResult.Ready(positionalArg) : ArgumentSetParseResult.FailedParsing); } }
private ArgumentSetParseResult TryParseNamedArgument( string argument, string argumentPrefix, ArgumentNameType 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 > argumentPrefix.Length) { 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 == ArgumentNameType.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(ArgumentNameType.ShortName, possibleShortName, out ArgumentDefinition arg)) { parsedArgs = null; return(ArgumentSetParseResult.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.RequiresOptionArgumentEx(_options) && 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(ArgumentSetParseResult.UnknownNamedArgument()); } args.Add(new ArgumentAndValue { Arg = arg, Value = lastChar ? optionArgument : null }); } // Special case: if no arguments were found, return an error. if (args.Count == 0) { parsedArgs = null; return(ArgumentSetParseResult.FailedParsing); } parsedArgs = args; } else { // Try to look up the argument by name. if (!ArgumentSet.TryGetNamedArgument(namedArgType, options, out ArgumentDefinition arg)) { parsedArgs = null; return(ArgumentSetParseResult.UnknownNamedArgument(namedArgType, options)); } parsedArgs = new[] { new ArgumentAndValue { Arg = arg, Value = optionArgument } }; } var lastArg = parsedArgs.GetLastOrDefault(); var lastArgTakesRestOfLine = lastArg?.Arg.TakesRestOfLine ?? false; // 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). if (lastArg != null && lastArg.Arg.RequiresOptionArgumentEx(_options) && string.IsNullOrEmpty(lastArg.Value)) { return(ArgumentSetParseResult.RequiresOptionArgument(lastArg.Arg)); } return(ArgumentSetParseResult.Ready(lastArg?.Arg)); }
private ArgumentSetParseResult TryParseNextNamedArgument(IReadOnlyList <string> args, int index, string longNameArgumentPrefix, string shortNameArgumentPrefix, object destination, out int argsConsumed) { argsConsumed = 1; var argument = args[index]; var result = ArgumentSetParseResult.UnknownNamedArgument(); IReadOnlyList <ArgumentAndValue> parsedArgs = null; if (result.IsUnknown && longNameArgumentPrefix != null) { result = TryParseNamedArgument(argument, longNameArgumentPrefix, ArgumentNameType.LongName, out parsedArgs); } if (result.IsUnknown && shortNameArgumentPrefix != null) { result = TryParseNamedArgument(argument, shortNameArgumentPrefix, ArgumentNameType.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 == ArgumentSetParseResultType.RequiresOptionArgument && ArgumentSet.Attribute.AllowNamedArgumentValueAsSucceedingToken && index + 1 < args.Count) { var lastParsedArg = parsedArgs.GetLast(); Debug.Assert(lastParsedArg.Arg.RequiresOptionArgumentEx(_options)); Debug.Assert(string.IsNullOrEmpty(parsedArgs.GetLast().Value)); ++index; ++argsConsumed; lastParsedArg.Value = args[index]; result = ArgumentSetParseResult.Ready(lastParsedArg.Arg); } if (!result.IsReady) { ReportUnrecognizedArgument(result, argument); return(result); } Debug.Assert(parsedArgs != null); foreach (var parsedArg in parsedArgs) { // TODO: Obviate the need to use string.Empty here. var argValue = parsedArg.Value ?? string.Empty; var argState = GetStateForArgument(parsedArg.Arg, destination); if (parsedArg.Arg.TakesRestOfLine) { if (!argState.TrySetRestOfLine(new[] { argValue }.Concat(args.Skip(index + 1)))) { result = ArgumentSetParseResult.FailedParsing; continue; } argsConsumed += args.Count - index; // skip the rest of the line } else { if (!TryParseAndStore(argState, argValue)) { result = ArgumentSetParseResult.FailedParsing; continue; } } } return(result); }