improve native completion detection
This commit is contained in:
parent
7c6a37fd13
commit
9557fcc1c4
1 changed files with 65 additions and 25 deletions
90
bin/main.ml
90
bin/main.ml
|
|
@ -13,8 +13,8 @@
|
|||
*
|
||||
* the indexing pipeline for each binary:
|
||||
* a. classify the binary (skip? try --help? try native completions?)
|
||||
* b. if the tool has native nushell completion support, try various
|
||||
* subcommand patterns ("completions nushell", "--completion nushell", etc.)
|
||||
* b. if the tool has native nushell completion support, run --help and
|
||||
* discover subcommands containing "complet", then try them with "nushell"
|
||||
* c. otherwise, run the tool with --help/-h and parse the output
|
||||
* d. recursively resolve subcommands (depth-limited to 5)
|
||||
* e. after binaries, parse manpages for any commands not yet covered
|
||||
|
|
@ -255,7 +255,7 @@ let elf_scan path needles =
|
|||
|
||||
(* detect nix-generated c wrapper scripts and extract the real binary path.
|
||||
* nix's makeCWrapper creates small c programs that set up the environment
|
||||
* and exec the real binary. these wrappers won't contain "-h" or "completion"
|
||||
* and exec the real binary. these wrappers won't contain "-h" or "complet"
|
||||
* in their own binary (they're just wrappers), so elf_scan would say "skip".
|
||||
* this function reads the wrapper source to find the actual /nix/store/.../bin/...
|
||||
* target path, so we can try --help on the real binary instead.
|
||||
|
|
@ -330,8 +330,8 @@ type bin_class = Skip | Try_help | Try_native_and_help
|
|||
|
||||
(* classify an elf binary path for indexing. *)
|
||||
let classify_elf path =
|
||||
let scan = elf_scan path ["-h"; "completion"] in
|
||||
if Hashtbl.mem scan "completion" then Try_native_and_help
|
||||
let scan = elf_scan path ["-h"; "complet"] in
|
||||
if Hashtbl.mem scan "complet" then Try_native_and_help
|
||||
else if Hashtbl.mem scan "-h" then Try_help
|
||||
else Skip
|
||||
|
||||
|
|
@ -341,7 +341,7 @@ let classify_elf path =
|
|||
* 2. not executable -> Skip
|
||||
* 3. script (has shebang) -> resolve through nix script wrapper if possible,
|
||||
* otherwise Try_help
|
||||
* 4. elf binary containing "completion" -> Try_native_and_help
|
||||
* 4. elf binary containing "complet" -> Try_native_and_help
|
||||
* 5. elf binary containing "-h" -> Try_help
|
||||
* 6. nix c wrapper -> Try_help (the wrapper itself is just an exec shim)
|
||||
* 7. otherwise -> Skip (binary has no help infrastructure) *)
|
||||
|
|
@ -374,28 +374,68 @@ let num_cores () =
|
|||
close_in ic; max 1 !count
|
||||
with _ -> 4
|
||||
|
||||
(* extract words from text that contain any of the given substrings.
|
||||
* words are sequences of [a-zA-Z0-9_-] optionally prefixed with --.
|
||||
* returns a deduplicated list. *)
|
||||
let extract_matching_words text needles =
|
||||
let len = String.length text in
|
||||
let module SSet = Set.Make(String) in
|
||||
let words = ref SSet.empty in
|
||||
let i = ref 0 in
|
||||
while !i < len do
|
||||
while !i < len && not (text.[!i] >= 'a' && text.[!i] <= 'z'
|
||||
|| text.[!i] >= 'A' && text.[!i] <= 'Z'
|
||||
|| text.[!i] = '-') do
|
||||
incr i
|
||||
done;
|
||||
let start = !i in
|
||||
while !i < len && (text.[!i] >= 'a' && text.[!i] <= 'z'
|
||||
|| text.[!i] >= 'A' && text.[!i] <= 'Z'
|
||||
|| text.[!i] >= '0' && text.[!i] <= '9'
|
||||
|| text.[!i] = '-' || text.[!i] = '_') do
|
||||
incr i
|
||||
done;
|
||||
if !i > start then begin
|
||||
let word = String.sub text start (!i - start) in
|
||||
let lower = String.lowercase_ascii word in
|
||||
if List.exists (fun needle ->
|
||||
try ignore (Str.search_forward (Str.regexp_string needle) lower 0); true
|
||||
with Not_found -> false
|
||||
) needles then
|
||||
words := SSet.add word !words
|
||||
end
|
||||
done;
|
||||
SSet.elements !words
|
||||
|
||||
(* try to get native nushell completions from a binary.
|
||||
* tries several common subcommand patterns that tools use for shell completions.
|
||||
* returns the first one that produces valid nushell source code.
|
||||
* the 500ms timeout is generous enough for most tools but prevents hangs.
|
||||
* runs --help, scans the output for words containing completion-related
|
||||
* substrings ("complet"), then tries each match as a subcommand or flag
|
||||
* with "nushell" as the argument.
|
||||
*
|
||||
* the patterns cover: cobra (go), clap (rust), click (python), and various
|
||||
* ad-hoc implementations. *)
|
||||
* this catches arbitrary patterns (completions, generate-completions,
|
||||
* shell-completion, gen-completions, etc.) without maintaining a hardcoded
|
||||
* list. the worst case is a few failed attempts before falling back to
|
||||
* manpage/--help parsing. *)
|
||||
let try_native_completion bin_path =
|
||||
List.find_map (fun args ->
|
||||
match run_cmd args 500 with
|
||||
| Some text when is_nushell_source text -> Some text
|
||||
| _ -> None
|
||||
) [
|
||||
[bin_path; "completions"; "nushell"];
|
||||
[bin_path; "completion"; "nushell"];
|
||||
[bin_path; "--completions"; "nushell"];
|
||||
[bin_path; "--completion"; "nushell"];
|
||||
[bin_path; "generate-completion"; "nushell"];
|
||||
[bin_path; "--generate-completion"; "nushell"];
|
||||
[bin_path; "gen-completions"; "nushell"];
|
||||
[bin_path; "shell-completions"; "nushell"];
|
||||
]
|
||||
let help_text = match run_cmd [bin_path; "--help"] 500 with
|
||||
| Some t -> t | None -> "" in
|
||||
if help_text = "" then None
|
||||
else
|
||||
let candidates = extract_matching_words help_text ["complet"] in
|
||||
List.find_map (fun word ->
|
||||
let attempts =
|
||||
if String.starts_with ~prefix:"--" word then
|
||||
[[bin_path; word; "nushell"]]
|
||||
else
|
||||
[[bin_path; word; "nushell"];
|
||||
[bin_path; "--" ^ word; "nushell"]]
|
||||
in
|
||||
List.find_map (fun args ->
|
||||
match run_cmd args 500 with
|
||||
| Some text when is_nushell_source text -> Some text
|
||||
| _ -> None
|
||||
) attempts
|
||||
) candidates
|
||||
|
||||
(* parse a manpage file, extracting the command name, its flags/subcommands,
|
||||
* and any clap-style per-subcommand sections.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue