feature: suggest nearest match if user give unrecognized option
similar to git `Did you mean '...'?`, this feature put `std/editDistance` into action help users to quickly recognize his error instead of plain `Unrecognized option '...'.`
This commit is contained in:
parent
7176de4ddb
commit
5e732379f4
|
@ -1,5 +1,5 @@
|
||||||
import
|
import
|
||||||
std/[options, strutils, wordwrap],
|
std/[options, strutils, wordwrap, editdistance],
|
||||||
stew/shims/macros,
|
stew/shims/macros,
|
||||||
confutils/[defs, cli_parser, config_file]
|
confutils/[defs, cli_parser, config_file]
|
||||||
|
|
||||||
|
@ -409,6 +409,36 @@ proc findSubCmd(cmd: CmdInfo, name: string): CmdInfo =
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
proc distanceOpt(opts: openarray[OptInfo], name: string): (OptInfo, int) =
|
||||||
|
# find nearest match using `editDistance`
|
||||||
|
if opts.len == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var
|
||||||
|
currOpt = opts[0]
|
||||||
|
currDist = editDistance(currOpt.name, name)
|
||||||
|
|
||||||
|
for i in 1..<opts.len:
|
||||||
|
let distance = editDistance(opts[i].name, name)
|
||||||
|
if distance < currDist:
|
||||||
|
currOpt = opts[i]
|
||||||
|
currDist = distance
|
||||||
|
|
||||||
|
(currOpt, currDist)
|
||||||
|
|
||||||
|
proc distanceOpt(cmds: openarray[CmdInfo], name: string): OptInfo =
|
||||||
|
if cmds.len == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var (currOpt, currDist) = distanceOpt(cmds[0].opts, name)
|
||||||
|
for i in 1..<cmds.len:
|
||||||
|
let (opt, distance) = distanceOpt(cmds[i].opts, name)
|
||||||
|
if distance < currDist:
|
||||||
|
currOpt = opt
|
||||||
|
currDist = distance
|
||||||
|
|
||||||
|
currOpt
|
||||||
|
|
||||||
proc startsWithIgnoreStyle(s: string, prefix: string): bool =
|
proc startsWithIgnoreStyle(s: string, prefix: string): bool =
|
||||||
# Similar in spirit to cmpIgnoreStyle, but compare only the prefix.
|
# Similar in spirit to cmpIgnoreStyle, but compare only the prefix.
|
||||||
var i = 0
|
var i = 0
|
||||||
|
@ -1002,11 +1032,18 @@ proc load*(Configuration: type,
|
||||||
opt = findOpt(defaultCmd.opts, key)
|
opt = findOpt(defaultCmd.opts, key)
|
||||||
if opt != nil:
|
if opt != nil:
|
||||||
activateCmd(subCmdDiscriminator, defaultCmd)
|
activateCmd(subCmdDiscriminator, defaultCmd)
|
||||||
|
else:
|
||||||
|
# we will fail, but before that, suggest nearest match from defaultCmd too.
|
||||||
|
activeCmds.add defaultCmd
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
if opt != nil:
|
if opt != nil:
|
||||||
applySetter(opt.idx, val)
|
applySetter(opt.idx, val)
|
||||||
|
else:
|
||||||
|
let opt = distanceOpt(activeCmds, key) # suggest nearest match
|
||||||
|
if opt != nil:
|
||||||
|
fail "Unrecognized option '$1'. Did you mean '$2'?" % [key, opt.name]
|
||||||
else:
|
else:
|
||||||
fail "Unrecognized option '$1'" % [key]
|
fail "Unrecognized option '$1'" % [key]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue