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
79
80
|
module OI = Colitur_kernel.Overlay_ini
type t = {
lang : string option;
overlays : string list;
template : string option;
format : string option;
unknown_keys : string list;
unknown_sections : string list;
}
let empty =
{ lang = None; overlays = []; template = None; format = None; unknown_keys = [];
unknown_sections = [] }
let lang t = t.lang
let overlays t = t.overlays
let template t = t.template
let format t = t.format
let unknown_keys t = t.unknown_keys
let unknown_sections t = t.unknown_sections
let of_string text =
match OI.parse_sections text with
| Error e -> Error e
| Ok sections ->
(* Any section that is not [defaults] is unrecognised -- including a
plain typo such as [deafults] -- and must be REPORTED, never
silently dropped: that is precisely the failure this feature exists
to surface. *)
let unknown_sections =
List.filter_map
(fun (s : OI.section) -> if s.OI.name = "defaults" then None else Some s.OI.name)
sections
in
(* Merge EVERY section named [defaults], not just the first: [lang.ml]'s
[of_string] was fixed this morning to fold over all matching
sections rather than take [List.find_opt]'s first match, because a
hand-edited file WILL grow duplicate headers as a user appends to it
over time. Both modules parse the same reader
([Overlay_ini.parse_sections]) over the same file format, so they
must not disagree about what a duplicate [section] header means --
taking only the first [defaults] block here silently discarded every
later one, with nothing pointing back at the parser. Folding a
single accumulator across every matching section, in file order,
keeps this consistent with the within-section behaviour below (last
[k]-match wins): a scalar key repeated across two blocks resolves to
the LATER value, and [overlay] keeps accumulating across every
block, not only its first. *)
let defaults_sections =
List.filter (fun (s : OI.section) -> s.OI.name = "defaults") sections
in
let acc =
(* Cons then reverse once at the very end, not `@ [v]` per line: the
latter is O(n^2) over the field count, a real hang on a
machine-generated file with many overlay lines. Reversing only
after every section has been folded (not once per section) is
what keeps [overlay] and [unknown_keys] in file order across
block boundaries, not merely within one block. *)
List.fold_left
(fun acc (s : OI.section) ->
List.fold_left
(fun acc (k, v) ->
match k with
| "lang" -> { acc with lang = Some v }
| "template" -> { acc with template = Some v }
| "format" -> { acc with format = Some v }
(* accumulates: a user has more than one overlay *)
| "overlay" -> { acc with overlays = v :: acc.overlays }
| other -> { acc with unknown_keys = other :: acc.unknown_keys })
acc s.OI.fields)
empty defaults_sections
in
let acc = { acc with overlays = List.rev acc.overlays; unknown_keys = List.rev acc.unknown_keys } in
Ok { acc with unknown_sections }
let resolve ~flag ~config ~default =
match flag with
| Some v -> (v, "flag")
| None -> ( match config with Some v -> (v, "config") | None -> (default, "default"))
|