diff --git a/source/dub/dub.d b/source/dub/dub.d index 4d46021..ebfd72e 100644 --- a/source/dub/dub.d +++ b/source/dub/dub.d @@ -616,17 +616,15 @@ auto versionStrings = ps.getVersions(dep); depVers[dep] = versionStrings[$-1].toString; } catch(Exception e){ - auto packages = ps.getPackageNames(); - string[][size_t] lds; //holds the levenshteinDistance from dep for each package - foreach(pack; packages){ - lds[dep.levenshteinDistance(pack)] ~= pack; - } - auto closestKey = lds.keys.sort.front; - if(closestKey <= 4){ - logError("Error, no package \"%s\" found. Did you mean %s?", dep, lds[closestKey]); - } else{ - logError("Error, no package \"%s\" found. Exiting...", dep); - } + import std.range : take; + logError("Package '%s' was not found", dep); + auto candidates = ps.getPackageNames() + .fuzzySearch(dep) + .take(4); + if(candidates.length > 1) + logInfo("Did you mean one of: %-(%s%|, %)?", candidates); + else if(candidates.length == 1) + logInfo("Did you mean %s?", candidates.front); return; } } diff --git a/source/dub/internal/utils.d b/source/dub/internal/utils.d index e71be4e..834c3be 100644 --- a/source/dub/internal/utils.d +++ b/source/dub/internal/utils.d @@ -225,3 +225,18 @@ auto idx = distMap.countUntil!(a => a <= distance); return (idx == -1) ? null : array[idx]; } + +/** + Searches for close matches to input in range. R must be a range of strings + Note: Sorts the strings range. Use std.range.indexed to avoid this... + */ +auto fuzzySearch(R)(R strings, string input){ + import std.algorithm : levenshteinDistance, schwartzSort, partition3; + import std.traits : isSomeString; + import std.range : ElementType; + + static assert(isSomeString!(ElementType!R), "Cannot call fuzzy search on non string rang"); + immutable threshold = input.length / 4; + return strings.partition3!((a, b) => a.length + threshold < b.length)(input)[1] + .schwartzSort!(p => levenshteinDistance(input.toUpper, p.toUpper)); +}