/// <summary>
        /// Returns suggestions given the user input.
        /// </summary>
        /// <param name="inputCommandName">The command name extracted from the user input.</param>
        /// <param name="inputParameterSet">The parameter set extracted from the user input.</param>
        /// <param name="rawUserInput">The string format of the command line from user input.</param>
        /// <param name="presentCommands">Commands already present. Contents may be added to this collection.</param>
        /// <param name="suggestionCount">The number of suggestions to return.</param>
        /// <param name="maxAllowedCommandDuplicate">The maximum amount of the same commands in the list of predictions.</param>
        /// <param name="cancellationToken">The cancellation token</param>
        /// <returns>The collections of suggestions.</returns>
        public CommandLineSuggestion GetSuggestion(string inputCommandName,
                                                   ParameterSet inputParameterSet,
                                                   string rawUserInput,
                                                   IDictionary <string, int> presentCommands,
                                                   int suggestionCount,
                                                   int maxAllowedCommandDuplicate,
                                                   CancellationToken cancellationToken)
        {
            Validation.CheckArgument(!string.IsNullOrWhiteSpace(inputCommandName), $"{nameof(inputCommandName)} cannot be null or whitespace.");
            Validation.CheckArgument(inputParameterSet, $"{nameof(inputParameterSet)} cannot be null.");
            Validation.CheckArgument(!string.IsNullOrWhiteSpace(rawUserInput), $"{nameof(rawUserInput)} cannot be null or whitespace.");
            Validation.CheckArgument(presentCommands, $"{nameof(presentCommands)} cannot be null.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(suggestionCount > 0, $"{nameof(suggestionCount)} must be larger than 0.");
            Validation.CheckArgument <ArgumentOutOfRangeException>(maxAllowedCommandDuplicate > 0, $"{nameof(maxAllowedCommandDuplicate)} must be larger than 0.");

            const int             commandCollectionCapacity = 10;
            CommandLineSuggestion result = new();
            var duplicateResults         = new Dictionary <string, DuplicateResult>(commandCollectionCapacity, StringComparer.OrdinalIgnoreCase);

            var isCommandNameComplete = (inputParameterSet.Parameters.Count > 0) || rawUserInput.EndsWith(' ');

            Func <string, bool> commandNameQuery = (command) => command.Equals(inputCommandName, StringComparison.OrdinalIgnoreCase);

            if (!isCommandNameComplete)
            {
                commandNameQuery = (command) => command.StartsWith(inputCommandName, StringComparison.OrdinalIgnoreCase);
            }

            // Try to find the matching command and arrange the parameters in the order of the input.
            //
            // Predictions should be flexible, e.g. if "Command -Name N -Location L" is a possibility,
            // then "Command -Location L -Name N" should also be possible.
            //
            // resultBuilder and usedParams are used to store the information to construct the result.
            // We want to avoid too much heap allocation for the performance purpose.

            const int parameterCollectionCapacity = 10;
            var       resultBuilder = new StringBuilder();
            var       usedParams    = new HashSet <int>(parameterCollectionCapacity);

            for (var i = 0; i < _commandLinePredictions.Count && result.Count < suggestionCount; ++i)
            {
                if (commandNameQuery(_commandLinePredictions[i].Name))
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    resultBuilder.Clear();
                    resultBuilder.Append(_commandLinePredictions[i].Name);
                    usedParams.Clear();
                    string commandNoun = ParameterValuePredictor.GetCommandNoun(_commandLinePredictions[i].Name)?.ToLower();

                    if (DoesPredictionParameterSetMatchInput(resultBuilder, inputParameterSet, commandNoun, _commandLinePredictions[i].ParameterSet, usedParams))
                    {
                        PredictRestOfParameters(resultBuilder, commandNoun, _commandLinePredictions[i].ParameterSet.Parameters, usedParams);

                        if (resultBuilder.Length <= rawUserInput.Length)
                        {
                            // We don't add anything to to the raw user input. So skip this.
                            continue;
                        }

                        var prediction = resultBuilder.ToString();

                        if (!presentCommands.ContainsKey(_commandLinePredictions[i].Name))
                        {
                            result.AddSuggestion(new PredictiveSuggestion(prediction, _commandLinePredictions[i].Description), _commandLinePredictions[i].SourceText);
                            presentCommands.Add(_commandLinePredictions[i].Name, 1);
                        }
                        else if (presentCommands[_commandLinePredictions[i].Name] < maxAllowedCommandDuplicate)
                        {
                            result.AddSuggestion(new PredictiveSuggestion(prediction, _commandLinePredictions[i].Description), _commandLinePredictions[i].SourceText);
                            presentCommands[_commandLinePredictions[i].Name] += 1;
                        }
                        else
                        {
                            _ = duplicateResults.TryAdd(prediction, new DuplicateResult(_commandLinePredictions[i].SourceText, _commandLinePredictions[i].Description));
                        }
                    }
                }
            }

            var resultCount = result.Count;

            if ((resultCount < suggestionCount) && (duplicateResults.Count > 0))
            {
                foreach (var temp in duplicateResults.Take(suggestionCount - resultCount))
                {
                    result.AddSuggestion(new PredictiveSuggestion(temp.Key, temp.Value.Description), temp.Value.Source);
                }
            }

            return(result);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Iterate over command elements to extract local parameter values.
        ///
        /// Store these values by a key
        /// consisting of the suffix of the command + the parameter name.  There are some exceptions, e.g.
        /// credential, location, where the parameter name itself is the key.
        ///
        /// For example, New-AzResourceGroup -Name Hello -Location 'EastUS' will store into local parameters:
        ///   ResourceGroupName => Hello
        ///   Location => 'EastUS'
        /// </summary>
        /// <param name="command">The command ast element</param>
        /// <remarks>
        /// This doesn't support positional parameter.
        /// </remarks>
        private void ExtractLocalParameters(CommandAst command)
        {
            // Azure PowerShell command is in the form of {Verb}-Az{Noun}, e.g. New-AzResource.
            // We need to extract the noun to construct the parameter name.

            var commandName = command.GetCommandName();
            var commandNoun = ParameterValuePredictor.GetCommandNoun(commandName)?.ToLower();

            if (commandNoun == null)
            {
                return;
            }

            Dictionary <string, string> commandNounMap = null;

            _commandParamToResourceMap?.TryGetValue(commandNoun, out commandNounMap);

            bool isParameterUpdated = false;

            var parameterSet = new ParameterSet(command, _azContext);

            for (var i = 0; i < parameterSet.Parameters.Count; ++i)
            {
                var parameterName  = parameterSet.Parameters[i].Name.ToLower();
                var parameterValue = parameterSet.Parameters[i].Value;

                if (string.IsNullOrWhiteSpace(parameterValue) || string.IsNullOrWhiteSpace(parameterName))
                {
                    continue;
                }

                var parameterKey = parameterName;
                var mappedValue  = parameterKey;
                if (commandNounMap?.TryGetValue(parameterName, out mappedValue) == true)
                {
                    parameterKey = mappedValue;
                }

                this._localParameterValues.AddOrUpdate(parameterKey, parameterValue, (k, v) => parameterValue);
                isParameterUpdated = true;
            }

            if (isParameterUpdated)
            {
                _cancellationTokenSource?.Cancel();
                _cancellationTokenSource = new CancellationTokenSource();
                var cancellationToken = _cancellationTokenSource.Token;

                Task.Run(() =>
                {
                    String localParameterValuesJson = JsonSerializer.Serialize <ConcurrentDictionary <string, string> >(_localParameterValues, JsonUtilities.DefaultSerializerOptions);
                    _mutex.WaitOne();
                    cancellationToken.ThrowIfCancellationRequested();
                    try
                    {
                        System.IO.File.WriteAllText(_paramValueHistoryFilePath, localParameterValuesJson);
                    }
                    finally
                    {
                        _mutex.ReleaseMutex();
                    }
                }, cancellationToken);
            }
        }