1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-10 19:39:15 +00:00

Merge upstream

This commit is contained in:
Marcel Robitaille
2019-01-20 16:16:12 -04:00
12 changed files with 407 additions and 47 deletions

View File

@@ -212,7 +212,7 @@ 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 Either use the command line switch `-i --ignore` or add an entry in the dotfile
directly in the `cmpignore` entry (see [Config](#config)). directly in the `cmpignore` entry (see [Config](#config)).
The pattern follows Unix shell-style wildcards like for example `*/path/file`. The ignore pattern must follow Unix shell-style wildcards like for example `*/path/file`.
Make sure to quote those when using wildcards in the config 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 It is also possible to install all dotfiles for a specific profile
@@ -474,6 +474,21 @@ $ dotdrop update ~/.vimrc
$ dotdrop update --key f_vimrc $ dotdrop update --key f_vimrc
``` ```
It is possible to ignore files to update using unix pattern by providing those
either through the switch `-i --ignore` or as part of the dotfile under the
key `upignore` (see [Config](#config)).
The ignore pattern must follow Unix shell-style wildcards like for example `*/path/file`.
Make sure to quote those when using wildcards in the config file.
```yaml
dotfiles:
d_vim
dst: ~/.vim
src: vim
upignore:
- "*/undo-dir"
- "*/plugged"
```
There are two cases when updating a dotfile: There are two cases when updating a dotfile:
**The dotfile doesn't use [templating](#template)** **The dotfile doesn't use [templating](#template)**
@@ -592,6 +607,7 @@ the following entries:
* `src`: dotfile path within the `dotpath` (can use `variables` and `dynvariables`, make sure to quote). * `src`: dotfile path within the `dotpath` (can use `variables` and `dynvariables`, make sure to quote).
* `link`: if true dotdrop will create a symlink instead of copying (default *false*). * `link`: if true dotdrop will create a symlink instead of copying (default *false*).
* `cmpignore`: list of pattern to ignore when comparing (enclose in quotes when using wildcards). * `cmpignore`: list of pattern to ignore when comparing (enclose in quotes when using wildcards).
* `upignore`: list of pattern to ignore when updating (enclose in quotes when using wildcards).
* `actions`: list of action keys that need to be defined in the **actions** entry below. * `actions`: list of action keys that need to be defined in the **actions** entry below.
* `trans`: transformation key to apply when installing this dotfile (must be defined in the **trans** entry below). * `trans`: transformation key to apply when installing this dotfile (must be defined in the **trans** entry below).
* `trans_write`: transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below). * `trans_write`: transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below).
@@ -606,6 +622,8 @@ the following entries:
ignoreempty: <true|false> ignoreempty: <true|false>
cmpignore: cmpignore:
- "<ignore-pattern>" - "<ignore-pattern>"
upignore:
- "<ignore-pattern>"
actions: actions:
- <action-key> - <action-key>
trans: <transformation-key> trans: <transformation-key>
@@ -616,6 +634,8 @@ the following entries:
need to be managed need to be managed
* `dotfiles`: the dotfiles associated to this profile * `dotfiles`: the dotfiles associated to this profile
* `include`: include all dotfiles from another profile (optional) * `include`: include all dotfiles from another profile (optional)
* `variables`: profile specific variables (see [Variables](#variables))
* `dynvariables`: profile specific interpreted variables (see [Interpreted variables](#interpreted-variables))
```yaml ```yaml
<some-name-usually-the-hostname>: <some-name-usually-the-hostname>:
@@ -627,6 +647,10 @@ the following entries:
include: include:
- <some-other-profile> - <some-other-profile>
- ... - ...
variables:
<name>: <value>
dynvariables:
<name>: <value>
``` ```
* **actions** entry (optional): a list of action (see [Use actions](#use-actions)) * **actions** entry (optional): a list of action (see [Use actions](#use-actions))
@@ -743,6 +767,7 @@ For example in the config file:
```yaml ```yaml
variables: variables:
var1: some variable content var1: some variable content
var2: some other content
``` ```
These can then be used in any template with These can then be used in any template with
@@ -750,6 +775,26 @@ These can then be used in any template with
{{@@ var1 @@}} {{@@ var1 @@}}
``` ```
Profile variables will take precedence over globally defined variables what
means that you could do something like this:
```yaml
variables:
git_email: home@email.com
dotfiles:
f_gitconfig:
dst: ~/.gitconfig
src: gitconfig
profiles:
work:
dotfiles:
- f_gitconfig
variables:
git_email: work@email.com
private:
dotfiles:
- f_gitconfig
```
## Interpreted variables ## Interpreted variables
It is also possible to have *dynamic* variables in the sense that their It is also possible to have *dynamic* variables in the sense that their
@@ -768,6 +813,9 @@ These can be used as any variables in the templates
{{@@ dvar1 @@}} {{@@ dvar1 @@}}
``` ```
As for variables (see [Variables](#variables)) profile dynvariables will take
precedence over globally defined dynvariables.
## Environment variables ## Environment variables
It's possible to access environment variables inside the templates. It's possible to access environment variables inside the templates.

View File

@@ -7,7 +7,6 @@ handle the comparison of dotfiles and local deployment
import os import os
import filecmp import filecmp
import fnmatch
# local imports # local imports
from dotdrop.logger import Logger from dotdrop.logger import Logger
@@ -34,7 +33,7 @@ class Comparator:
def _comp_file(self, left, right, ignore): def _comp_file(self, left, right, ignore):
"""compare a file""" """compare a file"""
if self._ignore([left, right], ignore): if utils.must_ignore([left, right], ignore, debug=self.debug):
if self.debug: if self.debug:
self.log.dbg('ignoring diff {} and {}'.format(left, right)) self.log.dbg('ignoring diff {} and {}'.format(left, right))
return '' return ''
@@ -44,7 +43,7 @@ class Comparator:
"""compare a directory""" """compare a directory"""
if not os.path.exists(right): if not os.path.exists(right):
return '' return ''
if self._ignore([left, right], ignore): if utils.must_ignore([left, right], ignore, debug=self.debug):
if self.debug: if self.debug:
self.log.dbg('ignoring diff {} and {}'.format(left, right)) self.log.dbg('ignoring diff {} and {}'.format(left, right))
return '' return ''
@@ -54,13 +53,15 @@ class Comparator:
comp = filecmp.dircmp(left, right) comp = filecmp.dircmp(left, right)
# handle files only in deployed file # handle files only in deployed file
for i in comp.left_only: for i in comp.left_only:
if self._ignore([os.path.join(left, i)], ignore): if utils.must_ignore([os.path.join(left, i)],
ignore, debug=self.debug):
continue continue
ret.append('only in left: \"{}\"\n'.format(i)) ret.append('=> \"{}\" does not exist on local\n'.format(i))
for i in comp.right_only: for i in comp.right_only:
if self._ignore([os.path.join(right, i)], ignore): if utils.must_ignore([os.path.join(right, i)],
ignore, debug=self.debug):
continue continue
ret.append('only in right: \"{}\"\n'.format(i)) ret.append('=> \"{}\" does not exist in dotdrop\n'.format(i))
# same left and right but different type # same left and right but different type
funny = comp.common_funny funny = comp.common_funny
@@ -90,17 +91,5 @@ class Comparator:
if header: if header:
lshort = os.path.basename(left) lshort = os.path.basename(left)
rshort = os.path.basename(right) rshort = os.path.basename(right)
diff = 'diff \"{}\":\n{}'.format(lshort, diff) diff = '=> diff \"{}\":\n{}'.format(lshort, diff)
return 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

View File

@@ -45,7 +45,7 @@ class Cfg:
# template variables # template variables
key_variables = 'variables' key_variables = 'variables'
# shell variable # shell variables
key_dynvariables = 'dynvariables' key_dynvariables = 'dynvariables'
# dotfiles keys # dotfiles keys
@@ -58,6 +58,7 @@ class Cfg:
key_dotfiles_actions = 'actions' key_dotfiles_actions = 'actions'
key_dotfiles_trans_r = 'trans' key_dotfiles_trans_r = 'trans'
key_dotfiles_trans_w = 'trans_write' key_dotfiles_trans_w = 'trans_write'
key_dotfiles_upignore = 'upignore'
# profiles keys # profiles keys
key_profiles = 'profiles' key_profiles = 'profiles'
@@ -112,13 +113,14 @@ class Cfg:
# represents all dotfiles per profile by profile key # represents all dotfiles per profile by profile key
# NOT linked inside the yaml dict (self.content) # NOT linked inside the yaml dict (self.content)
self.prodots = {} self.prodots = {}
if not self._load_file(): if not self._load_file():
raise ValueError('config is not valid') raise ValueError('config is not valid')
def eval_dotfiles(self, profile, debug=False): def eval_dotfiles(self, profile, debug=False):
"""resolve dotfiles src/dst templates""" """resolve dotfiles src/dst templating"""
t = Templategen(profile=profile, t = Templategen(profile=profile,
variables=self.get_variables(), variables=self.get_variables(profile),
debug=debug) debug=debug)
for d in self.get_dotfiles(profile): for d in self.get_dotfiles(profile):
d.src = t.generate_string(d.src) d.src = t.generate_string(d.src)
@@ -275,15 +277,20 @@ class Cfg:
trans_r = None trans_r = None
trans_w = None trans_w = None
# parse ignore pattern # parse cmpignore pattern
ignores = v[self.key_dotfiles_cmpignore] if \ cmpignores = v[self.key_dotfiles_cmpignore] if \
self.key_dotfiles_cmpignore in v else [] self.key_dotfiles_cmpignore in v else []
# parse upignore pattern
upignores = v[self.key_dotfiles_upignore] if \
self.key_dotfiles_upignore in v else []
# create new dotfile # create new dotfile
self.dotfiles[k] = Dotfile(k, dst, src, self.dotfiles[k] = Dotfile(k, dst, src,
link=link, actions=actions, link=link, actions=actions,
trans_r=trans_r, trans_w=trans_w, trans_r=trans_r, trans_w=trans_w,
cmpignore=ignores, noempty=noempty) cmpignore=cmpignores, noempty=noempty,
upignore=upignores)
# assign dotfiles to each profile # assign dotfiles to each profile
for k, v in self.lnk_profiles.items(): for k, v in self.lnk_profiles.items():
@@ -604,15 +611,35 @@ class Cfg:
"""return all defined settings""" """return all defined settings"""
return self.lnk_settings.copy() return self.lnk_settings.copy()
def get_variables(self): def get_variables(self, profile):
"""return the variables for this profile"""
variables = {} variables = {}
# global variables
if self.key_variables in self.content: if self.key_variables in self.content:
variables.update(self.content[self.key_variables]) variables.update(self.content[self.key_variables])
# global dynvariables
if self.key_dynvariables in self.content: if self.key_dynvariables in self.content:
# interpret dynamic variables # interpret dynamic variables
dynvars = self.content[self.key_dynvariables] dynvars = self.content[self.key_dynvariables]
for key, cmd in dynvars.items(): for k, v in dynvars.items():
variables[key] = shell(cmd) variables[k] = shell(v)
if profile not in self.lnk_profiles:
return variables
# profile variables
var = self.lnk_profiles[profile]
if self.key_variables in var.keys():
for k, v in var[self.key_variables].items():
variables[k] = v
# profile dynvariables
if self.key_dynvariables in var.keys():
for k, v in var[self.key_dynvariables].items():
variables[k] = shell(v)
return variables return variables
def dump(self): def dump(self):

View File

@@ -44,7 +44,8 @@ Usage:
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>... dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>] dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [-C <file>...] [-i <pattern>...] [-o <opts>] [-C <file>...] [-i <pattern>...]
dotdrop update [-fdVbk] [-c <path>] [-p <profile>] [<path>...] dotdrop update [-fdVbk] [-c <path>] [-p <profile>]
[-i <pattern>...] [<path>...]
dotdrop listfiles [-VTb] [-c <path>] [-p <profile>] dotdrop listfiles [-VTb] [-c <path>] [-p <profile>]
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...] dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
dotdrop list [-Vb] [-c <path>] dotdrop list [-Vb] [-c <path>]
@@ -55,13 +56,13 @@ Options:
-p --profile=<profile> Specify the profile to use [default: {}]. -p --profile=<profile> Specify the profile to use [default: {}].
-c --cfg=<path> Path to the config [default: config.yaml]. -c --cfg=<path> Path to the config [default: config.yaml].
-C --file=<path> Path of dotfile to compare. -C --file=<path> Path of dotfile to compare.
-i --ignore=<pattern> Pattern to ignore when diffing. -i --ignore=<pattern> Pattern to ignore.
-o --dopts=<opts> Diff options [default: ]. -o --dopts=<opts> Diff options [default: ].
-n --nodiff Do not diff when installing. -n --nodiff Do not diff when installing.
-t --temp Install to a temporary directory for review. -t --temp Install to a temporary directory for review.
-T --template Only template dotfiles. -T --template Only template dotfiles.
-D --showdiff Show a diff before overwriting. -D --showdiff Show a diff before overwriting.
-l --link Import and link. -l --inv-link Invert the value of "link_by_default" when importing.
-f --force Do not warn if exists. -f --force Do not warn if exists.
-k --key Treat <path> as a dotfile key. -k --key Treat <path> as a dotfile key.
-V --verbose Be verbose. -V --verbose Be verbose.
@@ -170,7 +171,10 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
LOG.dbg('comparing {}'.format(dotfile)) LOG.dbg('comparing {}'.format(dotfile))
src = dotfile.src src = dotfile.src
if not os.path.lexists(os.path.expanduser(dotfile.dst)): if not os.path.lexists(os.path.expanduser(dotfile.dst)):
LOG.emph('\"{}\" does not exist on local\n'.format(dotfile.dst)) line = '=> compare {}: \"{}\" does not exist on local'
LOG.log(line.format(dotfile.key, dotfile.dst))
same = False
continue
tmpsrc = None tmpsrc = None
if dotfile.trans_r: if dotfile.trans_r:
@@ -178,12 +182,14 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
tmpsrc = apply_trans(opts, dotfile) tmpsrc = apply_trans(opts, dotfile)
if not tmpsrc: if not tmpsrc:
# could not apply trans # could not apply trans
same = False
continue continue
src = tmpsrc src = tmpsrc
# install dotfile to temporary dir # install dotfile to temporary dir
ret, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst) ret, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst)
if not ret: if not ret:
# failed to install to tmp # failed to install to tmp
same = False
continue continue
ignores = list(set(ignore + dotfile.cmpignore)) ignores = list(set(ignore + dotfile.cmpignore))
diff = comp.compare(insttmp, dotfile.dst, ignore=ignores) diff = comp.compare(insttmp, dotfile.dst, ignore=ignores)
@@ -194,23 +200,24 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
remove(tmpsrc) remove(tmpsrc)
if diff == '': if diff == '':
if opts['debug']: if opts['debug']:
LOG.dbg('diffing \"{}\" VS \"{}\"'.format(dotfile.key, line = '=> compare {}: diffing with \"{}\"'
dotfile.dst)) LOG.dbg(line.format(dotfile.key, dotfile.dst))
LOG.dbg('same file') LOG.dbg('same file')
else: else:
LOG.log('diffing \"{}\" VS \"{}\"'.format(dotfile.key, line = '=> compare {}: diffing with \"{}\"'
dotfile.dst)) LOG.log(line.format(dotfile.key, dotfile.dst))
LOG.emph(diff) LOG.emph(diff)
same = False same = False
return same return same
def cmd_update(opts, conf, paths, iskey=False): def cmd_update(opts, conf, paths, iskey=False, ignore=[]):
"""update the dotfile(s) from path(s) or key(s)""" """update the dotfile(s) from path(s) or key(s)"""
ret = True ret = True
updater = Updater(conf, opts['dotpath'], opts['dry'], updater = Updater(conf, opts['dotpath'], opts['dry'],
opts['safe'], iskey=iskey, debug=opts['debug']) opts['safe'], iskey=iskey,
debug=opts['debug'], ignore=[])
if not iskey: if not iskey:
# update paths # update paths
if opts['debug']: if opts['debug']:
@@ -434,9 +441,9 @@ def main():
opts['profile'] = args['--profile'] opts['profile'] = args['--profile']
opts['safe'] = not args['--force'] opts['safe'] = not args['--force']
opts['installdiff'] = not args['--nodiff'] opts['installdiff'] = not args['--nodiff']
opts['link'] = args['--link'] opts['link'] = args['--inv-link']
opts['debug'] = args['--verbose'] opts['debug'] = args['--verbose']
opts['variables'] = conf.get_variables() opts['variables'] = conf.get_variables(opts['profile'])
opts['showdiff'] = opts['showdiff'] or args['--showdiff'] opts['showdiff'] = opts['showdiff'] or args['--showdiff']
if opts['debug']: if opts['debug']:
@@ -495,7 +502,8 @@ def main():
if opts['debug']: if opts['debug']:
LOG.dbg('running cmd: update') LOG.dbg('running cmd: update')
iskey = args['--key'] iskey = args['--key']
ret = cmd_update(opts, conf, args['<path>'], iskey=iskey) ret = cmd_update(opts, conf, args['<path>'], iskey=iskey,
ignore=args['--ignore'])
elif args['detail']: elif args['detail']:
# detail files # detail files

View File

@@ -12,7 +12,8 @@ class Dotfile:
def __init__(self, key, dst, src, def __init__(self, key, dst, src,
actions={}, trans_r=None, trans_w=None, actions={}, trans_r=None, trans_w=None,
link=LinkTypes.NOLINK, cmpignore=[], noempty=False): link=LinkTypes.NOLINK, cmpignore=[], noempty=False,
upignore=[]):
# key of dotfile in the config # key of dotfile in the config
self.key = key self.key = key
# path where to install this dotfile # path where to install this dotfile
@@ -31,6 +32,8 @@ class Dotfile:
self.cmpignore = cmpignore self.cmpignore = cmpignore
# do not deploy empty file # do not deploy empty file
self.noempty = noempty self.noempty = noempty
# pattern to ignore when updating
self.upignore = upignore
def __str__(self): def __str__(self):
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"' msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"'

View File

@@ -70,7 +70,7 @@ class Templategen:
filetype = filetype.strip() filetype = filetype.strip()
if self.debug: if self.debug:
self.log.dbg('\"{}\" filetype: {}'.format(src, filetype)) self.log.dbg('\"{}\" filetype: {}'.format(src, filetype))
istext = 'text' in filetype istext = 'text' in filetype or 'empty' in filetype
if self.debug: if self.debug:
self.log.dbg('\"{}\" is text: {}'.format(src, istext)) self.log.dbg('\"{}\" is text: {}'.format(src, istext))
if not istext: if not istext:

View File

@@ -21,13 +21,14 @@ TILD = '~'
class Updater: class Updater:
def __init__(self, conf, dotpath, dry, safe, def __init__(self, conf, dotpath, dry, safe,
iskey=False, debug=False): iskey=False, debug=False, ignore=[]):
self.conf = conf self.conf = conf
self.dotpath = dotpath self.dotpath = dotpath
self.dry = dry self.dry = dry
self.safe = safe self.safe = safe
self.iskey = iskey self.iskey = iskey
self.debug = debug self.debug = debug
self.ignore = ignore
self.log = Logger() self.log = Logger()
def update_path(self, path, profile): def update_path(self, path, profile):
@@ -58,9 +59,16 @@ class Updater:
"""update dotfile from file pointed by path""" """update dotfile from file pointed by path"""
ret = False ret = False
new_path = None new_path = None
self.ignores = list(set(self.ignore + dotfile.upignore))
if self.debug:
self.log.dbg('ignore pattern(s): {}'.format(self.ignores))
left = os.path.expanduser(path) left = os.path.expanduser(path)
right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src) right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src)
right = os.path.expanduser(right) right = os.path.expanduser(right)
if self._ignore([left, right]):
return True
if dotfile.trans_w: if dotfile.trans_w:
# apply write transformation if any # apply write transformation if any
new_path = self._apply_trans_w(path, dotfile) new_path = self._apply_trans_w(path, dotfile)
@@ -137,6 +145,8 @@ class Updater:
def _handle_file(self, left, right, compare=True): def _handle_file(self, left, right, compare=True):
"""sync left (deployed file) and right (dotdrop dotfile)""" """sync left (deployed file) and right (dotdrop dotfile)"""
if self._ignore([left, right]):
return True
if self.debug: if self.debug:
self.log.dbg('update for file {} and {}'.format(left, right)) self.log.dbg('update for file {} and {}'.format(left, right))
if self._is_template(right): if self._is_template(right):
@@ -169,6 +179,8 @@ class Updater:
# paths must be absolute (no tildes) # paths must be absolute (no tildes)
left = os.path.expanduser(left) left = os.path.expanduser(left)
right = os.path.expanduser(right) right = os.path.expanduser(right)
if self._ignore([left, right]):
return True
# find the differences # find the differences
diff = filecmp.dircmp(left, right, ignore=None) diff = filecmp.dircmp(left, right, ignore=None)
# handle directories diff # handle directories diff
@@ -179,6 +191,8 @@ class Updater:
left, right = diff.left, diff.right left, right = diff.left, diff.right
if self.debug: if self.debug:
self.log.dbg('sync dir {} to {}'.format(left, right)) self.log.dbg('sync dir {} to {}'.format(left, right))
if self._ignore([left, right]):
return True
# create dirs that don't exist in dotdrop # create dirs that don't exist in dotdrop
for toadd in diff.left_only: for toadd in diff.left_only:
@@ -188,6 +202,8 @@ class Updater:
continue continue
# match to dotdrop dotpath # match to dotdrop dotpath
new = os.path.join(right, toadd) new = os.path.join(right, toadd)
if self._ignore([exist, new]):
continue
if self.dry: if self.dry:
self.log.dry('would cp -r {} {}'.format(exist, new)) self.log.dry('would cp -r {} {}'.format(exist, new))
continue continue
@@ -202,6 +218,8 @@ class Updater:
if not os.path.isdir(old): if not os.path.isdir(old):
# ignore files for now # ignore files for now
continue continue
if self._ignore([old]):
continue
if self.dry: if self.dry:
self.log.dry('would rm -r {}'.format(old)) self.log.dry('would rm -r {}'.format(old))
continue continue
@@ -219,6 +237,8 @@ class Updater:
for f in fdiff: for f in fdiff:
fleft = os.path.join(left, f) fleft = os.path.join(left, f)
fright = os.path.join(right, f) fright = os.path.join(right, f)
if self._ignore([fleft, fright]):
continue
if self.dry: if self.dry:
self.log.dry('would cp {} {}'.format(fleft, fright)) self.log.dry('would cp {} {}'.format(fleft, fright))
continue continue
@@ -233,6 +253,8 @@ class Updater:
# ignore dirs, done above # ignore dirs, done above
continue continue
new = os.path.join(right, toadd) new = os.path.join(right, toadd)
if self._ignore([exist, new]):
continue
if self.dry: if self.dry:
self.log.dry('would cp {} {}'.format(exist, new)) self.log.dry('would cp {} {}'.format(exist, new))
continue continue
@@ -248,6 +270,8 @@ class Updater:
if os.path.isdir(new): if os.path.isdir(new):
# ignore dirs, done above # ignore dirs, done above
continue continue
if self._ignore([new]):
continue
if self.dry: if self.dry:
self.log.dry('would rm {}'.format(new)) self.log.dry('would rm {}'.format(new))
continue continue
@@ -275,3 +299,10 @@ class Updater:
if self.safe and not self.log.ask(msg): if self.safe and not self.log.ask(msg):
return False return False
return True return True
def _ignore(self, paths):
if utils.must_ignore(paths, self.ignores, debug=self.debug):
if self.debug:
self.log.dbg('ignoring update for {}'.format(paths))
return True
return False

View File

@@ -12,6 +12,7 @@ import uuid
import shlex import shlex
import functools import functools
import operator import operator
import fnmatch
from shutil import rmtree from shutil import rmtree
# local import # local import
@@ -115,3 +116,16 @@ def strip_home(path):
def flatten(a): def flatten(a):
"""flatten list""" """flatten list"""
return functools.reduce(operator.iconcat, a, []) return functools.reduce(operator.iconcat, a, [])
def must_ignore(paths, ignores, debug=False):
"""return true if any paths in list matches any ignore patterns"""
if not ignores:
return False
for p in paths:
for i in ignores:
if fnmatch.fnmatch(p, i):
if debug:
LOG.dbg('ignore \"{}\" match: {}'.format(i, p))
return True
return False

130
tests-ng/profile-dynvariables.sh Executable file
View File

@@ -0,0 +1,130 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test variables per profile
# 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 global testenv"
scr=`mktemp`
chmod +x ${scr}
echo -e "#!/bin/bash\necho $TESTENV\n" >> ${scr}
export TESTENV2="this is my profile testenv"
scr2=`mktemp`
chmod +x ${scr2}
echo -e "#!/bin/bash\necho $TESTENV2\n" >> ${scr2}
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
variables:
gvar1: "global1"
gvar2: "global2"
dynvariables:
gdvar1: head -1 /proc/meminfo
gdvar2: "echo 'this is some test' | rev | tr ' ' ','"
gdvar3: ${scr}
dotfiles:
f_abc:
dst: ${tmpd}/abc
src: abc
profiles:
p1:
variables:
gvar1: "local1"
lvar1: "local2"
dynvariables:
gdvar3: ${scr2}
pdvar1: "echo 'abc' | rev"
dotfiles:
- f_abc
_EOF
cat ${cfg}
# create the dotfile
echo "===================" > ${tmps}/dotfiles/abc
echo "{{@@ gvar1 @@}}" >> ${tmps}/dotfiles/abc
echo "{{@@ gvar2 @@}}" >> ${tmps}/dotfiles/abc
echo "{{@@ gdvar1 @@}}" >> ${tmps}/dotfiles/abc
echo "{{@@ gdvar2 @@}}" >> ${tmps}/dotfiles/abc
echo "{{@@ gdvar3 @@}}" >> ${tmps}/dotfiles/abc
echo "{{@@ lvar1 @@}}" >> ${tmps}/dotfiles/abc
echo "{{@@ pdvar1 @@}}" >> ${tmps}/dotfiles/abc
echo "===================" >> ${tmps}/dotfiles/abc
# install
cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V
#cat ${tmpd}/abc
# test variables
grep '^local1' ${tmpd}/abc >/dev/null
grep '^global2' ${tmpd}/abc >/dev/null
grep '^local2' ${tmpd}/abc >/dev/null
# test dynvariables
grep "^MemTotal" ${tmpd}/abc >/dev/null
grep '^tset,emos,si,siht' ${tmpd}/abc >/dev/null
grep "^${TESTENV2}" ${tmpd}/abc > /dev/null
grep "^cba" ${tmpd}/abc >/dev/null
#cat ${tmpd}/abc
## CLEANING
rm -rf ${tmps} ${tmpd} ${scr} ${scr2}
echo "OK"
exit 0

107
tests-ng/update-ignore.sh Executable file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test ignore update
# 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
################################################################
# dotdrop directory
tmps=`mktemp -d`
dt="${tmps}/dotfiles"
mkdir -p ${dt}
mkdir -p ${dt}/a/{b,c}
echo 'a' > ${dt}/a/b/abfile
echo 'a' > ${dt}/a/c/acfile
# fs dotfiles
tmpd=`mktemp -d`
cp -r ${dt}/a ${tmpd}/
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: false
create: true
dotpath: dotfiles
dotfiles:
f_abc:
dst: ${tmpd}/a
src: a
upignore:
- "*/cfile"
- "*/newfile"
- "*/newdir"
profiles:
p1:
dotfiles:
- f_abc
_EOF
cat ${cfg}
#tree ${dt}
# edit/add files
echo "[+] edit/add files"
touch ${tmpd}/a/newfile
echo 'b' > ${tmpd}/a/c/acfile
mkdir -p ${tmpd}/a/newdir/b
touch ${tmpd}/a/newdir/b/c
#tree ${tmpd}/a
# update
echo "[+] update"
cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc
#tree ${dt}
# check files haven't been updated
grep 'b' ${dt}/a/c/acfile >/dev/null
[ -e ${dt}/a/newfile ] && exit 1
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

View File

@@ -6,6 +6,8 @@
set -ev set -ev
# PEP8 tests # PEP8 tests
which pycodestyle 2>/dev/null
[ "$?" != "0" ] && echo "Install pycodestyle" && exit 1
pycodestyle --ignore=W605 dotdrop/ pycodestyle --ignore=W605 dotdrop/
pycodestyle tests/ pycodestyle tests/
pycodestyle scripts/ pycodestyle scripts/

View File

@@ -277,11 +277,12 @@ exec bspwm
src = create_random_file(src_dir)[0] src = create_random_file(src_dir)[0]
logger = MagicMock() logger = MagicMock()
templater = MagicMock()
installer = Installer() installer = Installer()
installer.log.err = logger installer.log.err = logger
# pass src file not src dir # pass src file not src dir
res = installer.linkall(templater=MagicMock(), src=src, dst='/dev/null', res = installer.linkall(templater=templater, src=src, dst='/dev/null',
actions=[]) actions=[])
# ensure nothing performed # ensure nothing performed