// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using Microsoft.Repl.Parsing; namespace Microsoft.Repl.Commanding { public class DefaultCommandInput where TParseResult : ICoreParseResult { public DefaultCommandInput(IReadOnlyList commandName, IReadOnlyList arguments, IReadOnlyDictionary> options, InputElement selectedElement) { CommandName = commandName; Arguments = arguments; Options = options; SelectedElement = selectedElement; } public static bool TryProcess(CommandInputSpecification spec, TParseResult parseResult, out DefaultCommandInput result, out IReadOnlyList processingIssues) { List issues = null; List commandNameElements = null; foreach (IReadOnlyList commandName in spec.CommandName) { if (TryProcessCommandName(commandName, parseResult, out List nameElements, out issues)) { commandNameElements = nameElements; break; } } if (commandNameElements is null) { result = null; processingIssues = issues; return false; } List arguments = new List(); Dictionary options = new Dictionary(); 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) || 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 null)) { 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 null)) { 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> optionsByNormalForm = new Dictionary>(StringComparer.Ordinal); foreach (KeyValuePair entry in options) { if (entry.Key.NormalizedText is null) { continue; } if (!optionsByNormalForm.TryGetValue(entry.Key.NormalizedText, out IReadOnlyList rawBucket)) { optionsByNormalForm[entry.Key.NormalizedText] = rawBucket = new List(); } List bucket = (List) rawBucket; bucket.Add(entry.Value); } foreach (CommandOptionSpecification optionSpec in spec.Options) { if (!optionsByNormalForm.TryGetValue(optionSpec.Id, out IReadOnlyList values)) { optionsByNormalForm[optionSpec.Id] = values = new List(); } if (values.Count < optionSpec.MinimumOccurrences || values.Count > optionSpec.MaximumOccurrences) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.OptionUseCountOutOfRange, values.Count.ToString())); } } result = new DefaultCommandInput(commandNameElements, arguments, optionsByNormalForm, selectedElement); processingIssues = issues; return issues.Count == 0; } private static bool TryProcessCommandName(IReadOnlyList commandName, TParseResult parseResult, out List nameElements, out List processingIssues) { List issues = new List(); List commandNameElements = new List(); if (commandName.Count > parseResult.Sections.Count) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, commandName[parseResult.Sections.Count])); } for (int i = 0; i < commandName.Count && i < parseResult.Sections.Count; ++i) { if (!string.Equals(commandName[i], parseResult.Sections[i], StringComparison.OrdinalIgnoreCase)) { issues.Add(new CommandInputProcessingIssue(CommandInputProcessingIssueKind.CommandMismatch, parseResult.Sections[i])); } commandNameElements.Add(new InputElement(CommandInputLocation.CommandName, parseResult.Sections[i], commandName[i], i)); } processingIssues = issues; //If we have a command name mismatch, no point in continuing if (issues.Count > 0) { nameElements = null; return false; } nameElements = commandNameElements; return true; } public InputElement SelectedElement { get; } public IReadOnlyList CommandName { get; } public IReadOnlyList Arguments { get; } public IReadOnlyDictionary> Options { get; } } }