From 4d0a1a798a8533d7993d6007ba7c23adbb9ef3b5 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Thu, 15 Nov 2018 23:25:42 +0100 Subject: [PATCH] adding dynamic/interpreted template variables --- README.md | 74 +++++++++++++++++++------- dotdrop/config.py | 13 ++++- dotdrop/utils.py | 7 ++- tests-ng/dynvariables.sh | 109 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 23 deletions(-) create mode 100755 tests-ng/dynvariables.sh diff --git a/README.md b/README.md index 8bc0ae1..2d673f2 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,10 @@ why [dotdrop](https://github.com/deadc0de6/dotdrop) rocks. * [Config](#config) * [Templating](#templating) + + * [Available variables](#available-variables) + * [Dotdrop header](#dotdrop-header) + * [Example](#example) * [User tricks](#user-tricks) * [People using dotdrop](#people-using-dotdrop) @@ -623,12 +627,18 @@ the following entries: : ``` -* **variables** entry (optional): a list of template variables (see [Available variables](#available-variables)) +* **variables** entry (optional): a list of template variables (see [Variables](#variables)) ``` : ``` +* **dynvariables** entry (optional): a list of interpreted variables (see [Interpreted variables](#interpreted-variables)) + +``` + : > +``` + ## All dotfiles for a profile To use all defined dotfiles for a profile, simply use @@ -673,9 +683,10 @@ Here profile *host1* contains all the dotfiles defined for *host2* plus `f_xinit ## Ignore empty template -It is possible not to deploy template file if their rendered content -is empty. Simply set the global setting `ignoreempty` to true for this -behavior for all dotfiles or specifically to one or more dotfile entries. +It is possible to avoid having an empty rendered template being +deployed by setting the `ignoreempty` entry to *true*. This can be set +globally for all dotfiles or only for specific dotfiles. +For more see the [Config](#config). # Templating @@ -695,13 +706,18 @@ Note that dotdrop uses different delimiters than ## Available variables +Following template variables are available: + * `{{@@ profile @@}}` contains the profile provided to dotdrop. * `{{@@ env['MY_VAR'] @@}}` contains environment variables (see [Environment variables](#environment-variables)). * `{{@@ header() @@}}` insert dotdrop header (see [Dotdrop header](#dotdrop-header)). +* defined variables (see [Variables](#variables)) +* interpreted variables (see [Interpreted variables](#interpreted-variables)) -Addionally to the above, variables can be added in the config file under -the `variables` entry. The variables added there are directly reachable in -any templates. +## Variables + +Variables can be added in the config file under the `variables` entry. +The variables added there are directly reachable in any templates. For example in the config file: ```yaml @@ -714,26 +730,24 @@ These can then be used in any template with {{@@ var1 @@}} ``` -## Dotdrop header +## Interpreted variables -Dotdrop is able to insert a header in the generated dotfiles. This allows -to remind anyone opening the file for editing that this file is managed by dotdrop. +It is also possible to have *dynamic* variables in the sense that their +content will be interpreted by the shell before being replaced in the templates. -Here's what it looks like: -``` -This dotfile is managed using dotdrop +For example: +```yaml +dynvariables: + dvar1: head -1 /proc/meminfo + dvar2: "echo 'this is some test' | rev | tr ' ' ','" + dvar3: /tmp/my_shell_script.sh ``` -The header can be automatically added using jinja2 directive: +These can be used as any variables in the templates ``` -{{@@ header() @@}} +{{@@ dvar1 @@}} ``` -Properly commenting the header in templates is the responsability of the user -as jinja2 has no way of knowing what is the proper char(s) used for comments. -Either prepend the directive with the commenting char(s) used in the dotfile (for example `# {{@@ header() @@}}`) -or provide it as an argument `{{@@ header('# ') @@}}`. The result is equivalent. - ## Environment variables It's possible to access environment variables inside the templates. @@ -766,6 +780,26 @@ alias dotdrop='eval $(grep -v "^#" ~/dotfiles/.env) /usr/bin/dotdrop --cfg=~/dot The above aliases load all the variables from `~/dotfiles/.env` (while omitting lines starting with `#`) before calling dotdrop. +## Dotdrop header + +Dotdrop is able to insert a header in the generated dotfiles. This allows +to remind anyone opening the file for editing that this file is managed by dotdrop. + +Here's what it looks like: +``` +This dotfile is managed using dotdrop +``` + +The header can be automatically added using jinja2 directive: +``` +{{@@ header() @@}} +``` + +Properly commenting the header in templates is the responsability of the user +as jinja2 has no way of knowing what is the proper char(s) used for comments. +Either prepend the directive with the commenting char(s) used in the dotfile (for example `# {{@@ header() @@}}`) +or provide it as an argument `{{@@ header('# ') @@}}`. The result is equivalent. + ## Debug template To debug the result of a template, one can install the dotfiles to a temporary diff --git a/dotdrop/config.py b/dotdrop/config.py index f5e1c82..3c759b5 100644 --- a/dotdrop/config.py +++ b/dotdrop/config.py @@ -13,6 +13,7 @@ import shlex from dotdrop.dotfile import Dotfile from dotdrop.logger import Logger from dotdrop.action import Action, Transform +from dotdrop.utils import * TILD = '~' @@ -44,6 +45,8 @@ class Cfg: # template variables key_variables = 'variables' + # shell variable + key_dynvariables = 'dynvariables' # dotfiles keys key_dotfiles = 'dotfiles' @@ -521,9 +524,15 @@ class Cfg: return self.lnk_settings.copy() def get_variables(self): + variables = {} if self.key_variables in self.content: - return self.content[self.key_variables] - return {} + variables.update(self.content[self.key_variables]) + if self.key_dynvariables in self.content: + # interpret dynamic variables + dynvars = self.content[self.key_dynvariables] + for key, cmd in dynvars.items(): + variables[key] = shell(cmd) + return variables def dump(self): """return a dump of the config""" diff --git a/dotdrop/utils.py b/dotdrop/utils.py index cbbce97..6e1c606 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -19,7 +19,7 @@ LOG = Logger() def run(cmd, raw=True, debug=False, checkerr=False): - """run a command in the shell (expects a list)""" + """run a command (expects a list)""" if debug: LOG.dbg('exec: {}'.format(' '.join(cmd))) p = subprocess.Popen(cmd, shell=False, @@ -38,6 +38,11 @@ def run(cmd, raw=True, debug=False, checkerr=False): return ret == 0, lines +def shell(cmd): + """run a command in the shell (expects a string)""" + return subprocess.getoutput(cmd) + + def diff(src, dst, raw=True, opts='', debug=False): """call unix diff to compare two files""" cmd = 'diff -r {} \"{}\" \"{}\"'.format(opts, src, dst) diff --git a/tests-ng/dynvariables.sh b/tests-ng/dynvariables.sh new file mode 100755 index 0000000..94ac58e --- /dev/null +++ b/tests-ng/dynvariables.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2017, deadc0de6 +# +# test dynamic variables from yaml file +# returns 1 in case of error +# + +# exit on first error +set -e + +# all this crap to get current path +rl="readlink -f" +if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +#hash dotdrop >/dev/null 2>&1 +#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1 + +#echo "called with ${1}" + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "\e[96m\e[1m==> RUNNING $(basename $BASH_SOURCE) <==\e[0m" + +################################################################ +# this is the test +################################################################ + +# the dotfile source +tmps=`mktemp -d` +mkdir -p ${tmps}/dotfiles +# the dotfile destination +tmpd=`mktemp -d` +#echo "dotfile destination: ${tmpd}" + +# create a shell script +export TESTENV="this is my testenv" +scr=`mktemp` +chmod +x ${scr} +echo -e "#!/bin/bash\necho $TESTENV\n" >> ${scr} + +# create the config file +cfg="${tmps}/config.yaml" + +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles +variables: + var1: "this is some test" +dynvariables: + dvar1: head -1 /proc/meminfo + dvar2: "echo 'this is some test' | rev | tr ' ' ','" + dvar3: ${scr} +dotfiles: + f_abc: + dst: ${tmpd}/abc + src: abc +profiles: + p1: + dotfiles: + - f_abc +_EOF +cat ${cfg} + +# create the dotfile +echo "{{@@ var1 @@}}" > ${tmps}/dotfiles/abc +echo "{{@@ dvar1 @@}}" >> ${tmps}/dotfiles/abc +echo "{{@@ dvar2 @@}}" >> ${tmps}/dotfiles/abc +echo "{{@@ dvar3 @@}}" >> ${tmps}/dotfiles/abc +echo "test" >> ${tmps}/dotfiles/abc + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V + +#cat ${tmpd}/abc + +grep '^this is some test' ${tmpd}/abc >/dev/null +grep "^MemTotal" ${tmpd}/abc >/dev/null +grep '^tset,emos,si,siht' ${tmpd}/abc >/dev/null +grep "^${TESTENV}" ${tmpd}/abc > /dev/null + +#cat ${tmpd}/abc + +## CLEANING +rm -rf ${tmps} ${tmpd} ${scr} + +echo "OK" +exit 0