mirror of
https://github.com/deadc0de6/dotdrop.git
synced 2026-02-06 17:12:59 +00:00
Merge upstream
This commit is contained in:
@@ -7,7 +7,6 @@ handle the comparison of dotfiles and local deployment
|
||||
|
||||
import os
|
||||
import filecmp
|
||||
import fnmatch
|
||||
|
||||
# local imports
|
||||
from dotdrop.logger import Logger
|
||||
@@ -34,7 +33,7 @@ class Comparator:
|
||||
|
||||
def _comp_file(self, left, right, ignore):
|
||||
"""compare a file"""
|
||||
if self._ignore([left, right], ignore):
|
||||
if utils.must_ignore([left, right], ignore, debug=self.debug):
|
||||
if self.debug:
|
||||
self.log.dbg('ignoring diff {} and {}'.format(left, right))
|
||||
return ''
|
||||
@@ -44,7 +43,7 @@ class Comparator:
|
||||
"""compare a directory"""
|
||||
if not os.path.exists(right):
|
||||
return ''
|
||||
if self._ignore([left, right], ignore):
|
||||
if utils.must_ignore([left, right], ignore, debug=self.debug):
|
||||
if self.debug:
|
||||
self.log.dbg('ignoring diff {} and {}'.format(left, right))
|
||||
return ''
|
||||
@@ -54,13 +53,15 @@ class Comparator:
|
||||
comp = filecmp.dircmp(left, right)
|
||||
# handle files only in deployed file
|
||||
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
|
||||
ret.append('only in left: \"{}\"\n'.format(i))
|
||||
ret.append('=> \"{}\" does not exist on local\n'.format(i))
|
||||
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
|
||||
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
|
||||
funny = comp.common_funny
|
||||
@@ -90,17 +91,5 @@ class Comparator:
|
||||
if header:
|
||||
lshort = os.path.basename(left)
|
||||
rshort = os.path.basename(right)
|
||||
diff = 'diff \"{}\":\n{}'.format(lshort, diff)
|
||||
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
|
||||
|
||||
@@ -45,7 +45,7 @@ class Cfg:
|
||||
|
||||
# template variables
|
||||
key_variables = 'variables'
|
||||
# shell variable
|
||||
# shell variables
|
||||
key_dynvariables = 'dynvariables'
|
||||
|
||||
# dotfiles keys
|
||||
@@ -58,6 +58,7 @@ class Cfg:
|
||||
key_dotfiles_actions = 'actions'
|
||||
key_dotfiles_trans_r = 'trans'
|
||||
key_dotfiles_trans_w = 'trans_write'
|
||||
key_dotfiles_upignore = 'upignore'
|
||||
|
||||
# profiles keys
|
||||
key_profiles = 'profiles'
|
||||
@@ -112,13 +113,14 @@ class Cfg:
|
||||
# represents all dotfiles per profile by profile key
|
||||
# NOT linked inside the yaml dict (self.content)
|
||||
self.prodots = {}
|
||||
|
||||
if not self._load_file():
|
||||
raise ValueError('config is not valid')
|
||||
|
||||
def eval_dotfiles(self, profile, debug=False):
|
||||
"""resolve dotfiles src/dst templates"""
|
||||
"""resolve dotfiles src/dst templating"""
|
||||
t = Templategen(profile=profile,
|
||||
variables=self.get_variables(),
|
||||
variables=self.get_variables(profile),
|
||||
debug=debug)
|
||||
for d in self.get_dotfiles(profile):
|
||||
d.src = t.generate_string(d.src)
|
||||
@@ -275,15 +277,20 @@ class Cfg:
|
||||
trans_r = None
|
||||
trans_w = None
|
||||
|
||||
# parse ignore pattern
|
||||
ignores = v[self.key_dotfiles_cmpignore] if \
|
||||
# parse cmpignore pattern
|
||||
cmpignores = v[self.key_dotfiles_cmpignore] if \
|
||||
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
|
||||
self.dotfiles[k] = Dotfile(k, dst, src,
|
||||
link=link, actions=actions,
|
||||
trans_r=trans_r, trans_w=trans_w,
|
||||
cmpignore=ignores, noempty=noempty)
|
||||
cmpignore=cmpignores, noempty=noempty,
|
||||
upignore=upignores)
|
||||
|
||||
# assign dotfiles to each profile
|
||||
for k, v in self.lnk_profiles.items():
|
||||
@@ -604,15 +611,35 @@ class Cfg:
|
||||
"""return all defined settings"""
|
||||
return self.lnk_settings.copy()
|
||||
|
||||
def get_variables(self):
|
||||
def get_variables(self, profile):
|
||||
"""return the variables for this profile"""
|
||||
variables = {}
|
||||
|
||||
# global variables
|
||||
if self.key_variables in self.content:
|
||||
variables.update(self.content[self.key_variables])
|
||||
|
||||
# global dynvariables
|
||||
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)
|
||||
for k, v in dynvars.items():
|
||||
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
|
||||
|
||||
def dump(self):
|
||||
|
||||
@@ -44,7 +44,8 @@ Usage:
|
||||
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>...
|
||||
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
|
||||
[-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 detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
|
||||
dotdrop list [-Vb] [-c <path>]
|
||||
@@ -55,13 +56,13 @@ Options:
|
||||
-p --profile=<profile> Specify the profile to use [default: {}].
|
||||
-c --cfg=<path> Path to the config [default: config.yaml].
|
||||
-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: ].
|
||||
-n --nodiff Do not diff when installing.
|
||||
-t --temp Install to a temporary directory for review.
|
||||
-T --template Only template dotfiles.
|
||||
-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.
|
||||
-k --key Treat <path> as a dotfile key.
|
||||
-V --verbose Be verbose.
|
||||
@@ -170,7 +171,10 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
|
||||
LOG.dbg('comparing {}'.format(dotfile))
|
||||
src = dotfile.src
|
||||
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
|
||||
if dotfile.trans_r:
|
||||
@@ -178,12 +182,14 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
|
||||
tmpsrc = apply_trans(opts, dotfile)
|
||||
if not tmpsrc:
|
||||
# could not apply trans
|
||||
same = False
|
||||
continue
|
||||
src = tmpsrc
|
||||
# install dotfile to temporary dir
|
||||
ret, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst)
|
||||
if not ret:
|
||||
# failed to install to tmp
|
||||
same = False
|
||||
continue
|
||||
ignores = list(set(ignore + dotfile.cmpignore))
|
||||
diff = comp.compare(insttmp, dotfile.dst, ignore=ignores)
|
||||
@@ -194,23 +200,24 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
|
||||
remove(tmpsrc)
|
||||
if diff == '':
|
||||
if opts['debug']:
|
||||
LOG.dbg('diffing \"{}\" VS \"{}\"'.format(dotfile.key,
|
||||
dotfile.dst))
|
||||
line = '=> compare {}: diffing with \"{}\"'
|
||||
LOG.dbg(line.format(dotfile.key, dotfile.dst))
|
||||
LOG.dbg('same file')
|
||||
else:
|
||||
LOG.log('diffing \"{}\" VS \"{}\"'.format(dotfile.key,
|
||||
dotfile.dst))
|
||||
line = '=> compare {}: diffing with \"{}\"'
|
||||
LOG.log(line.format(dotfile.key, dotfile.dst))
|
||||
LOG.emph(diff)
|
||||
same = False
|
||||
|
||||
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)"""
|
||||
ret = True
|
||||
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:
|
||||
# update paths
|
||||
if opts['debug']:
|
||||
@@ -434,9 +441,9 @@ def main():
|
||||
opts['profile'] = args['--profile']
|
||||
opts['safe'] = not args['--force']
|
||||
opts['installdiff'] = not args['--nodiff']
|
||||
opts['link'] = args['--link']
|
||||
opts['link'] = args['--inv-link']
|
||||
opts['debug'] = args['--verbose']
|
||||
opts['variables'] = conf.get_variables()
|
||||
opts['variables'] = conf.get_variables(opts['profile'])
|
||||
opts['showdiff'] = opts['showdiff'] or args['--showdiff']
|
||||
|
||||
if opts['debug']:
|
||||
@@ -495,7 +502,8 @@ def main():
|
||||
if opts['debug']:
|
||||
LOG.dbg('running cmd: update')
|
||||
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']:
|
||||
# detail files
|
||||
|
||||
@@ -12,7 +12,8 @@ class Dotfile:
|
||||
|
||||
def __init__(self, key, dst, src,
|
||||
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
|
||||
self.key = key
|
||||
# path where to install this dotfile
|
||||
@@ -31,6 +32,8 @@ class Dotfile:
|
||||
self.cmpignore = cmpignore
|
||||
# do not deploy empty file
|
||||
self.noempty = noempty
|
||||
# pattern to ignore when updating
|
||||
self.upignore = upignore
|
||||
|
||||
def __str__(self):
|
||||
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"'
|
||||
|
||||
@@ -70,7 +70,7 @@ class Templategen:
|
||||
filetype = filetype.strip()
|
||||
if self.debug:
|
||||
self.log.dbg('\"{}\" filetype: {}'.format(src, filetype))
|
||||
istext = 'text' in filetype
|
||||
istext = 'text' in filetype or 'empty' in filetype
|
||||
if self.debug:
|
||||
self.log.dbg('\"{}\" is text: {}'.format(src, istext))
|
||||
if not istext:
|
||||
|
||||
@@ -21,13 +21,14 @@ TILD = '~'
|
||||
class Updater:
|
||||
|
||||
def __init__(self, conf, dotpath, dry, safe,
|
||||
iskey=False, debug=False):
|
||||
iskey=False, debug=False, ignore=[]):
|
||||
self.conf = conf
|
||||
self.dotpath = dotpath
|
||||
self.dry = dry
|
||||
self.safe = safe
|
||||
self.iskey = iskey
|
||||
self.debug = debug
|
||||
self.ignore = ignore
|
||||
self.log = Logger()
|
||||
|
||||
def update_path(self, path, profile):
|
||||
@@ -58,9 +59,16 @@ class Updater:
|
||||
"""update dotfile from file pointed by path"""
|
||||
ret = False
|
||||
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)
|
||||
right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src)
|
||||
right = os.path.expanduser(right)
|
||||
|
||||
if self._ignore([left, right]):
|
||||
return True
|
||||
if dotfile.trans_w:
|
||||
# apply write transformation if any
|
||||
new_path = self._apply_trans_w(path, dotfile)
|
||||
@@ -137,6 +145,8 @@ class Updater:
|
||||
|
||||
def _handle_file(self, left, right, compare=True):
|
||||
"""sync left (deployed file) and right (dotdrop dotfile)"""
|
||||
if self._ignore([left, right]):
|
||||
return True
|
||||
if self.debug:
|
||||
self.log.dbg('update for file {} and {}'.format(left, right))
|
||||
if self._is_template(right):
|
||||
@@ -169,6 +179,8 @@ class Updater:
|
||||
# paths must be absolute (no tildes)
|
||||
left = os.path.expanduser(left)
|
||||
right = os.path.expanduser(right)
|
||||
if self._ignore([left, right]):
|
||||
return True
|
||||
# find the differences
|
||||
diff = filecmp.dircmp(left, right, ignore=None)
|
||||
# handle directories diff
|
||||
@@ -179,6 +191,8 @@ class Updater:
|
||||
left, right = diff.left, diff.right
|
||||
if self.debug:
|
||||
self.log.dbg('sync dir {} to {}'.format(left, right))
|
||||
if self._ignore([left, right]):
|
||||
return True
|
||||
|
||||
# create dirs that don't exist in dotdrop
|
||||
for toadd in diff.left_only:
|
||||
@@ -188,6 +202,8 @@ class Updater:
|
||||
continue
|
||||
# match to dotdrop dotpath
|
||||
new = os.path.join(right, toadd)
|
||||
if self._ignore([exist, new]):
|
||||
continue
|
||||
if self.dry:
|
||||
self.log.dry('would cp -r {} {}'.format(exist, new))
|
||||
continue
|
||||
@@ -202,6 +218,8 @@ class Updater:
|
||||
if not os.path.isdir(old):
|
||||
# ignore files for now
|
||||
continue
|
||||
if self._ignore([old]):
|
||||
continue
|
||||
if self.dry:
|
||||
self.log.dry('would rm -r {}'.format(old))
|
||||
continue
|
||||
@@ -219,6 +237,8 @@ class Updater:
|
||||
for f in fdiff:
|
||||
fleft = os.path.join(left, f)
|
||||
fright = os.path.join(right, f)
|
||||
if self._ignore([fleft, fright]):
|
||||
continue
|
||||
if self.dry:
|
||||
self.log.dry('would cp {} {}'.format(fleft, fright))
|
||||
continue
|
||||
@@ -233,6 +253,8 @@ class Updater:
|
||||
# ignore dirs, done above
|
||||
continue
|
||||
new = os.path.join(right, toadd)
|
||||
if self._ignore([exist, new]):
|
||||
continue
|
||||
if self.dry:
|
||||
self.log.dry('would cp {} {}'.format(exist, new))
|
||||
continue
|
||||
@@ -248,6 +270,8 @@ class Updater:
|
||||
if os.path.isdir(new):
|
||||
# ignore dirs, done above
|
||||
continue
|
||||
if self._ignore([new]):
|
||||
continue
|
||||
if self.dry:
|
||||
self.log.dry('would rm {}'.format(new))
|
||||
continue
|
||||
@@ -275,3 +299,10 @@ class Updater:
|
||||
if self.safe and not self.log.ask(msg):
|
||||
return False
|
||||
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
|
||||
|
||||
@@ -12,6 +12,7 @@ import uuid
|
||||
import shlex
|
||||
import functools
|
||||
import operator
|
||||
import fnmatch
|
||||
from shutil import rmtree
|
||||
|
||||
# local import
|
||||
@@ -115,3 +116,16 @@ def strip_home(path):
|
||||
def flatten(a):
|
||||
"""flatten list"""
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user