1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-04 21:29:43 +00:00

adding ability to update by key or on all keys of a specific profile

This commit is contained in:
deadc0de6
2018-10-08 21:36:12 +02:00
parent b71cbefa10
commit 2b901d5608
5 changed files with 211 additions and 30 deletions

View File

@@ -46,7 +46,7 @@ Usage:
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [-C <file>...] [-i <pattern>...]
dotdrop update [-fdVb] [-c <path>] [-p <profile>] <path>...
dotdrop update [-fdVbk] [-c <path>] [-p <profile>] [<path>...]
dotdrop listfiles [-VTb] [-c <path>] [-p <profile>]
dotdrop list [-Vb] [-c <path>]
dotdrop --help
@@ -64,6 +64,7 @@ Options:
-D --showdiff Show a diff before overwriting.
-l --link Import and link.
-f --force Do not warn if exists.
-k --key Treat <path> as a dotfile key.
-V --verbose Be verbose.
-d --dry Dry run.
-b --no-banner Do not display the banner.
@@ -203,21 +204,41 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
return same
def cmd_update(opts, conf, paths):
"""update the dotfile(s) from path(s)"""
def cmd_update(opts, conf, paths, iskey=False):
"""update the dotfile(s) from path(s) or key(s)"""
ret = True
updater = Updater(conf, opts['dotpath'], opts['dry'],
opts['safe'], opts['debug'])
for path in paths:
updater.update(path, opts['profile'])
opts['safe'], iskey=iskey, debug=opts['debug'])
if not iskey:
# update paths
if opts['debug']:
LOG.dbg('update by paths: {}'.format(paths))
for path in paths:
if not updater.update_path(path, opts['profile']):
ret = False
else:
# update keys
keys = paths
if not keys:
# if not provided, take all keys
keys = [d.key for d in conf.get_dotfiles(opts['profile'])]
if opts['debug']:
LOG.dbg('update by keys: {}'.format(keys))
for key in keys:
if not updater.update_key(key, opts['profile']):
ret = False
return ret
def cmd_importer(opts, conf, paths):
"""import dotfile(s) from paths"""
ret = True
home = os.path.expanduser(TILD)
cnt = 0
for path in paths:
if not os.path.lexists(path):
LOG.err('\"{}\" does not exist, ignored!'.format(path))
ret = False
continue
dst = path.rstrip(os.sep)
dst = os.path.abspath(dst)
@@ -243,6 +264,7 @@ def cmd_importer(opts, conf, paths):
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True)
if not r:
LOG.err('importing \"{}\" failed!'.format(path))
ret = False
continue
cmd = ['cp', '-R', '-L', dst, srcf]
if opts['dry']:
@@ -253,6 +275,7 @@ def cmd_importer(opts, conf, paths):
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True)
if not r:
LOG.err('importing \"{}\" failed!'.format(path))
ret = False
continue
if linkit:
remove(dst)
@@ -269,6 +292,7 @@ def cmd_importer(opts, conf, paths):
else:
conf.save()
LOG.log('\n{} file(s) imported.'.format(cnt))
return True
def cmd_list_profiles(conf):
@@ -386,19 +410,27 @@ def main():
if args['list']:
# list existing profiles
if opts['debug']:
LOG.dbg('running cmd: list')
cmd_list_profiles(conf)
elif args['listfiles']:
# list files for selected profile
if opts['debug']:
LOG.dbg('running cmd: listfiles')
cmd_list_files(opts, conf, templateonly=args['--template'])
elif args['install']:
# install the dotfiles stored in dotdrop
if opts['debug']:
LOG.dbg('running cmd: install')
ret = cmd_install(opts, conf, temporary=args['--temp'],
keys=args['<key>'])
elif args['compare']:
# compare local dotfiles with dotfiles stored in dotdrop
if opts['debug']:
LOG.dbg('running cmd: compare')
tmp = get_tmpdir()
opts['dopts'] = args['--dopts']
ret = cmd_compare(opts, conf, tmp, focus=args['--file'],
@@ -408,11 +440,16 @@ def main():
elif args['import']:
# import dotfile(s)
cmd_importer(opts, conf, args['<path>'])
if opts['debug']:
LOG.dbg('running cmd: import')
ret = cmd_importer(opts, conf, args['<path>'])
elif args['update']:
# update a dotfile
cmd_update(opts, conf, args['<path>'])
if opts['debug']:
LOG.dbg('running cmd: update')
iskey = args['--key']
ret = cmd_update(opts, conf, args['<path>'], iskey=iskey)
except KeyboardInterrupt:
LOG.err('interrupted')

View File

@@ -19,32 +19,45 @@ TILD = '~'
class Updater:
def __init__(self, conf, dotpath, dry, safe, debug):
def __init__(self, conf, dotpath, dry, safe,
iskey=False, debug=False):
self.home = os.path.expanduser(TILD)
self.conf = conf
self.dotpath = dotpath
self.dry = dry
self.safe = safe
self.iskey = iskey
self.debug = debug
self.log = Logger()
def update(self, path, profile):
def update_path(self, path, profile):
"""update the dotfile installed on path"""
if not os.path.lexists(path):
self.log.err('\"{}\" does not exist!'.format(path))
return False
left = self._normalize(path)
dotfile = self._get_dotfile(left, profile)
path = self._normalize(path)
dotfile = self._get_dotfile_by_path(path, profile)
if not dotfile:
return False
if self.debug:
self.log.dbg('updating {} from {}'.format(dotfile, path))
self.log.dbg('updating {} from path \"{}\"'.format(dotfile, path))
return self._update(path, dotfile)
def update_key(self, key, profile):
"""update the dotfile referenced by key"""
dotfile = self._get_dotfile_by_key(key, profile)
if not dotfile:
return False
if self.debug:
self.log.dbg('updating {} from key \"{}\"'.format(dotfile, key))
path = self._normalize(dotfile.dst)
return self._update(path, dotfile)
def _update(self, path, dotfile):
"""update dotfile from file pointed by path"""
left = os.path.expanduser(path)
right = os.path.join(self.conf.abs_dotpath(self.dotpath), dotfile.src)
# expands user
left = os.path.expanduser(left)
right = os.path.expanduser(right)
# go through all files and update
if os.path.isdir(path):
return self._handle_dir(left, right)
return self._handle_file(left, right)
@@ -60,7 +73,20 @@ class Updater:
path = os.path.join(TILD, path)
return path
def _get_dotfile(self, path, profile):
def _get_dotfile_by_key(self, key, profile):
"""get the dotfile matching this key"""
dotfiles = self.conf.get_dotfiles(profile)
subs = [d for d in dotfiles if d.key == key]
if not subs:
self.log.err('key \"{}\" not found!'.format(path))
return None
if len(subs) > 1:
found = ','.join([d.src for d in dotfiles])
self.log.err('multiple dotfiles found: {}'.format(found))
return None
return subs[0]
def _get_dotfile_by_path(self, path, profile):
"""get the dotfile matching this path"""
dotfiles = self.conf.get_dotfiles(profile)
subs = [d for d in dotfiles if d.dst == path]
@@ -114,7 +140,7 @@ class Updater:
# find the differences
diff = filecmp.dircmp(left, right, ignore=None)
# handle directories diff
self._merge_dirs(diff)
return self._merge_dirs(diff)
def _merge_dirs(self, diff):
"""Synchronize directories recursively."""
@@ -123,8 +149,6 @@ class Updater:
self.log.dbg('sync dir {} to {}'.format(left, right))
# create dirs that don't exist in dotdrop
if self.debug:
self.log.dbg('handle dirs that do not exist in dotdrop')
for toadd in diff.left_only:
exist = os.path.join(left, toadd)
if not os.path.isdir(exist):
@@ -141,8 +165,6 @@ class Updater:
shutil.copytree(exist, new)
# remove dirs that don't exist in deployed version
if self.debug:
self.log.dbg('remove dirs that do not exist in deployed version')
for toremove in diff.right_only:
old = os.path.join(right, toremove)
if not os.path.isdir(old):
@@ -159,8 +181,6 @@ class Updater:
# handle files diff
# sync files that exist in both but are different
if self.debug:
self.log.dbg('sync files that exist in both but are different')
fdiff = diff.diff_files
fdiff.extend(diff.funny_files)
fdiff.extend(diff.common_funny)
@@ -175,8 +195,6 @@ class Updater:
self._handle_file(fleft, fright, compare=False)
# copy files that don't exist in dotdrop
if self.debug:
self.log.dbg('copy files not existing in dotdrop')
for toadd in diff.left_only:
exist = os.path.join(left, toadd)
if os.path.isdir(exist):
@@ -191,8 +209,6 @@ class Updater:
shutil.copyfile(exist, new)
# remove files that don't exist in deployed version
if self.debug:
self.log.dbg('remove files that do not exist in deployed version')
for toremove in diff.right_only:
new = os.path.join(right, toremove)
if not os.path.exists(new):

View File

@@ -47,6 +47,7 @@ echo "RUNNING $(basename $BASH_SOURCE)"
# dotdrop directory
basedir=`mktemp -d`
dotfiles="${basedir}/dotfiles"
echo "dotdrop dir: ${basedir}"
# the dotfile
tmpd=`mktemp -d`
@@ -63,7 +64,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}
echo "changed" > ${token}
# update
cd ${ddpath} | ${bin} update -f -c ${cfg} ${tmpd}
cd ${ddpath} | ${bin} update -f -c ${cfg} ${tmpd} --verbose
grep 'changed' ${token} >/dev/null 2>&1

104
tests-ng/update-with-key.sh Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test updates with key
# 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 "RUNNING $(basename $BASH_SOURCE)"
################################################################
# this is the test
################################################################
# dotdrop directory
basedir=`mktemp -d`
echo "[+] dotdrop dir: ${basedir}"
echo "[+] dotpath dir: ${basedir}/dotfiles"
# the dotfile to be imported
tmpd=`mktemp -d`
# originally imported directory
echo 'unique' > ${tmpd}/uniquefile
uniquefile_key="f_uniquefile"
echo 'unique2' > ${tmpd}/uniquefile2
uniquefile2_key="f_uniquefile2"
mkdir ${tmpd}/dir1
touch ${tmpd}/dir1/dir1f1
mkdir ${tmpd}/dir1/dir1dir1
dir1_key="d_dir1"
# create the config file
cfg="${basedir}/config.yaml"
create_conf ${cfg} # sets token
# import dir1
echo "[+] import"
cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/dir1
cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/uniquefile
cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/uniquefile2
# make some modification
echo "[+] modify"
echo 'changed' > ${tmpd}/uniquefile
echo 'changed' > ${tmpd}/uniquefile2
echo 'new' > ${tmpd}/dir1/dir1dir1/new
# update by key
echo "[+] updating single key"
cd ${ddpath} | ${bin} update -c ${cfg} -k -f --verbose ${uniquefile_key}
# ensure changes applied correctly (only to uniquefile)
diff ${tmpd}/uniquefile ${basedir}/dotfiles/${tmpd}/uniquefile # should be same
set +e
diff ${tmpd}/uniquefile2 ${basedir}/dotfiles/${tmpd}/uniquefile2 # should be different
[ "${?}" != "1" ] && exit 1
set -e
# update all keys
echo "[+] updating all keys"
cd ${ddpath} | ${bin} update -c ${cfg} -k -f --verbose
# ensure all changes applied
diff ${tmpd} ${basedir}/dotfiles/${tmpd}
## CLEANING
rm -rf ${basedir} ${tmpd}
echo "OK"
exit 0

View File

@@ -51,6 +51,10 @@ class TestUpdate(unittest.TestCase):
self.assertTrue(os.path.exists(d1))
self.addCleanup(clean, d1)
d2, c2 = create_random_file(fold_config)
self.assertTrue(os.path.exists(d2))
self.addCleanup(clean, d2)
# create the directory to test
dpath = os.path.join(fold_config, get_string(5))
dir1 = create_dir(dpath)
@@ -65,7 +69,7 @@ class TestUpdate(unittest.TestCase):
create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile)
dfiles = [d1, dir1]
dfiles = [d1, dir1, d2]
# import the files
cmd_importer(opts, conf, dfiles)
@@ -94,6 +98,25 @@ class TestUpdate(unittest.TestCase):
newcontent = open(dirf1, 'r').read()
self.assertTrue(newcontent == 'newcontent')
edit_content(d2, 'newcontentbykey')
# update it by key
dfiles = conf.get_dotfiles(profile)
d2key = ''
for ds in dfiles:
t = os.path.expanduser(ds.dst)
if t == d2:
d2key = ds.key
break
self.assertTrue(d2key != '')
opts['safe'] = False
opts['debug'] = True
cmd_update(opts, conf, [d2key], iskey=True)
# test content
newcontent = open(d2, 'r').read()
self.assertTrue(newcontent == 'newcontentbykey')
def main():
unittest.main()