mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Add support to show suggestions on CommandNotFound exception (#8458)
* support fuzzy matching with get-command and on CommandNotFound exception, show suggestion * fix CodeFactor issues * change algorithm to use Damerau-Levenshtein Distance which is more useful in finding close matches
This commit is contained in:
@@ -44,8 +44,10 @@ namespace System.Management.Automation
|
||||
string commandName,
|
||||
IEnumerable<string> lookupPaths,
|
||||
ExecutionContext context,
|
||||
Collection<string> acceptableCommandNames)
|
||||
Collection<string> acceptableCommandNames,
|
||||
bool useFuzzyMatch = false)
|
||||
{
|
||||
_useFuzzyMatch = useFuzzyMatch;
|
||||
string[] commandPatterns;
|
||||
if (acceptableCommandNames != null)
|
||||
{
|
||||
@@ -420,11 +422,29 @@ namespace System.Management.Automation
|
||||
// to forcefully use null if pattern is "."
|
||||
if (pattern.Length != 1 || pattern[0] != '.')
|
||||
{
|
||||
var matchingFiles = Directory.EnumerateFiles(directory, pattern);
|
||||
if (_useFuzzyMatch)
|
||||
{
|
||||
var files = new List<string>();
|
||||
var matchingFiles = Directory.EnumerateFiles(directory);
|
||||
foreach (string file in matchingFiles)
|
||||
{
|
||||
if (FuzzyMatcher.IsFuzzyMatch(Path.GetFileName(file), pattern))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
}
|
||||
|
||||
result = _postProcessEnumeratedFiles != null
|
||||
? _postProcessEnumeratedFiles(matchingFiles.ToArray())
|
||||
: matchingFiles;
|
||||
result = _postProcessEnumeratedFiles != null
|
||||
? _postProcessEnumeratedFiles(files.ToArray())
|
||||
: files;
|
||||
}
|
||||
else
|
||||
{
|
||||
var matchingFiles = Directory.EnumerateFiles(directory, pattern);
|
||||
result = _postProcessEnumeratedFiles != null
|
||||
? _postProcessEnumeratedFiles(matchingFiles.ToArray())
|
||||
: matchingFiles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -559,6 +579,8 @@ namespace System.Management.Automation
|
||||
private string[] _orderedPathExt;
|
||||
private Collection<string> _acceptableCommandNames;
|
||||
|
||||
private bool _useFuzzyMatch = false;
|
||||
|
||||
#endregion private members
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,7 +659,9 @@ namespace System.Management.Automation
|
||||
|
||||
foreach (KeyValuePair<string, AliasInfo> aliasEntry in _context.EngineSessionState.GetAliasTable())
|
||||
{
|
||||
if (aliasMatcher.IsMatch(aliasEntry.Key))
|
||||
if (aliasMatcher.IsMatch(aliasEntry.Key) ||
|
||||
(_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) &&
|
||||
FuzzyMatcher.IsFuzzyMatch(aliasEntry.Key, _commandName)))
|
||||
{
|
||||
matchingAliases.Add(aliasEntry.Value);
|
||||
}
|
||||
@@ -737,7 +739,9 @@ namespace System.Management.Automation
|
||||
|
||||
foreach (DictionaryEntry functionEntry in _context.EngineSessionState.GetFunctionTable())
|
||||
{
|
||||
if (functionMatcher.IsMatch((string)functionEntry.Key))
|
||||
if (functionMatcher.IsMatch((string)functionEntry.Key) ||
|
||||
(_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) &&
|
||||
FuzzyMatcher.IsFuzzyMatch(functionEntry.Key.ToString(), _commandName)))
|
||||
{
|
||||
matchingFunction.Add((CommandInfo)functionEntry.Value);
|
||||
}
|
||||
@@ -968,7 +972,9 @@ namespace System.Management.Automation
|
||||
{
|
||||
foreach (CmdletInfo cmdlet in cmdletList)
|
||||
{
|
||||
if (cmdletMatcher.IsMatch(cmdlet.Name))
|
||||
if (cmdletMatcher.IsMatch(cmdlet.Name) ||
|
||||
(_commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch) &&
|
||||
FuzzyMatcher.IsFuzzyMatch(cmdlet.Name, _commandName)))
|
||||
{
|
||||
if (string.IsNullOrEmpty(PSSnapinQualifiedCommandName.PSSnapInName) ||
|
||||
(PSSnapinQualifiedCommandName.PSSnapInName.Equals(
|
||||
@@ -1380,7 +1386,8 @@ namespace System.Management.Automation
|
||||
_commandName,
|
||||
_context.CommandDiscovery.GetLookupDirectoryPaths(),
|
||||
_context,
|
||||
acceptableCommandNames: null);
|
||||
acceptableCommandNames: null,
|
||||
useFuzzyMatch: _commandResolutionOptions.HasFlag(SearchResolutionOptions.FuzzyMatch));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1578,5 +1585,8 @@ namespace System.Management.Automation
|
||||
ResolveFunctionPatterns = 0x02,
|
||||
CommandNameIsPattern = 0x04,
|
||||
SearchAllScopes = 0x08,
|
||||
|
||||
/// <summary>Use fuzzy matching.</summary>
|
||||
FuzzyMatch = 0x10,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Management.Automation;
|
||||
using System.Management.Automation.Internal;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Management.Automation.Language;
|
||||
using System.Reflection;
|
||||
@@ -349,6 +350,14 @@ namespace Microsoft.PowerShell.Commands
|
||||
|
||||
private PSTypeName[] _parameterTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parameter that enables using fuzzy matching.
|
||||
/// </summary>
|
||||
[Parameter(ParameterSetName = "AllCommandSet")]
|
||||
public SwitchParameter UseFuzzyMatching { get; set; }
|
||||
|
||||
private List<CommandScore> _commandScores = new List<CommandScore>();
|
||||
|
||||
#endregion Definitions of cmdlet parameters
|
||||
|
||||
#region Overrides
|
||||
@@ -418,7 +427,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
// We do not show the pithy aliases (not of the format Verb-Noun) and applications by default.
|
||||
// We will show them only if the Name, All and totalCount are not specified.
|
||||
if ((this.Name == null) && (!_all) && TotalCount == -1)
|
||||
if ((this.Name == null) && (!_all) && TotalCount == -1 && !UseFuzzyMatching)
|
||||
{
|
||||
CommandTypes commandTypesToIgnore = 0;
|
||||
|
||||
@@ -499,6 +508,11 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
CommandOrigin origin = this.MyInvocation.CommandOrigin;
|
||||
|
||||
if (UseFuzzyMatching)
|
||||
{
|
||||
results = _commandScores.OrderBy(x => x.Score).Select(x => x.Command).ToList();
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
foreach (CommandInfo result in results)
|
||||
{
|
||||
@@ -715,6 +729,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
bool isPattern = WildcardPattern.ContainsWildcardCharacters(plainCommandName);
|
||||
if (UseFuzzyMatching)
|
||||
{
|
||||
options |= SearchResolutionOptions.FuzzyMatch;
|
||||
isPattern = true;
|
||||
}
|
||||
|
||||
if (isPattern)
|
||||
{
|
||||
options |= SearchResolutionOptions.CommandNameIsPattern;
|
||||
@@ -756,7 +776,32 @@ namespace Microsoft.PowerShell.Commands
|
||||
{
|
||||
if (TotalCount < 0 || count < TotalCount)
|
||||
{
|
||||
foreach (CommandInfo command in System.Management.Automation.Internal.ModuleUtils.GetMatchingCommands(plainCommandName, this.Context, this.MyInvocation.CommandOrigin, rediscoverImportedModules: true, moduleVersionRequired: _isFullyQualifiedModuleSpecified))
|
||||
IEnumerable<CommandInfo> commands;
|
||||
if (UseFuzzyMatching)
|
||||
{
|
||||
foreach (var commandScore in System.Management.Automation.Internal.ModuleUtils.GetFuzzyMatchingCommands(
|
||||
plainCommandName,
|
||||
this.Context,
|
||||
this.MyInvocation.CommandOrigin,
|
||||
rediscoverImportedModules: true,
|
||||
moduleVersionRequired: _isFullyQualifiedModuleSpecified))
|
||||
{
|
||||
_commandScores.Add(commandScore);
|
||||
}
|
||||
|
||||
commands = _commandScores.Select(x => x.Command).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
commands = System.Management.Automation.Internal.ModuleUtils.GetMatchingCommands(
|
||||
plainCommandName,
|
||||
this.Context,
|
||||
this.MyInvocation.CommandOrigin,
|
||||
rediscoverImportedModules: true,
|
||||
moduleVersionRequired: _isFullyQualifiedModuleSpecified);
|
||||
}
|
||||
|
||||
foreach (CommandInfo command in commands)
|
||||
{
|
||||
// Cannot pass in "command" by ref (foreach iteration variable)
|
||||
CommandInfo current = command;
|
||||
@@ -902,6 +947,12 @@ namespace Microsoft.PowerShell.Commands
|
||||
break;
|
||||
}
|
||||
|
||||
if (UseFuzzyMatching)
|
||||
{
|
||||
int score = FuzzyMatcher.GetDamerauLevenshteinDistance(current.Name, commandName);
|
||||
_commandScores.Add(new CommandScore(current, score));
|
||||
}
|
||||
|
||||
_accumulatedResults.Add(current);
|
||||
|
||||
if (ArgumentList != null)
|
||||
|
||||
@@ -381,16 +381,38 @@ namespace System.Management.Automation.Internal
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of fuzzy matching commands and their scores.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Command pattern.</param>
|
||||
/// <param name="context">Execution context.</param>
|
||||
/// <param name="commandOrigin">Command origin.</param>
|
||||
/// <param name="rediscoverImportedModules">If true, rediscovers imported modules.</param>
|
||||
/// <param name="moduleVersionRequired">Specific module version to be required.</param>
|
||||
/// <returns>IEnumerable tuple containing the CommandInfo and the match score.</returns>
|
||||
internal static IEnumerable<CommandScore> GetFuzzyMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false)
|
||||
{
|
||||
foreach (CommandInfo command in GetMatchingCommands(pattern, context, commandOrigin, rediscoverImportedModules, moduleVersionRequired, useFuzzyMatching: true))
|
||||
{
|
||||
int score = FuzzyMatcher.GetDamerauLevenshteinDistance(command.Name, pattern);
|
||||
if (score <= FuzzyMatcher.MinimumDistance)
|
||||
{
|
||||
yield return new CommandScore(command, score);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of matching commands.
|
||||
/// </summary>
|
||||
/// <param name="pattern">Command pattern.</param>
|
||||
/// <param name="commandOrigin"></param>
|
||||
/// <param name="context"></param>
|
||||
/// <param name="rediscoverImportedModules"></param>
|
||||
/// <param name="moduleVersionRequired"></param>
|
||||
/// <returns></returns>
|
||||
internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false)
|
||||
/// <param name="context">Execution context.</param>
|
||||
/// <param name="commandOrigin">Command origin.</param>
|
||||
/// <param name="rediscoverImportedModules">If true, rediscovers imported modules.</param>
|
||||
/// <param name="moduleVersionRequired">Specific module version to be required.</param>
|
||||
/// <param name="useFuzzyMatching">Use fuzzy matching.</param>
|
||||
/// <returns>Returns CommandInfo IEnumerable.</returns>
|
||||
internal static IEnumerable<CommandInfo> GetMatchingCommands(string pattern, ExecutionContext context, CommandOrigin commandOrigin, bool rediscoverImportedModules = false, bool moduleVersionRequired = false, bool useFuzzyMatching = false)
|
||||
{
|
||||
// Otherwise, if it had wildcards, just return the "AvailableCommand"
|
||||
// type of command info.
|
||||
@@ -400,9 +422,7 @@ namespace System.Management.Automation.Internal
|
||||
PSModuleAutoLoadingPreference moduleAutoLoadingPreference = CommandDiscovery.GetCommandDiscoveryPreference(context, SpecialVariables.PSModuleAutoLoadingPreferenceVarPath, "PSModuleAutoLoadingPreference");
|
||||
|
||||
if ((moduleAutoLoadingPreference != PSModuleAutoLoadingPreference.None) &&
|
||||
((commandOrigin == CommandOrigin.Internal) || ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public))
|
||||
)
|
||||
)
|
||||
((commandOrigin == CommandOrigin.Internal) || ((cmdletInfo != null) && (cmdletInfo.Visibility == SessionStateEntryVisibility.Public))))
|
||||
{
|
||||
foreach (string modulePath in GetDefaultAvailableModuleFiles(isForAutoDiscovery: false, context))
|
||||
{
|
||||
@@ -429,7 +449,8 @@ namespace System.Management.Automation.Internal
|
||||
|
||||
foreach (KeyValuePair<string, CommandInfo> entry in psModule.ExportedCommands)
|
||||
{
|
||||
if (commandPattern.IsMatch(entry.Value.Name))
|
||||
if (commandPattern.IsMatch(entry.Value.Name) ||
|
||||
(useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(entry.Value.Name, pattern)))
|
||||
{
|
||||
CommandInfo current = null;
|
||||
switch (entry.Value.CommandType)
|
||||
@@ -487,7 +508,8 @@ namespace System.Management.Automation.Internal
|
||||
string commandName = pair.Key;
|
||||
CommandTypes commandTypes = pair.Value;
|
||||
|
||||
if (commandPattern.IsMatch(commandName))
|
||||
if (commandPattern.IsMatch(commandName) ||
|
||||
(useFuzzyMatching && FuzzyMatcher.IsFuzzyMatch(commandName, pattern)))
|
||||
{
|
||||
bool shouldExportCommand = true;
|
||||
|
||||
@@ -557,5 +579,17 @@ namespace System.Management.Automation.Internal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct CommandScore
|
||||
{
|
||||
public CommandScore(CommandInfo command, int score)
|
||||
{
|
||||
Command = command;
|
||||
Score = score;
|
||||
}
|
||||
|
||||
public CommandInfo Command;
|
||||
public int Score;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,15 @@ namespace System.Management.Automation
|
||||
{
|
||||
internal enum SuggestionMatchType
|
||||
{
|
||||
/// <summary>Match on a command.</summary>
|
||||
Command = 0,
|
||||
/// <summary>Match based on exception message.</summary>
|
||||
Error = 1,
|
||||
Dynamic = 2
|
||||
/// <summary>Match by running a script block.</summary>
|
||||
Dynamic = 2,
|
||||
|
||||
/// <summary>Match by fully qualified ErrorId.</summary>
|
||||
ErrorId = 3
|
||||
}
|
||||
|
||||
#region Public HostUtilities Class
|
||||
@@ -58,6 +64,13 @@ namespace System.Management.Automation
|
||||
$formatString -f $lastError.TargetObject,"".\$($lastError.TargetObject)""
|
||||
";
|
||||
|
||||
private static string s_getFuzzyMatchedCommands = @"
|
||||
[System.Diagnostics.DebuggerHidden()]
|
||||
param([string] $formatString)
|
||||
|
||||
$formatString -f [string]::Join(', ', (Get-Command $lastError.TargetObject -UseFuzzyMatch | Select-Object -First 10 -ExpandProperty Name))
|
||||
";
|
||||
|
||||
private static ArrayList s_suggestions = new ArrayList(
|
||||
new Hashtable[] {
|
||||
NewSuggestion(1, "Transactions", SuggestionMatchType.Command, "^Start-Transaction",
|
||||
@@ -68,7 +81,15 @@ namespace System.Management.Automation
|
||||
ScriptBlock.CreateDelayParsedScriptBlock(s_checkForCommandInCurrentDirectoryScript, isProductCode: true),
|
||||
ScriptBlock.CreateDelayParsedScriptBlock(s_createCommandExistsInCurrentDirectoryScript, isProductCode: true),
|
||||
new object[] { CodeGeneration.EscapeSingleQuotedStringContent(SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory) },
|
||||
true)
|
||||
true),
|
||||
NewSuggestion(
|
||||
id: 4,
|
||||
category: "General",
|
||||
matchType: SuggestionMatchType.ErrorId,
|
||||
rule: "CommandNotFoundException",
|
||||
suggestion: ScriptBlock.CreateDelayParsedScriptBlock(s_getFuzzyMatchedCommands, isProductCode: true),
|
||||
suggestionArgs: new object[] { CodeGeneration.EscapeSingleQuotedStringContent(SuggestionStrings.Suggestion_CommandNotFound) },
|
||||
enabled: true)
|
||||
}
|
||||
);
|
||||
|
||||
@@ -428,6 +449,13 @@ namespace System.Management.Automation
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (matchType == SuggestionMatchType.ErrorId)
|
||||
{
|
||||
if (lastError != null && lastError is ErrorRecord errorRecord)
|
||||
{
|
||||
matchText = errorRecord.FullyQualifiedErrorId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
suggestion["Enabled"] = false;
|
||||
@@ -511,6 +539,13 @@ namespace System.Management.Automation
|
||||
/// <summary>
|
||||
/// Create suggestion with string rule and suggestion.
|
||||
/// </summary>
|
||||
/// <param name="id">Identifier for the suggestion.</param>
|
||||
/// <param name="category">Category for the suggestion.</param>
|
||||
/// <param name="matchType">Suggestion match type.</param>
|
||||
/// <param name="rule">Rule to match.</param>
|
||||
/// <param name="suggestion">Suggestion to return.</param>
|
||||
/// <param name="enabled">True if the suggestion is enabled.</param>
|
||||
/// <returns>Hashtable representing the suggestion.</returns>
|
||||
private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, string rule, string suggestion, bool enabled)
|
||||
{
|
||||
Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase);
|
||||
@@ -525,6 +560,32 @@ namespace System.Management.Automation
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create suggestion with string rule and scriptblock suggestion.
|
||||
/// </summary>
|
||||
/// <param name="id">Identifier for the suggestion.</param>
|
||||
/// <param name="category">Category for the suggestion.</param>
|
||||
/// <param name="matchType">Suggestion match type.</param>
|
||||
/// <param name="rule">Rule to match.</param>
|
||||
/// <param name="suggestion">Scriptblock to run that returns the suggestion.</param>
|
||||
/// <param name="suggestionArgs">Arguments to pass to suggestion scriptblock.</param>
|
||||
/// <param name="enabled">True if the suggestion is enabled.</param>
|
||||
/// <returns>Hashtable representing the suggestion.</returns>
|
||||
private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, string rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled)
|
||||
{
|
||||
Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase);
|
||||
|
||||
result["Id"] = id;
|
||||
result["Category"] = category;
|
||||
result["MatchType"] = matchType;
|
||||
result["Rule"] = rule;
|
||||
result["Suggestion"] = suggestion;
|
||||
result["SuggestionArgs"] = suggestionArgs;
|
||||
result["Enabled"] = enabled;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create suggestion with scriptblock rule and suggestion.
|
||||
/// </summary>
|
||||
|
||||
@@ -126,6 +126,9 @@
|
||||
<data name="Suggestion_CommandExistsInCurrentDirectory" xml:space="preserve">
|
||||
<value>The command {0} was not found, but does exist in the current location. PowerShell does not load commands from the current location by default. If you trust this command, instead type: "{1}". See "get-help about_Command_Precedence" for more details.</value>
|
||||
</data>
|
||||
<data name="Suggestion_CommandNotFound" xml:space="preserve">
|
||||
<value>The most similar commands are: {0}.</value>
|
||||
</data>
|
||||
<data name="RuleMustBeScriptBlock" xml:space="preserve">
|
||||
<value>Rule must be a ScriptBlock for dynamic match types.</value>
|
||||
</data>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace System.Management.Automation
|
||||
{
|
||||
internal static class FuzzyMatcher
|
||||
{
|
||||
public const int MinimumDistance = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Determine if the two strings are considered similar.
|
||||
/// </summary>
|
||||
/// <param name="string1">The first string to compare.</param>
|
||||
/// <param name="string2">The second string to compare.</param>
|
||||
/// <returns>True if the two strings have a distance <= MinimumDistance.</returns>
|
||||
public static bool IsFuzzyMatch(string string1, string string2)
|
||||
{
|
||||
return GetDamerauLevenshteinDistance(string1, string2) <= MinimumDistance;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Compute the distance between two strings.
|
||||
/// Based off https://www.csharpstar.com/csharp-string-distance-algorithm/.
|
||||
/// </summary>
|
||||
/// <param name="string1">The first string to compare.</param>
|
||||
/// <param name="string2">The second string to compare.</param>
|
||||
/// <returns>The distance value where the lower the value the shorter the distance between the two strings representing a closer match.</returns>
|
||||
public static int GetDamerauLevenshteinDistance(string string1, string string2)
|
||||
{
|
||||
var bounds = new { Height = string1.Length + 1, Width = string2.Length + 1 };
|
||||
|
||||
int[,] matrix = new int[bounds.Height, bounds.Width];
|
||||
|
||||
for (int height = 0; height < bounds.Height; height++) { matrix[height, 0] = height; };
|
||||
for (int width = 0; width < bounds.Width; width++) { matrix[0, width] = width; };
|
||||
|
||||
for (int height = 1; height < bounds.Height; height++)
|
||||
{
|
||||
for (int width = 1; width < bounds.Width; width++)
|
||||
{
|
||||
int cost = (string1[height - 1] == string2[width - 1]) ? 0 : 1;
|
||||
int insertion = matrix[height, width - 1] + 1;
|
||||
int deletion = matrix[height - 1, width] + 1;
|
||||
int substitution = matrix[height - 1, width - 1] + cost;
|
||||
|
||||
int distance = Math.Min(insertion, Math.Min(deletion, substitution));
|
||||
|
||||
if (height > 1 && width > 1 && string1[height - 1] == string2[width - 2] && string1[height - 2] == string2[width - 1])
|
||||
{
|
||||
distance = Math.Min(distance, matrix[height - 2, width - 2] + cost);
|
||||
}
|
||||
|
||||
matrix[height, width] = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return matrix[bounds.Height - 1, bounds.Width - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
Describe "Get-Command CI tests" -Tag Feature {
|
||||
Context "-UseFuzzyMatch tests" {
|
||||
It "Should match cmdlets" {
|
||||
$cmds = Get-Command get-hlp -UseFuzzyMatch
|
||||
$cmds.Count | Should -BeGreaterThan 0
|
||||
$cmds[0].Name | Should -BeExactly 'Get-Help' -Because "This should be closest match so shows up first"
|
||||
}
|
||||
|
||||
It "Should match native commands" {
|
||||
$ping = "ping"
|
||||
if ($IsWindows) {
|
||||
$ping = "PING.EXE"
|
||||
}
|
||||
|
||||
$cmds = Get-Command pin -UseFuzzyMatch
|
||||
$cmds.Count | Should -BeGreaterThan 0
|
||||
$cmds.Name | Should -Contain $ping
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user