summaryrefslogtreecommitdiff
path: root/src/diff_show.ml
blob: fad9b34834303a09dde69d520417d484fa71bf3d (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
open Diff

module ValueS = Config_tree.ValueS

let annotate_rendered change rendered =
    let mark =
        match change with
        | Unchanged -> " "
        | Added -> "+"
        | Subtracted -> "-"
        | Updated _ -> ">"
    in
    let lst = String.split_on_char '\n' rendered in
    let marked = List.map (fun x -> match x with "" -> x | _ -> mark ^ x) lst in
    String.concat "\n" marked

let get_level_at_path node path =
    (* alert exn Config_tree.is_tag_value:
        [Vytree.Empty_path] called in pattern path non-empty
        [Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
     *)
    let f level p =
        if (Config_tree.is_tag_value[@alert "-exn"]) node p then level
        else level + 1
    in
    match path with
    | [] -> 0
    | _ ->
        List.fold_left f 0 (Util.flag path) - 1

let render_level_open indent node path =
    (* alert exn Config_tree.is_tag, Config_tree.is_tag_value:
        [Vytree.Empty_path] called in branch path non-empty
        [Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
     *)
    if Util.is_empty path || (Config_tree.is_tag[@alert "-exn"]) node path then
        ""
    else
    let level = get_level_at_path node path in
    let indent_str = Config_tree.make_indent indent level in
    if (Config_tree.is_tag_value[@alert "-exn"]) node path then
        let tag_node =
            match Util.get_last_n path 1 with
            | None -> (* not possible as path non-empty *) "none"
            | Some n -> n
        in
        let tag_value =
            match Util.get_last path with
            | None -> (* not possible as path non-empty *) "none"
            | Some v -> v
        in
        Printf.sprintf "%s%s %s {\n" indent_str tag_node tag_value
    else
        let name =
            match Util.get_last path with
            | None -> (* not possible as path non-empty *) "none"
            | Some v -> v
        in
        Printf.sprintf "%s%s {\n" indent_str name

let render_level_close indent node path =
    (* alert exn Config_tree.is_tag:
        [Vytree.Empty_path] called in branch path non-empty
        [Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
     *)
    if Util.is_empty path || (Config_tree.is_tag[@alert "-exn"]) node path then
        ""
    else
    let level = get_level_at_path node path in
    let indent_str = Config_tree.make_indent indent level in
    Printf.sprintf "%s}\n" indent_str

module Diff_show = struct
    type t = { left: Config_tree.t;
               right: Config_tree.t;
               rt: Reference_tree.t;
               base_path: string list;
               open_blocks: string list list;
               config_diff: string;
             }

    let make_init l r rt path = { left = l;
                                  right = r;
                                  rt = rt;
                                  base_path = path;
                                  open_blocks = [];
                                  config_diff = "";
                                }


    let diff_func ?(descent=true) (path : string list) res (m : change) =
        (* alert exn Vytree.get, Reference_tree.refpath, Config_tree.get_values,
           Reference_tree.is_multi, Config_tree.is_tag_value:
            [Vytree.Empty_path] checked at only point possible (Unchanged)
            [Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
         *)

        let indent = 4 in
        (* the only subtlety in all this is the bookkeeping of closing open
           braces at correct level:
           (1) a rendered line with open brace will occur before the next
           depth-first step
           (2) at each return to (local) root, the path is checked for the
           matching closing brace
           explicitly: the record field open_blocks is a list of open paths
           ordered by reverse inclusion, which are closed when the path being
           passed to config_diff no longer contains that element
         *)
        let rec close_blocks s l =
            match l with
            | [] -> s, []
            | h :: tl ->
                if Util.is_sublist h path then
                    s, l
                else
                    let rendered = render_level_close indent res.left h in
                    let s' = s ^ annotate_rendered Unchanged rendered
                    in close_blocks s' tl
        in
        let diff_str, rev_blocks = close_blocks res.config_diff res.open_blocks
        in
        match m with
        | Added ->
            let node = (Vytree.get[@alert "-exn"]) res.right path in
            let level = get_level_at_path res.right path in
            let rendered =
                Config_tree.render_node indent level node
            in
            let rev_diff = diff_str ^ annotate_rendered m rendered in
            {res with config_diff = rev_diff; open_blocks = rev_blocks;}
        | Subtracted ->
            let node = (Vytree.get[@alert "-exn"]) res.left path in
            let level = get_level_at_path res.left path in
            let rendered =
                Config_tree.render_node indent level node
            in
            let rev_diff = diff_str ^ annotate_rendered m rendered in
            {res with config_diff = rev_diff; open_blocks = rev_blocks;}
        | Unchanged ->
            begin
            match descent with
            | false ->
                let rendered = render_level_open indent res.left path in
                let rev_diff = diff_str ^ annotate_rendered m rendered in
                {res with config_diff = rev_diff; open_blocks = path::rev_blocks}
            | true ->
                match path with
                | [] -> (* case left = right *)
                    let rendered =
                        Config_tree.render_config res.left
                    in
                    let rev_diff = diff_str ^ annotate_rendered m rendered in
                    {res with config_diff = rev_diff; open_blocks = rev_blocks;}
                | _ ->
                    let level = get_level_at_path res.left path in
                    let node = (Vytree.get[@alert "-exn"]) res.left path in
                    let rendered =
                        Config_tree.render_node indent level node
                    in
                    let rev_diff = diff_str ^ annotate_rendered m rendered in
                    {res with config_diff = rev_diff; open_blocks = rev_blocks;}
            end
        | Updated v ->
            let refp =
                (Reference_tree.refpath[@alert "-exn"]) res.rt (res.base_path @ path) in
            let multi = (Reference_tree.is_multi[@alert "-exn"]) res.rt refp in
            let level = get_level_at_path res.left path in
            let indent_str =
                Config_tree.make_indent indent level
            in
            let name =
                match Util.get_last path with
                | None -> (* not possible *) "none"
                | Some n -> n
            in
            match multi with
            | false ->
                let rendered =
                    Config_tree.render_values indent_str name v
                in
                let rev_diff = diff_str ^ annotate_rendered m rendered in
                {res with config_diff = rev_diff; open_blocks = rev_blocks;}
            | true ->
                let ov = (Config_tree.get_values[@alert "-exn"]) res.left path in
                let ov_set = ValueS.of_list ov in
                let v_set = ValueS.of_list v in
                let sub_vals = ValueS.elements (ValueS.diff ov_set v_set) in
                let add_vals = ValueS.elements (ValueS.diff v_set ov_set) in
                let inter_vals = ValueS.elements (ValueS.inter ov_set v_set) in
                let sub_rendered =
                    match sub_vals with
                    | [] -> ""
                    | _ ->
                        Config_tree.render_values indent_str name sub_vals
                in
                let sub_diff = annotate_rendered Subtracted sub_rendered in
                let add_rendered =
                    match add_vals with
                    | [] -> ""
                    | _ ->
                        Config_tree.render_values indent_str name add_vals
                in
                let add_diff = annotate_rendered Added add_rendered in
                let inter_rendered =
                    match inter_vals with
                    | [] -> ""
                    | _ ->
                        Config_tree.render_values indent_str name inter_vals
                in
                let inter_diff = annotate_rendered Unchanged inter_rendered in
                let value_diff = sub_diff ^ inter_diff ^ add_diff in
                let rev_diff = diff_str ^ value_diff in
                {res with config_diff = rev_diff; open_blocks = rev_blocks;}
end

module D = Diff(Diff_show)

(* call recursive diff on config_trees with config_diff as the diff_func *)
let diff_show rt path left right =
    (* raises:
        [Incommensurable]
        [Empty_comparison]
     *)
    if (Vytree.name_of_node left) <> (Vytree.name_of_node right) then
        raise Incommensurable
    else
        let (left, right) =
            if not (Util.is_empty path) then
            let with_node =
            match Reference_tree.get_path_type rt path with
            | `Leaf -> true
            | _ -> false
            in
            (Config_tree.get_subtree ~with_node left path,
            Config_tree.get_subtree ~with_node right path)
            else (left, right)
        in
        let init = Diff_show.make_init left right rt path in
        let ret = D.diff init left right in
        (* close final braces *)
        let res = Diff_show.diff_func ~descent:false [] ret Unchanged in
        res.config_diff