1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
(** A flat INI front end for overlay files.
This is a CONVENIENCE FORMAT, not a second data model. It parses to exactly
the {!Overlay.t} the S-expression form parses to, and everything downstream
-- merge, diagnostics, validation -- is the same code on the same values.
There is deliberately no second semantics to keep in step.
It is also deliberately LESS EXPRESSIVE than the sexp form. It covers [Add],
[Suppress] and single-field [Edit], which is what a diocesan or parish
calendar needs; [Replace], multi-field edits and citation edits are not
expressible and the parser says so by name rather than failing obscurely.
Anything it cannot say is a reason to write sexp, not a reason to grow this.
{1 Format}
Section names are slugs. A [\[overlay\]] section carries the file's id.
{v
[overlay]
id = my-parish
[our-patron]
date = 07-11
rank = class-3
colour = white
name.en = St Example, Patron
[some-universal-slug]
suppress = yes
v}
Dates take three forms, matching {!Date_spec}: [MM-DD], [easter+N] or
[easter-N], and [mon/day/nth] such as [oct/sun/1] or [oct/sun/-1]. *)
(** One [section] of a flat INI file. Exposed so other libraries (the language
and config files) reuse this reader rather than growing a second one that
would drift in its comment, quoting and trimming rules. *)
type section = { name : string; fields : (string * string) list }
(** Split INI text into sections. [\[section\]] headers, [key = value] lines,
';' and '#' comments, blank lines ignored. Never raises. *)
val parse_sections : string -> (section list, string) result
(** [parse ~rank_of_string text] is the overlay [text] denotes.
[rank_of_string] is supplied by the rite, exactly as [Overlay.load] takes
[rank_of_sexp]: the kernel does not know one rite's rank vocabulary from
another's.
Errors carry the section name and the offending value, never a source-file
path -- this format exists for people who are not reading the source. *)
val parse :
rank_of_string:(string -> 'r option) -> string -> ('r Overlay.t, string) result
(** [to_sexp_string t] renders [t] as the S-expression form, with a header
noting that it was generated. *)
val to_sexp_string : ('r -> Sexplib0.Sexp.t) -> 'r Overlay.t -> string
(** [convert ~rank_of_string ~rank_to_sexp ~rank_of_sexp text] parses [text],
renders it, and PROVES the rendering before returning it: the emitted text
is parsed back with the very function the engine uses to load an overlay,
and the result must equal what the INI denoted.
That check is the point of this module. A transpiler that emits
syntactically valid but semantically wrong output is the failure mode a
convenience format invites, and it is one [colitur check] could not catch,
since the emitted file would parse cleanly and simply mean something else.
Verifying the round trip here makes that class of bug impossible to ship
rather than merely unlikely.
[Error] on a parse failure, and on a round-trip mismatch -- which is a bug
in this module, and says so. *)
val convert :
rank_of_string:(string -> 'r option) ->
rank_to_sexp:('r -> Sexplib0.Sexp.t) ->
rank_of_sexp:(Sexplib0.Sexp.t -> 'r) ->
string ->
(string, string) result
|