(* Bootstraps data/ef/lectionary.sexp from lectio's tridentine-lectionary.ini. Lives in tools/ and not the kernel for the same reason bootstrap_sanctoral.ml does: reading someone else's INI needs a reader, which does not belong in a pure kernel. Only `first` and `gospel` are mapped -- the plan's Global Constraints fix the scope at Epistle + Gospel, and lectio's data carries nothing else. *) open Colitur_kernel let default_source = "../lectio/internal/caldata/tridentine-lectionary.ini" let default_dest = "data/ef/lectionary.sexp" let die fmt = Printf.ksprintf (fun s -> prerr_endline ("bootstrap_lectionary: " ^ s); exit 1) fmt type section = { name : string; fields : (string * string) list } let parse_ini path = let ic = try open_in path with Sys_error e -> die "%s" e in let sections = ref [] and cur = ref None in let flush () = match !cur with | Some (n, fs) -> sections := { name = n; fields = List.rev fs } :: !sections | None -> () in (try while true do let line = String.trim (input_line ic) in if line = "" || line.[0] = ';' || line.[0] = '#' then () else if line.[0] = '[' then begin flush (); cur := Some (String.sub line 1 (String.length line - 2), []) end else match String.index_opt line '=' with | None -> die "%s: cannot parse line %S" path line | Some i -> let k = String.trim (String.sub line 0 i) in let v = String.trim (String.sub line (i + 1) (String.length line - i - 1)) in cur := (match !cur with | Some (n, fs) -> Some (n, (k, v) :: fs) | None -> die "%s: field %S before any section" path k) done with End_of_file -> ()); flush (); close_in ic; List.rev !sections let convert sec = let slug = match Slug.of_string sec.name with | Ok s -> s | Error e -> die "bad slug %S: %s" sec.name e in let cite part key = match List.assoc_opt key sec.fields with | None | Some "" -> None | Some reference -> Some { Citation.part; reference } in let cs = List.filter_map Fun.id [ cite Citation.First "first"; cite Citation.Gospel "gospel" ] in if cs = [] then die "%s: no first/gospel field" sec.name; if List.length cs = 1 then die "%s: has one reading, not two -- an Epistle without a Gospel (or the \ reverse) is malformed and must be investigated, not silently shipped" sec.name; (slug, cs) let sha256 path = let ic = Unix.open_process_in (Printf.sprintf "sha256sum %s" (Filename.quote path)) in let line = try input_line ic with End_of_file -> die "sha256sum failed" in ignore (Unix.close_process_in ic); List.hd (String.split_on_char ' ' line) let () = let src = if Array.length Sys.argv > 1 then Sys.argv.(1) else default_source in let dst = if Array.length Sys.argv > 2 then Sys.argv.(2) else default_dest in let secs = parse_ini src in let entries = List.map convert secs in let lect = match Lectionary.of_entries entries with | Ok l -> l | Error e -> die "%s" e in let oc = open_out dst in Printf.fprintf oc "; data/ef/lectionary.sexp -- EF (1962) temporal lectionary (Epistle +\n\ ; Gospel citations, never scripture text), bootstrapped from lectio.\n\ ; Generator: tools/bootstrap_lectionary.ml -- do not hand-edit; re-run the\n\ ; generator against a newer lectio and commit the diff instead.\n\ ;\n\ ; Source: %s\n\ ; SHA-256: %s\n\ ; %d entries. Regenerate with:\n\ ; eval $(opam env) && dune exec tools/bootstrap_lectionary.exe -- %s %s\n" src (sha256 src) (List.length entries) src dst; Sexplib.Sexp.output_hum oc (Lectionary.sexp_of_t lect); output_char oc '\n'; close_out oc; Printf.printf "bootstrap_lectionary: %d entries -> %s\n" (List.length entries) dst