Blame SOURCES/0002-v2v-add-Var_expander.patch

46b2f6
From a98136d6ee36df15a226f853d47bd803a7a25329 Mon Sep 17 00:00:00 2001
46b2f6
From: Pino Toscano <ptoscano@redhat.com>
46b2f6
Date: Tue, 19 Feb 2019 14:54:31 +0100
46b2f6
Subject: [PATCH] v2v: add Var_expander
46b2f6
46b2f6
This helper module provides a facility to replace %{FOO}-like variables
46b2f6
in text strings with user-provided content.
46b2f6
46b2f6
(cherry picked from commit a27748d7000f417c16045967497208d275a09ce8)
46b2f6
---
46b2f6
 .gitignore                |   1 +
46b2f6
 v2v/Makefile.am           |  32 ++++++++++-
46b2f6
 v2v/dummy.c               |   2 +
46b2f6
 v2v/var_expander.ml       |  72 ++++++++++++++++++++++++
46b2f6
 v2v/var_expander.mli      |  82 +++++++++++++++++++++++++++
46b2f6
 v2v/var_expander_tests.ml | 113 ++++++++++++++++++++++++++++++++++++++
46b2f6
 6 files changed, 300 insertions(+), 2 deletions(-)
46b2f6
 create mode 100644 v2v/dummy.c
46b2f6
 create mode 100644 v2v/var_expander.ml
46b2f6
 create mode 100644 v2v/var_expander.mli
46b2f6
 create mode 100644 v2v/var_expander_tests.ml
46b2f6
46b2f6
diff --git a/.gitignore b/.gitignore
46b2f6
index 637bf7765..f2efcdde2 100644
46b2f6
--- a/.gitignore
46b2f6
+++ b/.gitignore
46b2f6
@@ -693,6 +693,7 @@ Makefile.in
46b2f6
 /v2v/uefi.ml
46b2f6
 /v2v/uefi.mli
46b2f6
 /v2v/v2v_unit_tests
46b2f6
+/v2v/var_expander_tests
46b2f6
 /v2v/virt-v2v
46b2f6
 /v2v/virt-v2v.1
46b2f6
 /v2v/virt-v2v-copy-to-local
46b2f6
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
46b2f6
index 2312812fb..f196be81d 100644
46b2f6
--- a/v2v/Makefile.am
46b2f6
+++ b/v2v/Makefile.am
46b2f6
@@ -98,6 +98,7 @@ SOURCES_MLI = \
46b2f6
 	utils.mli \
46b2f6
 	v2v.mli \
46b2f6
 	vCenter.mli \
46b2f6
+	var_expander.mli \
46b2f6
 	windows.mli \
46b2f6
 	windows_virtio.mli
46b2f6
 
46b2f6
@@ -106,6 +107,7 @@ SOURCES_ML = \
46b2f6
 	types.ml \
46b2f6
 	uefi.ml \
46b2f6
 	utils.ml \
46b2f6
+	var_expander.ml \
46b2f6
 	python_script.ml \
46b2f6
 	name_from_disk.ml \
46b2f6
 	vCenter.ml \
46b2f6
@@ -442,7 +444,7 @@ TESTS += \
46b2f6
 endif
46b2f6
 
46b2f6
 if HAVE_OCAML_PKG_OUNIT
46b2f6
-TESTS += v2v_unit_tests
46b2f6
+TESTS += v2v_unit_tests var_expander_tests
46b2f6
 endif
46b2f6
 
46b2f6
 if ENABLE_APPLIANCE
46b2f6
@@ -651,7 +653,7 @@ EXTRA_DIST += \
46b2f6
 # Unit tests.
46b2f6
 check_PROGRAMS =
46b2f6
 if HAVE_OCAML_PKG_OUNIT
46b2f6
-check_PROGRAMS += v2v_unit_tests
46b2f6
+check_PROGRAMS += v2v_unit_tests var_expander_tests
46b2f6
 endif
46b2f6
 
46b2f6
 v2v_unit_tests_BOBJECTS = \
46b2f6
@@ -671,13 +673,28 @@ v2v_unit_tests_SOURCES = $(virt_v2v_SOURCES)
46b2f6
 v2v_unit_tests_CPPFLAGS = $(virt_v2v_CPPFLAGS)
46b2f6
 v2v_unit_tests_CFLAGS = $(virt_v2v_CFLAGS)
46b2f6
 
46b2f6
+var_expander_tests_BOBJECTS = \
46b2f6
+	var_expander.cmo \
46b2f6
+	var_expander_tests.cmo
46b2f6
+var_expander_tests_XOBJECTS = $(var_expander_tests_BOBJECTS:.cmo=.cmx)
46b2f6
+
46b2f6
+var_expander_tests_SOURCES = dummy.c
46b2f6
+var_expander_tests_CPPFLAGS = $(virt_v2v_CPPFLAGS)
46b2f6
+var_expander_tests_CFLAGS = $(virt_v2v_CFLAGS)
46b2f6
+
46b2f6
 if !HAVE_OCAMLOPT
46b2f6
 # Can't call this v2v_unit_tests_OBJECTS because automake gets confused.
46b2f6
 v2v_unit_tests_THEOBJECTS = $(v2v_unit_tests_BOBJECTS)
46b2f6
 v2v_unit_tests.cmo: OCAMLPACKAGES += -package oUnit
46b2f6
+
46b2f6
+var_expander_tests_THEOBJECTS = $(var_expander_tests_BOBJECTS)
46b2f6
+var_expander_tests.cmo: OCAMLPACKAGES += -package oUnit
46b2f6
 else
46b2f6
 v2v_unit_tests_THEOBJECTS = $(v2v_unit_tests_XOBJECTS)
46b2f6
 v2v_unit_tests.cmx: OCAMLPACKAGES += -package oUnit
46b2f6
+
46b2f6
+var_expander_tests_THEOBJECTS = $(var_expander_tests_XOBJECTS)
46b2f6
+var_expander_tests.cmx: OCAMLPACKAGES += -package oUnit
46b2f6
 endif
46b2f6
 
46b2f6
 v2v_unit_tests_DEPENDENCIES = \
46b2f6
@@ -696,6 +713,17 @@ v2v_unit_tests_LINK = \
46b2f6
 	  $(OCAMLLINKFLAGS) \
46b2f6
 	  $(v2v_unit_tests_THEOBJECTS) -o $@
46b2f6
 
46b2f6
+var_expander_tests_DEPENDENCIES = \
46b2f6
+	$(var_expander_tests_THEOBJECTS) \
46b2f6
+	../common/mlpcre/mlpcre.$(MLARCHIVE) \
46b2f6
+	$(top_srcdir)/ocaml-link.sh
46b2f6
+var_expander_tests_LINK = \
46b2f6
+	$(top_srcdir)/ocaml-link.sh -cclib '$(OCAMLCLIBS)' -- \
46b2f6
+	  $(OCAMLFIND) $(BEST) $(OCAMLFLAGS) \
46b2f6
+	  $(OCAMLPACKAGES) -package oUnit \
46b2f6
+	  $(OCAMLLINKFLAGS) \
46b2f6
+	  $(var_expander_tests_THEOBJECTS) -o $@
46b2f6
+
46b2f6
 # Dependencies.
46b2f6
 .depend: \
46b2f6
 	$(srcdir)/*.mli \
46b2f6
diff --git a/v2v/dummy.c b/v2v/dummy.c
46b2f6
new file mode 100644
46b2f6
index 000000000..ebab6198c
46b2f6
--- /dev/null
46b2f6
+++ b/v2v/dummy.c
46b2f6
@@ -0,0 +1,2 @@
46b2f6
+/* Dummy source, to be used for OCaml-based tools with no C sources. */
46b2f6
+enum { foo = 1 };
46b2f6
diff --git a/v2v/var_expander.ml b/v2v/var_expander.ml
46b2f6
new file mode 100644
46b2f6
index 000000000..24b9bafe3
46b2f6
--- /dev/null
46b2f6
+++ b/v2v/var_expander.ml
46b2f6
@@ -0,0 +1,72 @@
46b2f6
+(* virt-v2v
46b2f6
+ * Copyright (C) 2019 Red Hat Inc.
46b2f6
+ *
46b2f6
+ * This program is free software; you can redistribute it and/or modify
46b2f6
+ * it under the terms of the GNU General Public License as published by
46b2f6
+ * the Free Software Foundation; either version 2 of the License, or
46b2f6
+ * (at your option) any later version.
46b2f6
+ *
46b2f6
+ * This program is distributed in the hope that it will be useful,
46b2f6
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
46b2f6
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46b2f6
+ * GNU General Public License for more details.
46b2f6
+ *
46b2f6
+ * You should have received a copy of the GNU General Public License along
46b2f6
+ * with this program; if not, write to the Free Software Foundation, Inc.,
46b2f6
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
46b2f6
+ *)
46b2f6
+
46b2f6
+open Std_utils
46b2f6
+
46b2f6
+exception Invalid_variable of string
46b2f6
+
46b2f6
+let var_re = PCRE.compile "(^|[^%])%{([^}]+)}"
46b2f6
+
46b2f6
+let check_variable var =
46b2f6
+  String.iter (
46b2f6
+    function
46b2f6
+    | '0'..'9'
46b2f6
+    | 'a'..'z'
46b2f6
+    | 'A'..'Z'
46b2f6
+    | '_'
46b2f6
+    | '-' -> ()
46b2f6
+    | _ -> raise (Invalid_variable var)
46b2f6
+  ) var
46b2f6
+
46b2f6
+let scan_variables str =
46b2f6
+  let res = ref [] in
46b2f6
+  let offset = ref 0 in
46b2f6
+  while PCRE.matches ~offset:!offset var_re str; do
46b2f6
+    let var = PCRE.sub 2 in
46b2f6
+    check_variable var;
46b2f6
+    let _, end_ = PCRE.subi 0 in
46b2f6
+    List.push_back res var;
46b2f6
+    offset := end_
46b2f6
+  done;
46b2f6
+  List.remove_duplicates !res
46b2f6
+
46b2f6
+let replace_fn str fn =
46b2f6
+  let res = ref str in
46b2f6
+  let offset = ref 0 in
46b2f6
+  while PCRE.matches ~offset:!offset var_re !res; do
46b2f6
+    let var = PCRE.sub 2 in
46b2f6
+    check_variable var;
46b2f6
+    let start_, end_ = PCRE.subi 0 in
46b2f6
+    match fn var with
46b2f6
+    | None ->
46b2f6
+      offset := end_
46b2f6
+    | Some text ->
46b2f6
+      let prefix_len =
46b2f6
+        let prefix_start, prefix_end = PCRE.subi 1 in
46b2f6
+        prefix_end - prefix_start in
46b2f6
+      res := (String.sub !res 0 (start_ + prefix_len)) ^ text ^ (String.sub !res end_ (String.length !res - end_));
46b2f6
+      offset := start_ + prefix_len + String.length text
46b2f6
+  done;
46b2f6
+  !res
46b2f6
+
46b2f6
+let replace_list str lst =
46b2f6
+  let fn var =
46b2f6
+    try Some (List.assoc var lst)
46b2f6
+    with Not_found -> None
46b2f6
+  in
46b2f6
+  replace_fn str fn
46b2f6
diff --git a/v2v/var_expander.mli b/v2v/var_expander.mli
46b2f6
new file mode 100644
46b2f6
index 000000000..80aa33c2c
46b2f6
--- /dev/null
46b2f6
+++ b/v2v/var_expander.mli
46b2f6
@@ -0,0 +1,82 @@
46b2f6
+(* virt-v2v
46b2f6
+ * Copyright (C) 2019 Red Hat Inc.
46b2f6
+ *
46b2f6
+ * This program is free software; you can redistribute it and/or modify
46b2f6
+ * it under the terms of the GNU General Public License as published by
46b2f6
+ * the Free Software Foundation; either version 2 of the License, or
46b2f6
+ * (at your option) any later version.
46b2f6
+ *
46b2f6
+ * This program is distributed in the hope that it will be useful,
46b2f6
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
46b2f6
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46b2f6
+ * GNU General Public License for more details.
46b2f6
+ *
46b2f6
+ * You should have received a copy of the GNU General Public License along
46b2f6
+ * with this program; if not, write to the Free Software Foundation, Inc.,
46b2f6
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
46b2f6
+ *)
46b2f6
+
46b2f6
+(** Simple variable expander.
46b2f6
+
46b2f6
+    This module provides the support to expand variables in strings,
46b2f6
+    specified in the form of [%{name}].
46b2f6
+
46b2f6
+    For example:
46b2f6
+
46b2f6
+{v
46b2f6
+let str = "variable-%{INDEX} in %{INDEX} replaced %{INDEX} times"
46b2f6
+let index = ref 0
46b2f6
+let fn = function
46b2f6
+  | "INDEX" ->
46b2f6
+    incr index;
46b2f6
+    Some (string_of_int !index)
46b2f6
+  | _ -> None
46b2f6
+in
46b2f6
+let str = Var_expander.replace_fn str fn
46b2f6
+(* now str is "variable-1 in 2 replaced 3 times" *)
46b2f6
+v}
46b2f6
+
46b2f6
+    The names of variables can contain only ASCII letters (uppercase,
46b2f6
+    and lowercase), digits, underscores, and dashes.
46b2f6
+
46b2f6
+    The replacement is done in a single pass: this means that if a
46b2f6
+    variable is replaced with the text of a variable, that new text
46b2f6
+    is kept as is in the final output.  In practice:
46b2f6
+
46b2f6
+{v
46b2f6
+let str = "%{VAR}"
46b2f6
+let str = Var_expander.replace_list str [("VAR", "%{VAR}")]
46b2f6
+(* now str is "%{VAR}" *)
46b2f6
+v}
46b2f6
+*)
46b2f6
+
46b2f6
+exception Invalid_variable of string
46b2f6
+(** Invalid variable name error.
46b2f6
+
46b2f6
+    In case a variable contains characters not allowed, then this
46b2f6
+    exception with the actual unacceptable variable. *)
46b2f6
+
46b2f6
+val scan_variables : string -> string list
46b2f6
+(** Scan the pattern string for all the variables available.
46b2f6
+
46b2f6
+    This can raise {!Invalid_variable} in case there are invalid
46b2f6
+    variable names. *)
46b2f6
+
46b2f6
+val replace_fn : string -> (string -> string option) -> string
46b2f6
+(** Replaces a string expanding all the variables.
46b2f6
+
46b2f6
+    The replacement function specify how a variable is replaced;
46b2f6
+    if [None] is returned, then that variable is not replaced.
46b2f6
+
46b2f6
+    This can raise {!Invalid_variable} in case there are invalid
46b2f6
+    variable names. *)
46b2f6
+
46b2f6
+val replace_list : string -> (string * string) list -> string
46b2f6
+(** Replaces a string expanding all the variables.
46b2f6
+
46b2f6
+    The replacement list specify how a variable is replaced;
46b2f6
+    if it is not specified in the list, then that variable is not
46b2f6
+    replaced.
46b2f6
+
46b2f6
+    This can raise {!Invalid_variable} in case there are invalid
46b2f6
+    variable names. *)
46b2f6
diff --git a/v2v/var_expander_tests.ml b/v2v/var_expander_tests.ml
46b2f6
new file mode 100644
46b2f6
index 000000000..35b628369
46b2f6
--- /dev/null
46b2f6
+++ b/v2v/var_expander_tests.ml
46b2f6
@@ -0,0 +1,113 @@
46b2f6
+(* virt-v2v
46b2f6
+ * Copyright (C) 2019 Red Hat Inc.
46b2f6
+ *
46b2f6
+ * This program is free software; you can redistribute it and/or modify
46b2f6
+ * it under the terms of the GNU General Public License as published by
46b2f6
+ * the Free Software Foundation; either version 2 of the License, or
46b2f6
+ * (at your option) any later version.
46b2f6
+ *
46b2f6
+ * This program is distributed in the hope that it will be useful,
46b2f6
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
46b2f6
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46b2f6
+ * GNU General Public License for more details.
46b2f6
+ *
46b2f6
+ * You should have received a copy of the GNU General Public License along
46b2f6
+ * with this program; if not, write to the Free Software Foundation, Inc.,
46b2f6
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
46b2f6
+ *)
46b2f6
+
46b2f6
+open Printf
46b2f6
+open OUnit
46b2f6
+
46b2f6
+open Std_utils
46b2f6
+
46b2f6
+let assert_equal_string = assert_equal ~printer:identity
46b2f6
+let assert_equal_stringlist = assert_equal ~printer:(fun x -> "(" ^ (String.escaped (String.concat "," x)) ^ ")")
46b2f6
+
46b2f6
+let replace_none_fn _ = None
46b2f6
+let replace_empty_fn _ = Some ""
46b2f6
+
46b2f6
+let test_no_replacement () =
46b2f6
+  assert_equal_string "" (Var_expander.replace_fn "" replace_none_fn);
46b2f6
+  assert_equal_string "x" (Var_expander.replace_fn "x" replace_none_fn);
46b2f6
+  assert_equal_string "%{}" (Var_expander.replace_fn "%{}" replace_none_fn);
46b2f6
+  assert_equal_string "%{EMPTY}" (Var_expander.replace_fn "%{EMPTY}" replace_none_fn);
46b2f6
+  assert_equal_string "%{EMPTY} %{no}" (Var_expander.replace_fn "%{EMPTY} %{no}" replace_none_fn);
46b2f6
+  assert_equal_string "a %{EMPTY} b" (Var_expander.replace_fn "a %{EMPTY} b" replace_none_fn);
46b2f6
+  ()
46b2f6
+
46b2f6
+let test_replacements () =
46b2f6
+  assert_equal_string "" (Var_expander.replace_fn "%{EMPTY}" replace_empty_fn);
46b2f6
+  assert_equal_string "x " (Var_expander.replace_fn "x %{EMPTY}" replace_empty_fn);
46b2f6
+  assert_equal_string "xy" (Var_expander.replace_fn "x%{EMPTY}y" replace_empty_fn);
46b2f6
+  assert_equal_string "x<->y" (Var_expander.replace_fn "x%{FOO}y" (function | "FOO" -> Some "<->" | _ -> None));
46b2f6
+  assert_equal_string "a x b" (Var_expander.replace_fn "a %{FOO} b" (function | "FOO" -> Some "x" | _ -> None));
46b2f6
+  assert_equal_string "%{FOO} x" (Var_expander.replace_fn "%{FOO} %{BAR}" (function | "BAR" -> Some "x" | _ -> None));
46b2f6
+  assert_equal_string "%{FOO}" (Var_expander.replace_fn "%{BAR}" (function | "BAR" -> Some "%{FOO}" | _ -> None));
46b2f6
+  assert_equal_string "%{FOO} x" (Var_expander.replace_fn "%{BAR} %{FOO}" (function | "BAR" -> Some "%{FOO}" | "FOO" -> Some "x" | _ -> None));
46b2f6
+  begin
46b2f6
+    let str = "%{INDEX}, %{INDEX}, %{INDEX}" in
46b2f6
+    let index = ref 0 in
46b2f6
+    let fn = function
46b2f6
+      | "INDEX" ->
46b2f6
+        incr index;
46b2f6
+        Some (string_of_int !index)
46b2f6
+      | _ -> None
46b2f6
+    in
46b2f6
+    assert_equal_string "1, 2, 3" (Var_expander.replace_fn str fn)
46b2f6
+  end;
46b2f6
+  ()
46b2f6
+
46b2f6
+let test_escape () =
46b2f6
+  assert_equal_string "%%{FOO}" (Var_expander.replace_fn "%%{FOO}" replace_empty_fn);
46b2f6
+  assert_equal_string "x %%{FOO} x" (Var_expander.replace_fn "%{FOO} %%{FOO} %{FOO}" (function | "FOO" -> Some "x" | _ -> None));
46b2f6
+  ()
46b2f6
+
46b2f6
+let test_list () =
46b2f6
+  assert_equal_string "x %{NONE}" (Var_expander.replace_list "%{FOO} %{NONE}" [("FOO", "x")]);
46b2f6
+  ()
46b2f6
+
46b2f6
+let test_scan_variables () =
46b2f6
+  let assert_invalid_variable var =
46b2f6
+    let str = "%{" ^ var ^ "}" in
46b2f6
+    assert_raises (Var_expander.Invalid_variable var)
46b2f6
+                  (fun () -> Var_expander.scan_variables str)
46b2f6
+  in
46b2f6
+  assert_equal_stringlist [] (Var_expander.scan_variables "");
46b2f6
+  assert_equal_stringlist [] (Var_expander.scan_variables "foo");
46b2f6
+  assert_equal_stringlist ["FOO"] (Var_expander.scan_variables "%{FOO}");
46b2f6
+  assert_equal_stringlist ["FOO"; "BAR"] (Var_expander.scan_variables "%{FOO} %{BAR}");
46b2f6
+  assert_equal_stringlist ["FOO"; "BAR"] (Var_expander.scan_variables "%{FOO} %{BAR} %{FOO}");
46b2f6
+  assert_equal_stringlist ["FOO"; "BAR"] (Var_expander.scan_variables "%{FOO} %%{ESCAPED} %{BAR}");
46b2f6
+  assert_invalid_variable "FOO/BAR";
46b2f6
+  ()
46b2f6
+
46b2f6
+let test_errors () =
46b2f6
+  let assert_invalid_variable var =
46b2f6
+    let str = "%{" ^ var ^ "}" in
46b2f6
+    assert_raises (Var_expander.Invalid_variable var)
46b2f6
+                  (fun () -> Var_expander.replace_fn str replace_none_fn)
46b2f6
+  in
46b2f6
+  assert_invalid_variable "FOO/BAR";
46b2f6
+  assert_invalid_variable "FOO:BAR";
46b2f6
+  assert_invalid_variable "FOO(BAR";
46b2f6
+  assert_invalid_variable "FOO)BAR";
46b2f6
+  assert_invalid_variable "FOO@BAR";
46b2f6
+  ()
46b2f6
+
46b2f6
+(* Suites declaration. *)
46b2f6
+let suite =
46b2f6
+  TestList ([
46b2f6
+    "basic" >::: [
46b2f6
+      "no_replacement" >:: test_no_replacement;
46b2f6
+      "replacements" >:: test_replacements;
46b2f6
+      "escape" >:: test_escape;
46b2f6
+      "list" >:: test_list;
46b2f6
+      "scan_variables" >:: test_scan_variables;
46b2f6
+      "errors" >:: test_errors;
46b2f6
+    ];
46b2f6
+  ])
46b2f6
+
46b2f6
+let () =
46b2f6
+  ignore (run_test_tt_main suite);
46b2f6
+  Printf.fprintf stderr "\n"
46b2f6
-- 
b155d0
2.26.2
46b2f6