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:
@@ -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')
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
104
tests-ng/update-with-key.sh
Executable 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
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user