mirror of
				https://github.com/TheLocehiliosan/yadm
				synced 2025-06-13 13:03:58 +00:00 
			
		
		
		
	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
					
				| @ -1,4 +1,5 @@ | ||||
| """Unit tests: template_default""" | ||||
| 
 | ||||
| import os | ||||
| 
 | ||||
| FILE_MODE = 0o754 | ||||
| @ -12,6 +13,7 @@ LOCAL_HOST = "default_Test+@-!^Host" | ||||
| 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 @@ Included section from else | ||||
| {{% 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 @@ Included section for distro_family = \ | ||||
| {{% 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 @@ default distro_family = >{LOCAL_DISTRO_FAMILY}< | ||||
| 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 user = {LOCAL_USER} ({LOCAL_USER} repeated) | ||||
| 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 @@ The first line | ||||
| 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 @@ Include basic again: | ||||
| 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""" | ||||
|  | ||||
							
								
								
									
										169
									
								
								yadm
									
									
									
									
									
								
							
							
						
						
									
										169
									
								
								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) | ||||
|   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 | ||||
| 
 | ||||
|   while ((res = getline <file) > 0) { | ||||
|     replace_vars() | ||||
|     print | ||||
|   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 <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) { | ||||
|     printf "%s:%d: error: could not read '%s'\n", FILENAME, NR, file | "cat 1>&2" | ||||
|     err = 1 | ||||
|   if (level > 0) { | ||||
|     current = 0 | ||||
|     error("unterminated if") | ||||
|   } | ||||
|   close(file) | ||||
|   next | ||||
|   exit 0 | ||||
| } | ||||
| { print } | ||||
| function replace_vars() { | ||||
|   for (label in c) { | ||||
|     gsub(("{{" blank "*yadm\\." label blank "*}}"), c[label]) | ||||
|   } | ||||
|   for (label in ENVIRON) { | ||||
|     gsub(("{{" blank "*env\\." label blank "*}}"), ENVIRON[label]) | ||||
|   } | ||||
| function error(text) { | ||||
|   printf "%s:%d: error: %s\n", | ||||
|     filename[current], line[current], text > "/dev/stderr" | ||||
|   exit 1 | ||||
| } | ||||
| function condition_helper(label, value) { | ||||
|   gsub(/[\\.^$(){}\[\]|*+?]/, "\\\\&", value) | ||||
|   return sprintf("yadm\\.%s" blank "*==" blank "*\"%s\"", label, value) | ||||
| } | ||||
| 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 | ||||
| 
 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user