From 9bcf070dfe89d8d720ff1a1699b1f60f5fd0a9d8 Mon Sep 17 00:00:00 2001 From: Erik Flodin Date: Fri, 9 Oct 2020 15:36:58 +0200 Subject: [PATCH] Add support for including files using the default template processor The syntax is '{% include "file" %}' where file is either an absolute path or a path relative to the current template file's directory. Variables in the included file will be replaced as for the main template. But the included file can't include files itself. --- test/test_unit_template_default.py | 64 ++++++++++++++++++++++++++++++ yadm | 28 ++++++++++++- yadm.1 | 8 +++- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/test/test_unit_template_default.py b/test/test_unit_template_default.py index cbc42d5..861f190 100644 --- a/test/test_unit_template_default.py +++ b/test/test_unit_template_default.py @@ -88,6 +88,36 @@ Included section for distro = {LOCAL_DISTRO} ({LOCAL_DISTRO} again) end of template ''' +INCLUDE_BASIC = 'basic\n' +INCLUDE_VARIABLES = f'''\ +included <{{{{ yadm.class }}}}> file + +empty line above +''' +INCLUDE_NESTED = 'no newline at the end' + +TEMPLATE_INCLUDE = '''\ +The first line +{% include empty %} +An empty file removes the line above +{%include basic%} +{% include "./variables.{{ yadm.os }}" %} +{% include dir/nested %} +Include basic again: +{% include basic %} +''' +EXPECTED_INCLUDE = f'''\ +The first line +An empty file removes the line above +basic +included <{LOCAL_CLASS}> file + +empty line above +no newline at the end +Include basic again: +basic +''' + def test_template_default(runner, yadm, tmpdir): """Test template_default""" @@ -132,3 +162,37 @@ def test_source(runner, yadm, tmpdir): assert run.err == '' assert output_file.read().strip() == str(input_file) assert os.stat(output_file).st_mode == os.stat(input_file).st_mode + + +def test_include(runner, yadm, tmpdir): + """Test include""" + + empty_file = tmpdir.join('empty') + empty_file.write('', ensure=True) + + basic_file = tmpdir.join('basic') + basic_file.write(INCLUDE_BASIC) + + variables_file = tmpdir.join(f'variables.{LOCAL_SYSTEM}') + variables_file.write(INCLUDE_VARIABLES) + + nested_file = tmpdir.join('dir').join('nested') + nested_file.write(INCLUDE_NESTED, ensure=True) + + input_file = tmpdir.join('input') + input_file.write(TEMPLATE_INCLUDE) + input_file.chmod(FILE_MODE) + output_file = tmpdir.join('output') + + script = f""" + YADM_TEST=1 source {yadm} + set_awk + local_class="{LOCAL_CLASS}" + local_system="{LOCAL_SYSTEM}" + template_default "{input_file}" "{output_file}" + """ + run = runner(command=['bash'], inp=script) + assert run.success + assert run.err == '' + assert output_file.read() == EXPECTED_INCLUDE + assert os.stat(output_file).st_mode == os.stat(input_file).st_mode diff --git a/yadm b/yadm index 57fbdee..f5545a9 100755 --- a/yadm +++ b/yadm @@ -357,8 +357,13 @@ BEGIN { els = "^{%" blank "*else" blank "*%}$" end = "^{%" blank "*endif" blank "*%}$" skp = "^{%" blank "*(if|else|endif)" + 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; @@ -370,7 +375,25 @@ $0 ~ vld, $0 ~ end { if ($0 ~ els || $0 ~ end) prt=1; if ($0 ~ skp) next; } -{ if (prt) print } +{ 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]) @@ -396,8 +419,9 @@ EOF -v user="$local_user" \ -v distro="$local_distro" \ -v source="$input" \ + -v source_dir="$(dirname "$input")" \ "$awk_pgm" \ - "$input" > "$temp_file" + "$input" > "$temp_file" || rm -f "$temp_file" if [ -f "$temp_file" ] ; then copy_perms "$input" "$temp_file" diff --git a/yadm.1 b/yadm.1 index 98b372b..5995ede 100644 --- a/yadm.1 +++ b/yadm.1 @@ -708,6 +708,7 @@ with the following content config={{yadm.class}}-{{yadm.os}} {% else %} config=dev-whatever + {% include "whatever.extra" %} {% endif %} would output a file named @@ -716,9 +717,12 @@ with the following content if the user is "harvey": config=work-Linux -and the following otherwise: +and the following otherwise (if +.I whatever.extra +contains admin=false): config=dev-whatever + admin=false An equivalent Jinja template named .I whatever##template.j2 @@ -728,6 +732,7 @@ would look like: config={{YADM_CLASS}}-{{YADM_OS}} {% else -%} config=dev-whatever + {% include 'whatever.extra' %} {% endif -%} An equivalent ESH templated named @@ -738,6 +743,7 @@ would look like: config=<%= $YADM_CLASS %>-<%= $YADM_OS %> <% else -%> config=dev-whatever + <%+ whatever.extra %> <% fi -%> .SH ENCRYPTION