SAVE GAME

This commit is contained in:
atagen 2026-03-23 02:17:42 +11:00
parent 5de8e62e66
commit 1d0d3465c1
7 changed files with 641 additions and 10 deletions

View file

@ -234,7 +234,43 @@ let subcommand_entry =
else
char ' ' *> char ' ' *> inline_ws *>
rest_of_line <* eol >>| fun desc ->
{ name; desc = String.trim desc }
{ name = String.lowercase_ascii name;
desc = let t = String.trim desc in
if String.length t >= 2 && t.[0] = '-' && t.[1] = ' ' then
String.trim (String.sub t 2 (String.length t - 2))
else t }
(* --- Section header detection --- *)
(* Detect lines like "Arguments:", "POSITIONALS:", etc. that introduce
positional-argument sections (where name+desc lines are NOT subcommands) *)
let is_arg_section s =
let lc = String.lowercase_ascii (String.trim s) in
let base = if String.ends_with ~suffix:":" lc
then String.sub lc 0 (String.length lc - 1) |> String.trim
else lc in
base = "arguments" || base = "args" || base = "positionals"
|| base = "positional arguments"
(* A section header: left-aligned (or lightly indented) text ending with ':',
not starting with '-'. Must be consumed BEFORE subcommand_entry in choice. *)
let section_header =
available >>= fun avail ->
if avail = 0 then fail "eof"
else
peek_string (min avail 80) >>= fun preview ->
(* Extract just the first line from the preview *)
let first_line = match String.index_opt preview '\n' with
| Some i -> String.sub preview 0 i
| None -> preview in
let t = String.trim first_line in
let len = String.length t in
let indent = let i = ref 0 in
while !i < String.length first_line && (first_line.[!i] = ' ' || first_line.[!i] = '\t') do incr i done;
!i in
if len >= 2 && t.[len - 1] = ':' && t.[0] <> '-' && indent <= 4 then
rest_of_line <* eol_strict >>| fun line -> is_arg_section line
else fail "not a section header"
(* --- Top-level parser --- *)
@ -247,6 +283,10 @@ let help_parser =
let try_entry =
entry >>| fun e -> `Entry e
in
(* Detect section headers to track arg vs command sections *)
let try_section =
section_header >>| fun is_arg -> `Section is_arg
in
(* Try to parse a subcommand *)
let try_subcommand =
subcommand_entry >>| fun sc -> `Subcommand sc
@ -255,10 +295,17 @@ let help_parser =
let try_skip =
skip_non_option_line >>| fun () -> `Skip
in
many (choice [ try_entry; try_subcommand; try_skip ]) >>| fun items ->
many (choice [ try_entry; try_section; try_subcommand; try_skip ]) >>| fun items ->
let entries = List.filter_map (function `Entry e -> Some e | _ -> None) items in
(* Only keep subcommands that didn't appear under an Arguments/Positionals section *)
let subcommands =
List.filter_map (function `Subcommand sc -> Some sc | _ -> None) items
List.fold_left (fun (in_arg_sec, acc) item ->
match item with
| `Section is_arg -> (is_arg, acc)
| `Subcommand sc when not in_arg_sec -> (in_arg_sec, sc :: acc)
| _ -> (in_arg_sec, acc)
) (false, []) items
|> snd |> List.rev
|> List.fold_left (fun acc sc ->
match List.assoc_opt sc.name acc with
| Some prev when String.length prev.desc >= String.length sc.desc -> acc