From 02cb9708e1d3c5c3d50b187b3aec95cade05c760 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Tue, 11 Apr 2023 21:34:32 +0200 Subject: [PATCH] support nested variables --- dotdrop/cfg_yaml.py | 19 +++++++---- dotdrop/dotdrop.py | 19 +++++------ dotdrop/templategen.py | 66 +++++++++++++++++++++++++++++++++++--- dotdrop/updater.py | 5 +-- tests-ng/variables-tree.sh | 18 ++++------- 5 files changed, 94 insertions(+), 33 deletions(-) diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index ade9add..848c772 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -1398,11 +1398,11 @@ class CfgYaml: template an item using the templategen will raise an exception if template failed and exc_if_fail """ - if not Templategen.var_is_template(item): + if not Templategen.string_is_template(item): return item try: val = item - while Templategen.var_is_template(val): + while Templategen.string_is_template(val): val = self._tmpl.generate_string(val) except UndefinedException as exc: if exc_if_fail: @@ -1502,14 +1502,21 @@ class CfgYaml: templ = Templategen(variables=var, func_file=func_files, filter_file=filter_files) + newvars = variables.copy() for k in variables.keys(): val = variables[k] while Templategen.var_is_template(val): - val = templ.generate_string(val) - variables[k] = val - templ.update_variables(variables) - if variables is self.variables: + val = templ.generate_string_or_dict(val) + if isinstance(val, dict): + for sub in val: + subkey = f'{k}.{sub}' + newvars[subkey] = val[sub] + else: + newvars[k] = val + templ.update_variables(newvars) + if newvars is self.variables: self._redefine_templater() + variables = newvars def _get_profile_included_vars(self): """ diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index d1c2b44..939c201 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -141,9 +141,10 @@ def _dotfile_compare(opts, dotfile, tmp): ignores = patch_ignores(ignores, dotfile.dst, debug=opts.debug) insttmp = None - if dotfile.template and Templategen.is_template(src, - ignore=ignores, - debug=opts.debug): + if dotfile.template and \ + Templategen.path_is_template(src, + ignore=ignores, + debug=opts.debug): # install dotfile to temporary dir for compare ret, err, insttmp = inst.install_to_temp(templ, tmp, src, dotfile.dst, is_template=True, @@ -217,7 +218,7 @@ def _dotfile_install(opts, dotfile, tmpdir=None): ignores = list(set(opts.install_ignore + dotfile.instignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=opts.debug) - is_template = dotfile.template and Templategen.is_template( + is_template = dotfile.template and Templategen.path_is_template( dotfile.src, ignore=ignores, ) @@ -243,7 +244,7 @@ def _dotfile_install(opts, dotfile, tmpdir=None): return False, dotfile.key, None src = tmp # make sure to re-evaluate if is template - is_template = dotfile.template and Templategen.is_template( + is_template = dotfile.template and Templategen.path_is_template( src, ignore=ignores, ) @@ -389,7 +390,7 @@ def _workdir_enum(opts): if dotfile.link == LinkTypes.NOLINK: # ignore not link files continue - if not Templategen.is_template(src): + if not Templategen.path_is_template(src): # ignore not template continue newpath = pivot_path(dotfile.dst, opts.workdir, @@ -582,7 +583,7 @@ def cmd_files(opts): for dotfile in opts.dotfiles: if opts.files_templateonly: src = os.path.join(opts.dotpath, dotfile.src) - if not Templategen.is_template(src): + if not Templategen.path_is_template(src): continue if opts.files_grepable: fmt = f'{dotfile.key},' @@ -746,7 +747,7 @@ def _detail(dotpath, dotfile): path = os.path.join(dotpath, os.path.expanduser(dotfile.src)) if not os.path.isdir(path): template = 'no' - if dotfile.template and Templategen.is_template(path): + if dotfile.template and Templategen.path_is_template(path): template = 'yes' LOG.sub(f'{path} (template:{template})') else: @@ -754,7 +755,7 @@ def _detail(dotpath, dotfile): for file in files: fpath = os.path.join(root, file) template = 'no' - if dotfile.template and Templategen.is_template(fpath): + if dotfile.template and Templategen.path_is_template(fpath): template = 'yes' LOG.sub(f'{fpath} (template:{template})') diff --git a/dotdrop/templategen.py b/dotdrop/templategen.py index a3e9865..8e75f9d 100644 --- a/dotdrop/templategen.py +++ b/dotdrop/templategen.py @@ -110,6 +110,38 @@ class Templategen: err = f'undefined variable: {exc.message}' raise UndefinedException(err) from exc + def generate_dict(self, dic): + """ + template each entry of the dict where only + the value of each entry is templated (recursively) + may raise a UndefinedException + in case a variable is undefined + """ + if not dic: + return dic + for key in dic: + value = dic[key] + if isinstance(value, str): + dic[key] = self.generate_string(value) + continue + if isinstance(value, dict): + dic[key] = self.generate_dict(value) + continue + continue + return dic + + def generate_string_or_dict(self, content): + """ + render template from string or dict + may raise a UndefinedException + in case a variable is undefined + """ + if isinstance(content, str): + return self.generate_string(content) + if isinstance(content, dict): + return self.generate_dict(content) + raise UndefinedError(f'could not template {content}') + def add_tmp_vars(self, newvars=None): """add vars to the globals, make sure to call restore_vars""" saved_variables = self.variables.copy() @@ -217,7 +249,7 @@ class Templategen: return data.decode('utf-8', 'replace') @staticmethod - def is_template(path, ignore=None, debug=False): + def path_is_template(path, ignore=None, debug=False): """recursively check if any file is a template within path""" if debug: LOG.dbg(f'is template: {path}', force=True) @@ -240,19 +272,45 @@ class Templategen: fpath = os.path.join(path, entry) if not os.path.isfile(fpath): # recursively explore directory - if Templategen.is_template(fpath, ignore=ignore, debug=debug): + if Templategen.path_is_template(fpath, + ignore=ignore, + debug=debug): return True else: # check if file is a template - if Templategen._is_template(fpath, ignore=ignore, debug=debug): + if Templategen._is_template(fpath, + ignore=ignore, + debug=debug): return True return False @staticmethod - def var_is_template(string): + def string_is_template(string): """check if variable contains template(s)""" return VAR_START in str(string) + @staticmethod + def dict_is_template(dic): + """check if dict contains template(s)""" + for key in dic: + value = dic[key] + if isinstance(value, str): + isit = Templategen.string_is_template(value) + if isit: + return True + elif isinstance(value, dict): + return Templategen.dict_is_template(value) + return False + + @staticmethod + def var_is_template(something): + """check if string or dict is template""" + if isinstance(something, str): + return Templategen.string_is_template(something) + if isinstance(something, dict): + return Templategen.dict_is_template(something) + return False + @staticmethod def _is_template(path, ignore, debug=False): """test if file pointed by path is a template""" diff --git a/dotdrop/updater.py b/dotdrop/updater.py index 97e5a77..f5ccaf5 100644 --- a/dotdrop/updater.py +++ b/dotdrop/updater.py @@ -166,8 +166,9 @@ class Updater: return tmp def _is_template(self, path): - if not Templategen.is_template(path, ignore=self.ignores, - debug=self.debug): + if not Templategen.path_is_template(path, + ignore=self.ignores, + debug=self.debug): self.log.dbg(f'{path} is NO template') return False self.log.warn(f'{path} uses template, update manually') diff --git a/tests-ng/variables-tree.sh b/tests-ng/variables-tree.sh index 465ba51..8269fde 100755 --- a/tests-ng/variables-tree.sh +++ b/tests-ng/variables-tree.sh @@ -8,7 +8,7 @@ ## start-cookie set -e -cur=$(cd "$(dirname "${BASH_SOURCE:-$0}")" && pwd) +cur=$(cd "$(dirname "${0}")" && pwd) ddpath="${cur}/../" export PYTHONPATH="${ddpath}:${PYTHONPATH}" altbin="python3 -m dotdrop.dotdrop" @@ -61,23 +61,17 @@ _EOF #cat ${cfg} # create the dotfile -echo "wow1: {{@@ wow1 @@}}" > "${tmps}"/dotfiles/abc -echo "wow2: {{@@ wow2 @@}}" >> "${tmps}"/dotfiles/abc +echo "wow1={{@@ wow1 @@}}" > "${tmps}"/dotfiles/abc +echo "wow2={{@@ z2.wow2 @@}}" >> "${tmps}"/dotfiles/abc # install cd "${ddpath}" | ${bin} install -f -c "${cfg}" -p p1 --verbose cat "${tmpd}"/abc -#[ ! -e "${tmpd}"/abc ] && echo "abc not installed" && exit 1 -#grep '^this is some test' "${tmpd}"/abc >/dev/null -#grep '^12' "${tmpd}"/abc >/dev/null -#grep '^another test' "${tmpd}"/abc >/dev/null -# -#[ ! -e "${tmpd}"/def ] && echo "def not installed" && exit 1 -#grep '^test_def' "${tmpd}"/def >/dev/null - -#cat ${tmpd}/abc +[ ! -e "${tmpd}"/abc ] && echo "abc not installed" && exit 1 +grep '^wow1=hello1' "${tmpd}"/abc >/dev/null +grep '^wow2=hello2' "${tmpd}"/abc >/dev/null echo "OK" exit 0