예제 #1
0
 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);
 }
예제 #2
0
        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);
        }
예제 #3
0
        /*
         * /// <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);
                }
            }
        }
예제 #4
0
        /// <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);
        }
예제 #5
0
        /// <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);
        }