Skip to content

Commit e0c260b

Browse files
committed
fix(cli): use dispatcher entries for command suggestions
1 parent dc5c875 commit e0c260b

File tree

1 file changed

+76
-4
lines changed

1 file changed

+76
-4
lines changed

src/CLI.cpp

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
#include <algorithm>
5757
#include <cctype>
5858
#include <filesystem>
59+
#include <functional>
60+
#include <climits>
61+
#include <functional>
62+
#include <climits>
5963

6064
namespace vix
6165
{
@@ -92,6 +96,57 @@ namespace vix
9296
return std::nullopt;
9397
}
9498

99+
static int levenshtein_distance(const std::string &a, const std::string &b)
100+
{
101+
const size_t m = a.size();
102+
const size_t n = b.size();
103+
104+
std::vector<int> prev(n + 1), curr(n + 1);
105+
106+
for (size_t j = 0; j <= n; ++j)
107+
prev[j] = j;
108+
109+
for (size_t i = 1; i <= m; ++i)
110+
{
111+
curr[0] = i;
112+
for (size_t j = 1; j <= n; ++j)
113+
{
114+
int cost = (a[i - 1] == b[j - 1]) ? 0 : 1;
115+
116+
curr[j] = std::min({prev[j] + 1,
117+
curr[j - 1] + 1,
118+
prev[j - 1] + cost});
119+
}
120+
prev = curr;
121+
}
122+
123+
return prev[n];
124+
}
125+
126+
static std::optional<std::string> find_closest_command(
127+
const std::string &input,
128+
const std::unordered_map<std::string, vix::cli::dispatch::Entry> &entries)
129+
{
130+
int bestScore = INT_MAX;
131+
std::string best;
132+
133+
for (const auto &[name, _] : entries)
134+
{
135+
int d = levenshtein_distance(input, name);
136+
137+
if (d < bestScore)
138+
{
139+
bestScore = d;
140+
best = name;
141+
}
142+
}
143+
144+
if (bestScore <= 3)
145+
return best;
146+
147+
return std::nullopt;
148+
}
149+
95150
void apply_log_level_from_env(Logger &logger)
96151
{
97152
if (const char *env = vix::utils::vix_getenv("VIX_LOG_LEVEL"))
@@ -290,8 +345,17 @@ namespace vix
290345
{
291346
if (!dispatcher.has(cmd))
292347
{
293-
std::cerr << "vix: unknown command '" << cmd << "'\n\n";
294-
return help({});
348+
std::cerr << "error: unrecognized subcommand\n\n";
349+
350+
auto suggestion = find_closest_command(cmd, dispatcher.entries());
351+
352+
if (suggestion.has_value())
353+
{
354+
std::cerr << " tip: a similar command exists: '"
355+
<< suggestion.value() << "'\n";
356+
}
357+
358+
return 1;
295359
}
296360
return dispatcher.help(cmd);
297361
}
@@ -307,8 +371,16 @@ namespace vix
307371

308372
if (!dispatcher.has(cmd))
309373
{
310-
std::cerr << "vix: unknown command '" << cmd << "'\n\n";
311-
help({});
374+
std::cerr << "error: unrecognized subcommand\n\n";
375+
376+
auto suggestion = find_closest_command(cmd, dispatcher.entries());
377+
378+
if (suggestion.has_value())
379+
{
380+
std::cerr << " tip: a similar command exists: '"
381+
<< suggestion.value() << "'\n";
382+
}
383+
312384
return 1;
313385
}
314386

0 commit comments

Comments
 (0)