Merge pull request #497 from erijo/template-default
Rewrite default template to handle nested ifs, != and env vars in if
This commit is contained in:
commit
d74a41b1b4
2 changed files with 168 additions and 76 deletions
|
@ -1,4 +1,5 @@
|
||||||
"""Unit tests: template_default"""
|
"""Unit tests: template_default"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
FILE_MODE = 0o754
|
FILE_MODE = 0o754
|
||||||
|
@ -12,6 +13,7 @@ LOCAL_HOST = "default_Test+@-!^Host"
|
||||||
LOCAL_USER = "default_Test+@-!^User"
|
LOCAL_USER = "default_Test+@-!^User"
|
||||||
LOCAL_DISTRO = "default_Test+@-!^Distro"
|
LOCAL_DISTRO = "default_Test+@-!^Distro"
|
||||||
LOCAL_DISTRO_FAMILY = "default_Test+@-!^Family"
|
LOCAL_DISTRO_FAMILY = "default_Test+@-!^Family"
|
||||||
|
ENV_VAR = "default_Test+@-!^Env"
|
||||||
TEMPLATE = f"""
|
TEMPLATE = f"""
|
||||||
start of template
|
start of template
|
||||||
default class = >{{{{yadm.class}}}}<
|
default class = >{{{{yadm.class}}}}<
|
||||||
|
@ -30,6 +32,9 @@ Included section from else
|
||||||
{{% if yadm.class == "wrongclass1" %}}
|
{{% if yadm.class == "wrongclass1" %}}
|
||||||
wrong class 1
|
wrong class 1
|
||||||
{{% endif %}}
|
{{% endif %}}
|
||||||
|
{{% if yadm.class != "wronglcass" %}}
|
||||||
|
Included section from !=
|
||||||
|
{{% endif\t\t %}}
|
||||||
{{% if yadm.class == "{LOCAL_CLASS}" %}}
|
{{% if yadm.class == "{LOCAL_CLASS}" %}}
|
||||||
Included section for class = {{{{yadm.class}}}} ({{{{yadm.class}}}} repeated)
|
Included section for class = {{{{yadm.class}}}} ({{{{yadm.class}}}} repeated)
|
||||||
Multiple lines
|
Multiple lines
|
||||||
|
@ -97,6 +102,13 @@ Included section for distro_family = \
|
||||||
{{% if yadm.distro_family == "wrongfamily2" %}}
|
{{% if yadm.distro_family == "wrongfamily2" %}}
|
||||||
wrong family 2
|
wrong family 2
|
||||||
{{% endif %}}
|
{{% 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
|
end of template
|
||||||
"""
|
"""
|
||||||
EXPECTED = f"""
|
EXPECTED = f"""
|
||||||
|
@ -111,6 +123,7 @@ default distro_family = >{LOCAL_DISTRO_FAMILY}<
|
||||||
classes = >{LOCAL_CLASS2}
|
classes = >{LOCAL_CLASS2}
|
||||||
{LOCAL_CLASS}<
|
{LOCAL_CLASS}<
|
||||||
Included section from else
|
Included section from else
|
||||||
|
Included section from !=
|
||||||
Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated)
|
Included section for class = {LOCAL_CLASS} ({LOCAL_CLASS} repeated)
|
||||||
Multiple lines
|
Multiple lines
|
||||||
Included section for second class
|
Included section for second class
|
||||||
|
@ -121,6 +134,8 @@ Included section for user = {LOCAL_USER} ({LOCAL_USER} repeated)
|
||||||
Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again)
|
Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again)
|
||||||
Included section for distro_family = \
|
Included section for distro_family = \
|
||||||
{LOCAL_DISTRO_FAMILY} ({LOCAL_DISTRO_FAMILY} again)
|
{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
|
end of template
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -138,7 +153,7 @@ The first line
|
||||||
An empty file removes the line above
|
An empty file removes the line above
|
||||||
{%include basic%}
|
{%include basic%}
|
||||||
{% include "./variables.{{ yadm.os }}" %}
|
{% include "./variables.{{ yadm.os }}" %}
|
||||||
{% include dir/nested %}
|
{% include dir/nested %}
|
||||||
Include basic again:
|
Include basic again:
|
||||||
{% include basic %}
|
{% include basic %}
|
||||||
"""
|
"""
|
||||||
|
@ -154,6 +169,42 @@ Include basic again:
|
||||||
basic
|
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):
|
def test_template_default(runner, yadm, tmpdir):
|
||||||
"""Test template_default"""
|
"""Test template_default"""
|
||||||
|
@ -182,7 +233,7 @@ def test_template_default(runner, yadm, tmpdir):
|
||||||
local_distro_family="{LOCAL_DISTRO_FAMILY}"
|
local_distro_family="{LOCAL_DISTRO_FAMILY}"
|
||||||
template_default "{input_file}" "{output_file}"
|
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.success
|
||||||
assert run.err == ""
|
assert run.err == ""
|
||||||
assert output_file.read() == EXPECTED
|
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
|
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):
|
def test_env(runner, yadm, tmpdir):
|
||||||
"""Test env"""
|
"""Test env"""
|
||||||
|
|
||||||
input_file = tmpdir.join("input")
|
input_file = tmpdir.join("input")
|
||||||
input_file.write("{{env.PWD}}", ensure=True)
|
input_file.write("{{env.PWD}}", ensure=True)
|
||||||
input_file.chmod(FILE_MODE)
|
|
||||||
output_file = tmpdir.join("output")
|
output_file = tmpdir.join("output")
|
||||||
|
|
||||||
script = f"""
|
script = f"""
|
||||||
|
|
169
yadm
169
yadm
|
@ -368,87 +368,110 @@ function template_default() {
|
||||||
# the explicit "space + tab" character class used below is used because not
|
# the explicit "space + tab" character class used below is used because not
|
||||||
# all versions of awk seem to support the POSIX character classes [[:blank:]]
|
# all versions of awk seem to support the POSIX character classes [[:blank:]]
|
||||||
read -r -d '' awk_pgm << "EOF"
|
read -r -d '' awk_pgm << "EOF"
|
||||||
# built-in default template processor
|
|
||||||
BEGIN {
|
BEGIN {
|
||||||
blank = "[ ]"
|
yadm["class"] = class
|
||||||
c["class"] = class
|
yadm["classes"] = classes
|
||||||
c["classes"] = classes
|
yadm["arch"] = arch
|
||||||
c["arch"] = arch
|
yadm["os"] = os
|
||||||
c["os"] = os
|
yadm["hostname"] = host
|
||||||
c["hostname"] = host
|
yadm["user"] = user
|
||||||
c["user"] = user
|
yadm["distro"] = distro
|
||||||
c["distro"] = distro
|
yadm["distro_family"] = distro_family
|
||||||
c["distro_family"] = distro_family
|
yadm["source"] = source
|
||||||
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 <file) > 0) {
|
VARIABLE = "(env|yadm)\\.[a-zA-Z0-9_]+"
|
||||||
replace_vars()
|
|
||||||
print
|
current = 0
|
||||||
|
filename[current] = ARGV[1]
|
||||||
|
line[current] = 0
|
||||||
|
|
||||||
|
level = 0
|
||||||
|
skip[level] = 0
|
||||||
|
|
||||||
|
for (; current >= 0; --current) {
|
||||||
|
while ((res = getline <filename[current]) > 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] "'") }
|
||||||
}
|
}
|
||||||
if (res < 0) {
|
if (level > 0) {
|
||||||
printf "%s:%d: error: could not read '%s'\n", FILENAME, NR, file | "cat 1>&2"
|
current = 0
|
||||||
err = 1
|
error("unterminated if")
|
||||||
}
|
}
|
||||||
close(file)
|
exit 0
|
||||||
next
|
|
||||||
}
|
}
|
||||||
{ print }
|
function error(text) {
|
||||||
function replace_vars() {
|
printf "%s:%d: error: %s\n",
|
||||||
for (label in c) {
|
filename[current], line[current], text > "/dev/stderr"
|
||||||
gsub(("{{" blank "*yadm\\." label blank "*}}"), c[label])
|
exit 1
|
||||||
}
|
|
||||||
for (label in ENVIRON) {
|
|
||||||
gsub(("{{" blank "*env\\." label blank "*}}"), ENVIRON[label])
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function condition_helper(label, value) {
|
function replace_vars(input) {
|
||||||
gsub(/[\\.^$(){}\[\]|*+?]/, "\\\\&", value)
|
output = ""
|
||||||
return sprintf("yadm\\.%s" blank "*==" blank "*\"%s\"", label, value)
|
while (match(input, "\\{\\{[ \t]*" VARIABLE "[ \t]*\\}\\}")) {
|
||||||
}
|
if (RSTART > 1) {
|
||||||
function conditions() {
|
output = output substr(input, 0, RSTART - 1)
|
||||||
pattern = ifs blank "+("
|
}
|
||||||
for (label in c) {
|
data = substr(input, RSTART + 2, RLENGTH - 4)
|
||||||
if (label != "class") {
|
input = substr(input, RSTART + RLENGTH)
|
||||||
value = c[label]
|
|
||||||
pattern = sprintf("%s%s|", pattern, condition_helper(label, value));
|
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")
|
return output input
|
||||||
for (idx in cls_array) {
|
|
||||||
value = cls_array[idx]
|
|
||||||
pattern = sprintf("%s%s|", pattern, condition_helper("class", value));
|
|
||||||
}
|
|
||||||
sub(/\|$/, ")" blank "*%}$", pattern)
|
|
||||||
return pattern
|
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue