public static bool TryProcess(CommandInputSpecification spec, TParseResult parseResult, out DefaultCommandInput <TParseResult> result, out IReadOnlyList <CommandInputProcessingIssue> processingIssues)
        {
            spec = spec ?? throw new ArgumentNullException(nameof(spec));

            List <CommandInputProcessingIssue> issues = null;
            List <InputElement> commandNameElements   = null;

            foreach (IReadOnlyList <string> commandName in spec.CommandName)
            {
                if (TryProcessCommandName(commandName, parseResult, out List <InputElement> nameElements, out issues))
                {
                    commandNameElements = nameElements;
                    break;
                }
            }

            if (commandNameElements is null)
            {
                result           = null;
                processingIssues = issues;
                return(false);
            }

            List <InputElement> arguments = new List <InputElement>();
            Dictionary <InputElement, InputElement> options = new Dictionary <InputElement, InputElement>();
            InputElement currentOption = null;
            CommandOptionSpecification currentOptionSpec = null;
            InputElement selectedElement = null;

            for (int i = spec.CommandName.Count; i < parseResult.Sections.Count; ++i)
            {
                //If we're not looking at an option name
                if (!parseResult.Sections[i].StartsWith(spec.OptionPreamble.ToString(), StringComparison.OrdinalIgnoreCase) || parseResult.IsQuotedSection(i))
                {
                    if (currentOption is null)
                    {
                        InputElement currentElement = new InputElement(CommandInputLocation.Argument, parseResult.Sections[i], parseResult.Sections[i], i);

                        if (i == parseResult.SelectedSection)
                        {
                            selectedElement = currentElement;
                        }

                        arguments.Add(currentElement);
                    }
                    else
                    {
                        //If the option isn't a defined one or it is and indicates that it accepts a value, add the section as an option value,
                        //  otherwise add it as an argument
                        if (currentOptionSpec?.AcceptsValue ?? true)
                        {
                            InputElement currentElement = new InputElement(currentOption, CommandInputLocation.OptionValue, parseResult.Sections[i], parseResult.Sections[i], i);

                            if (i == parseResult.SelectedSection)
                            {
                                selectedElement = currentElement;
                            }

                            options[currentOption] = currentElement;
                            currentOption          = null;
                            currentOptionSpec      = null;
                        }
                        else
                        {
                            InputElement currentElement = new InputElement(CommandInputLocation.Argument, parseResult.Sections[i], parseResult.Sections[i], i);

                            if (i == parseResult.SelectedSection)
                            {
                                selectedElement = currentElement;
                            }

                            arguments.Add(currentElement);
                        }
                    }
                }
                //If we are looking at an option name
                else
                {
                    //Otherwise, check to see whether the previous option had a required argument before committing it
                    if (currentOption is object)
                    {
                        options[currentOption] = null;

                        if (currentOptionSpec?.RequiresValue ?? false)
                        {
                            issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.MissingRequiredOptionInput, currentOption.Text));
                        }
                    }

                    CommandOptionSpecification optionSpec = spec.Options.FirstOrDefault(x => x.Forms.Any(y => string.Equals(y, parseResult.Sections[i], StringComparison.Ordinal)));

                    if (optionSpec is null)
                    {
                        issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.UnknownOption, parseResult.Sections[i]));
                    }

                    currentOption = new InputElement(CommandInputLocation.OptionName, parseResult.Sections[i], optionSpec?.Id, i);

                    if (i == parseResult.SelectedSection)
                    {
                        selectedElement = currentOption;
                    }

                    currentOptionSpec = optionSpec;
                }
            }

            //Clear any option in progress
            if (currentOption is object)
            {
                options[currentOption] = null;

                if (currentOptionSpec?.RequiresValue ?? false)
                {
                    issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.MissingRequiredOptionInput, currentOption.Text));
                }
            }

            //Check to make sure our argument count is in range, if not add an issue
            if (arguments.Count > spec.MaximumArguments || arguments.Count < spec.MinimumArguments)
            {
                issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.ArgumentCountOutOfRange, arguments.Count.ToString()));
            }

            //Build up the dictionary of options by normal form, then validate counts for every option in the spec
            Dictionary <string, IReadOnlyList <InputElement> > optionsByNormalForm = new Dictionary <string, IReadOnlyList <InputElement> >(StringComparer.Ordinal);

            foreach (KeyValuePair <InputElement, InputElement> entry in options)
            {
                if (entry.Key.NormalizedText is null)
                {
                    continue;
                }

                if (!optionsByNormalForm.TryGetValue(entry.Key.NormalizedText, out IReadOnlyList <InputElement> rawBucket))
                {
                    optionsByNormalForm[entry.Key.NormalizedText] = rawBucket = new List <InputElement>();
                }

                List <InputElement> bucket = (List <InputElement>)rawBucket;
                bucket.Add(entry.Value);
            }

            foreach (CommandOptionSpecification optionSpec in spec.Options)
            {
                if (!optionsByNormalForm.TryGetValue(optionSpec.Id, out IReadOnlyList <InputElement> values))
                {
                    optionsByNormalForm[optionSpec.Id] = values = new List <InputElement>();
                }

                if (values.Count < optionSpec.MinimumOccurrences || values.Count > optionSpec.MaximumOccurrences)
                {
                    issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.OptionUseCountOutOfRange, values.Count.ToString()));
                }
            }

            result           = new DefaultCommandInput <TParseResult>(commandNameElements, arguments, optionsByNormalForm, selectedElement);
            processingIssues = issues;
            return(issues.Count == 0);
        }
 protected abstract string GetHelpDetails(IShellState shellState, TProgramState programState, DefaultCommandInput <TParseResult> commandInput, TParseResult parseResult);
        public IEnumerable <string> Suggest(IShellState shellState, TProgramState programState, TParseResult parseResult)
        {
            DefaultCommandInput <TParseResult> .TryProcess(InputSpec, parseResult, out DefaultCommandInput <TParseResult> commandInput, out IReadOnlyList <CommandInputProcessingIssue> _);

            string normalCompletionString = parseResult.SelectedSection == parseResult.Sections.Count
                ? string.Empty
                : parseResult.Sections[parseResult.SelectedSection].Substring(0, parseResult.CaretPositionWithinSelectedSection);

            //If we're completing in a name position, offer completion for the command name
            if (parseResult.SelectedSection < InputSpec.CommandName.Count)
            {
                IReadOnlyList <string> commandName = null;
                for (int j = 0; j < InputSpec.CommandName.Count; ++j)
                {
                    bool success = true;
                    for (int i = 0; i < parseResult.SelectedSection; ++i)
                    {
                        if (!string.Equals(InputSpec.CommandName[j][i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase))
                        {
                            success = false;
                            break;
                        }
                    }

                    if (success)
                    {
                        commandName = InputSpec.CommandName[j];
                        break;
                    }
                }

                if (commandName is null)
                {
                    return(null);
                }

                if (commandName[parseResult.SelectedSection].StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase))
                {
                    return(new[] { commandName[parseResult.SelectedSection] });
                }
            }

            if (commandInput is null)
            {
                return(null);
            }

            if (normalCompletionString.StartsWith(InputSpec.OptionPreamble.ToString()))
            {
                return(GetOptionCompletions(commandInput, normalCompletionString));
            }

            IEnumerable <string> completions   = Enumerable.Empty <string>();
            CommandInputLocation?inputLocation = commandInput.SelectedElement?.Location;

            if (inputLocation != CommandInputLocation.OptionValue && commandInput.Arguments.Count < InputSpec.MaximumArguments)
            {
                IEnumerable <string> results = GetArgumentSuggestionsForText(shellState, programState, parseResult, commandInput, normalCompletionString);

                if (results != null)
                {
                    completions = results;
                }
            }

            switch (inputLocation)
            {
            case CommandInputLocation.OptionName:
            {
                IEnumerable <string> results = GetOptionCompletions(commandInput, normalCompletionString);

                if (results != null)
                {
                    completions = completions.Union(results);
                }

                break;
            }

            case CommandInputLocation.OptionValue:
            {
                IEnumerable <string> results = GetOptionValueCompletions(shellState, programState, commandInput.SelectedElement.Owner.NormalizedText, commandInput, parseResult, normalCompletionString);

                if (results != null)
                {
                    completions = completions.Union(results);
                }

                break;
            }

            case CommandInputLocation.Argument:
            {
                IEnumerable <string> argumentResults = GetArgumentSuggestionsForText(shellState, programState, parseResult, commandInput, normalCompletionString);

                if (argumentResults != null)
                {
                    completions = completions.Union(argumentResults);
                }

                if (string.IsNullOrEmpty(normalCompletionString))
                {
                    IEnumerable <string> results = GetOptionCompletions(commandInput, normalCompletionString);

                    if (results != null)
                    {
                        completions = completions.Union(results);
                    }
                }

                break;
            }
            }

            return(completions);
        }
 protected abstract Task ExecuteAsync(IShellState shellState, TProgramState programState, DefaultCommandInput <TParseResult> commandInput, TParseResult parseResult, CancellationToken cancellationToken);
 protected virtual bool CanHandle(IShellState shellState, TProgramState programState, DefaultCommandInput <TParseResult> commandInput)
 {
     return(true);
 }
 private IEnumerable <string> GetOptionCompletions(DefaultCommandInput <TParseResult> commandInput, string normalCompletionString)
 {
     return(InputSpec.Options.Where(x => commandInput.Options[x.Id].Count < x.MaximumOccurrences)
            .SelectMany(x => x.Forms)
            .Where(x => x.StartsWith(normalCompletionString, StringComparison.OrdinalIgnoreCase)));
 }
 protected virtual IEnumerable <string> GetArgumentSuggestionsForText(IShellState shellState, TProgramState programState, TParseResult parseResult, DefaultCommandInput <TParseResult> commandInput, string normalCompletionString)
 {
     return(null);
 }
 protected virtual IEnumerable <string> GetOptionValueCompletions(IShellState shellState, TProgramState programState, string optionId, DefaultCommandInput <TParseResult> commandInput, TParseResult parseResult, string normalizedCompletionText)
 {
     return(null);
 }