internal ParseResult(ParseRun run) { _run = run; Command = run.Commands[run.Commands.Count - 1]; Arguments = run.Arguments .Select(ar => ar.Value) .ToList(); Options = run.Options .ToDictionary(rootOptionRun => rootOptionRun.Option.Name, rootOptionRun => rootOptionRun.Value); }
private ParseRun CreateRun(IReadOnlyList <string> tokens) { var run = new ParseRun(); run.Commands.Add(Command); Command currentCommand = Command; // Go through the tokens and find all the subcommands and their arguments and options. for (int i = 0; i < tokens.Count; i++) { string token = tokens[i]; // All options for the current command are considered. So, add them all. // This is not the case for arguments. Only the arguments for the innermost command are considered. //run.Options.AddRange(currentCommand.Options.Select(o => new OptionRun(o, currentCommand))); // Check if subcommand exists under the current command with the token as a name. Command subcommand = currentCommand.Commands[token]; if (subcommand != null) { // Add the command and move to the next command level. run.Commands.Add(subcommand); currentCommand = subcommand; } else { // All tokens from the current token are considered the args for the command. run.Tokens = new List <string>(tokens.Count - i + 1); for (int j = i; j < tokens.Count; j++) { run.Tokens.Add(tokens[j]); } // We're done with subcommands. Break out of this loop. break; } } // Add all the options for the current command. run.Options.AddRange(currentCommand.Options.Select(o => new OptionRun(o))); // Add all the arguments for the current command. run.Arguments.AddRange(currentCommand.Arguments.Select(a => new ArgumentRun(a))); // To avoid null-ref exceptions in case no tokens are specified, assign run.Tokens if it // is null. if (run.Tokens is null) { run.Tokens = new List <string>(0); } return(run); }
/* * /// <summary> * /// Figure out the groups that match the specified options and arguments. * /// <para/> * /// Only arguments and options that have those groups will be considered for further * /// processing. * /// </summary> * /// <param name="run"> * /// The <see cref="ParseRun"/> instance that contains the specified options. * /// </param> * /// <param name="specifiedArguments">The specified arguments.</param> * /// <exception cref="ArgumentNullException"> * /// Thrown if the <paramref name="specifiedArguments"/> is <c>null</c>. * /// </exception> * private IReadOnlyList<int> GetMatchingGroups(ParseRun run, IList<string> specifiedArguments) * { * // Get the option runs for only the specified options. * IEnumerable<OptionRun> specifiedOptions = run.Options.Where(or => or.Occurrences > 0); * * // For the specified option runs and all the argument runs, get the distinct set of groups. * IEnumerable<int> groups = specifiedOptions * .SelectMany(or => or.Option.Groups) * .Union(run.Arguments.SelectMany(ar => ar.Argument.Groups)) * .Distinct(); * * // Find the common groups across all specified option runs. * foreach (OptionRun or in specifiedOptions) * groups = groups.Intersect(or.Option.Groups); * * // In case no groups remain, we have two possibilities. * if (!groups.Any()) * { * // Check if all options and arguments are optional. * bool allArgsOptional = run.Options.All(or => or.Option.Usage.MinOccurrences == 0) && run.Arguments.All(ar => ar.Argument.IsOptional); * * // If all options and arguments are optional, then no args were specified for this * // command, so simply return a default of group 0. * if (allArgsOptional) * return new[] { 0 }; * * // If at least one option or argument is required, then this is an invalid scenario and we should throw an exception. * // Return an empty list to indicate an error. * return new List<int>(0); * } * * // For the remaining groups, check whether the number of specified arguments falls in the * // range of the argument runs that have the group. * // Add any matching groups to a list. * var matchingArgumentGroups = new List<int>(); * foreach (int group in groups) * { * // Find all argument runs that have the group. * IList<ArgumentRun> groupArguments = run.Arguments * .Where(ar => ar.Argument.Groups.Contains(group)) * .ToList(); * * // Calculate the minimum and maximum possible number of arguments that can be specified, * // based on the selected argument groups. * int minOccurences = groupArguments.Count(ar => !ar.Argument.IsOptional); * int maxOccurences = groupArguments.Count == 0 ? 0 * : groupArguments.Count + groupArguments[groupArguments.Count - 1].Argument.MaxOccurences - 1; * * // If the number of specified arguments falls into the calculated range, then this * // group is valid, so add it to the list. * if (specifiedArguments.Count >= minOccurences && specifiedArguments.Count <= maxOccurences) * matchingArgumentGroups.Add(group); * } * * return matchingArgumentGroups; * }*/ private void TrimRunsToMatchingGroups(ParseRun run, IReadOnlyList <int> matchingGroups) { for (int i = run.Options.Count - 1; i >= 0; i--) { if (!run.Options[i].Option.Groups.Any(grp => matchingGroups.Contains(grp))) { run.Options.RemoveAt(i); } } for (int i = run.Arguments.Count - 1; i >= 0; i--) { if (!run.Arguments[i].Argument.Groups.Any(grp => matchingGroups.Contains(grp))) { run.Arguments.RemoveAt(i); } } }
/// <summary> /// Parses the given set of tokens based on the rules specified by the <see cref="Command"/>. /// </summary> /// <param name="tokens">Token strings to parse.</param> /// <returns>A <see cref="ParseResult" /> instance.</returns> public ParseResult Parse(IEnumerable <string> tokens) { DebugOutput.Write("Tokens passed", tokens); IReadOnlyList <string> tokenList = tokens.ToList(); // Creates a ParseRun instance, which specifies the sequence of commands specified and // the tokens and any options and arguments that apply to the specified commands. ParseRun run = CreateRun(tokenList); // Extract out just the option and argument objects from their respective run collections. // We want to pass these to parser style methods that only need to deal with the Option // and Argument objects and not have to deal with the 'run' aspects. See some of the calls // to protected methods from this method. IReadOnlyList <Option> justOptions = run.Options.Select(o => o.Option).ToList(); IReadOnlyList <Argument> justArguments = run.Arguments.Select(a => a.Argument).ToList(); // Even though the caller can define the grouping, the parser style can override it based // on the available options and arguments. See the UnixArgStyle class for an example. Grouping = ArgStyle.GetGrouping(Grouping, justOptions, justArguments); // Validate all the available options based on the parser style rules. // See the UnixArgStyle for an example. ArgStyle.ValidateDefinedOptions(justOptions); // Identify all tokens as options or arguments. Identified option details are stored in // the Option instance itself. Identified arguments are returned from the method. List <string> specifiedArguments = ArgStyle.IdentifyTokens(run.Tokens, run.Options, Grouping).ToList(); // Get the groups that match the specified options and arguments. IReadOnlyList <int> matchingGroups = GetMatchingGroups(run, specifiedArguments); // If no groups match, we cannot proceed, so throw a parser exception. if (matchingGroups.Count == 0) { throw new ParserException(-1, "The specified arguments and options are invalid."); } // Trim the argument and option runs to those that contain the matcing groups. TrimRunsToMatchingGroups(run, matchingGroups); //TODO: Come back to this later //if (ConfigReader != null) // specifiedArguments = ConfigReader.Run(specifiedArguments, Options); // Process the specified options and arguments, and resolve their values. ProcessOptions(run.Options); ProcessArguments(specifiedArguments, run.Arguments); var parseResult = new ParseResult(run); // Runs the custom validator, if it is assigned. CommandCustomValidator customCommandValidator = parseResult.Command.CustomValidator; string validationError = customCommandValidator?.Invoke(parseResult.Arguments, parseResult.Options); if (validationError != null) { throw new ValidationException(validationError, null, null); } return(parseResult); }
/// <summary> /// Figure out the groups that match the specified options and arguments. /// <para/> /// Only arguments and options that have those groups will be considered for further /// processing. /// </summary> /// <param name="run"> /// The <see cref="ParseRun"/> instance that contains the specified options. /// </param> /// <param name="specifiedArguments">The specified arguments.</param> /// <exception cref="ParserException"> /// Thrown if args from different groups are specified. /// </exception> private IReadOnlyList <int> GetMatchingGroups(ParseRun run, IList <string> specifiedArguments) { IReadOnlyList <OptionRun> specifiedOptions = run.Options.Where(or => or.Occurrences > 0).ToList(); IEnumerable <int> groups = null; if (specifiedOptions.Count > 0) { groups = specifiedOptions[0].Option.Groups; if (specifiedOptions.Count > 1) { for (int i = 1; i < specifiedOptions.Count; i++) { groups = groups.Intersect(specifiedOptions[i].Option.Groups); if (!groups.Any()) { throw new ParserException(-1, $"The '{specifiedOptions[i - 1].Option.Name}' option cannot be specified with the '{specifiedOptions[i].Option.Name}'."); } } } } if (groups is null) { groups = run.Arguments.SelectMany(ar => ar.Argument.Groups).Distinct(); } var matchingGroups = new List <int>(); foreach (int group in groups) { // Find all argument runs that have the group. IList <ArgumentRun> groupArguments = run.Arguments .Where(ar => ar.Argument.Groups.Contains(group)) .ToList(); // Calculate the minimum and maximum possible number of arguments that can be specified, // based on the selected argument groups. int minOccurences = groupArguments.Count(ar => !ar.Argument.IsOptional); int maxOccurences = groupArguments.Count == 0 ? 0 : groupArguments.Count + groupArguments[groupArguments.Count - 1].Argument.MaxOccurences - 1; // If the number of specified arguments falls into the calculated range, then this // group is valid, so add it to the list. if (specifiedArguments.Count >= minOccurences && specifiedArguments.Count <= maxOccurences) { matchingGroups.Add(group); } } // In case no groups remain, we have two possibilities. if (matchingGroups.Count == 0) { // Check if all options and arguments are optional. bool allArgsOptional = run.Options.All(or => or.Option.Usage.MinOccurrences == 0) && run.Arguments.All(ar => ar.Argument.IsOptional); // If all options and arguments are optional, then no args were specified for this // command, so simply return a default of group 0. if (allArgsOptional) { return new[] { 0 } } ; // If at least one option or argument is required, then this is an invalid scenario and we should throw an exception. throw new ParserException(-1, "You have specified args that do not go together."); } return(matchingGroups); }