comprehensive completion generation: native, manpage, --help
Three-strategy pipeline with priority: native completion generators (e.g. CMD completions nushell) > manpage parsing > --help fallback. Single `generate` command produces one module-wrapped .nu file per command. Parallel execution scaled to cores, 200ms timeouts, ELF string scanning to skip binaries without -h support, native gzip decompression via camlzip, SYNOPSIS-based subcommand detection, nix3 manpage strategy, deduplication, nushell builtin exclusion.
This commit is contained in:
parent
01ccf64efc
commit
7f0ec8ab4d
9 changed files with 937 additions and 265 deletions
198
lib/nushell.ml
198
lib/nushell.ml
|
|
@ -1,6 +1,90 @@
|
|||
open Parser
|
||||
|
||||
(* Map a param name/type hint to a nushell type *)
|
||||
(* Nushell built-in commands and keywords *)
|
||||
let nushell_builtins = [
|
||||
"alias"; "all"; "ansi"; "any"; "append"; "ast"; "attr";
|
||||
"bits"; "break"; "bytes";
|
||||
"cal"; "cd"; "char"; "chunk-by"; "chunks"; "clear"; "collect";
|
||||
"columns"; "commandline"; "compact"; "complete"; "config"; "const";
|
||||
"continue"; "cp";
|
||||
"date"; "debug"; "decode"; "def"; "default"; "describe"; "detect";
|
||||
"do"; "drop"; "du";
|
||||
"each"; "echo"; "encode"; "enumerate"; "error"; "every"; "exec";
|
||||
"exit"; "explain"; "explore"; "export"; "export-env"; "extern";
|
||||
"fill"; "filter"; "find"; "first"; "flatten"; "for"; "format"; "from";
|
||||
"generate"; "get"; "glob"; "grid"; "group-by";
|
||||
"hash"; "headers"; "help"; "hide"; "hide-env"; "histogram";
|
||||
"history"; "http";
|
||||
"if"; "ignore"; "input"; "insert"; "inspect"; "interleave"; "into";
|
||||
"is-admin"; "is-empty"; "is-not-empty"; "is-terminal"; "items";
|
||||
"job"; "join";
|
||||
"keybindings"; "kill";
|
||||
"last"; "length"; "let"; "let-env"; "lines"; "load-env"; "loop"; "ls";
|
||||
"match"; "math"; "merge"; "metadata"; "mkdir"; "mktemp"; "module";
|
||||
"move"; "mut"; "mv";
|
||||
"nu-check"; "nu-highlight";
|
||||
"open"; "overlay";
|
||||
"panic"; "par-each"; "parse"; "path"; "plugin"; "port"; "prepend"; "print"; "ps";
|
||||
"query";
|
||||
"random"; "reduce"; "reject"; "rename"; "return"; "reverse"; "rm";
|
||||
"roll"; "rotate"; "run-external";
|
||||
"save"; "schema"; "scope"; "select"; "seq"; "shuffle"; "skip"; "sleep";
|
||||
"slice"; "sort"; "sort-by"; "source"; "source-env"; "split"; "start";
|
||||
"stor"; "str"; "sys";
|
||||
"table"; "take"; "tee"; "term"; "timeit"; "to"; "touch"; "transpose";
|
||||
"try"; "tutor";
|
||||
"ulimit"; "umask"; "uname"; "uniq"; "uniq-by"; "unlet"; "update";
|
||||
"upsert"; "url"; "use";
|
||||
"values"; "version"; "view";
|
||||
"watch"; "where"; "which"; "while"; "whoami"; "window"; "with-env"; "wrap";
|
||||
"zip";
|
||||
]
|
||||
|
||||
let builtin_set = lazy (
|
||||
let tbl = Hashtbl.create (List.length nushell_builtins) in
|
||||
List.iter (fun s -> Hashtbl.replace tbl s true) nushell_builtins;
|
||||
tbl)
|
||||
|
||||
let is_nushell_builtin cmd =
|
||||
Hashtbl.mem (Lazy.force builtin_set) cmd
|
||||
|
||||
let dedup_entries entries =
|
||||
let key_of entry =
|
||||
match entry.switch with
|
||||
| Short c -> Printf.sprintf "-%c" c
|
||||
| Long l | Both (_, l) -> Printf.sprintf "--%s" l
|
||||
in
|
||||
let score entry =
|
||||
let sw = match entry.switch with Both _ -> 10 | _ -> 0 in
|
||||
let p = match entry.param with Some _ -> 5 | None -> 0 in
|
||||
let d = min 5 (String.length entry.desc / 10) in
|
||||
sw + p + d
|
||||
in
|
||||
let best = Hashtbl.create 64 in
|
||||
List.iter (fun e ->
|
||||
let k = key_of e in
|
||||
match Hashtbl.find_opt best k with
|
||||
| Some prev when score prev >= score e -> ()
|
||||
| _ -> Hashtbl.replace best k e
|
||||
) entries;
|
||||
let covered_shorts = Hashtbl.create 16 in
|
||||
Hashtbl.iter (fun _ e ->
|
||||
match e.switch with
|
||||
| Both (c, _) -> Hashtbl.replace covered_shorts c true
|
||||
| _ -> ()
|
||||
) best;
|
||||
let seen = Hashtbl.create 64 in
|
||||
List.filter_map (fun e ->
|
||||
let k = key_of e in
|
||||
if Hashtbl.mem seen k then None
|
||||
else
|
||||
match e.switch with
|
||||
| Short c when Hashtbl.mem covered_shorts c -> None
|
||||
| _ ->
|
||||
Hashtbl.add seen k true;
|
||||
Hashtbl.find_opt best k
|
||||
) entries
|
||||
|
||||
let nushell_type_of_param = function
|
||||
| "FILE" | "file" | "PATH" | "path" | "DIR" | "dir" | "DIRECTORY"
|
||||
| "FILENAME" | "PATTERNFILE" -> "path"
|
||||
|
|
@ -8,75 +92,63 @@ let nushell_type_of_param = function
|
|||
| "LINES" | "DEPTH" | "depth" -> "int"
|
||||
| _ -> "string"
|
||||
|
||||
(* Escape a nushell string: wrap in double quotes, escape inner quotes *)
|
||||
let escape_nu s =
|
||||
let buf = Buffer.create (String.length s + 2) in
|
||||
String.iter (fun c ->
|
||||
match c with
|
||||
| '"' -> Buffer.add_string buf "\\\""
|
||||
| '\\' -> Buffer.add_string buf "\\\\"
|
||||
| _ -> Buffer.add_char buf c
|
||||
) s;
|
||||
Buffer.contents buf
|
||||
if not (String.contains s '"') && not (String.contains s '\\') then s
|
||||
else begin
|
||||
let buf = Buffer.create (String.length s + 4) in
|
||||
String.iter (fun c -> match c with
|
||||
| '"' -> Buffer.add_string buf "\\\""
|
||||
| '\\' -> Buffer.add_string buf "\\\\"
|
||||
| _ -> Buffer.add_char buf c
|
||||
) s;
|
||||
Buffer.contents buf
|
||||
end
|
||||
|
||||
(* Format a single flag for nushell extern *)
|
||||
let format_flag entry =
|
||||
let buf = Buffer.create 64 in
|
||||
Buffer.add_string buf " ";
|
||||
(* Flag name *)
|
||||
(match entry.switch with
|
||||
| Both (s, l) ->
|
||||
Buffer.add_string buf (Printf.sprintf "--%s(-%c)" l s)
|
||||
| Long l ->
|
||||
Buffer.add_string buf (Printf.sprintf "--%s" l)
|
||||
| Short s ->
|
||||
Buffer.add_string buf (Printf.sprintf "-%c" s));
|
||||
(* Type annotation *)
|
||||
(match entry.param with
|
||||
| Some (Mandatory name) ->
|
||||
Buffer.add_string buf ": ";
|
||||
Buffer.add_string buf (nushell_type_of_param name)
|
||||
| Some (Optional name) ->
|
||||
Buffer.add_string buf ": ";
|
||||
Buffer.add_string buf (nushell_type_of_param name)
|
||||
| None -> ());
|
||||
(* Description as comment *)
|
||||
if String.length entry.desc > 0 then begin
|
||||
(* Pad to align comments *)
|
||||
let current_len = Buffer.length buf in
|
||||
let target = max (current_len + 1) 40 in
|
||||
for _ = current_len to target - 1 do
|
||||
Buffer.add_char buf ' '
|
||||
done;
|
||||
Buffer.add_string buf "# ";
|
||||
Buffer.add_string buf entry.desc
|
||||
end;
|
||||
Buffer.contents buf
|
||||
let name = match entry.switch with
|
||||
| Both (s, l) -> Printf.sprintf "--%s(-%c)" l s
|
||||
| Long l -> Printf.sprintf "--%s" l
|
||||
| Short s -> Printf.sprintf "-%c" s
|
||||
in
|
||||
let typed = match entry.param with
|
||||
| Some (Mandatory p) | Some (Optional p) -> ": " ^ nushell_type_of_param p
|
||||
| None -> ""
|
||||
in
|
||||
let flag = " " ^ name ^ typed in
|
||||
if String.length entry.desc = 0 then flag
|
||||
else
|
||||
let pad_len = max 1 (40 - String.length flag) in
|
||||
flag ^ String.make pad_len ' ' ^ "# " ^ entry.desc
|
||||
|
||||
let write_extern buf cmd_name result =
|
||||
let entries = dedup_entries result.entries in
|
||||
Printf.bprintf buf "export extern \"%s\" [\n" (escape_nu cmd_name);
|
||||
List.iter (fun e ->
|
||||
Buffer.add_string buf (format_flag e); Buffer.add_char buf '\n'
|
||||
) entries;
|
||||
Buffer.add_string buf "]\n";
|
||||
List.iter (fun (sc : subcommand) ->
|
||||
Printf.bprintf buf "\nexport extern \"%s %s\" [ # %s\n]\n"
|
||||
(escape_nu cmd_name) (escape_nu sc.name) (escape_nu sc.desc)
|
||||
) result.subcommands
|
||||
|
||||
(* Generate nushell extern definition for a command *)
|
||||
let generate_extern cmd_name result =
|
||||
let buf = Buffer.create 1024 in
|
||||
(* Main extern with flags *)
|
||||
Buffer.add_string buf (Printf.sprintf "export extern \"%s\" [\n" (escape_nu cmd_name));
|
||||
List.iter (fun entry ->
|
||||
Buffer.add_string buf (format_flag entry);
|
||||
Buffer.add_char buf '\n'
|
||||
) result.entries;
|
||||
Buffer.add_string buf "]\n";
|
||||
(* Subcommand externs *)
|
||||
List.iter (fun (sc : subcommand) ->
|
||||
Buffer.add_string buf
|
||||
(Printf.sprintf "\nexport extern \"%s %s\" [ # %s\n]\n"
|
||||
(escape_nu cmd_name) (escape_nu sc.name) (escape_nu sc.desc))
|
||||
) result.subcommands;
|
||||
write_extern buf cmd_name result;
|
||||
Buffer.contents buf
|
||||
|
||||
(* Generate a complete nushell module *)
|
||||
let generate_module cmd_name result =
|
||||
Printf.sprintf "module %s-completions {\n%s}\n"
|
||||
cmd_name (generate_extern cmd_name result)
|
||||
let module_name_of cmd_name =
|
||||
let s = String.map (function
|
||||
| ('a'..'z' | 'A'..'Z' | '0'..'9' | '-' | '_') as c -> c | _ -> '-') cmd_name in
|
||||
s ^ "-completions"
|
||||
|
||||
let generate_module cmd_name result =
|
||||
let m = module_name_of cmd_name in
|
||||
let buf = Buffer.create 1024 in
|
||||
Printf.bprintf buf "module %s {\n" m;
|
||||
write_extern buf cmd_name result;
|
||||
Printf.bprintf buf "}\n\nuse %s *\n" m;
|
||||
Buffer.contents buf
|
||||
|
||||
(* Generate from manpage entries (no subcommands) *)
|
||||
let generate_extern_from_entries cmd_name entries =
|
||||
let result = { entries; subcommands = [] } in
|
||||
generate_extern cmd_name result
|
||||
generate_extern cmd_name { entries; subcommands = [] }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue