diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/config_diff.ml | 110 | ||||
-rw-r--r-- | src/config_diff.mli | 12 | ||||
-rw-r--r-- | src/config_file.ml | 44 | ||||
-rw-r--r-- | src/config_file.mli | 4 | ||||
-rw-r--r-- | src/config_tree.ml | 75 | ||||
-rw-r--r-- | src/config_tree.mli | 15 | ||||
-rw-r--r-- | src/dune | 2 | ||||
-rw-r--r-- | src/generate.ml | 50 | ||||
-rw-r--r-- | src/generate.mli | 5 | ||||
-rw-r--r-- | src/internal.ml | 66 | ||||
-rw-r--r-- | src/internal.mli | 19 | ||||
-rw-r--r-- | src/reference_tree.ml | 318 | ||||
-rw-r--r-- | src/reference_tree.mli | 44 | ||||
-rw-r--r-- | src/tree_alg.ml | 68 | ||||
-rw-r--r-- | src/util.ml | 74 | ||||
-rw-r--r-- | src/util.mli | 22 | ||||
-rw-r--r-- | src/value_checker.ml | 84 | ||||
-rw-r--r-- | src/value_checker.mli | 14 | ||||
-rw-r--r-- | src/vyos1x_lexer.mll | 2 | ||||
-rw-r--r-- | src/vyos1x_parser.mly | 4 | ||||
-rw-r--r-- | src/vytree.ml | 21 | ||||
-rw-r--r-- | src/vytree.mli | 4 |
22 files changed, 957 insertions, 100 deletions
diff --git a/src/config_diff.ml b/src/config_diff.ml index 3529a5d..87b2663 100644 --- a/src/config_diff.ml +++ b/src/config_diff.ml @@ -27,6 +27,7 @@ module Diff_cstore = struct type t = { left: Config_tree.t; right: Config_tree.t; handle: int; + out: string; } end @@ -43,19 +44,23 @@ let eval_result : type a. a result -> a = function type 'a diff_func = ?recurse:bool -> string list -> 'a result -> change -> 'a result let make_diff_trees l r = Diff_tree { left = l; right = r; - add = (Config_tree.make ""); - sub = (Config_tree.make ""); - del = (Config_tree.make ""); - inter = (Config_tree.make ""); + add = (Config_tree.make ""); + sub = (Config_tree.make ""); + del = (Config_tree.make ""); + inter = (Config_tree.make ""); } -let make_diff_string l r = Diff_string { - left = l; right = r; - skel = (Config_tree.make ""); - ppath = []; - udiff = ""; +let make_diff_string l r = Diff_string { left = l; right = r; + skel = (Config_tree.make ""); + ppath = []; + udiff = ""; } +let make_diff_cstore l r h = Diff_cstore { left = l; right = r; + handle = h; + out = ""; +} + let name_of n = Vytree.name_of_node n let data_of n = Vytree.data_of_node n let children_of n = Vytree.children_of_node n @@ -75,6 +80,9 @@ module TreeOrd = struct end module ChildrenS = Set.Make(TreeOrd) +(* unordered set of values *) +module ValueSet = Set.Make(String) + let (^~) (node : Config_tree.t) (node' : Config_tree.t) = name_of node = name_of node' && (data_of node).values <> (data_of node').values @@ -173,8 +181,6 @@ let clone ?(recurse=true) ?(set_values=None) old_root new_root path = let path_remaining = Vylist.complement path path_existing in clone_path ~recurse:recurse ~set_values:set_values old_root new_root path_existing path_remaining -let is_empty l = (l = []) - (* define the diff_func *) let decorate_trees ?(recurse=true) (path : string list) (Diff_tree res) (m : change) = match m with @@ -197,14 +203,14 @@ let decorate_trees ?(recurse=true) (path : string list) (Diff_tree res) (m : cha 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_tree = - if not (is_empty sub_vals) then + if not (Util.is_empty sub_vals) then clone ~set_values:(Some sub_vals) res.left res.sub path else res.sub in let del_tree = - if not (is_empty sub_vals) then - if (is_empty add_vals) && (is_empty inter_vals) then + if not (Util.is_empty sub_vals) then + if (Util.is_empty add_vals) && (Util.is_empty inter_vals) then (* delete whole node, not just values *) clone ~set_values:(Some []) res.left res.del path else @@ -213,13 +219,13 @@ let decorate_trees ?(recurse=true) (path : string list) (Diff_tree res) (m : cha res.del in let add_tree = - if not (is_empty add_vals) then + if not (Util.is_empty add_vals) then clone ~set_values:(Some add_vals) res.right res.add path else res.add in let inter_tree = - if not (is_empty inter_vals) then + if not (Util.is_empty inter_vals) then clone ~set_values:(Some inter_vals) res.left res.inter path else res.inter @@ -256,6 +262,33 @@ let diff_tree path left right = let ret = make Config_tree.default_data "" [add_node; sub_node; del_node; int_node] in ret +(* convenience function needed for commit algorithm: + we need a hybrid tree between the 'del' tree and the 'sub' tree, namely: + in case the del tree has a terminal tag node (== all tag values have + been removed) add tag node values for proper removal in commit execution + *) + +let get_tagged_delete_tree dt = + let del_tree = Config_tree.get_subtree dt ["del"] in + let sub_tree = Config_tree.get_subtree dt ["sub"] in + let f (p, a) _t = + let q = List.rev p in + match q with + | [] -> (p, a) + | _ -> + if Config_tree.is_tag a q && Vytree.is_terminal_path a q then + let children = Vytree.children_of_path sub_tree q in + let insert_child path node name = + Vytree.insert ~position:Lexical node (path @ [name]) Config_tree.default_data + in + let a' = List.fold_left (insert_child q) a children in + (p, a') + else + (p, a) + in + snd (Vytree.fold_tree_with_path f ([], del_tree) del_tree) + + (* the following builds a diff_func to return a unified diff string of configs or config commands *) @@ -332,14 +365,14 @@ let unified_diff ?(cmds=false) ?recurse:_ (path : string list) (Diff_string res) 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 str_diff = - if not (is_empty sub_vals) then + if not (Util.is_empty sub_vals) then let sub_tree = clone ~set_values:(Some sub_vals) res.left res.skel path in str_diff ^ (removed_lines ~cmds:cmds sub_tree path) else str_diff in let str_diff = - if not (is_empty add_vals) then + if not (Util.is_empty add_vals) then let add_tree = clone ~set_values:(Some add_vals) res.right res.skel path in str_diff ^ (added_lines ~cmds:cmds add_tree path) @@ -390,14 +423,6 @@ let show_diff ?(cmds=false) path left right = in strs -let is_terminal_path node path = - try - let n = Vytree.get node path in - match (Vytree.children_of_node n) with - | [] -> true - | _ -> false - with Vytree.Nonexistent_path -> false - (* mask function; mask applied on right *) let mask_func ?recurse:_ (path : string list) (Diff_tree res) (m : change) = match m with @@ -405,7 +430,7 @@ let mask_func ?recurse:_ (path : string list) (Diff_tree res) (m : change) = | Subtracted -> (match path with | [_] -> Diff_tree {res with left = Vytree.delete res.left path} - | _ -> if not (is_terminal_path res.right (list_but_last path)) then + | _ -> if not (Vytree.is_terminal_path res.right (list_but_last path)) then Diff_tree {res with left = Vytree.delete res.left path} else Diff_tree (res)) | Unchanged -> Diff_tree (res) @@ -424,29 +449,10 @@ let union_of_values (n : Config_tree.t) (m : Config_tree.t) = let set_m = ValueS.of_list (data_of m).values in ValueS.elements (ValueS.union set_n set_m) -let union_of_children n m = - let set_n = ChildrenS.of_list (children_of n) in - let set_m = ChildrenS.of_list (children_of m) in - ChildrenS.elements (ChildrenS.union set_n set_m) - -(* tree_union is currently used only for unit tests, so only values of data - are considered. Should there be a reason to expose it in the future, - consistency check and union of remaining data will need to be added. - *) -let rec tree_union s t = - let child_of_union s t c = - let s_c = Vytree.find s (name_of c) in - let t_c = Vytree.find t (name_of c) in - match s_c, t_c with - | Some child, None -> clone s t [(name_of child)] - | None, Some _ -> t - | Some u, Some v -> - if u ^~ v then - let values = union_of_values u v in - let data = {(data_of v) with Config_tree.values = values} in - Vytree.replace t (Vytree.make data (name_of v)) - else - Vytree.replace t (tree_union u v) - | None, None -> raise Nonexistent_child +let tree_union s t = + let f u v = + let values = union_of_values u v in + let data = {(data_of v) with Config_tree.values = values} in + Vytree.make_full data (name_of v) (children_of v) in - List.fold_left (fun x c -> child_of_union s x c) t (union_of_children s t) + Tree_alg.ConfigAlg.tree_union s t f diff --git a/src/config_diff.mli b/src/config_diff.mli index 6adf5a7..4262a58 100644 --- a/src/config_diff.mli +++ b/src/config_diff.mli @@ -1,3 +1,4 @@ +type change = Unchanged | Added | Subtracted | Updated of string list module Diff_tree : sig type t = { left: Config_tree.t; @@ -11,7 +12,7 @@ end module Diff_string : sig type t = { left: Config_tree.t; - right : Config_tree.t; + right: Config_tree.t; skel: Config_tree.t; ppath: string list; udiff: string; @@ -22,6 +23,7 @@ module Diff_cstore : sig type t = { left: Config_tree.t; right: Config_tree.t; handle: int; + out: string; } end @@ -30,11 +32,19 @@ type _ result = | Diff_string : Diff_string.t -> Diff_string.t result | Diff_cstore : Diff_cstore.t -> Diff_cstore.t result +val eval_result : 'a result -> 'a + +type 'a diff_func = ?recurse:bool -> string list -> 'a result -> change -> 'a result +val diff : string list -> 'a diff_func -> 'a result -> Config_tree.t option * Config_tree.t option -> 'a result + exception Incommensurable exception Empty_comparison exception Nonexistent_child +val clone : ?recurse:bool -> ?set_values:string list option -> Config_tree.t -> Config_tree.t ->string list -> Config_tree.t val diff_tree : string list -> Config_tree.t -> Config_tree.t -> Config_tree.t val show_diff : ?cmds:bool -> string list -> Config_tree.t -> Config_tree.t -> string val tree_union : Config_tree.t -> Config_tree.t -> Config_tree.t val mask_tree : Config_tree.t -> Config_tree.t -> Config_tree.t +val make_diff_cstore : Config_tree.t -> Config_tree.t -> int -> Diff_cstore.t result +val get_tagged_delete_tree : Config_tree.t -> Config_tree.t diff --git a/src/config_file.ml b/src/config_file.ml new file mode 100644 index 0000000..17d3f1b --- /dev/null +++ b/src/config_file.ml @@ -0,0 +1,44 @@ +(* strip commponent version string *) +let strip_version s = + let rex = Pcre.regexp ~flags:[`MULTILINE;`DOTALL] "(^//.*)" in + let res = Pcre.split ~max:0 ~rex s in + match res with + | h :: _ -> Ok h + | [] -> Error "Failure stripping version string from config" + +let load_config file = + try + let chan = open_in file in + let s = really_input_string chan (in_channel_length chan) in + let () = close_in chan in + let prep = strip_version s in + let s = match prep with + | Ok s -> s + | Error msg -> raise (Sys_error msg) + in + let config = Parser.from_string s in + Ok config + with + | Sys_error msg -> Error msg + | Util.Syntax_error (opt, msg) -> + begin + match opt with + | None -> + let out = Printf.sprintf "Parse error: %s\n" msg + in Error out + | Some (line, pos) -> + let out = Printf.sprintf "Parse error: %s line %d pos %d\n" msg line pos + in Error out + end + +let save_config ct file = + try + let s = Config_tree.render_config ct in + let chan = open_out file in + let () = output_string chan s in + let () = close_out chan in + let () = Unix.chmod file 0o664 in + Ok () + with + Sys_error msg -> Error msg + diff --git a/src/config_file.mli b/src/config_file.mli new file mode 100644 index 0000000..3a87b4a --- /dev/null +++ b/src/config_file.mli @@ -0,0 +1,4 @@ + +val load_config : string -> (Config_tree.t, string) result + +val save_config : Config_tree.t -> string -> (unit, string) result diff --git a/src/config_tree.ml b/src/config_tree.ml index 531cae8..4d0f5df 100644 --- a/src/config_tree.ml +++ b/src/config_tree.ml @@ -10,6 +10,7 @@ type config_node_data = { values: string list; comment: string option; tag: bool; + leaf: bool; } [@@deriving yojson] type t = config_node_data Vytree.t [@@deriving yojson] @@ -18,8 +19,11 @@ let default_data = { values = []; comment = None; tag = false; + leaf = false; } +let default = Vytree.make default_data "" + let make name = Vytree.make default_data name let op_to_string op = @@ -28,7 +32,7 @@ let op_to_string op = | Delete -> "delete" let replace_value node path value = - let data = {default_data with values=[value]} in + let data = {default_data with values=[value]; leaf=true} in Vytree.update node path data let add_value node path value = @@ -39,18 +43,25 @@ let add_value node path value = | Some _ -> raise Duplicate_value | None -> let values = values @ [value] in - Vytree.update node path ({data with values=values}) + Vytree.update node path ({data with values=values; leaf=true}) 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} + Vytree.update node path {data with values=values; leaf=true} let set_value node path value behaviour = match behaviour with | AddValue -> add_value node path value | ReplaceValue -> replace_value node path value +let create_node node path = + if (Vytree.exists node path) then raise Useless_set + else + let path_existing = Vytree.get_existent_path node path in + let path_remaining = Vylist.complement path path_existing in + Vytree.insert_multi_level default_data node path_existing path_remaining default_data + let set node path value behaviour = if (Vytree.exists node path) then (match value with @@ -60,7 +71,8 @@ let set node path value behaviour = 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 end_data = {default_data with values=values; leaf=true} in + Vytree.insert_multi_level default_data node path_existing path_remaining end_data let get_values node path = let node' = Vytree.get node path in @@ -101,6 +113,19 @@ let is_tag node path = let data = Vytree.get_data node path in data.tag +let is_tag_value node path = + match path with + | [] | [_] -> false + | _ -> is_tag node (Util.drop_last path) + +let set_leaf node path leaf = + let data = Vytree.get_data node path in + Vytree.update node path {data with leaf=leaf} + +let is_leaf node path = + let data = Vytree.get_data node path in + data.leaf + let get_subtree ?(with_node=false) node path = try let n = Vytree.get node path in @@ -110,6 +135,28 @@ let get_subtree ?(with_node=false) node path = Vytree.make_full default_data "" (Vytree.children_of_node n) with Vytree.Nonexistent_path -> make "" +let value_paths_of_tree node = + let func ct (p, a) _t = + match p with + | [] -> (p, a) + | _ -> + let q = List.rev p in + if not (Vytree.is_terminal_path ct q) then + (p, a) + else + let vals = get_values ct q in + match vals with + | [] -> (p, q::a) + | _ as vs -> + let a' = + let f acc v = + let q' = q @ [v] in + q'::acc + in List.fold_left f a vs + in (p, a') + in List.rev (snd (Vytree.fold_tree_with_path (func node) ([], []) node)) + + module Renderer = struct (* Rendering configs as set commands *) @@ -132,13 +179,12 @@ struct (* Now handle the different cases for nodes with and without children *) match child_names with | [] -> - (* This is a leaf node *) let values = List.map Util.escape_string data.values in let cmds = begin match values with | [] -> - (* Valueless leaf node *) + (* Valueless leaf node or a non-leaf node *) String.concat " " new_path |> Printf.sprintf "%s %s" (op_to_string op) | [v] -> (* Single value, just one command *) @@ -150,7 +196,6 @@ struct in if comment_cmd = "" then cmds else Printf.sprintf "%s\n%s" cmds comment_cmd | _ :: _ -> - (* A node with children *) let children = List.map (fun n -> Vytree.get ct [n]) child_names in let rendered_children = List.map (render_commands ~op:op new_path) children in let cmds = String.concat "\n" rendered_children in @@ -184,10 +229,14 @@ struct let data = Vytree.data_of_node node in let is_tag = data.tag in let comment = render_comment indent_str data.comment in - let values = render_values ~ord_val:ord_val indent_str name data.values in let children = Vytree.children_of_node node in match children with - | [] -> Printf.sprintf "%s%s" comment values + | [] -> + if data.leaf then + let values = render_values ~ord_val:ord_val indent_str name data.values in + Printf.sprintf "%s%s" comment values + else + Printf.sprintf "%s%s%s {\n%s}\n" comment indent_str name indent_str | _ :: _ -> if is_tag then begin @@ -267,6 +316,14 @@ let render_commands ?(op=Set) node path = let render_config ?(ord_val=false) = Renderer.render_config ~ord_val:ord_val +let render_at_level node path = + let node = + match path with + | [] -> node + | _ -> Vytree.get node path + in + render_config node + let render_json = JSONRenderer.render_json let render_json_ast c = to_yojson c |> Yojson.Safe.to_string diff --git a/src/config_tree.mli b/src/config_tree.mli index 990bb49..be746a0 100644 --- a/src/config_tree.mli +++ b/src/config_tree.mli @@ -10,14 +10,19 @@ type config_node_data = { values : string list; comment : string option; tag : bool; + leaf: bool; } [@@deriving yojson] type t = config_node_data Vytree.t [@@deriving yojson] val default_data : config_node_data +val default : t + val make : string -> t +val create_node : t -> string list -> t + val set : t -> string list -> string option -> value_behaviour -> t val delete : t -> string list -> string option -> t @@ -34,8 +39,16 @@ val set_tag : t -> string list -> bool -> t val is_tag : t -> string list -> bool +val is_tag_value : t -> string list -> bool + +val set_leaf : t -> string list -> bool -> t + +val is_leaf : t -> string list -> bool + val get_subtree : ?with_node:bool -> t -> string list -> t +val value_paths_of_tree : t -> string list list + val render_commands : ?op:command -> t -> string list -> string val render_config : ?ord_val:bool -> t -> string @@ -43,3 +56,5 @@ val render_config : ?ord_val:bool -> t -> string val render_json : t -> string val render_json_ast : t -> string + +val render_at_level : t -> string list -> string @@ -6,7 +6,7 @@ (library (name vyos1x) (public_name vyos1x-config) - (libraries yojson menhirLib fileutils pcre xml-light) + (libraries yojson menhirLib fileutils pcre xml-light unix containers) (preprocess (pps ppx_deriving_yojson)) (foreign_stubs (language c) diff --git a/src/generate.ml b/src/generate.ml index e7801c5..28f0be1 100644 --- a/src/generate.ml +++ b/src/generate.ml @@ -2,6 +2,8 @@ exception Load_error of string exception Write_error of string +module I = Internal.Make(Reference_tree) + let load_interface_definitions dir = let open Reference_tree in let dir_paths = FileUtil.ls dir in @@ -20,7 +22,7 @@ let load_interface_definitions dir = | Error msg -> Error msg end with Bad_interface_definition msg -> Error msg -let reference_tree_to_json from_dir to_file = +let interface_definitions_to_cache from_dir cache_path = let ref_tree_result = load_interface_definitions from_dir in @@ -29,11 +31,55 @@ let reference_tree_to_json from_dir to_file = | Ok ref -> ref | Error msg -> raise (Load_error msg) in + I.write_internal ref_tree cache_path + +let reference_tree_cache_to_json cache_path render_file = + let ref_tree = + I.read_internal cache_path + in let out = Reference_tree.render_json ref_tree in let oc = try - open_out to_file + open_out render_file with Sys_error msg -> raise (Write_error msg) in Printf.fprintf oc "%s" out; close_out oc + +let merge_reference_tree_cache cache_dir primary_name result_name = + let file_arr = Sys.readdir cache_dir in + let file_list' = Array.to_list file_arr in + let file_list = + List.filter (fun x -> x <> primary_name && x <> result_name) file_list' in + let file_path_list = + List.map (FilePath.concat cache_dir) file_list in + let primary_tree = I.read_internal (FilePath.concat cache_dir primary_name) in + let ref_trees = List.map I.read_internal file_path_list in + match ref_trees with + | [] -> + I.write_internal primary_tree (FilePath.concat cache_dir result_name) + | _ -> + let f _ v = v in + let res = List.fold_left (fun p r -> Tree_alg.RefAlg.tree_union r p f) primary_tree ref_trees in + I.write_internal res (FilePath.concat cache_dir result_name) + +let reference_tree_to_json ?(internal_cache="") from_dir to_file = + let ref_tree_result = + load_interface_definitions from_dir + in + let ref_tree = + match ref_tree_result with + | Ok ref -> ref + | Error msg -> raise (Load_error msg) + in + let out = Reference_tree.render_json ref_tree in + let oc = + try + open_out to_file + with Sys_error msg -> raise (Write_error msg) + in + Printf.fprintf oc "%s" out; + close_out oc; + match internal_cache with + | "" -> () + | _ -> I.write_internal ref_tree internal_cache diff --git a/src/generate.mli b/src/generate.mli index e121d1f..4243ef0 100644 --- a/src/generate.mli +++ b/src/generate.mli @@ -2,4 +2,7 @@ exception Load_error of string exception Write_error of string val load_interface_definitions : string -> (Reference_tree.t, string) result -val reference_tree_to_json : string -> string -> unit +val reference_tree_to_json : ?internal_cache:string -> string -> string -> unit +val interface_definitions_to_cache : string -> string -> unit +val reference_tree_cache_to_json : string -> string -> unit +val merge_reference_tree_cache : string -> string -> string -> unit diff --git a/src/internal.ml b/src/internal.ml new file mode 100644 index 0000000..a57f3db --- /dev/null +++ b/src/internal.ml @@ -0,0 +1,66 @@ +exception Read_error of string +exception Write_error of string + +module type T = + sig + type t + val to_yojson : t -> Yojson.Safe.t + val of_yojson : Yojson.Safe.t -> t Ppx_deriving_yojson_runtime.error_or + val default : t + end + +module type FI = functor (M: T) -> + sig + val write_internal : M.t -> string -> unit + val read_internal : string -> M.t + val replace_internal : string -> string -> unit + end + +module Make : FI = functor (M: T) -> struct + let write_internal x file_name = + let yt = M.to_yojson x in + let ys = Yojson.Safe.to_string yt in + let fd = Unix.openfile file_name [Unix.O_CREAT;Unix.O_WRONLY] 0o664 in + let () = + try + Unix.lockf fd Unix.F_TLOCK 0 + with _ -> + Unix.close fd; raise (Write_error "write lock unavailable") + in + let oc = Unix.out_channel_of_descr fd in + let () = Unix.ftruncate fd 0 in + let () = Printf.fprintf oc "%s" ys in + let () = Unix.fsync fd in + let () = Unix.lockf fd Unix.F_ULOCK 0 in + close_out_noerr oc + + let read_internal file_name = + let fd = + try + Unix.openfile file_name [Unix.O_RDONLY] 0o664 + with Unix.Unix_error (e,f,p) -> + let out = + Printf.sprintf "%s %s: %s" (Unix.error_message e) f p + in raise (Read_error out) + in + let () = + try + Unix.lockf fd Unix.F_TEST 0 + with _ -> + Unix.close fd; raise (Read_error "read lock unavailable") + in + let ic = Unix.in_channel_of_descr fd in + let ys = really_input_string ic (in_channel_length ic) in + let yt = Yojson.Safe.from_string ys in + let ct_res = M.of_yojson yt in + let ct = Result.value ct_res ~default:M.default in + close_in_noerr ic; ct + + let replace_internal dst src = + let tmp = src ^ ".tmp" in + try + let () = FileUtil.cp ~force:Force [src] tmp in + let () = FileUtil.rm ~force:Force [dst] in + FileUtil.mv ~force:Force tmp dst + with _ -> raise (Write_error "replace error") +end diff --git a/src/internal.mli b/src/internal.mli new file mode 100644 index 0000000..a82232d --- /dev/null +++ b/src/internal.mli @@ -0,0 +1,19 @@ +exception Read_error of string +exception Write_error of string + +module type T = + sig + type t + val to_yojson : t -> Yojson.Safe.t + val of_yojson : Yojson.Safe.t -> t Ppx_deriving_yojson_runtime.error_or + val default : t + end + +module type FI = functor (M : T) -> + sig + val write_internal : M.t -> string -> unit + val read_internal : string -> M.t + val replace_internal : string -> string -> unit + end + +module Make : FI diff --git a/src/reference_tree.ml b/src/reference_tree.ml index 4889734..d39cc96 100644 --- a/src/reference_tree.ml +++ b/src/reference_tree.ml @@ -8,21 +8,34 @@ let node_type_to_yojson = function | Tag -> `String "tag" | Other -> `String "other" -type value_constraint = - | Regex of string [@name "regex"] - | External of string * string option [@name "exec"] - [@@deriving yojson] +let node_type_of_yojson = function + | `String "leaf" -> Ok Leaf + | `String "tag" -> Ok Tag + | `String "other" -> Ok Other + | json -> Error (Yojson.Safe.to_string json) type completion_help_type = | List of string [@name "list"] | Path of string [@name "path"] | Script of string [@name "script"] - [@@deriving to_yojson] + [@@deriving yojson] + +type doc_hints = { + text: string; + hint_type: string; +} [@@deriving yojson] + +type docs = { + headline: string; + text: string; + usageExample: string; + hints: doc_hints list; +} [@@deriving yojson] type ref_node_data = { node_type: node_type; - constraints: value_constraint list; - constraint_group: value_constraint list; + constraints: Value_checker.value_constraint list; + constraint_group: Value_checker.value_constraint list; constraint_error_message: string; completion_help: completion_help_type list; help: string; @@ -34,9 +47,10 @@ type ref_node_data = { default_value: string option; hidden: bool; secret: bool; -} [@@deriving to_yojson] + docs: docs; +} [@@deriving yojson] -type t = ref_node_data Vytree.t [@@deriving to_yojson] +type t = ref_node_data Vytree.t [@@deriving yojson] exception Bad_interface_definition of string @@ -57,6 +71,12 @@ let default_data = { default_value = None; hidden = false; secret = false; + docs = { + headline = ""; + text = ""; + usageExample = ""; + hints = []; + }; } let default = Vytree.make default_data "" @@ -79,7 +99,7 @@ let completion_help_type_of_string v s = | _ -> raise (Bad_interface_definition (Printf.sprintf "list, path, or script expected, %s found" s)) -(** Find a child node in xml-lite *) +(** Find a child node in xml-light *) let find_xml_child name xml = let find_aux e = match e with @@ -118,7 +138,7 @@ let load_completion_help_from_xml d c = match c with | Xml.Element (_, _, [Xml.PCData s]) -> l @ [completion_help_type_of_string (Xml.tag c) s] - | _ -> raise (Bad_interface_definition "Malformed completion help") + | _ -> raise (Bad_interface_definition ("Malformed completion help :" ^ Xml.to_string c)) in Xml.fold aux [] c in let l = d.completion_help in let l' = l @ res in @@ -128,32 +148,60 @@ let load_constraint_from_xml d c = let aux d c = match c with | Xml.Element ("regex", _, [Xml.PCData s]) -> - let cs = (Regex s) :: d.constraints in + let cs = (Value_checker.Regex s) :: d.constraints in {d with constraints=cs} | Xml.Element ("validator", [("name", n); ("argument", a)], _) -> - let cs = (External (n, Some a)) :: d.constraints in + let cs = (Value_checker.External (n, Some a)) :: d.constraints in {d with constraints=cs} | Xml.Element ("validator", [("name", n)], _) -> - let cs = (External (n, None)) :: d.constraints in + let cs = (Value_checker.External (n, None)) :: d.constraints in {d with constraints=cs} - | _ -> raise (Bad_interface_definition "Malformed constraint") + | _ -> raise (Bad_interface_definition ("Malformed constraint: " ^ Xml.to_string c)) in Xml.fold aux d c let load_constraint_group_from_xml d c = let aux d c = match c with | Xml.Element ("regex", _, [Xml.PCData s]) -> - let cs = (Regex s) :: d.constraint_group in + let cs = (Value_checker.Regex s) :: d.constraint_group in {d with constraint_group=cs} | Xml.Element ("validator", [("name", n); ("argument", a)], _) -> - let cs = (External (n, Some a)) :: d.constraint_group in + let cs = (Value_checker.External (n, Some a)) :: d.constraint_group in {d with constraint_group=cs} | Xml.Element ("validator", [("name", n)], _) -> - let cs = (External (n, None)) :: d.constraint_group in + let cs = (Value_checker.External (n, None)) :: d.constraint_group in {d with constraint_group=cs} - | _ -> raise (Bad_interface_definition "Malformed constraint") + | _ -> raise (Bad_interface_definition ("Malformed constraint: " ^ Xml.to_string c)) in Xml.fold aux d c +let load_docs_hints d c = + let aux d c = + match c with + | Xml.Element ("hints", attrs, [Xml.PCData s]) -> + let hint_type = List.assoc "type" attrs in + let hint = { text = s; hint_type = hint_type } in + let new_docs = { d.docs with hints = hint :: d.docs.hints } in + { d with docs = new_docs } + | _ -> raise (Bad_interface_definition ("Malformed hint: " ^ Xml.to_string c)) + in aux d c + +let load_docs_from_xml d x = + let aux d x = + match x with + | Xml.Element ("headline", _, [Xml.PCData s]) -> + let new_docs = {d.docs with headline = s} in + {d with docs = new_docs} + | Xml.Element ("text", _, [Xml.PCData s]) -> + let new_docs = {d.docs with text = s} in + {d with docs = new_docs} + | Xml.Element ("hints", _, _) -> + load_docs_hints d x + | Xml.Element ("usageExample", _, [Xml.PCData s]) -> + let new_docs = {d.docs with usageExample = s} in + {d with docs = new_docs} + | _ -> d (* Ignore unknown elements instead of raising an error *) + in Xml.fold aux d x + let data_from_xml d x = let aux d x = match x with @@ -171,7 +219,8 @@ let data_from_xml d x = {d with priority=Some i} | Xml.Element ("hidden", _, _) -> {d with hidden=true} | Xml.Element ("secret", _, _) -> {d with secret=true} - | _ -> raise (Bad_interface_definition "Malformed property tag") + | Xml.Element ("docs", _, _) -> load_docs_from_xml d x + | _ -> raise (Bad_interface_definition ("Malformed property tag: " ^ Xml.to_string x)) in Xml.fold aux d x let rec insert_from_xml basepath reftree xml = @@ -229,6 +278,176 @@ let load_from_xml reftree file = let s = Printf.sprintf ": line %d in file %s" pos.eline file in raise (Bad_interface_definition ((Xml.error_msg msg)^s)) +(* Validation function *) + +let has_illegal_characters name = + (** Checks if string name has illegal characters in it. + All whitespace, curly braces, square brackets, and quotes + are disallowed due to their special significance to the curly config + format parser *) + try Some (Pcre.get_substring (Pcre.exec ~pat:"[\\s\\{\\}\\[\\]\"\'#]" name) 0) + with Not_found -> None + +let format_out l = + let fl = List.filter (fun s -> (String.length s) > 0) l in + String.concat "\n\n" fl + +(** Takes a list of string that represents a configuration path that may have + node value at the end, validates it, and splits it into path and value parts. + + A list of strings is a valid path that can be created in the config tree unless: + 1. It's a tag node without a child + 2. It's a tag node with an invalid tag value + 3. It's a non-valueless leaf node without a value + 4. It's a valueless leaf node with a value + 5. It's a non-valueless leaf node with an invalid value + 6. It's a node that is neither leaf nor tag value with a name that + doesn't exist in the reference tree + *) +let validate_path validators_dir node path = + let show_path p = Printf.sprintf "[%s]" @@ Util.string_of_list (List.rev p) in + let rec aux node path acc = + let data = Vytree.data_of_node node in + match data.node_type with + | Leaf -> + begin + match path with + | [] -> + if data.valueless then () + else + let msg = + Printf.sprintf "Configuration path %s requires a value" (show_path acc) + in raise (Validation_error msg) + | [p] -> + if not data.valueless then + let res = + try Value_checker.validate_any validators_dir data.constraints p + with Value_checker.Bad_validator msg -> raise (Validation_error msg) + in + match res with + | None -> () + | Some out -> + let ret = format_out [out; data.constraint_error_message] + in raise (Validation_error ret) + else + let msg = Printf.sprintf "Node %s cannot have a value" (show_path acc) + in raise (Validation_error msg) + | _ -> + let msg = Printf.sprintf "Path %s is too long" (show_path acc) + in raise (Validation_error msg) + end + | Tag -> + begin + match path with + | p :: p' :: ps -> + begin + match (has_illegal_characters p) with + | Some c -> + let msg = + Printf.sprintf "Illegal character \"%s\" in node name \"%s\"" c p + in raise (Validation_error msg) + | None -> + let res = + try Value_checker.validate_any validators_dir data.constraints p + with Value_checker.Bad_validator msg -> raise (Validation_error msg) + in + begin + match res with + | None -> + let child = Vytree.find node p' in + begin + match child with + | Some c -> aux c ps (p' :: p :: acc) + | None -> + let msg = + Printf.sprintf "Node %s has no child %s" (show_path acc) p' + in raise (Validation_error msg) + end + | Some out -> + let msg = + Printf.sprintf "%s is not a valid child name for node %s" p (show_path acc) + in + let ret = format_out [out; data.constraint_error_message; msg] + in raise (Validation_error ret) + end + end + | [p] -> + begin + match (has_illegal_characters p) with + | Some c -> + let msg = + Printf.sprintf "Illegal character \"%s\" in node name \"%s\"" c p + in raise (Validation_error msg) + | None -> + let res = + try Value_checker.validate_any validators_dir data.constraints p + with Value_checker.Bad_validator msg -> raise (Validation_error msg) + in + begin + match res with + | None -> () + | Some out -> + let msg = + Printf.sprintf "%s is not a valid child name for node %s" p (show_path acc) + in + let ret = format_out [out; data.constraint_error_message; msg] + in raise (Validation_error ret) + end + end + | _ -> + let msg = + Printf.sprintf "Configuration path %s requires a value" (show_path acc) + in raise (Validation_error msg) + end + | Other -> + begin + match path with + | [] -> () + | p :: ps -> + let child = Vytree.find node p in + match child with + | Some c -> aux c ps (p :: acc) + | None -> + let msg = Printf.sprintf "Path %s is incomplete" (show_path acc) + in raise (Validation_error msg) + end + in aux node path [] + +(* This is only to be used after the path has been validated *) +let split_path node path = + let rec aux node path acc = + let data = Vytree.data_of_node node in + match data.node_type with + | Leaf -> + begin + match path with + | [] -> (List.rev acc, None) + | [p] -> (List.rev acc, Some p) + | _ -> (List.rev acc, None) + end + | Tag -> + begin + match path with + | p :: p' :: ps -> + (let child = Vytree.find node p' in + match child with + | Some c -> aux c ps (p' :: p :: acc) + | None -> (List.rev acc, None)) + | [p] -> (List.rev (p :: acc), None) + | _ -> (List.rev acc, None) + end + | Other -> + begin + match path with + | [] -> (List.rev acc, None) + | p :: ps -> + let child = Vytree.find node p in + match child with + | Some c -> aux c ps (p :: acc) + | None -> (List.rev acc, None) + end + in aux node path [] + let is_multi reftree path = let data = Vytree.get_data reftree path in data.multi @@ -261,6 +480,10 @@ let get_owner reftree path = let data = Vytree.get_data reftree path in data.owner +let get_priority reftree path = + let data = Vytree.get_data reftree path in + data.priority + let get_help_string reftree path = let data = Vytree.get_data reftree path in data.help @@ -275,6 +498,61 @@ let get_completion_data reftree path = (data.node_type, data.multi, data.help) in List.map aux (Vytree.children_of_node @@ Vytree.get reftree path) +(* Convert from config path to reference tree path *) +let refpath reftree path = + let rec aux acc p = + match acc, p with + | [], h :: tl -> aux (acc @ [h]) tl + | _, [h] -> if is_tag reftree acc then acc else acc @ [h] + | _, h :: h' :: tl -> if is_tag reftree acc then aux (acc @ [h']) tl + else aux (acc @ [h]) ([h'] @ tl) + | _, [] -> acc + in aux [] path + +let flag path = + let len = List.length path in + let aux p i = + Util.drop_last_n p (len - i - 1) + in + List.mapi (fun k _ -> aux path k) path + +let set_tag_data rtree ctree path = + let ext = Vytree.exists ctree path in + match ext with + | false -> ctree + | true -> + let set_tag rt ct p = + let refp = refpath rt p in + if is_tag rt refp && not (Config_tree.is_tag_value ct p) + then Config_tree.set_tag ct p true + else ct + in + List.fold_left (set_tag rtree) ctree (flag path) + +let set_leaf_data rtree ctree path = + let ext = Vytree.exists ctree path in + match ext with + | false -> ctree + | true -> + let refp = refpath rtree path in + if is_leaf rtree refp then Config_tree.set_leaf ctree path true + else ctree + +let get_ceil_data f reftree path = + let data_of_path d p = + let data = Vytree.get_data reftree p in + match (f data) with + | Some d' -> Some d' + | None -> d + in + let rec aux d acc p = + match acc, p with + | _, h :: tl -> + let acc' = acc @ [h] in + aux (data_of_path d (refpath reftree acc')) acc' tl + | _, [] -> d + in aux None [] path + module JSONRenderer = struct let render_data data = diff --git a/src/reference_tree.mli b/src/reference_tree.mli index a18d875..9c583df 100644 --- a/src/reference_tree.mli +++ b/src/reference_tree.mli @@ -3,21 +3,28 @@ type node_type = | Tag | Other -type value_constraint = - | Regex of string [@name "regex"] - | External of string * string option [@name "exec"] - [@@deriving yojson] - type completion_help_type = | List of string [@name "list"] | Path of string [@name "path"] | Script of string [@name "script"] - [@@deriving to_yojson] + [@@deriving yojson] + +type doc_hints = { + text: string; + hint_type: string; +} [@@deriving yojson] + +type docs = { + headline: string; + text: string; + usageExample: string; + hints: doc_hints list; +} [@@deriving to_yojson] type ref_node_data = { node_type: node_type; - constraints: value_constraint list; - constraint_group: value_constraint list; + constraints: Value_checker.value_constraint list; + constraint_group: Value_checker.value_constraint list; constraint_error_message: string; completion_help: completion_help_type list; help: string; @@ -29,9 +36,10 @@ type ref_node_data = { default_value: string option; hidden: bool; secret: bool; -} [@@deriving to_yojson] + docs: docs; +} [@@deriving yojson] -type t = ref_node_data Vytree.t [@@deriving to_yojson] +type t = ref_node_data Vytree.t [@@deriving yojson] exception Bad_interface_definition of string @@ -43,6 +51,12 @@ val default : t val load_from_xml : t -> string -> t +val find_xml_child : string -> Xml_light_types.xml -> Xml_light_types.xml option + +val validate_path : string -> t -> string list -> unit + +val split_path : t -> string list -> string list * string option + val is_multi : t -> string list -> bool val is_hidden : t -> string list -> bool @@ -57,10 +71,20 @@ val is_valueless : t -> string list -> bool val get_owner : t -> string list -> string option +val get_priority : t -> string list -> string option + val get_help_string : t -> string list -> string val get_value_help : t -> string list -> (string * string) list val get_completion_data : t -> string list -> (node_type * bool * string) list +val refpath : t -> string list -> string list + +val get_ceil_data : (ref_node_data -> string option) -> t -> string list -> string option + +val set_tag_data : t -> Config_tree.t -> string list -> Config_tree.t + +val set_leaf_data : t -> Config_tree.t -> string list -> Config_tree.t + val render_json : t -> string diff --git a/src/tree_alg.ml b/src/tree_alg.ml new file mode 100644 index 0000000..23af99d --- /dev/null +++ b/src/tree_alg.ml @@ -0,0 +1,68 @@ +exception Incompatible_union +exception Nonexistent_child + +module type Data = sig + type t +end + +module type Tree = sig + module D: Data + type t = D.t Vytree.t +end + +module Tree_impl (D: Data): Tree with module D = D = struct + module D = D + type t = D.t Vytree.t +end + +module Alg (D: Data) (T: Tree with module D = D) = struct + module TreeOrd = struct + type t = T.t + let compare a b = + Util.lexical_numeric_compare (Vytree.name_of_node a) (Vytree.name_of_node b) + end + module SetT = Set.Make(TreeOrd) + + let union_of_children n m = + let set_n = SetT.of_list (Vytree.children_of_node n) in + let set_m = SetT.of_list (Vytree.children_of_node m) in + SetT.elements (SetT.union set_n set_m) + + let find_child n c = Vytree.find n (Vytree.name_of_node c) + + let insert_child n c = Vytree.insert ~position:Vytree.Lexical ~children:(Vytree.children_of_node c) n [(Vytree.name_of_node c)] (Vytree.data_of_node c) + + let replace_child n c = + Vytree.replace n c + + let rec tree_union s t f = + if (Vytree.name_of_node s) <> (Vytree.name_of_node t) then + raise Incompatible_union + else + let child_of_union s t c = + let s_c = find_child s c in + let t_c = find_child t c in + match s_c, t_c with + | Some child, None -> + insert_child t child + | None, Some _ -> t + | Some u, Some v -> + if (Vytree.data_of_node u <> Vytree.data_of_node v) then + replace_child t (tree_union u (f u v) f) + else + replace_child t (tree_union u v f) + | None, None -> raise Nonexistent_child + in + List.fold_left (fun x c -> child_of_union s x c) t (union_of_children s t) +end + +module ConfigData: Data with type t = Config_tree.config_node_data = struct + type t = Config_tree.config_node_data +end + +module RefData: Data with type t = Reference_tree.ref_node_data = struct + type t = Reference_tree.ref_node_data +end + +module ConfigAlg = Alg(ConfigData)(Tree_impl(ConfigData)) +module RefAlg = Alg(RefData)(Tree_impl(RefData)) diff --git a/src/util.ml b/src/util.ml index 168589d..8fc5899 100644 --- a/src/util.ml +++ b/src/util.ml @@ -85,3 +85,77 @@ let lexical_numeric_compare s t = (** Convert a relative path to an absolute path based on the current working directory *) let absolute_path relative_path = FilePath.make_absolute (Sys.getcwd ()) relative_path + +(** Convert a list of strings to a string of unquoted, space separated words *) +let string_of_list ss = + let rec aux xs acc = + match xs with + | [] -> acc + | x :: xs' -> aux xs' (Printf.sprintf "%s %s" acc x) + in + match ss with + | [] -> "" + | x :: xs -> Printf.sprintf "%s%s" x (aux xs "") + +(** Convert a list of strings to JSON *) +let json_of_list ss = + let ss = List.map (fun x -> `String x) ss in + Yojson.Safe.to_string (`List ss) + +(** Split string on whitespace, excluding single-quoted phrases, + as needed for parsing vyconf request path option **) +let list_of_path p = + let seg = String.trim p |> String.split_on_char '\'' in + match seg with + | [h] -> Pcre.split ~pat:"\\s+" h + | h :: h' :: _ -> (Pcre.split ~pat:"\\s+" h) @ [h'] + | _ -> [] + + +let drop_last l = + let rec aux acc l = + match l with + | [] | [_] -> List.rev acc + | hd :: tl -> + let acc' = hd :: acc in + aux acc' tl + in + aux [] l + +let drop_last_n l n = + let rec aux k l = + match l with + | [] -> [] + | _ -> if k <= 0 then l else aux (k - 1) (drop_last l) + in aux n l + +let drop_first l = + match l with + | [] -> [] + | _ :: tl -> tl + +let rec get_last l = + match l with + | [] -> None + | h :: [] -> Some h + | _ :: tl -> get_last tl + +let get_last_n l n = + get_last (drop_last_n l n) + +let lex_order l k = + let c = compare (get_last l) (get_last k) in + match c with + | 0 -> compare (drop_last l) (drop_last k) + | _ as r -> r + +let colex_order l k = + let rec comp x y = + let c = compare (get_last x) (get_last y) in + match c with + | 0 -> comp (drop_last x) (drop_last y) + | _ as r -> r + in comp l k + +let is_empty l = + List.compare_length_with l 0 = 0 diff --git a/src/util.mli b/src/util.mli index f9bfba6..fc25cff 100644 --- a/src/util.mli +++ b/src/util.mli @@ -9,3 +9,25 @@ val default : 'a -> 'a option -> 'a val lexical_numeric_compare : string -> string -> int val absolute_path : FilePath.filename -> FilePath.filename + +val string_of_list : string list -> string + +val json_of_list : string list -> string + +val list_of_path : string -> string list + +val drop_last : 'a list -> 'a list + +val drop_last_n : 'a list -> int -> 'a list + +val drop_first : 'a list -> 'a list + +val get_last : 'a list -> 'a option + +val get_last_n : 'a list -> int -> 'a option + +val lex_order : string list -> string list -> int + +val colex_order : string list -> string list -> int + +val is_empty : 'a list -> bool diff --git a/src/value_checker.ml b/src/value_checker.ml new file mode 100644 index 0000000..9e6dae8 --- /dev/null +++ b/src/value_checker.ml @@ -0,0 +1,84 @@ +module F = Filename + +(*type value_constraint = Regex of string | External of string * string option*) +type value_constraint = + | Regex of string [@name "regex"] + | External of string * string option [@name "exec"] + [@@deriving yojson] + +exception Bad_validator of string + +let validate_value dir buf value_constraint value = + match value_constraint with + | Regex s -> + (try + let _ = Pcre.exec ~pat:(Printf.sprintf "^%s$" s) value in true + with Not_found -> false) + | External (v, c) -> + (* XXX: Unix.open_process_in is "shelling out", which is a bad idea on multiple levels, + especially when the input comes directly from the user... + We should do something about it. + *) + let validator = F.concat dir v in + let cmd = + match c with + | Some arg -> + let safe_arg = Printf.sprintf "%s" (Pcre.qreplace ~pat:"\"" ~templ:"\\\"" arg) in + Printf.sprintf "%s %s \'%s\' 2>&1" validator safe_arg value + | None -> + Printf.sprintf "%s \'%s\' 2>&1" validator value + in + let () = Unix.putenv "vyos_libexec_dir" "/usr/libexec/vyos" in + let () = Unix.putenv "vyos_validators_dir" "/usr/libexec/vyos/validators" in + let chan = Unix.open_process_in cmd in + let out = try CCIO.read_all chan with _ -> "" in + let result = Unix.close_process_in chan in + match result with + | Unix.WEXITED 0 -> true + | Unix.WEXITED 127 -> + raise (Bad_validator (Printf.sprintf "Could not execute validator %s" validator)) + | _ -> + let () = Buffer.add_string buf out in + false + +(* If no constraints given, consider it valid. + Otherwise consider it valid if it satisfies at least one constraint *) +let validate_any validators constraints value = + let buf = Buffer.create 4096 in + let validate_exists validators constraints value = + match constraints with + | [] -> true + | _ -> + try + List.exists (fun c -> validate_value validators buf c value) constraints + with Bad_validator e -> let () = Buffer.add_string buf e in false + in + match validate_exists validators constraints value with + | true -> + let () = Buffer.clear buf in + None + | false -> + let out = Buffer.contents buf in + let () = Buffer.clear buf in + Some out + +(* If no constraints given, consider it valid. + Otherwise consider it valid if it satisfies all constraints *) +let validate_all validators constraints value = + let buf = Buffer.create 4096 in + let validate_forall validators constraints value = + match constraints with + | [] -> true + | _ -> + try + List.for_all (fun c -> validate_value validators buf c value) constraints + with Bad_validator e -> let () = Buffer.add_string buf e in false + in + match validate_forall validators constraints value with + | true -> + let () = Buffer.clear buf in + None + | false -> + let out = Buffer.contents buf in + let () = Buffer.clear buf in + Some out diff --git a/src/value_checker.mli b/src/value_checker.mli new file mode 100644 index 0000000..d4ae516 --- /dev/null +++ b/src/value_checker.mli @@ -0,0 +1,14 @@ +(*type value_constraint = Regex of string | External of string * string option*) + +type value_constraint = + | Regex of string [@name "regex"] + | External of string * string option [@name "exec"] + [@@deriving yojson] + +exception Bad_validator of string + +val validate_value : string -> Buffer.t -> value_constraint -> string -> bool + +val validate_any : string -> value_constraint list -> string -> string option + +val validate_all : string -> value_constraint list -> string -> string option diff --git a/src/vyos1x_lexer.mll b/src/vyos1x_lexer.mll index a363c65..34cf294 100644 --- a/src/vyos1x_lexer.mll +++ b/src/vyos1x_lexer.mll @@ -69,8 +69,6 @@ rule token vy_inside_node = parse { (false, LEFT_BRACE) } | '}' { (false, RIGHT_BRACE) } -| "//" [^ '\n']* - { token vy_inside_node lexbuf } | [^ ' ' '\t' '\n' '\r' '{' '}' '"' ''' ]+ as s { (true, IDENTIFIER s) } | eof diff --git a/src/vyos1x_parser.mly b/src/vyos1x_parser.mly index 8b2d7d8..d3ae5dc 100644 --- a/src/vyos1x_parser.mly +++ b/src/vyos1x_parser.mly @@ -49,10 +49,10 @@ value: leaf_node_body: | comment = comments; name = IDENTIFIER; value = value; - { Vytree.make_full {default_data with values=[value]; comment=comment} name []} + { Vytree.make_full {default_data with values=[value]; comment=comment; leaf=true} name []} | comment = comments; name = IDENTIFIER; (* valueless node *) - { Vytree.make_full {default_data with comment=comment} name [] } + { Vytree.make_full {default_data with comment=comment; leaf=true} name [] } ; leaf_node: diff --git a/src/vytree.ml b/src/vytree.ml index 05b3088..dec1666 100644 --- a/src/vytree.ml +++ b/src/vytree.ml @@ -207,3 +207,24 @@ let move node path position = let child = get node path in let node = delete node path in insert ~position:position ~children:child.children node path child.data + +let is_terminal_path node path = + try + let n = get node path in + match (children_of_node n) with + | [] -> true + | _ -> false + with Nonexistent_path -> false + +let rec fold_tree_with_path f (p', a) t = + let p = + match name_of_node t with + | "" -> p' + | name -> name :: p' + in + let children = children_of_node t in + match children with + | [] -> (Util.drop_first p), snd (f (p, a) t) + | c -> let res = + List.fold_left (fold_tree_with_path f) (f (p, a) t) c in + (Util.drop_first p), snd res diff --git a/src/vytree.mli b/src/vytree.mli index 412379a..9839728 100644 --- a/src/vytree.mli +++ b/src/vytree.mli @@ -56,3 +56,7 @@ val sort_children : (string -> string -> int) -> 'a t -> 'a t val copy : 'a t -> string list -> string list -> 'a t val move : 'a t -> string list -> position -> 'a t + +val is_terminal_path : 'a t -> string list -> bool + +val fold_tree_with_path: (string list * 'acc -> 'b t -> string list * 'acc) -> string list * 'acc -> 'b t -> string list * 'acc |