View raw

1 (** Pre-scan phase for file-local declarations. 2 3 Scans the entire input for syntax-affecting keywords like #+TODO, 4 #+SEQ_TODO, and #+TYP_TODO before the main parse. These declarations may 5 appear anywhere in the file and affect how headings are interpreted. *) 6 7 let lstrip = String_util.lstrip 8 let strip = String_util.strip 9 10 (** Parse a TODO keyword line value into a todo_sequence. Format: "KEYWORD1 11 KEYWORD2 | DONE1 DONE2" If no | is present, all keywords are active and 12 there are no done keywords. *) 13 let parse_todo_value value = 14 let value = strip value in 15 if String.length value = 0 then None 16 else 17 let words = 18 String.split_on_char ' ' value |> List.filter (fun s -> s <> "") 19 in 20 let rec split_at_bar acc = function 21 | [] -> (List.rev acc, []) 22 | "|" :: rest -> (List.rev acc, rest) 23 | w :: rest -> split_at_bar (w :: acc) rest 24 in 25 let active, done_ = split_at_bar [] words in 26 if active = [] && done_ = [] then None else Some { Config.active; done_ } 27 28 (** Extract the value part after #+KEY: from a line. *) 29 let extract_keyword_value line = 30 (* Line format: #+KEY: VALUE *) 31 let line = lstrip line in 32 if String.length line < 2 || line.[0] <> '#' || line.[1] <> '+' then None 33 else 34 let rest = String.sub line 2 (String.length line - 2) in 35 match String.index_opt rest ':' with 36 | None -> None 37 | Some colon_pos -> 38 let key = String.uppercase_ascii (String.sub rest 0 colon_pos) in 39 let value = 40 if colon_pos + 1 < String.length rest then 41 String.sub rest (colon_pos + 1) (String.length rest - colon_pos - 1) 42 else "" 43 in 44 Some (key, strip value) 45 46 (** Scan the input for TODO keyword declarations. Returns a [Config.t] with all 47 discovered TODO sequences. *) 48 let scan input = 49 let lines = String.split_on_char '\n' input in 50 let sequences = 51 List.filter_map 52 (fun line -> 53 match extract_keyword_value line with 54 | Some (key, value) 55 when key = "TODO" || key = "SEQ_TODO" || key = "TYP_TODO" -> 56 parse_todo_value value 57 | _ -> None) 58 lines 59 in 60 match sequences with 61 | [] -> Config.default 62 | seqs -> { Config.todo_sequences = seqs } 63