open Inshellah.Parser open Inshellah.Manpage open Inshellah.Nushell let failures = ref 0 let passes = ref 0 let check name condition = if condition then begin incr passes; Printf.printf " PASS: %s\n" name end else begin incr failures; Printf.printf " FAIL: %s\n" name end let parse txt = match parse_help txt with | Ok r -> r | Error msg -> failwith (Printf.sprintf "parse_help failed: %s" msg) (* --- Help parser tests --- *) let test_gnu_basic () = Printf.printf "\n== GNU basic flags ==\n"; let r = parse " -a, --all do not ignore entries starting with .\n" in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "both switch" (e.switch = Both ('a', "all")); check "no param" (e.param = None); check "desc" (String.length e.desc > 0) let test_gnu_eq_param () = Printf.printf "\n== GNU = param ==\n"; let r = parse " --block-size=SIZE scale sizes by SIZE\n" in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "long switch" (e.switch = Long "block-size"); check "mandatory param" (e.param = Some (Mandatory "SIZE")) let test_gnu_opt_param () = Printf.printf "\n== GNU optional param ==\n"; let r = parse " --color[=WHEN] color the output WHEN\n" in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "long switch" (e.switch = Long "color"); check "optional param" (e.param = Some (Optional "WHEN")) let test_underscore_param () = Printf.printf "\n== Underscore in param (TIME_STYLE) ==\n"; let r = parse " --time-style=TIME_STYLE time/date format\n" in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "param with underscore" (e.param = Some (Mandatory "TIME_STYLE")) let test_short_only () = Printf.printf "\n== Short-only flag ==\n"; let r = parse " -v verbose output\n" in check "one entry" (List.length r.entries = 1); check "short switch" ((List.hd r.entries).switch = Short 'v') let test_long_only () = Printf.printf "\n== Long-only flag ==\n"; let r = parse " --help display help\n" in check "one entry" (List.length r.entries = 1); check "long switch" ((List.hd r.entries).switch = Long "help") let test_multiline_desc () = Printf.printf "\n== Multi-line description ==\n"; let r = parse {| --block-size=SIZE with -l, scale sizes by SIZE when printing them; e.g., '--block-size=M'; see SIZE format below |} in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "desc includes continuation" (String.length e.desc > 50) let test_multiple_entries () = Printf.printf "\n== Multiple entries ==\n"; let r = parse {| -a, --all do not ignore entries starting with . -A, --almost-all do not list implied . and .. --author with -l, print the author of each file |} in check "three entries" (List.length r.entries = 3) let test_clap_short_sections () = Printf.printf "\n== Clap short with section headers ==\n"; let r = parse {|INPUT OPTIONS: -e, --regexp=PATTERN A pattern to search for. -f, --file=PATTERNFILE Search for patterns from the given file. SEARCH OPTIONS: -s, --case-sensitive Search case sensitively. |} in check "three entries" (List.length r.entries = 3); let e = List.hd r.entries in check "first is regexp" (e.switch = Both ('e', "regexp")); check "first has param" (e.param = Some (Mandatory "PATTERN")) let test_clap_long_style () = Printf.printf "\n== Clap long style (desc below flag) ==\n"; let r = parse {| -H, --hidden Include hidden directories and files. --no-ignore Do not respect ignore files. |} in check "two entries" (List.length r.entries = 2); let e = List.hd r.entries in check "hidden switch" (e.switch = Both ('H', "hidden")); check "desc below" (String.length e.desc > 0) let test_clap_long_angle_param () = Printf.printf "\n== Clap long angle bracket param ==\n"; let r = parse {| --nonprintable-notation Set notation for non-printable characters. |} in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "long switch" (e.switch = Long "nonprintable-notation"); check "angle param" (e.param = Some (Mandatory "notation")) let test_space_upper_param () = Printf.printf "\n== Space-separated ALL_CAPS param ==\n"; let r = parse " -f, --foo FOO foo help\n" in check "one entry" (List.length r.entries = 1); let e = List.hd r.entries in check "switch" (e.switch = Both ('f', "foo")); check "space param" (e.param = Some (Mandatory "FOO")) let test_go_cobra_flags () = Printf.printf "\n== Go/Cobra flags ==\n"; let r = parse {|Flags: -D, --debug Enable debug mode -H, --host string Daemon socket to connect to -v, --version Print version information |} in check "three flag entries" (List.length r.entries = 3); (* Check the host flag has a type param *) let host = List.nth r.entries 1 in check "host switch" (host.switch = Both ('H', "host")); check "host type param" (host.param = Some (Mandatory "string")) let test_go_cobra_subcommands () = Printf.printf "\n== Go/Cobra subcommands ==\n"; let r = parse {|Common Commands: run Create and run a new container from an image exec Execute a command in a running container build Build an image from a Dockerfile |} in check "has subcommands" (List.length r.subcommands > 0) let test_busybox_tab () = Printf.printf "\n== Busybox tab-indented ==\n"; let r = parse "\t-1\tOne column output\n\t-a\tInclude names starting with .\n" in check "two entries" (List.length r.entries = 2); check "first is -1" ((List.hd r.entries).switch = Short '1') let test_no_debug_prints () = Printf.printf "\n== No debug side effects ==\n"; (* The old parser had print_endline at module load time. If we got here without "opt param is running" on stdout, we're good. *) check "no debug prints" true (* --- Manpage parser tests --- *) let test_manpage_tp_style () = Printf.printf "\n== Manpage .TP style ==\n"; let groff = {|.SH OPTIONS .TP \fB\-a\fR, \fB\-\-all\fR do not ignore entries starting with . .TP \fB\-A\fR, \fB\-\-almost\-all\fR do not list implied . and .. .TP \fB\-\-block\-size\fR=\fISIZE\fR with \fB\-l\fR, scale sizes by SIZE .SH AUTHOR Written by someone. |} in let entries = parse_manpage_string groff in check "three entries" (List.length entries = 3); if List.length entries >= 1 then begin let e = List.hd entries in check "first is -a/--all" (e.switch = Both ('a', "all")); check "first desc" (String.length e.desc > 0) end; if List.length entries >= 3 then begin let e = List.nth entries 2 in check "block-size switch" (e.switch = Long "block-size"); check "block-size param" (e.param = Some (Mandatory "SIZE")) end let test_manpage_ip_style () = Printf.printf "\n== Manpage .IP style ==\n"; let groff = {|.SH OPTIONS .IP "\fB\-k\fR, \fB\-\-insecure\fR" Allow insecure connections. .IP "\fB\-o\fR, \fB\-\-output\fR \fIfile\fR" Write output to file. .SH SEE ALSO |} in let entries = parse_manpage_string groff in check "two entries" (List.length entries = 2); if List.length entries >= 1 then begin let e = List.hd entries in check "first is -k/--insecure" (e.switch = Both ('k', "insecure")) end let test_manpage_groff_stripping () = Printf.printf "\n== Groff escape stripping ==\n"; let s = strip_groff_escapes {|\fB\-\-color\fR[=\fIWHEN\fR]|} in check "font escapes removed" (not (String.contains s 'f' && String.contains s 'B')); check "dashes converted" (String.contains s '-'); let s2 = strip_groff_escapes {|\(aqhello\(aq|} in check "aq -> quote" (String.contains s2 '\'') let test_manpage_empty_options () = Printf.printf "\n== Manpage with no OPTIONS section ==\n"; let groff = {|.SH NAME foo \- does stuff .SH DESCRIPTION Does stuff. |} in let entries = parse_manpage_string groff in check "no entries" (List.length entries = 0) (* --- Nushell generation tests --- *) let contains s sub = try let _ = Str.search_forward (Str.regexp_string sub) s 0 in true with Not_found -> false let test_nushell_basic () = Printf.printf "\n== Nushell basic extern ==\n"; let r = parse " -a, --all do not ignore entries starting with .\n" in let nu = generate_extern "ls" r in check "has extern" (contains nu "export extern \"ls\""); check "has --all(-a)" (contains nu "--all(-a)"); check "has comment" (contains nu "# do not ignore") let test_nushell_param_types () = Printf.printf "\n== Nushell param type mapping ==\n"; let r = parse {| -w, --width=COLS set output width --block-size=SIZE scale sizes -o, --output FILE output file |} in let nu = generate_extern "ls" r in check "COLS -> int" (contains nu "--width(-w): int"); check "SIZE -> string" (contains nu "--block-size: string"); check "FILE -> path" (contains nu "--output(-o): path") let test_nushell_subcommands () = Printf.printf "\n== Nushell subcommands ==\n"; let r = parse {|Common Commands: run Create and run a new container exec Execute a command Flags: -D, --debug Enable debug mode |} in let nu = generate_extern "docker" r in check "has main extern" (contains nu "export extern \"docker\""); check "has --debug" (contains nu "--debug(-D)"); check "has run subcommand" (contains nu "export extern \"docker run\""); check "has exec subcommand" (contains nu "export extern \"docker exec\"") let test_nushell_from_manpage () = Printf.printf "\n== Nushell from manpage ==\n"; let groff = {|.SH OPTIONS .TP \fB\-a\fR, \fB\-\-all\fR do not ignore entries starting with . .TP \fB\-\-block\-size\fR=\fISIZE\fR scale sizes by SIZE .SH AUTHOR |} in let entries = parse_manpage_string groff in let nu = generate_extern_from_entries "ls" entries in check "has extern" (contains nu "export extern \"ls\""); check "has --all(-a)" (contains nu "--all(-a)"); check "has --block-size" (contains nu "--block-size: string") let test_nushell_module () = Printf.printf "\n== Nushell module wrapper ==\n"; let r = parse " -v, --verbose verbose output\n" in let nu = generate_module "myapp" r in check "has module" (contains nu "module myapp-completions"); check "has extern inside" (contains nu "export extern \"myapp\""); check "has flag" (contains nu "--verbose(-v)") let () = Printf.printf "Running help parser tests...\n"; test_gnu_basic (); test_gnu_eq_param (); test_gnu_opt_param (); test_underscore_param (); test_short_only (); test_long_only (); test_multiline_desc (); test_multiple_entries (); test_clap_short_sections (); test_clap_long_style (); test_clap_long_angle_param (); test_space_upper_param (); test_go_cobra_flags (); test_go_cobra_subcommands (); test_busybox_tab (); test_no_debug_prints (); Printf.printf "\nRunning manpage parser tests...\n"; test_manpage_tp_style (); test_manpage_ip_style (); test_manpage_groff_stripping (); test_manpage_empty_options (); Printf.printf "\nRunning nushell generation tests...\n"; test_nushell_basic (); test_nushell_param_types (); test_nushell_subcommands (); test_nushell_from_manpage (); test_nushell_module (); Printf.printf "\n=== Results: %d passed, %d failed ===\n" !passes !failures; if !failures > 0 then exit 1