blob: 9fed4654b3b67e5b499388bf566df99bde742fbd (
plain)
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
type value_behaviour = AddValue | ReplaceValue
exception Duplicate_value
exception Node_has_no_value
exception No_such_value
exception Useless_set
type config_node_data = {
values: string list;
comment: string option;
inactive: bool;
ephemeral: bool;
} [@@deriving yojson]
type t = config_node_data Vytree.t [@@deriving yojson]
let default_data = {
values = [];
comment = None;
inactive = false;
ephemeral = false;
}
let make name = Vytree.make default_data name
let replace_value node path value =
let data = {default_data with values=[value]} in
Vytree.update node path data
let add_value node path value =
let node' = Vytree.get node path in
let data = Vytree.data_of_node node' in
let values = data.values in
match (Vylist.find (fun x -> x = value) values) with
| Some _ -> raise Duplicate_value
| None ->
let values = values @ [value] in
Vytree.update node path ({data with values=values})
let delete_value node path value =
let data = Vytree.data_of_node @@ Vytree.get node path in
let values = Vylist.remove (fun x -> x = value) data.values in
Vytree.update node path {data with values=values}
let set_value node path value behaviour =
match behaviour with
| AddValue -> add_value node path value
| ReplaceValue -> replace_value node path value
let set node path value behaviour =
if (Vytree.exists node path) then
(match value with
| None -> raise Useless_set
| Some v -> set_value node path v behaviour)
else
let path_existing = Vytree.get_existent_path node path in
let path_remaining = Vylist.complement path path_existing in
let values = match value with None -> [] | Some v -> [v] in
Vytree.insert_multi_level default_data node path_existing path_remaining {default_data with values=values}
let get_values node path =
let node' = Vytree.get node path in
let data = Vytree.data_of_node node' in
data.values
let get_value node path =
let values = get_values node path in
match values with
| [] -> raise Node_has_no_value
| x :: _ -> x
let delete node path value =
match value with
| Some v ->
(let values = get_values node path in
if Vylist.in_list values v then
(match values with
| [_] -> Vytree.delete node path
| _ -> delete_value node path v)
else raise No_such_value)
| None ->
Vytree.delete node path
let set_comment node path comment =
let data = Vytree.get_data node path in
Vytree.update node path {data with comment=comment}
let get_comment node path =
let data = Vytree.get_data node path in
data.comment
let set_inactive node path inactive =
let data = Vytree.get_data node path in
Vytree.update node path {data with inactive=inactive}
let is_inactive node path =
let data = Vytree.get_data node path in
data.inactive
let set_ephemeral node path ephemeral =
let data = Vytree.get_data node path in
Vytree.update node path {data with ephemeral=ephemeral}
let is_ephemeral node path =
let data = Vytree.get_data node path in
data.ephemeral
module Renderer =
struct
(* TODO Replace use of Printf with Format *)
module L = List
module S = String
module PF = Printf
module VT = Vytree
module RT = Reference_tree
(* Nodes are ordered based on a comarison of their names *)
let compare cmp node1 node2 =
let name1 = VT.name_of_node node1 in
let name2 = VT.name_of_node node2 in
cmp name1 name2
let indentation indent level = S.make (level * indent) ' '
let render_inactive data = if data.inactive then "#INACTIVE " else ""
let render_ephemeral data = if data.ephemeral then "#EPHEMERAL " else ""
let render_comment data indents =
match data.comment with
| None -> ""
(* Trailing indentation for the rest of the material on the next line *)
| Some c -> PF.sprintf "/*%s*/\n%s" c indents
let render_tag = function
| None -> ""
| Some tag -> PF.sprintf "%s " tag
let render_outer indents data name tag =
[render_comment data indents ;
render_inactive data;
render_ephemeral data;
render_tag tag;
name]
|> S.concat ""
let render_values = function
| [v] -> PF.sprintf "\"%s\";" v
| vs -> S.concat "; " vs |> PF.sprintf "[%s];"
let render_inner_and_outer indents inner outer =
if inner = ""
(* Hidden or empty descendents yield empty nodes *)
then PF.sprintf "%s%s { }" indents outer
else PF.sprintf "%s%s %s" indents outer inner
let render
?(indent=4)
?(reftree)
?(cmp=BatString.numeric_compare)
?(showephemeral=false)
?(showinactive=false)
(config_tree:t)
=
let is_hidden data =
(not showephemeral && data.ephemeral) ||
(not showinactive && data.inactive)
in
let rec render_node level ?tag node =
let data = VT.data_of_node node in
let name = VT.name_of_node node in
(* Hide inactive and ephemeral when necessary *)
if is_hidden data then ""
else
let indents = indentation indent level in
let outer = render_outer indents data name tag in
let inner = (* Children are ignored if the node has values *)
match data.values with
| [] -> VT.children_of_node node |> render_children level
| values -> render_values values
in
PF.sprintf "%s%s %s" indents outer inner
and render_children level = function
| [] -> "{ }"
| children ->
let indents = indentation indent level in
let render_child node = render_node (level + 1) node in
let rendered_children = L.map render_child children |> S.concat "\n"
in
if rendered_children = "" then "{ }"
else PF.sprintf "{\n%s\n%s}" rendered_children indents
in
(* Walks the reftree and config_tree side-by-side *)
let rec render_node_rt level tag rt node =
let data = VT.data_of_node node in
let name = VT.name_of_node node in
let rt_data = VT.data_of_node rt in
let rt_name = VT.name_of_node rt in
(* Hide inactive and ephemeral when necessary *)
if is_hidden data then ""
else
(* TODO refactor this ugly approach*)
let (outer_name, level', inner) =
let open RT in
let children = VT.children_of_node node in
let ordered = rt_data.keep_order in
match rt_data.node_type with
| Tag ->
("", 0, render_children_rt level (Some name) ordered rt children)
| Other ->
(name, level, render_children_rt level None ordered rt children)
| Leaf ->
(name, level, render_values data.values)
in
let indents = indentation indent level' in
let outer = render_outer indents data outer_name tag in
PF.sprintf "%s%s %s" indents outer inner
and render_children_rt level tag ordered rt = function
| [] -> "{ }"
| children ->
let is_tagged = BatOption.is_some tag in
let indents = indentation indent level in
let reorder nodes =
if ordered then nodes
else L.sort (compare cmp) nodes
in
let render_child node =
let level' = if is_tagged then level else level + 1 in
let node_reftree = VT.find rt (VT.name_of_node node) in
(* If there is no reftree for a node, default to stand-alone *)
match node_reftree with
| Some rt' -> render_node_rt level' tag rt' node
| None -> render_node level' ?tag node
in
let rendered_children = children
|> reorder
|> L.map render_child
|> S.concat "\n"
in
if rendered_children = "" then "{ }"
else if is_tagged
then rendered_children
else PF.sprintf "{\n%s\n%s}" rendered_children indents
in
match reftree with
| None -> render_node 0 config_tree
| Some rt -> render_node_rt 0 None rt config_tree
end (* Renderer *)
let render = Renderer.render
|