aboutsummaryrefslogtreecommitdiff
path: root/lib/naming/config.ml
blob: 7d87d62128b20d38571016f1b957e26aa4f7b09f (plain) (blame)
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"))