diff --git a/test/test_unit_template_default.py b/test/test_unit_template_default.py index 72438778..aaae0fec 100644 --- a/test/test_unit_template_default.py +++ b/test/test_unit_template_default.py @@ -1,4 +1,5 @@ """Unit tests: template_default""" + import os FILE_MODE = 0o754 @@ -12,6 +13,7 @@ LOCAL_USER = "default_Test+@-!^User" LOCAL_DISTRO = "default_Test+@-!^Distro" LOCAL_DISTRO_FAMILY = "default_Test+@-!^Family" +ENV_VAR = "default_Test+@-!^Env" TEMPLATE = f""" start of template default class = >{{{{yadm.class}}}}< @@ -30,6 +32,9 @@ {{% if yadm.class == "wrongclass1" %}} wrong class 1 {{% endif %}} +{{% if yadm.class != "wronglcass" %}} +Included section from != +{{% endif\t\t %}} {{% if yadm.class == "{LOCAL_CLASS}" %}} Included section for class = {{{{yadm.class}}}} ({{{{yadm.class}}}} repeated) Multiple lines @@ -97,6 +102,13 @@ {{% if yadm.distro_family == "wrongfamily2" %}} wrong family 2 {{% endif %}} +{{% if env.VAR == "{ENV_VAR}" %}} +Included section for env.VAR = {{{{env.VAR}}}} ({{{{env.VAR}}}} again) +{{% endif %}} +{{% if env.VAR == "wrongenvvar" %}} +wrong env.VAR +{{% endif %}} +yadm.no_such_var="{{{{ yadm.no_such_var }}}}" and env.NO_SUCH_VAR="{{{{ env.NO_SUCH_VAR }}}}" end of template """ EXPECTED = f""" @@ -111,6 +123,7 @@ classes = >{LOCAL_CLASS2} {LOCAL_CLASS}< Included section from else +Included section from != Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated) Multiple lines Included section for second class @@ -121,6 +134,8 @@ Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) Included section for distro_family = \ {LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again) +Included section for env.VAR = {ENV_VAR} ({ENV_VAR} again) +yadm.no_such_var="" and env.NO_SUCH_VAR="" end of template """ @@ -138,7 +153,7 @@ An empty file removes the line above {%include basic%} {% include "./variables.{{ yadm.os }}" %} -{% include dir/nested %} + {% include dir/nested %} Include basic again: {% include basic %} """ @@ -154,6 +169,42 @@ basic """ +TEMPLATE_NESTED_IFS = """\ +{% if yadm.user == "me" %} + print1 + {% if yadm.user == "me" %} + print2 + {% else %} + no print1 + {% endif %} +{% else %} + {% if yadm.user == "me" %} + no print2 + {% else %} + no print3 + {% endif %} +{% endif %} +{% if yadm.user != "me" %} + no print4 + {% if yadm.user == "me" %} + no print5 + {% else %} + no print6 + {% endif %} +{% else %} + {% if yadm.user == "me" %} + print3 + {% else %} + no print7 + {% endif %} +{% endif %} +""" +EXPECTED_NESTED_IFS = """\ + print1 + print2 + print3 +""" + def test_template_default(runner, yadm, tmpdir): """Test template_default""" @@ -182,7 +233,7 @@ def test_template_default(runner, yadm, tmpdir): local_distro_family="{LOCAL_DISTRO_FAMILY}" template_default "{input_file}" "{output_file}" """ - run = runner(command=["bash"], inp=script) + run = runner(command=["bash"], inp=script, env={"VAR": ENV_VAR}) assert run.success assert run.err == "" assert output_file.read() == EXPECTED @@ -243,12 +294,30 @@ def test_include(runner, yadm, tmpdir): assert os.stat(output_file).st_mode == os.stat(input_file).st_mode +def test_nested_ifs(runner, yadm, tmpdir): + """Test nested if statements""" + + input_file = tmpdir.join("input") + input_file.write(TEMPLATE_NESTED_IFS, ensure=True) + output_file = tmpdir.join("output") + + script = f""" + YADM_TEST=1 source {yadm} + set_awk + local_user="me" + template_default "{input_file}" "{output_file}" + """ + run = runner(command=["bash"], inp=script) + assert run.success + assert run.err == "" + assert output_file.read() == EXPECTED_NESTED_IFS + + def test_env(runner, yadm, tmpdir): """Test env""" input_file = tmpdir.join("input") input_file.write("{{env.PWD}}", ensure=True) - input_file.chmod(FILE_MODE) output_file = tmpdir.join("output") script = f""" diff --git a/yadm b/yadm index 09da278b..9dc9240d 100755 --- a/yadm +++ b/yadm @@ -368,87 +368,110 @@ function template_default() { # the explicit "space + tab" character class used below is used because not # all versions of awk seem to support the POSIX character classes [[:blank:]] read -r -d '' awk_pgm << "EOF" -# built-in default template processor BEGIN { - blank = "[ ]" - c["class"] = class - c["classes"] = classes - c["arch"] = arch - c["os"] = os - c["hostname"] = host - c["user"] = user - c["distro"] = distro - c["distro_family"] = distro_family - c["source"] = source - ifs = "^{%" blank "*if" - els = "^{%" blank "*else" blank "*%}$" - end = "^{%" blank "*endif" blank "*%}$" - skp = "^{%" blank "*(if|else|endif)" - vld = conditions() - inc_start = "^{%" blank "*include" blank "+\"?" - inc_end = "\"?" blank "*%}$" - inc = inc_start ".+" inc_end - prt = 1 - err = 0 -} -END { exit err } -{ replace_vars() } # variable replacements -$0 ~ vld, $0 ~ end { - if ($0 ~ vld || $0 ~ end) prt=1; - if ($0 ~ els) prt=0; - if ($0 ~ skp) next; -} -($0 ~ ifs && $0 !~ vld), $0 ~ end { - if ($0 ~ ifs && $0 !~ vld) prt=0; - if ($0 ~ els || $0 ~ end) prt=1; - if ($0 ~ skp) next; -} -{ if (!prt) next } -$0 ~ inc { - file = $0 - sub(inc_start, "", file) - sub(inc_end, "", file) - sub(/^[^\/].*$/, source_dir "/&", file) - - while ((res = getline 0) { - replace_vars() - print - } - if (res < 0) { - printf "%s:%d: error: could not read '%s'\n", FILENAME, NR, file | "cat 1>&2" - err = 1 - } - close(file) - next -} -{ print } -function replace_vars() { - for (label in c) { - gsub(("{{" blank "*yadm\\." label blank "*}}"), c[label]) + yadm["class"] = class + yadm["classes"] = classes + yadm["arch"] = arch + yadm["os"] = os + yadm["hostname"] = host + yadm["user"] = user + yadm["distro"] = distro + yadm["distro_family"] = distro_family + yadm["source"] = source + + VARIABLE = "(env|yadm)\\.[a-zA-Z0-9_]+" + + current = 0 + filename[current] = ARGV[1] + line[current] = 0 + + level = 0 + skip[level] = 0 + + for (; current >= 0; --current) { + while ((res = getline 0) { + ++line[current] + if ($0 ~ "^[ \t]*\\{%[ \t]*if[ \t]+" VARIABLE "[ \t]*[!=]=[ \t]*\".*\"[ \t]*%\\}$") { + if (skip[level]) { skip[++level] = 1; continue } + + match($0, VARIABLE) + lhs = substr($0, RSTART, RLENGTH) + match($0, /[!=]=/) + op = substr($0, RSTART, RLENGTH) + match($0, /".*"/) + rhs = replace_vars(substr($0, RSTART + 1, RLENGTH - 2)) + + if (lhs == "yadm.class") { + lhs = "not" rhs + split(classes, cls_array, "\n") + for (idx in cls_array) { + if (rhs == cls_array[idx]) { lhs = rhs; break } + } + } + else { + lhs = replace_vars("{{" lhs "}}") + } + + if (op == "==") { skip[++level] = lhs != rhs } + else { skip[++level] = lhs == rhs } + } + else if (/^[ \t]*\{%[ \t]*else[ \t]*%\}$/) { + if (level == 0 || skip[level] < 0) { error("else without matching if") } + skip[level] = skip[level] ? skip[level - 1] : -1 + } + else if (/^[ \t]*\{%[ \t]*endif[ \t]*%\}$/) { + if (--level < 0) { error("endif without matching if") } + } + else if (!skip[level]) { + $0 = replace_vars($0) + if (match($0, /^[ \t]*\{%[ \t]*include[ \t]+("[^"]+"|[^"]+)[ \t]*%\}$/)) { + include = $0 + sub(/^[ \t]*\{%[ \t]*include[ \t]+"?/, "", include) + sub(/"?[ \t]*%\}$/, "", include) + if (index(include, "/") != 1) { + include = source_dir "/" include + } + filename[++current] = include + line[current] = 0 + } + else { print } + } + } + if (res >= 0) { close(filename[current]) } + else if (current == 0) { error("could not read input file") } + else { --current; error("could not read include file '" filename[current + 1] "'") } } - for (label in ENVIRON) { - gsub(("{{" blank "*env\\." label blank "*}}"), ENVIRON[label]) + if (level > 0) { + current = 0 + error("unterminated if") } + exit 0 } -function condition_helper(label, value) { - gsub(/[\\.^$(){}\[\]|*+?]/, "\\\\&", value) - return sprintf("yadm\\.%s" blank "*==" blank "*\"%s\"", label, value) +function error(text) { + printf "%s:%d: error: %s\n", + filename[current], line[current], text > "/dev/stderr" + exit 1 } -function conditions() { - pattern = ifs blank "+(" - for (label in c) { - if (label != "class") { - value = c[label] - pattern = sprintf("%s%s|", pattern, condition_helper(label, value)); +function replace_vars(input) { + output = "" + while (match(input, "\\{\\{[ \t]*" VARIABLE "[ \t]*\\}\\}")) { + if (RSTART > 1) { + output = output substr(input, 0, RSTART - 1) + } + data = substr(input, RSTART + 2, RLENGTH - 4) + input = substr(input, RSTART + RLENGTH) + + gsub(/[ \t]+/, "", data) + split(data, fields, /\./) + + if (fields[1] == "env") { + output = output ENVIRON[fields[2]] + } + else { + output = output yadm[fields[2]] } } - split(classes, cls_array, "\n") - for (idx in cls_array) { - value = cls_array[idx] - pattern = sprintf("%s%s|", pattern, condition_helper("class", value)); - } - sub(/\|$/, ")" blank "*%}$", pattern) - return pattern + return output input } EOF