1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-04 15:39:43 +00:00
This commit is contained in:
deadc0de6
2018-07-21 13:53:05 +02:00
6 changed files with 365 additions and 40 deletions

90
dotdrop/comparator.py Normal file
View File

@@ -0,0 +1,90 @@
"""
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
handle the comparison of dotfiles and local deployment
"""
import os
import shutil
import filecmp
# local imports
from dotdrop.logger import Logger
import dotdrop.utils as utils
class Comparator:
def __init__(self, diffopts='', ignore=[], 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):
"""diff left (dotdrop dotfile) and right (deployed file)"""
left = os.path.expanduser(left)
right = os.path.expanduser(right)
if not os.path.isdir(left):
return self._comp_file(left, right)
return self._comp_dir(left, right)
def _comp_file(self, left, right):
"""compare a file"""
if left in self.ignore or right in self.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):
"""compare a directory"""
if left in self.ignore or right in self.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)
# handle files only in deployed file
for i in comp.left_only:
if os.path.join(left, i) in self.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:
continue
ret.append('only in right: \"{}\"\n'.format(i))
# same left and right but different type
funny = comp.common_funny
for i in funny:
lfile = os.path.join(left, i)
rfile = os.path.join(right, i)
short = os.path.basename(lfile)
# file vs dir
ret.append('different type: \"{}\"\n'.format(short))
# content is different
funny = comp.diff_files
funny.extend(comp.funny_files)
funny = list(set(funny))
for i in funny:
lfile = os.path.join(left, i)
rfile = os.path.join(right, i)
diff = self._diff(lfile, rfile, header=True)
ret.append(diff)
return ''.join(ret)
def _diff(self, left, right, header=False):
"""diff using the unix tool diff"""
diff = utils.diff(left, right, raw=False,
opts=self.diffopts, debug=self.debug)
if header:
lshort = os.path.basename(left)
rshort = os.path.basename(right)
diff = 'diff \"{}\":\n{}'.format(lshort, diff)
return diff

View File

@@ -16,6 +16,7 @@ from dotdrop.logger import Logger
from dotdrop.templategen import Templategen
from dotdrop.installer import Installer
from dotdrop.updater import Updater
from dotdrop.comparator import Comparator
from dotdrop.dotfile import Dotfile
from dotdrop.config import Cfg
from dotdrop.utils import *
@@ -39,8 +40,9 @@ Usage:
dotdrop install [-fndVb] [-c <path>] [-p <profile>]
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <paths>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [--files=<files>]
dotdrop update [-fdVb] [-c <path>] [-p <profile>] <paths>...
[-o <opts>] [-i <name>...]
[--files=<files>]
dotdrop update [-fdVb] [-c <path>] <path>
dotdrop listfiles [-Vb] [-c <path>] [-p <profile>]
dotdrop list [-Vb] [-c <path>]
dotdrop --help
@@ -50,6 +52,7 @@ Options:
-p --profile=<profile> Specify the profile to use [default: {}].
-c --cfg=<path> Path to the config [default: config.yaml].
--files=<files> Comma separated list of files to compare.
-i --ignore=<name> File name to ignore when diffing.
-o --dopts=<opts> Diff options [default: ].
-n --nodiff Do not diff when installing.
-l --link Import and link.
@@ -161,45 +164,57 @@ def _select(selections, dotfiles):
return selected, ret
def compare(opts, conf, tmp, focus=None):
def compare(opts, conf, tmp, focus=None, ignore=[]):
"""compare dotfiles and return True if all identical"""
dotfiles = conf.get_dotfiles(opts['profile'])
if dotfiles == []:
msg = 'no dotfiles defined for this profile (\"{}\")'
LOG.err(msg.format(opts['profile']))
return True
t = Templategen(base=opts['dotpath'], debug=opts['debug'])
inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], base=opts['dotpath'],
debug=opts['debug'])
# compare only specific files
ret = True
same = True
selected = dotfiles
if focus:
selected, ret = _select(focus.replace(' ', '').split(','), dotfiles)
if len(selected) < 1:
return ret
return False
t = Templategen(base=opts['dotpath'], debug=opts['debug'])
inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], base=opts['dotpath'],
debug=opts['debug'])
comp = Comparator(diffopts=opts['dopts'], debug=opts['debug'],
ignore=ignore)
for dotfile in selected:
if opts['debug']:
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))
tmpsrc = None
if dotfile.trans:
# apply transformation
tmpsrc = apply_trans(opts, dotfile)
if not tmpsrc:
# could not apply trans
continue
src = tmpsrc
# create a fake dotfile which is the result of the transformation
same, diff = inst.compare(t, tmp, opts['profile'],
src, dotfile.dst, opts=opts['dopts'])
# install dotfile to temporary dir
ret, insttmp = inst.install_to_temp(t, tmp, opts['profile'],
src, dotfile.dst)
if not ret:
# failed to install to tmp
continue
diff = comp.compare(insttmp, dotfile.dst)
if tmpsrc:
# clean tmp transformed dotfile if any
tmpsrc = os.path.join(opts['dotpath'], tmpsrc)
if os.path.exists(tmpsrc):
remove(tmpsrc)
if same:
if diff == '':
if opts['debug']:
LOG.dbg('diffing \"{}\" VS \"{}\"'.format(dotfile.key,
dotfile.dst))
@@ -208,9 +223,9 @@ def compare(opts, conf, tmp, focus=None):
LOG.log('diffing \"{}\" VS \"{}\"'.format(dotfile.key,
dotfile.dst))
LOG.emph(diff)
ret = False
same = False
return ret
return same
def update(opts, conf, paths):
@@ -345,7 +360,7 @@ def main():
# compare local dotfiles with dotfiles stored in dotdrop
tmp = get_tmpdir()
opts['dopts'] = args['--dopts']
ret = compare(opts, conf, tmp, args['--files'])
ret = compare(opts, conf, tmp, args['--files'], args['--ignore'])
if os.listdir(tmp):
LOG.raw('\ntemporary files available under {}'.format(tmp))
else:

View File

@@ -9,6 +9,7 @@ import os
# local imports
from dotdrop.logger import Logger
from dotdrop.comparator import Comparator
import dotdrop.utils as utils
@@ -193,11 +194,12 @@ class Installer:
tmpdst = os.path.join(tmpdir, sub)
return self.install(templater, profile, src, tmpdst), tmpdst
def compare(self, templater, tmpdir, profile, src, dst, opts=''):
def install_to_temp(self, templater, tmpdir, profile, src, dst):
"""compare a temporary generated dotfile with the local one"""
ret = False
tmpdst = ''
# saved some flags while comparing
self.comparing = True
retval = False, ''
drysaved = self.dry
self.dry = False
diffsaved = self.diff
@@ -208,26 +210,15 @@ class Installer:
src = os.path.expanduser(src)
dst = os.path.expanduser(dst)
if self.debug:
self.log.dbg('comparing {} and {}'.format(src, dst))
if not os.path.lexists(dst):
# destination dotfile does not exist
retval = False, '\"{}\" does not exist on local\n'.format(dst)
else:
# install the dotfile to a temp directory for comparing
ret, tmpdst = self._install_to_temp(templater, profile,
src, dst, tmpdir)
if ret:
if self.debug:
self.log.dbg('diffing {} and {}'.format(tmpdst, dst))
diff = utils.diff(tmpdst, dst, raw=False,
opts=opts, debug=self.debug)
if diff == '':
retval = True, ''
else:
retval = False, diff
self.log.dbg('tmp install {} to {}'.format(src, dst))
# install the dotfile to a temp directory for comparing
ret, tmpdst = self._install_to_temp(templater, profile,
src, dst, tmpdir)
if self.debug:
self.log.dbg('tmp installed in {}'.format(tmpdst))
# reset flags
self.dry = drysaved
self.diff = diffsaved
self.comparing = False
self.create = createsaved
return retval
return ret, tmpdst

100
tests-ng/compare-ignore.sh Executable file
View File

@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test updates
# 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`
# some files
mkdir -p ${tmpd}/{program,config}
touch ${tmpd}/program/a
touch ${tmpd}/config/a
# create the config file
cfg="${basedir}/config.yaml"
create_conf ${cfg} # sets token
# import
echo "[+] import"
cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/program
cd ${ddpath} | ${bin} import -c ${cfg} ${tmpd}/config
# add files
echo "[+] add files"
touch ${tmpd}/program/b
touch ${tmpd}/config/b
# expects diff
echo "[+] comparing normal"
set +e
cd ${ddpath} | ${bin} compare -c ${cfg} --verbose
[ "$?" = "0" ] && exit 1
set -e
# expects one diff
echo "[+] comparing with ignore"
set +e
cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=${tmpd}/config/b
[ "$?" = "0" ] && exit 1
set -e
# expects no diff
echo "[+] comparing with ignore pattern"
set +e
cd ${ddpath} | ${bin} compare -c ${cfg} --verbose --ignore=b
[ "$?" != "0" ] && exit 1
set -e
## CLEANING
rm -rf ${basedir} ${tmpd}
echo "OK"
exit 0

120
tests-ng/compare.sh Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test updates
# 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`
# single file
echo 'unique' > ${tmpd}/uniquefile
# hierarchy from https://pymotw.com/2/filecmp/
# create the hierarchy
# for dir1 (originally imported directory))))
mkdir ${tmpd}/dir1
touch ${tmpd}/dir1/file_only_in_dir1
mkdir -p ${tmpd}/dir1/dir_only_in_dir1
mkdir -p ${tmpd}/dir1/common_dir
echo 'this file is the same' > ${tmpd}/dir1/common_file
echo 'in dir1' > ${tmpd}/dir1/not_the_same
echo 'This is a file in dir1' > ${tmpd}/dir1/file_in_dir1
mkdir -p ${tmpd}/dir1/sub/sub2
mkdir -p ${tmpd}/dir1/notindir2/notindir2
echo 'first' > ${tmpd}/dir1/sub/sub2/different
#tree ${tmpd}/dir1
# create the hierarchy
# for dir2 (modified original for update)
mkdir ${tmpd}/dir2
touch ${tmpd}/dir2/file_only_in_dir2
mkdir -p ${tmpd}/dir2/dir_only_in_dir2
mkdir -p ${tmpd}/dir2/common_dir
echo 'this file is the same' > ${tmpd}/dir2/common_file
echo 'in dir2' > ${tmpd}/dir2/not_the_same
mkdir -p ${tmpd}/dir2/file_in_dir1
mkdir -p ${tmpd}/dir2/sub/sub2
echo 'modified' > ${tmpd}/dir2/sub/sub2/different
mkdir -p ${tmpd}/dir2/new/new2
#tree ${tmpd}/dir2
# 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
# let's see the dotpath
#tree ${basedir}/dotfiles
# change dir1 to dir2 in deployed
echo "[+] change dir"
rm -rf ${tmpd}/dir1
mv ${tmpd}/dir2 ${tmpd}/dir1
#tree ${tmpd}/dir1
# change unique file
echo 'changed' > ${tmpd}/uniquefile
# compare
echo "[+] comparing"
set +e
cd ${ddpath} | ${bin} compare -c ${cfg} --verbose
[ "$?" = "0" ] && exit 1
set -e
## CLEANING
rm -rf ${basedir} ${tmpd}
echo "OK"
exit 0

View File

@@ -14,6 +14,7 @@ from dotdrop.dotdrop import importer
from dotdrop.dotdrop import compare
from dotdrop.dotfile import Dotfile
from dotdrop.installer import Installer
from dotdrop.comparator import Comparator
from dotdrop.templategen import Templategen
from tests.helpers import *
@@ -32,12 +33,20 @@ class TestCompare(unittest.TestCase):
t = Templategen(base=opts['dotpath'], debug=True)
inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], base=opts['dotpath'], debug=True)
comp = Comparator()
results = {}
for dotfile in dotfiles:
same, _ = inst.compare(t, tmp, opts['profile'],
dotfile.src, dotfile.dst)
ret, insttmp = inst.install_to_temp(t, tmp, opts['profile'],
dotfile.src, dotfile.dst)
if not ret:
results[path] = False
continue
diff = comp.compare(insttmp, dotfile.dst)
print('XXXX diff for {} and {}:\n{}'.format(dotfile.src,
dotfile.dst,
diff))
path = os.path.expanduser(dotfile.dst)
results[path] = same
results[path] = diff == ''
return results
def edit_content(self, path, newcontent, binary=False):