Skip to content

Commit 2ff84a3

Browse files
committed
T7988: add diff function for show config
1 parent 1ff16c7 commit 2ff84a3

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed

src/config_diff.ml

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,24 @@ module Diff_compare = struct
2323
}
2424
end
2525

26+
module Diff_show = struct
27+
type t = { left: Config_tree.t;
28+
right: Config_tree.t;
29+
base_path: string list;
30+
open_blocks: string list list;
31+
config_diff: string;
32+
}
33+
end
34+
2635
type _ diff_result =
2736
| Diff_tree : Diff_tree.t -> Diff_tree.t diff_result
2837
| Diff_compare : Diff_compare.t -> Diff_compare.t diff_result
38+
| Diff_show : Diff_show.t -> Diff_show.t diff_result
2939

3040
let eval_diff_result : type a. a diff_result -> a = function
3141
| Diff_tree x -> x
3242
| Diff_compare x -> x
43+
| Diff_show x -> x
3344

3445
type 'a diff_func = ?recurse:bool -> string list -> 'a diff_result -> change -> 'a diff_result
3546

@@ -46,6 +57,12 @@ let make_diff_compare l r = Diff_compare { left = l; right = r;
4657
udiff = "";
4758
}
4859

60+
let make_diff_show l r path = Diff_show { left = l; right = r;
61+
base_path = path;
62+
open_blocks = [];
63+
config_diff = "";
64+
}
65+
4966
let name_of n = Vytree.name_of_node n
5067
let data_of n = Vytree.data_of_node n
5168
let children_of n = Vytree.children_of_node n
@@ -319,6 +336,7 @@ let get_tagged_delete_tree dt =
319336
(* the following builds a diff_func to return a unified diff string of
320337
configs or config commands for use in the config-mode 'compare' command
321338
*)
339+
322340
let list_but_last l =
323341
let len = List.length l in
324342
List.filteri (fun i _ -> i < len - 1) l
@@ -466,6 +484,230 @@ let diff_compare ?(cmds=false) path left right =
466484
in
467485
strs
468486

487+
(* the following builds a diff_func for 'show config' *)
488+
489+
let annotate_rendered change rendered =
490+
let mark =
491+
match change with
492+
| Unchanged -> " "
493+
| Added -> "+"
494+
| Subtracted -> "-"
495+
| Updated _ -> ">"
496+
in
497+
let lst = String.split_on_char '\n' rendered in
498+
let marked = List.map (fun x -> match x with "" -> x | _ -> mark ^ x) lst in
499+
String.concat "\n" marked
500+
501+
let get_level_at_path node path =
502+
(* alert exn Config_tree.is_tag_value:
503+
[Vytree.Empty_path] called in pattern path non-empty
504+
[Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
505+
*)
506+
let f level p =
507+
if (Config_tree.is_tag_value[@alert "-exn"]) node p then level
508+
else level + 1
509+
in
510+
match path with
511+
| [] -> 0
512+
| _ ->
513+
List.fold_left f 0 (Util.flag path) - 1
514+
515+
let render_level_open indent node path =
516+
(* alert exn Config_tree.is_tag, Config_tree.is_tag_value:
517+
[Vytree.Empty_path] called in branch path non-empty
518+
[Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
519+
*)
520+
if Util.is_empty path || (Config_tree.is_tag[@alert "-exn"]) node path then
521+
""
522+
else
523+
let level = get_level_at_path node path in
524+
let indent_str = Config_tree.make_indent indent level in
525+
if (Config_tree.is_tag_value[@alert "-exn"]) node path then
526+
let tag_node =
527+
match Util.get_last_n path 1 with
528+
| None -> (* not possible as path non-empty *) "none"
529+
| Some n -> n
530+
in
531+
let tag_value =
532+
match Util.get_last path with
533+
| None -> (* not possible as path non-empty *) "none"
534+
| Some v -> v
535+
in
536+
Printf.sprintf "%s%s %s {\n" indent_str tag_node tag_value
537+
else
538+
let name =
539+
match Util.get_last path with
540+
| None -> (* not possible as path non-empty *) "none"
541+
| Some v -> v
542+
in
543+
Printf.sprintf "%s%s {\n" indent_str name
544+
545+
let render_level_close indent node path =
546+
(* alert exn Config_tree.is_tag:
547+
[Vytree.Empty_path] called in branch path non-empty
548+
[Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
549+
*)
550+
if Util.is_empty path || (Config_tree.is_tag[@alert "-exn"]) node path then
551+
""
552+
else
553+
let level = get_level_at_path node path in
554+
let indent_str = Config_tree.make_indent indent level in
555+
Printf.sprintf "%s}\n" indent_str
556+
557+
let config_diff (rt : Reference_tree.t) ?(recurse=true) (path : string list) (Diff_show res) (m : change) =
558+
(* alert exn Vytree.get, Reference_tree.refpath, Config_tree.get_values,
559+
Reference_tree.is_multi, Config_tree.is_tag_value:
560+
[Vytree.Empty_path] checked at only point possible (Unchanged)
561+
[Vytree.Nonexistent_path] function diff never calls diff_func on nonexistent path
562+
*)
563+
564+
let indent = 4 in
565+
(* the only subtlety in all this is the bookkeeping of closing open
566+
braces at correct level:
567+
(1) a rendered line with open brace will occur before the next
568+
depth-first step
569+
(2) at each return to (local) root, the path is checked for the
570+
matching closing brace
571+
explicitly: the record field open_blocks is a list of open paths
572+
ordered by reverse inclusion, which are closed when the path being
573+
passed to config_diff no longer contains that element
574+
*)
575+
let rec close_blocks s l =
576+
match l with
577+
| [] -> s, []
578+
| h :: tl ->
579+
if Util.is_sublist h path then
580+
s, l
581+
else
582+
let rendered = render_level_close indent res.left h in
583+
let s' = s ^ annotate_rendered Unchanged rendered
584+
in close_blocks s' tl
585+
in
586+
let diff_str, rev_blocks = close_blocks res.config_diff res.open_blocks
587+
in
588+
match m with
589+
| Added ->
590+
let node =
591+
if (Config_tree.is_tag_value[@alert "-exn"]) res.right path then
592+
(Vytree.get[@alert "-exn"]) res.right (Util.drop_last path)
593+
else
594+
(Vytree.get[@alert "-exn"]) res.right path
595+
in
596+
let level = get_level_at_path res.right path in
597+
let rendered =
598+
Config_tree.render_node indent level node
599+
in
600+
let rev_diff = diff_str ^ annotate_rendered m rendered in
601+
Diff_show {res with config_diff = rev_diff; open_blocks = rev_blocks;}
602+
| Subtracted ->
603+
let node =
604+
if (Config_tree.is_tag_value[@alert "-exn"]) res.left path then
605+
(Vytree.get[@alert "-exn"]) res.left (Util.drop_last path)
606+
else
607+
(Vytree.get[@alert "-exn"]) res.left path
608+
in
609+
let level = get_level_at_path res.left path in
610+
let rendered =
611+
Config_tree.render_node indent level node
612+
in
613+
let rev_diff = diff_str ^ annotate_rendered m rendered in
614+
Diff_show {res with config_diff = rev_diff; open_blocks = rev_blocks;}
615+
| Unchanged ->
616+
begin
617+
match recurse with
618+
| false ->
619+
let rendered = render_level_open indent res.left path in
620+
let rev_diff = diff_str ^ annotate_rendered m rendered in
621+
Diff_show {res with config_diff = rev_diff; open_blocks = path::rev_blocks}
622+
| true ->
623+
match path with
624+
| [] -> (* case left = right *)
625+
let rendered =
626+
Config_tree.render_config res.left
627+
in
628+
let rev_diff = diff_str ^ annotate_rendered m rendered in
629+
Diff_show {res with config_diff = rev_diff; open_blocks = rev_blocks;}
630+
| _ ->
631+
let level = get_level_at_path res.left path in
632+
let node = (Vytree.get[@alert "-exn"]) res.left path in
633+
let rendered =
634+
Config_tree.render_node indent level node
635+
in
636+
let rev_diff = diff_str ^ annotate_rendered m rendered in
637+
Diff_show {res with config_diff = rev_diff; open_blocks = rev_blocks;}
638+
end
639+
| Updated v ->
640+
let refp =
641+
(Reference_tree.refpath[@alert "-exn"]) rt (res.base_path @ path) in
642+
let multi = (Reference_tree.is_multi[@alert "-exn"]) rt refp in
643+
let level = get_level_at_path res.left path in
644+
let indent_str =
645+
Config_tree.make_indent indent level
646+
in
647+
let name =
648+
match Util.get_last path with
649+
| None -> (* not possible *) "none"
650+
| Some n -> n
651+
in
652+
match multi with
653+
| false ->
654+
let rendered =
655+
Config_tree.render_values indent_str name v
656+
in
657+
let rev_diff = diff_str ^ annotate_rendered m rendered in
658+
Diff_show {res with config_diff = rev_diff; open_blocks = rev_blocks;}
659+
| true ->
660+
let ov = (Config_tree.get_values[@alert "-exn"]) res.left path in
661+
let ov_set = ValueS.of_list ov in
662+
let v_set = ValueS.of_list v in
663+
let sub_vals = ValueS.elements (ValueS.diff ov_set v_set) in
664+
let add_vals = ValueS.elements (ValueS.diff v_set ov_set) in
665+
let inter_vals = ValueS.elements (ValueS.inter ov_set v_set) in
666+
let sub_rendered =
667+
match sub_vals with
668+
| [] -> ""
669+
| _ ->
670+
Config_tree.render_values indent_str name sub_vals
671+
in
672+
let sub_diff = annotate_rendered Subtracted sub_rendered in
673+
let add_rendered =
674+
match add_vals with
675+
| [] -> ""
676+
| _ ->
677+
Config_tree.render_values indent_str name add_vals
678+
in
679+
let add_diff = annotate_rendered Added add_rendered in
680+
let inter_rendered =
681+
match inter_vals with
682+
| [] -> ""
683+
| _ ->
684+
Config_tree.render_values indent_str name inter_vals
685+
in
686+
let inter_diff = annotate_rendered Unchanged inter_rendered in
687+
let value_diff = sub_diff ^ inter_diff ^ add_diff in
688+
let rev_diff = diff_str ^ value_diff in
689+
Diff_show {res with config_diff = rev_diff; open_blocks = rev_blocks;}
690+
691+
(* call recursive diff on config_trees with config_diff as the diff_func *)
692+
let diff_show rt path left right =
693+
(* raises:
694+
[Incommensurable]
695+
[Empty_comparison]
696+
*)
697+
if (name_of left) <> (name_of right) then
698+
raise Incommensurable
699+
else
700+
let (left, right) =
701+
if not (Util.is_empty path) then
702+
(Config_tree.get_subtree left path, Config_tree.get_subtree right path)
703+
else (left, right)
704+
in
705+
let config_show = make_diff_show left right path in
706+
let d = diff [] (config_diff rt) config_show (Option.some left, Option.some right)
707+
in
708+
let diff_show_result = eval_diff_result d in
709+
diff_show_result.config_diff
710+
469711
(* mask function; mask applied on right *)
470712
let mask_func ?recurse:_ (path : string list) (Diff_tree res) (m : change) =
471713
(* alert exn Vytree.delete:

src/config_diff.mli

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,19 @@ module Diff_compare : sig
1919
}
2020
end
2121

22+
module Diff_show : sig
23+
type t = { left: Config_tree.t;
24+
right: Config_tree.t;
25+
base_path: string list;
26+
open_blocks: string list list;
27+
config_diff: string;
28+
}
29+
end
30+
2231
type _ diff_result =
2332
| Diff_tree : Diff_tree.t -> Diff_tree.t diff_result
2433
| Diff_compare : Diff_compare.t -> Diff_compare.t diff_result
34+
| Diff_show : Diff_show.t -> Diff_show.t diff_result
2535

2636
val eval_diff_result : 'a diff_result -> 'a
2737

@@ -43,6 +53,10 @@ val diff_compare : ?cmds:bool -> string list -> Config_tree.t -> Config_tree.t -
4353
[@@alert exn "Config_diff.Incommensurable"]
4454
[@@alert exn "Config_diff.Empty_comparison"]
4555

56+
val diff_show : Reference_tree.t -> string list -> Config_tree.t -> Config_tree.t -> string
57+
[@@alert exn "Config_diff.Incommensurable"]
58+
[@@alert exn "Config_diff.Empty_comparison"]
59+
4660
val tree_union : Config_tree.t -> Config_tree.t -> Config_tree.t
4761
[@@alert exn "Tree_alg.Incompatible_union"]
4862
[@@alert exn "Tree_alg.Nonexistent_child"]

0 commit comments

Comments
 (0)