cli11, bsd manual
This commit is contained in:
parent
9c7c528a0c
commit
18c97eacd0
7 changed files with 534 additions and 117 deletions
98
bin/main.ml
98
bin/main.ml
|
|
@ -2,6 +2,8 @@ open Inshellah.Parser
|
|||
open Inshellah.Manpage
|
||||
open Inshellah.Nushell
|
||||
|
||||
module SSet = Set.Make(String)
|
||||
|
||||
let usage () =
|
||||
Printf.eprintf
|
||||
{|inshellah - generate nushell completions
|
||||
|
|
@ -12,7 +14,10 @@ Usage:
|
|||
One .nu file per command.
|
||||
inshellah manpage FILE Parse a manpage and emit nushell extern
|
||||
inshellah manpage-dir DIR Batch-process manpages under DIR
|
||||
inshellah help CMD [ARGS...] Run CMD ARGS --help, parse and emit extern
|
||||
inshellah help [--iterative] CMD [ARGS...]
|
||||
Run CMD ARGS --help, parse and emit extern.
|
||||
Recursively resolves subcommands unless
|
||||
--iterative is given.
|
||||
inshellah parse-help CMD Read --help text from stdin, emit extern
|
||||
inshellah demo Run built-in demo
|
||||
|
||||
|
|
@ -94,21 +99,17 @@ let is_executable path =
|
|||
st.st_kind = Unix.S_REG && st.st_perm land 0o111 <> 0
|
||||
with Unix.Unix_error _ -> false
|
||||
|
||||
let is_shell_script path =
|
||||
let is_script path =
|
||||
try
|
||||
let real = Unix.realpath path in
|
||||
let ic = open_in_bin real in
|
||||
let line = (try let b = Bytes.create 256 in
|
||||
let n = input ic b 0 256 in
|
||||
let s = Bytes.sub_string b 0 n in
|
||||
(match String.index_opt s '\n' with Some i -> String.sub s 0 i | None -> s)
|
||||
with End_of_file -> "") in
|
||||
let has_shebang =
|
||||
try let b = Bytes.create 2 in
|
||||
really_input ic b 0 2;
|
||||
Bytes.get b 0 = '#' && Bytes.get b 1 = '!'
|
||||
with End_of_file -> false in
|
||||
close_in ic;
|
||||
String.length line >= 2 && line.[0] = '#' && line.[1] = '!'
|
||||
&& let shebang = String.lowercase_ascii line in
|
||||
List.exists (fun s -> contains_str shebang s)
|
||||
["bash"; "/sh "; "/sh\n"; "zsh"; "fish"; "nushell"; "/nu "; "/nu\n";
|
||||
"dash"; "ksh"; "csh"]
|
||||
has_shebang
|
||||
with _ -> false
|
||||
|
||||
let elf_scan path needles =
|
||||
|
|
@ -159,7 +160,7 @@ let elf_scan path needles =
|
|||
found
|
||||
|
||||
let skip_name name =
|
||||
String.length name = 0 || name.[0] = '.'
|
||||
String.length name = 0 || name = "-" || name.[0] = '.'
|
||||
|| String.starts_with ~prefix:"lib" name
|
||||
|| String.ends_with ~suffix:"-daemon" name
|
||||
|| String.ends_with ~suffix:"-wrapped" name
|
||||
|
|
@ -172,7 +173,7 @@ let classify_binary bindir name =
|
|||
else
|
||||
let path = Filename.concat bindir name in
|
||||
if not (is_executable path) then Skip
|
||||
else if is_shell_script path then Try_help
|
||||
else if is_script path then Try_help
|
||||
else
|
||||
let scan = elf_scan path ["-h"; "completion"] in
|
||||
if not (Hashtbl.mem scan "-h") then Skip
|
||||
|
|
@ -227,17 +228,50 @@ let cmd_manpage_dir dir =
|
|||
) (Sys.readdir subdir)
|
||||
) command_sections
|
||||
|
||||
let help_resolve cmd rest name =
|
||||
let resolve_one go rest name depth =
|
||||
match run_cmd (cmd :: rest @ ["--help"]) 10_000 with
|
||||
| None -> None
|
||||
| Some text ->
|
||||
(match parse_help text with
|
||||
| Error _ -> None
|
||||
| Ok r when depth >= 5 -> Some (generate_extern name r)
|
||||
| Ok r ->
|
||||
let main = generate_extern name { r with subcommands = [] } in
|
||||
let subs = List.map (fun (sc : subcommand) ->
|
||||
let sub_name = name ^ " " ^ sc.name in
|
||||
match go (rest @ [sc.name]) sub_name (depth + 1) with
|
||||
| Some s -> "\n" ^ s
|
||||
| None ->
|
||||
Printf.sprintf "\nexport extern \"%s\" [ # %s\n]\n"
|
||||
(escape_nu sub_name) (escape_nu sc.desc)
|
||||
) r.subcommands in
|
||||
Some (String.concat "" (main :: subs)))
|
||||
in
|
||||
let fix = ref (fun _ _ _ -> None) in
|
||||
fix := (fun rest name depth -> resolve_one !fix rest name depth);
|
||||
!fix rest name 0
|
||||
|
||||
let cmd_help args =
|
||||
match args with
|
||||
let iterative, cmd_args = match args with
|
||||
| "--iterative" :: rest -> (true, rest)
|
||||
| _ -> (false, args)
|
||||
in
|
||||
match cmd_args with
|
||||
| [] -> Printf.eprintf "error: help requires a command name\n"; exit 1
|
||||
| cmd :: rest ->
|
||||
let name = Filename.basename cmd in
|
||||
(match run_cmd (cmd :: rest @ ["--help"]) 10_000 with
|
||||
| None -> Printf.eprintf "no output from %s --help\n" name; exit 1
|
||||
| Some text ->
|
||||
(match parse_help text with
|
||||
| Ok r -> print_string (generate_extern name r)
|
||||
| Error msg -> Printf.eprintf "parse error for %s: %s\n" name msg; exit 1))
|
||||
let name = String.concat " " (Filename.basename cmd :: rest) in
|
||||
if iterative then
|
||||
(match run_cmd (cmd :: rest @ ["--help"]) 10_000 with
|
||||
| None -> Printf.eprintf "no output from %s --help\n" name; exit 1
|
||||
| Some text ->
|
||||
(match parse_help text with
|
||||
| Ok r -> print_string (generate_extern name r)
|
||||
| Error msg -> Printf.eprintf "parse error for %s: %s\n" name msg; exit 1))
|
||||
else
|
||||
(match help_resolve cmd rest name with
|
||||
| None -> Printf.eprintf "no output from %s --help\n" name; exit 1
|
||||
| Some output -> print_string output)
|
||||
|
||||
let cmd_parse_help cmd =
|
||||
let buf = Buffer.create 4096 in
|
||||
|
|
@ -261,23 +295,22 @@ let process_manpage file =
|
|||
with _ -> None
|
||||
|
||||
let manpaged_commands mandir =
|
||||
let cmds = Hashtbl.create 128 in
|
||||
List.iter (fun section ->
|
||||
List.fold_left (fun acc section ->
|
||||
let subdir = Filename.concat mandir (Printf.sprintf "man%d" section) in
|
||||
if Sys.file_exists subdir && Sys.is_directory subdir then
|
||||
Array.iter (fun f -> Hashtbl.replace cmds (cmd_name_of_manpage f) true)
|
||||
(Sys.readdir subdir)
|
||||
) command_sections;
|
||||
cmds
|
||||
Array.fold_left (fun acc f -> SSet.add (cmd_name_of_manpage f) acc)
|
||||
acc (Sys.readdir subdir)
|
||||
else acc
|
||||
) SSet.empty command_sections
|
||||
|
||||
let cmd_generate bindir mandir outdir =
|
||||
let done_cmds = Hashtbl.create 256 in
|
||||
let done_cmds = ref SSet.empty in
|
||||
let bins = Sys.readdir bindir in
|
||||
Array.sort String.compare bins;
|
||||
let manpaged = manpaged_commands mandir in
|
||||
let max_jobs = num_cores () in
|
||||
let classified = Array.map (fun name ->
|
||||
if Hashtbl.mem manpaged name then (name, Skip)
|
||||
if SSet.mem name manpaged then (name, Skip)
|
||||
else (name, classify_binary bindir name)
|
||||
) bins in
|
||||
let pending = ref [] in
|
||||
|
|
@ -328,7 +361,7 @@ let cmd_generate bindir mandir outdir =
|
|||
with _ -> exit 1)
|
||||
end else begin
|
||||
pending := pid :: !pending;
|
||||
Hashtbl.replace done_cmds name true
|
||||
done_cmds := SSet.add name !done_cmds
|
||||
end
|
||||
) classified;
|
||||
while !pending <> [] do
|
||||
|
|
@ -345,8 +378,9 @@ let cmd_generate bindir mandir outdir =
|
|||
| None -> ()
|
||||
| Some (cmd, result) ->
|
||||
let base = List.hd (String.split_on_char ' ' cmd) in
|
||||
if Hashtbl.mem done_cmds base then ()
|
||||
if SSet.mem cmd !done_cmds then ()
|
||||
else begin
|
||||
done_cmds := SSet.add cmd !done_cmds;
|
||||
let outpath = Filename.concat outdir (filename_of_cmd base ^ ".nu") in
|
||||
if Sys.file_exists outpath then begin
|
||||
let existing =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue