From ce53fb4339a2e04af40ac7876673b3b9b1a0c828 Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Wed, 19 Sep 2018 09:40:17 +0200 Subject: [PATCH] add ability to compare with patterns in dotfile (#57) --- README.md | 12 ++++++++++ dotdrop/comparator.py | 39 +++++++++++++++++++++---------- dotdrop/config.py | 8 ++++--- dotdrop/dotdrop.py | 6 ++--- dotdrop/dotfile.py | 5 +++- tests-ng/compare-ignore.sh | 48 ++++++++++++++++++++++++++++++++++---- tests.sh | 6 ++++- 7 files changed, 99 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index a1b83d1..b14836f 100644 --- a/README.md +++ b/README.md @@ -268,6 +268,15 @@ $ dotdrop compare The diffing is done by `diff` in the backend, one can provide specific options to diff using the `-o` switch. +It is possible to add patterns to ignore when using `compare` for example +when a directory is managed by dotdrop and might contain temporary files +that don't need to appear in the output of compare. + +Either use the command line switch `-i --ignore` or add an entry in the dotfile +directly in the `cmpignore` entry (see [Config](#config)). +The pattern follows Unix shell-style wildcards like for example `*/path/file`. +Make sure to quote those when using wildcards in the config file. + It is also possible to install all dotfiles for a specific profile in a temporary directory in order to manually compare them with the local version by using `install` and the `-t` switch. @@ -553,6 +562,7 @@ the following entries: * **dotfiles** entry: a list of dotfiles * When `link` is true, dotdrop will create a symlink instead of copying (default *false*). + * `cmpignore` contains a list of pattern to ignore when comparing (enclose in quotes when using wildcards). * `actions` contains a list of action keys that need to be defined in the **actions** entry below. * `trans` contains a list of transformation keys that need to be defined in the **trans** entry below. @@ -562,6 +572,8 @@ the following entries: src: # Optional link: + cmpignore: + - "" actions: - trans: diff --git a/dotdrop/comparator.py b/dotdrop/comparator.py index 91d6429..b516408 100644 --- a/dotdrop/comparator.py +++ b/dotdrop/comparator.py @@ -8,6 +8,7 @@ handle the comparison of dotfiles and local deployment import os import shutil import filecmp +import fnmatch # local imports from dotdrop.logger import Logger @@ -16,45 +17,47 @@ import dotdrop.utils as utils class Comparator: - def __init__(self, diffopts='', ignore=[], debug=False): + def __init__(self, diffopts='', debug=False): self.diffopts = diffopts - self.ignore = [os.path.expanduser(i) for i in ignore] self.debug = debug self.log = Logger() - def compare(self, left, right): + def compare(self, left, right, ignore=[]): """diff left (dotdrop dotfile) and right (deployed file)""" left = os.path.expanduser(left) right = os.path.expanduser(right) + if self.debug: + self.log.dbg('comparing {} and {}'.format(left, right)) + self.log.dbg('ignore pattern(s): {}'.format(ignore)) if not os.path.isdir(left): - return self._comp_file(left, right) - return self._comp_dir(left, right) + return self._comp_file(left, right, ignore) + return self._comp_dir(left, right, ignore) - def _comp_file(self, left, right): + def _comp_file(self, left, right, ignore): """compare a file""" - if left in self.ignore or right in self.ignore: + if self._ignore([left, right], ignore): if self.debug: self.log.dbg('ignoring diff {} and {}'.format(left, right)) return '' return self._diff(left, right) - def _comp_dir(self, left, right): + def _comp_dir(self, left, right, ignore): """compare a directory""" - if left in self.ignore or right in self.ignore: + if self._ignore([left, right], ignore): if self.debug: self.log.dbg('ignoring diff {} and {}'.format(left, right)) return '' if self.debug: self.log.dbg('compare {} and {}'.format(left, right)) ret = [] - comp = filecmp.dircmp(left, right, ignore=self.ignore) + comp = filecmp.dircmp(left, right) # handle files only in deployed file for i in comp.left_only: - if os.path.join(left, i) in self.ignore: + if self._ignore([os.path.join(left, i)], ignore): continue ret.append('only in left: \"{}\"\n'.format(i)) for i in comp.right_only: - if os.path.join(right, i) in self.ignore: + if self._ignore([os.path.join(right, i)], ignore): continue ret.append('only in right: \"{}\"\n'.format(i)) @@ -88,3 +91,15 @@ class Comparator: rshort = os.path.basename(right) diff = 'diff \"{}\":\n{}'.format(lshort, diff) return diff + + def _ignore(self, paths, ignore): + '''return True if any paths is ignored - not very efficient''' + if not ignore: + return False + for p in paths: + for i in ignore: + if fnmatch.fnmatch(p, i): + if self.debug: + self.log.dbg('ignore match {}'.format(p)) + return True + return False diff --git a/dotdrop/config.py b/dotdrop/config.py index 9516f38..7bce422 100644 --- a/dotdrop/config.py +++ b/dotdrop/config.py @@ -47,6 +47,7 @@ class Cfg: key_dotfiles_src = 'src' key_dotfiles_dst = 'dst' key_dotfiles_link = 'link' + key_dotfiles_cmpignore = 'cmpignore' key_dotfiles_actions = 'actions' key_dotfiles_trans = 'trans' @@ -198,10 +199,11 @@ class Cfg: msg += ' because link is True' self.log.warn(msg) trans = [] + ignores = v[self.key_dotfiles_cmpignore] if \ + self.key_dotfiles_cmpignore in v else [] self.dotfiles[k] = Dotfile(k, dst, src, - link=link, - actions=actions, - trans=trans) + link=link, actions=actions, + trans=trans, cmpignore=ignores) # assign dotfiles to each profile for k, v in self.lnk_profiles.items(): diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index f772091..e087348 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -195,8 +195,7 @@ def compare(opts, conf, tmp, focus=None, ignore=[]): inst = Installer(create=opts['create'], backup=opts['backup'], dry=opts['dry'], base=opts['dotpath'], workdir=opts['workdir'], debug=opts['debug']) - comp = Comparator(diffopts=opts['dopts'], debug=opts['debug'], - ignore=ignore) + comp = Comparator(diffopts=opts['dopts'], debug=opts['debug']) for dotfile in selected: if opts['debug']: @@ -218,7 +217,8 @@ def compare(opts, conf, tmp, focus=None, ignore=[]): if not ret: # failed to install to tmp continue - diff = comp.compare(insttmp, dotfile.dst) + ignores = list(set(ignore + dotfile.cmpignore)) + diff = comp.compare(insttmp, dotfile.dst, ignore=ignores) if tmpsrc: # clean tmp transformed dotfile if any tmpsrc = os.path.join(opts['dotpath'], tmpsrc) diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index 99fba5d..f2c9983 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -9,7 +9,8 @@ represents a dotfile in dotdrop class Dotfile: def __init__(self, key, dst, src, - actions={}, trans=[], link=False): + actions={}, trans=[], + link=False, cmpignore=[]): # key of dotfile in the config self.key = key # path where to install this dotfile @@ -22,6 +23,8 @@ class Dotfile: self.actions = actions # list of transformations self.trans = trans + # pattern to ignore when comparing + self.cmpignore = cmpignore def __str__(self): msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"' diff --git a/tests-ng/compare-ignore.sh b/tests-ng/compare-ignore.sh index 1141c82..5db6c2a 100755 --- a/tests-ng/compare-ignore.sh +++ b/tests-ng/compare-ignore.sh @@ -73,23 +73,61 @@ touch ${tmpd}/program/b touch ${tmpd}/config/b # expects diff -echo "[+] comparing normal" +echo "[+] comparing normal - 2 diffs" set +e cd ${ddpath} | ${bin} compare -c ${cfg} --verbose [ "$?" = "0" ] && exit 1 set -e # expects one diff -echo "[+] comparing with ignore" +patt="${tmpd}/config/b" +echo "[+] comparing with ignore (pattern: ${patt}) - 1 diff" set +e -cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${tmpd}/config/b +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt} [ "$?" = "0" ] && exit 1 set -e # expects no diff -echo "[+] comparing with ignore pattern" +patt="*b" +echo "[+] comparing with ignore (pattern: ${patt}) - 0 diff" set +e -cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=b +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt} +[ "$?" != "0" ] && exit 1 +set -e + +# expects one diff +patt="*/config/*b" +echo "[+] comparing with ignore (pattern: ${patt}) - 1 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${patt} +[ "$?" = "0" ] && exit 1 +set -e + +cat ${cfg} + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_config:/a \ \ \ \ cmpignore:\n\ \ \ \ - "*/config/b"' ${cfg} > ${cfg2} +cat ${cfg2} + +# expects one diff +echo "[+] comparing with ignore in dotfile - 1 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose +[ "$?" = "0" ] && exit 1 +set -e + +# adding ignore in dotfile +cfg2="${basedir}/config2.yaml" +sed '/d_config:/a \ \ \ \ cmpignore:\n\ \ \ \ - "*b"' ${cfg} > ${cfg2} +sed -i '/d_program:/a \ \ \ \ cmpignore:\n\ \ \ \ - "*b"' ${cfg2} +cat ${cfg2} + +# expects no diff +patt="*b" +echo "[+] comparing with ignore in dotfile - 0 diff" +set +e +cd ${ddpath} | ${bin} compare -c ${cfg2} --verbose [ "$?" != "0" ] && exit 1 set -e diff --git a/tests.sh b/tests.sh index a531088..c39b87f 100755 --- a/tests.sh +++ b/tests.sh @@ -8,9 +8,13 @@ set -ev pycodestyle --ignore=W605 dotdrop/ pycodestyle tests/ pycodestyle scripts/ + +# travis PYTHONPATH=dotdrop nosetests --with-coverage --cover-package=dotdrop -#PYTHONPATH=dotdrop nosetests -s --with-coverage --cover-package=dotdrop +# arch #PYTHONPATH=dotdrop python3 -m nose --with-coverage --cover-package=dotdrop +# others +#PYTHONPATH=dotdrop nosetests -s --with-coverage --cover-package=dotdrop # execute bash script tests for scr in tests-ng/*.sh; do