diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 7601e48..3dbe377 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -85,6 +85,7 @@ def cmd_install(o): return False t = Templategen(base=o.dotpath, variables=o.variables, + func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) tmpdir = None if o.install_temporary: @@ -205,6 +206,7 @@ def cmd_compare(o, tmp): return False t = Templategen(base=o.dotpath, variables=o.variables, + func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) inst = Installer(create=o.create, backup=o.backup, dry=o.dry, base=o.dotpath, diff --git a/dotdrop/settings.py b/dotdrop/settings.py index a6b3634..e375163 100644 --- a/dotdrop/settings.py +++ b/dotdrop/settings.py @@ -31,6 +31,8 @@ class Settings(DictParser): key_instignore = 'instignore' key_workdir = 'workdir' key_minversion = 'minversion' + key_func_file = 'func_file' + key_filter_file = 'filter_file' # import keys key_import_actions = 'import_actions' @@ -45,7 +47,7 @@ class Settings(DictParser): link_on_import=LinkTypes.NOLINK, longkey=False, upignore=[], cmpignore=[], instignore=[], workdir='~/.config/dotdrop', showdiff=False, - minversion=None): + minversion=None, func_file=[], filter_file=[]): self.backup = backup self.banner = banner self.create = create @@ -65,6 +67,8 @@ class Settings(DictParser): self.link_dotfile_default = LinkTypes.get(link_dotfile_default) self.link_on_import = LinkTypes.get(link_on_import) self.minversion = minversion + self.func_file = func_file + self.filter_file = filter_file def _serialize_seq(self, name, dic): """serialize attribute 'name' into 'dic'""" @@ -95,5 +99,7 @@ class Settings(DictParser): self._serialize_seq(self.key_cmpignore, dic) self._serialize_seq(self.key_upignore, dic) self._serialize_seq(self.key_instignore, dic) + self._serialize_seq(self.key_func_file, dic) + self._serialize_seq(self.key_filter_file, dic) return {self.key_yaml: dic} diff --git a/dotdrop/templategen.py b/dotdrop/templategen.py index d1db7db..ee0e3cb 100644 --- a/dotdrop/templategen.py +++ b/dotdrop/templategen.py @@ -23,10 +23,13 @@ COMMENT_END = '@@#}' class Templategen: - def __init__(self, base='.', variables={}, debug=False): + def __init__(self, base='.', variables={}, + func_file=[], filter_file=[], debug=False): """constructor @base: directory path where to search for templates @variables: dictionary of variables for templates + @func_file: file path to load functions from + @filter_file: file path to load filters from @debug: enable debug """ self.base = base.rstrip(os.sep) @@ -49,10 +52,19 @@ class Templategen: # adding header method self.env.globals['header'] = self._header # adding helper methods - self.env.globals['exists'] = jhelpers.exists - self.env.globals['exists_in_path'] = jhelpers.exists_in_path - self.env.globals['basename'] = jhelpers.basename - self.env.globals['dirname'] = jhelpers.dirname + if self.debug: + self.log.dbg('load global functions:') + self._load_funcs_to_dic(jhelpers, self.env.globals) + if func_file: + for f in func_file: + if self.debug: + self.log.dbg('load custom functions from {}'.format(f)) + self._load_path_to_dic(f, self.env.globals) + if filter_file: + for f in filter_file: + if self.debug: + self.log.dbg('load custom filters from {}'.format(f)) + self._load_path_to_dic(f, self.env.filters) if self.debug: self.log.dbg('template additional variables: {}'.format(variables)) @@ -84,6 +96,23 @@ class Templategen: """update variables""" self.env.globals.update(variables) + def _load_path_to_dic(self, path, dic): + mod = utils.get_module_from_path(path) + if not mod: + self.log.warn('cannot load module \"{}\"'.format(path)) + return + self._load_funcs_to_dic(mod, dic) + + def _load_funcs_to_dic(self, mod, dic): + """dynamically load functions from module to dic""" + if not mod or not dic: + return + funcs = utils.get_module_functions(mod) + for name, func in funcs: + if self.debug: + self.log.dbg('load function \"{}\"'.format(name)) + dic[name] = func + def _header(self, prepend=''): """add a comment usually in the header of a dotfile""" return '{}{}'.format(prepend, utils.header()) diff --git a/dotdrop/utils.py b/dotdrop/utils.py index f89c122..c4b82e2 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -11,6 +11,8 @@ import os import uuid import shlex import fnmatch +import inspect +import importlib from shutil import rmtree # local import @@ -192,3 +194,24 @@ def patch_ignores(ignores, prefix, debug=False): if debug: LOG.dbg('ignores after patching: {}'.format(new)) return new + + +def get_module_functions(mod): + """return a list of fonction from a module""" + funcs = [] + for m in inspect.getmembers(mod): + name, func = m + if not inspect.isfunction(func): + continue + funcs.append((name, func)) + return funcs + + +def get_module_from_path(path): + """get module from path""" + if not path or not os.path.exists(path): + return None + module_name = os.path.basename(path).rstrip('.py') + loader = importlib.machinery.SourceFileLoader(module_name, path) + mod = loader.load_module() + return mod diff --git a/tests-ng/filter_file.sh b/tests-ng/filter_file.sh new file mode 100755 index 0000000..8043941 --- /dev/null +++ b/tests-ng/filter_file.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2017, deadc0de6 +# +# test jinja2 filters from filter_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 "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# the dotfile source +tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +mkdir -p ${tmps}/dotfiles +# the dotfile destination +tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +#echo "dotfile destination: ${tmpd}" +filter_file=`mktemp` +filter_file2=`mktemp` + +# create the config file +cfg="${tmps}/config.yaml" + +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + filter_file: + - ${filter_file} + - ${filter_file2} +dotfiles: + f_abc: + dst: ${tmpd}/abc + src: abc +profiles: + p1: + dotfiles: + - f_abc +_EOF +#cat ${cfg} + +cat << _EOF > ${filter_file} +def filter1(arg1): + return "filtered" +def filter2(arg1, arg2=''): + return arg2 +_EOF + +cat << _EOF > ${filter_file2} +def filter3(integer): + return str(int(integer) - 10) +_EOF + +# create the dotfile +echo "this is the test dotfile" > ${tmps}/dotfiles/abc + +# test imported function +echo "{{@@ "abc" | filter1 @@}}" >> ${tmps}/dotfiles/abc +echo "{{@@ "arg1" | filter2('arg2') @@}}" >> ${tmps}/dotfiles/abc +echo "{{@@ "13" | filter3() @@}}" >> ${tmps}/dotfiles/abc + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V + +#cat ${tmpd}/abc + +grep '^filtered$' ${tmpd}/abc >/dev/null +grep '^arg2$' ${tmpd}/abc >/dev/null +grep '^3$' ${tmpd}/abc >/dev/null + +## CLEANING +rm -rf ${tmps} ${tmpd} ${filter_file} ${filter_file2} + +echo "OK" +exit 0 diff --git a/tests-ng/func_file.sh b/tests-ng/func_file.sh new file mode 100755 index 0000000..85e1eaa --- /dev/null +++ b/tests-ng/func_file.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2017, deadc0de6 +# +# test jinja2 functions from func_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 "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# this is the test +################################################################ + +# the dotfile source +tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +mkdir -p ${tmps}/dotfiles +# the dotfile destination +tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` +#echo "dotfile destination: ${tmpd}" +func_file=`mktemp` +func_file2=`mktemp` + +# create the config file +cfg="${tmps}/config.yaml" + +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + func_file: + - ${func_file} + - ${func_file2} +dotfiles: + f_abc: + dst: ${tmpd}/abc + src: abc +profiles: + p1: + dotfiles: + - f_abc +_EOF +#cat ${cfg} + +cat << _EOF > ${func_file} +def func1(something): + if something: + return True + return False +_EOF + +cat << _EOF > ${func_file2} +def func2(inp): + return not inp +_EOF + +# create the dotfile +echo "this is the test dotfile" > ${tmps}/dotfiles/abc + +# test imported function +echo "{%@@ if func1(True) @@%}" >> ${tmps}/dotfiles/abc +echo "this should exist" >> ${tmps}/dotfiles/abc +echo "{%@@ endif @@%}" >> ${tmps}/dotfiles/abc + +echo "{%@@ if not func1(False) @@%}" >> ${tmps}/dotfiles/abc +echo "this should exist too" >> ${tmps}/dotfiles/abc +echo "{%@@ endif @@%}" >> ${tmps}/dotfiles/abc + +echo "{%@@ if func2(True) @@%}" >> ${tmps}/dotfiles/abc +echo "nope" >> ${tmps}/dotfiles/abc +echo "{%@@ endif @@%}" >> ${tmps}/dotfiles/abc + +echo "{%@@ if func2(False) @@%}" >> ${tmps}/dotfiles/abc +echo "yes" >> ${tmps}/dotfiles/abc +echo "{%@@ endif @@%}" >> ${tmps}/dotfiles/abc + +# install +cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V + +#cat ${tmpd}/abc + +grep '^this should exist$' ${tmpd}/abc >/dev/null +grep '^this should exist too$' ${tmpd}/abc >/dev/null +grep '^yes$' ${tmpd}/abc >/dev/null +set +e +grep '^nope$' ${tmpd}/abc >/dev/null && exit 1 +set -e + +## CLEANING +rm -rf ${tmps} ${tmpd} ${func_file} ${func_file2} + +echo "OK" +exit 0 diff --git a/tests-ng/inst-link-default.sh b/tests-ng/inst-link-default.sh index 5ff2794..b175f98 100755 --- a/tests-ng/inst-link-default.sh +++ b/tests-ng/inst-link-default.sh @@ -159,7 +159,7 @@ cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V rm -rf ${tmpd}/abc ## CLEANING -rm -rf ${tmps} ${tmpd} ${scr} +rm -rf ${tmps} ${tmpd} echo "OK" exit 0 diff --git a/tests-ng/jhelpers.sh b/tests-ng/jhelpers.sh index ae72594..0e44adb 100755 --- a/tests-ng/jhelpers.sh +++ b/tests-ng/jhelpers.sh @@ -137,7 +137,7 @@ grep "dotfile dst filename: `basename ${tmpd}/def`" ${tmpd}/def grep "dotfile dst dirname: `dirname ${tmpd}/def`" ${tmpd}/def ## CLEANING -rm -rf ${tmps} ${tmpd} ${scr} +rm -rf ${tmps} ${tmpd} echo "OK" exit 0 diff --git a/tests-ng/recvariables.sh b/tests-ng/recvariables.sh index 47b9831..c1a4f3d 100755 --- a/tests-ng/recvariables.sh +++ b/tests-ng/recvariables.sh @@ -102,7 +102,7 @@ grep '^dvar4: var1 var2 var3' ${tmpd}/abc >/dev/null #cat ${tmpd}/abc ## CLEANING -rm -rf ${tmps} ${tmpd} ${scr} +rm -rf ${tmps} ${tmpd} echo "OK" exit 0 diff --git a/tests-ng/symlink.sh b/tests-ng/symlink.sh index 4d88ce2..829a9e7 100755 --- a/tests-ng/symlink.sh +++ b/tests-ng/symlink.sh @@ -215,7 +215,7 @@ cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V grep '^p1$' ${tmpd}/abc/file1 ## CLEANING -rm -rf ${tmps} ${tmpd} ${scr} +rm -rf ${tmps} ${tmpd} echo "OK" exit 0