diff --git a/src/nghttp.cc b/src/nghttp.cc index b0079285..da8d6258 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -1801,6 +1801,7 @@ int main(int argc, char **argv) } break; case '?': + util::show_candidates(argv[optind - 1], long_options); exit(EXIT_FAILURE); case 0: switch(flag) { diff --git a/src/nghttpd.cc b/src/nghttpd.cc index 9f3a5455..2da9d019 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -41,6 +41,7 @@ #include "app_helper.h" #include "HttpServer.h" +#include "util.h" namespace nghttp2 { @@ -178,6 +179,7 @@ int main(int argc, char **argv) } break; case '?': + util::show_candidates(argv[optind - 1], long_options); exit(EXIT_FAILURE); case 0: switch(flag) { diff --git a/src/shrpx.cc b/src/shrpx.cc index ec7d3947..158453e5 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -52,6 +52,9 @@ #include "shrpx_config.h" #include "shrpx_listen_handler.h" #include "shrpx_ssl.h" +#include "util.h" + +using namespace nghttp2; namespace shrpx { @@ -859,6 +862,7 @@ int main(int argc, char **argv) print_version(std::cout); exit(EXIT_SUCCESS); case '?': + util::show_candidates(argv[optind - 1], long_options); exit(EXIT_FAILURE); case 0: switch(flag) { diff --git a/src/util.cc b/src/util.cc index 30a3370e..d85becaa 100644 --- a/src/util.cc +++ b/src/util.cc @@ -29,6 +29,7 @@ #include #include #include +#include #include "timegm.h" @@ -281,6 +282,77 @@ void inp_strlower(std::string& s) } } +namespace { +// Calculates Damerau–Levenshtein distance between c-string a and b +// with given costs. swapcost, subcost, addcost and delcost are cost +// to swap 2 adjacent characters, substitute characters, add character +// and delete character respectively. +int levenshtein +(const char* a, + const char* b, + int swapcost, + int subcost, + int addcost, + int delcost) +{ + int alen = strlen(a); + int blen = strlen(b); + auto dp = std::vector>(3, std::vector(blen+1)); + for(int i = 0; i <= blen; ++i) { + dp[1][i] = i; + } + for(int i = 1; i <= alen; ++i) { + dp[0][0] = i; + for(int j = 1; j <= blen; ++j) { + dp[0][j] = dp[1][j-1]+(a[i-1] == b[j-1] ? 0 : subcost); + if(i >= 2 && j >= 2 && a[i-1] != b[j-1] && + a[i-2] == b[j-1] && a[i-1] == b[j-2]) { + dp[0][j] = std::min(dp[0][j], dp[2][j-2]+swapcost); + } + dp[0][j] = std::min(dp[0][j], + std::min(dp[1][j]+delcost, dp[0][j-1]+addcost)); + } + std::rotate(std::begin(dp), std::begin(dp)+2, std::end(dp)); + } + return dp[1][blen]; +} +} // namespace + +void show_candidates(const char *unkopt, option *options) +{ + for(; *unkopt == '-'; ++unkopt); + if(*unkopt == '\0') { + return; + } + auto cands = std::vector>(); + for(size_t i = 0; options[i].name != nullptr; ++i) { + // Use cost 0 for prefix match + if(istartsWith(options[i].name, unkopt)) { + cands.emplace_back(0, options[i].name); + continue; + } + // cost values are borrowed from git, help.c. + int sim = levenshtein(unkopt, options[i].name, 0, 2, 1, 3); + cands.emplace_back(sim, options[i].name); + } + if(cands.empty()) { + return; + } + std::sort(std::begin(cands), std::end(cands)); + int threshold = cands[0].first; + // threshold value is a magic value. + if(threshold > 6) { + return; + } + std::cerr << "\nDid you mean:\n"; + for(auto& item : cands) { + if(item.first > threshold) { + break; + } + std::cerr << "\t--" << item.second << "\n"; + } +} + } // namespace util } // namespace nghttp2 diff --git a/src/util.h b/src/util.h index 45b8db28..48f6a119 100644 --- a/src/util.h +++ b/src/util.h @@ -27,6 +27,9 @@ #include "nghttp2_config.h" +#include +#include + #include #include #include @@ -403,6 +406,8 @@ make_unique(size_t size) void to_token68(std::string& base64str); void to_base64(std::string& token68str); +void show_candidates(const char *unkopt, option *options); + } // namespace util } // namespace nghttp2