mirror of
https://github.com/deadc0de6/dotdrop.git
synced 2026-02-04 17:24:46 +00:00
implement import --as for #220
This commit is contained in:
@@ -147,6 +147,16 @@ class CfgAggregator:
|
|||||||
"""remove this dotfile from this profile"""
|
"""remove this dotfile from this profile"""
|
||||||
return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key)
|
return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key)
|
||||||
|
|
||||||
|
def _create_new_dotfile(self, src, dst, link):
|
||||||
|
"""create a new dotfile"""
|
||||||
|
# get a new dotfile with a unique key
|
||||||
|
key = self._get_new_dotfile_key(dst)
|
||||||
|
if self.debug:
|
||||||
|
self.log.dbg('new dotfile key: {}'.format(key))
|
||||||
|
# add the dotfile
|
||||||
|
self.cfgyaml.add_dotfile(key, src, dst, link)
|
||||||
|
return Dotfile(key, dst, src)
|
||||||
|
|
||||||
def new(self, src, dst, link):
|
def new(self, src, dst, link):
|
||||||
"""
|
"""
|
||||||
import a new dotfile
|
import a new dotfile
|
||||||
@@ -155,34 +165,31 @@ class CfgAggregator:
|
|||||||
@link: LinkType
|
@link: LinkType
|
||||||
"""
|
"""
|
||||||
dst = self.path_to_dotfile_dst(dst)
|
dst = self.path_to_dotfile_dst(dst)
|
||||||
|
dotfile = self.get_dotfile_by_src_dst(src, dst)
|
||||||
dotfile = self.get_dotfile_by_dst(dst)
|
|
||||||
if not dotfile:
|
if not dotfile:
|
||||||
# get a new dotfile with a unique key
|
dotfile = self._create_new_dotfile(src, dst, link)
|
||||||
key = self._get_new_dotfile_key(dst)
|
|
||||||
if self.debug:
|
|
||||||
self.log.dbg('new dotfile key: {}'.format(key))
|
|
||||||
# add the dotfile
|
|
||||||
self.cfgyaml.add_dotfile(key, src, dst, link)
|
|
||||||
dotfile = Dotfile(key, dst, src)
|
|
||||||
|
|
||||||
key = dotfile.key
|
key = dotfile.key
|
||||||
ret = self.cfgyaml.add_dotfile_to_profile(key, self.profile_key)
|
ret = self.cfgyaml.add_dotfile_to_profile(key, self.profile_key)
|
||||||
if self.debug:
|
if ret and self.debug:
|
||||||
msg = 'new dotfile {} to profile {}'
|
msg = 'new dotfile {} to profile {}'
|
||||||
self.log.dbg(msg.format(key, self.profile_key))
|
self.log.dbg(msg.format(key, self.profile_key))
|
||||||
|
|
||||||
# reload
|
|
||||||
self.cfgyaml.save()
|
self.cfgyaml.save()
|
||||||
if self.debug:
|
if ret:
|
||||||
self.log.dbg('reloading config')
|
# reload
|
||||||
self._load()
|
if self.debug:
|
||||||
|
self.log.dbg('reloading config')
|
||||||
|
olddebug = self.debug
|
||||||
|
self.debug = False
|
||||||
|
self._load()
|
||||||
|
self.debug = olddebug
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _get_new_dotfile_key(self, dst):
|
def _get_new_dotfile_key(self, dst):
|
||||||
"""return a new unique dotfile key"""
|
"""return a new unique dotfile key"""
|
||||||
path = os.path.expanduser(dst)
|
path = os.path.expanduser(dst)
|
||||||
existing_keys = [x.key for x in self.dotfiles]
|
existing_keys = self.cfgyaml.get_all_dotfile_keys()
|
||||||
if self.settings.longkey:
|
if self.settings.longkey:
|
||||||
return self._get_long_key(path, existing_keys)
|
return self._get_long_key(path, existing_keys)
|
||||||
return self._get_short_key(path, existing_keys)
|
return self._get_short_key(path, existing_keys)
|
||||||
@@ -257,11 +264,28 @@ class CfgAggregator:
|
|||||||
return path
|
return path
|
||||||
|
|
||||||
def get_dotfile_by_dst(self, dst):
|
def get_dotfile_by_dst(self, dst):
|
||||||
"""get a dotfile by dst"""
|
"""
|
||||||
|
get a list of dotfiles by dst
|
||||||
|
@dst: dotfile dst (on filesystem)
|
||||||
|
"""
|
||||||
|
dotfiles = []
|
||||||
dst = self._norm_path(dst)
|
dst = self._norm_path(dst)
|
||||||
for d in self.dotfiles:
|
for d in self.dotfiles:
|
||||||
left = self._norm_path(d.dst)
|
left = self._norm_path(d.dst)
|
||||||
if left == dst:
|
if left == dst:
|
||||||
|
dotfiles.append(d)
|
||||||
|
return dotfiles
|
||||||
|
|
||||||
|
def get_dotfile_by_src_dst(self, src, dst):
|
||||||
|
"""
|
||||||
|
get a dotfile by src and dst
|
||||||
|
@src: dotfile src (in dotpath)
|
||||||
|
@dst: dotfile dst (on filesystem)
|
||||||
|
"""
|
||||||
|
src = self.cfgyaml.resolve_dotfile_src(src)
|
||||||
|
dotfiles = self.get_dotfile_by_dst(dst)
|
||||||
|
for d in dotfiles:
|
||||||
|
if d.src == src:
|
||||||
return d
|
return d
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -815,6 +815,10 @@ class CfgYaml:
|
|||||||
self.dirty = True
|
self.dirty = True
|
||||||
return self.dirty
|
return self.dirty
|
||||||
|
|
||||||
|
def get_all_dotfile_keys(self):
|
||||||
|
"""return all existing dotfile keys"""
|
||||||
|
return self.dotfiles.keys()
|
||||||
|
|
||||||
def add_dotfile(self, key, src, dst, link):
|
def add_dotfile(self, key, src, dst, link):
|
||||||
"""add a new dotfile"""
|
"""add a new dotfile"""
|
||||||
if key in self.dotfiles.keys():
|
if key in self.dotfiles.keys():
|
||||||
|
|||||||
@@ -341,7 +341,16 @@ def cmd_importer(o):
|
|||||||
continue
|
continue
|
||||||
dst = path.rstrip(os.sep)
|
dst = path.rstrip(os.sep)
|
||||||
dst = os.path.abspath(dst)
|
dst = os.path.abspath(dst)
|
||||||
|
|
||||||
src = strip_home(dst)
|
src = strip_home(dst)
|
||||||
|
if o.import_as:
|
||||||
|
# handle import as
|
||||||
|
src = o.import_as.rstrip(os.sep)
|
||||||
|
src = os.path.abspath(src)
|
||||||
|
src = strip_home(src)
|
||||||
|
if o.debug:
|
||||||
|
LOG.dbg('import src for {}: {}'.format(dst, src))
|
||||||
|
|
||||||
strip = '.' + os.sep
|
strip = '.' + os.sep
|
||||||
if o.keepdot:
|
if o.keepdot:
|
||||||
strip = os.sep
|
strip = os.sep
|
||||||
@@ -358,6 +367,25 @@ def cmd_importer(o):
|
|||||||
if o.debug:
|
if o.debug:
|
||||||
LOG.dbg('import dotfile: src:{} dst:{}'.format(src, dst))
|
LOG.dbg('import dotfile: src:{} dst:{}'.format(src, dst))
|
||||||
|
|
||||||
|
# test no other dotfile exists with same
|
||||||
|
# dst for this profile but different src
|
||||||
|
dfs = o.conf.get_dotfile_by_dst(dst)
|
||||||
|
if dfs:
|
||||||
|
invalid = False
|
||||||
|
for df in dfs:
|
||||||
|
profiles = o.conf.get_profiles_by_dotfile_key(df.key)
|
||||||
|
profiles = [x.key for x in profiles]
|
||||||
|
if o.profile in profiles and \
|
||||||
|
not o.conf.get_dotfile_by_src_dst(src, dst):
|
||||||
|
# same profile
|
||||||
|
# different src
|
||||||
|
LOG.err('duplicate dotfile for this profile')
|
||||||
|
ret = False
|
||||||
|
invalid = True
|
||||||
|
break
|
||||||
|
if invalid:
|
||||||
|
continue
|
||||||
|
|
||||||
# prepare hierarchy for dotfile
|
# prepare hierarchy for dotfile
|
||||||
srcf = os.path.join(o.dotpath, src)
|
srcf = os.path.join(o.dotpath, src)
|
||||||
overwrite = not os.path.exists(srcf)
|
overwrite = not os.path.exists(srcf)
|
||||||
@@ -481,52 +509,54 @@ def cmd_remove(o):
|
|||||||
for key in paths:
|
for key in paths:
|
||||||
if not iskey:
|
if not iskey:
|
||||||
# by path
|
# by path
|
||||||
dotfile = o.conf.get_dotfile_by_dst(key)
|
dotfiles = o.conf.get_dotfile_by_dst(key)
|
||||||
if not dotfile:
|
if not dotfiles:
|
||||||
LOG.warn('{} ignored, does not exist'.format(key))
|
LOG.warn('{} ignored, does not exist'.format(key))
|
||||||
continue
|
continue
|
||||||
k = dotfile.key
|
|
||||||
else:
|
else:
|
||||||
# by key
|
# by key
|
||||||
dotfile = o.conf.get_dotfile(key)
|
dotfile = o.conf.get_dotfile(key)
|
||||||
if not dotfile:
|
if not dotfile:
|
||||||
LOG.warn('{} ignored, does not exist'.format(key))
|
LOG.warn('{} ignored, does not exist'.format(key))
|
||||||
continue
|
continue
|
||||||
k = key
|
dotfiles = [dotfile]
|
||||||
|
|
||||||
# ignore if uses any type of link
|
for dotfile in dotfiles:
|
||||||
if dotfile.link != LinkTypes.NOLINK:
|
k = dotfile.key
|
||||||
LOG.warn('dotfile uses link, remove manually')
|
# ignore if uses any type of link
|
||||||
continue
|
if dotfile.link != LinkTypes.NOLINK:
|
||||||
|
LOG.warn('dotfile uses link, remove manually')
|
||||||
|
continue
|
||||||
|
|
||||||
if o.debug:
|
if o.debug:
|
||||||
LOG.dbg('removing {}'.format(key))
|
LOG.dbg('removing {}'.format(key))
|
||||||
|
|
||||||
# make sure is part of the profile
|
# make sure is part of the profile
|
||||||
if dotfile.key not in [d.key for d in o.dotfiles]:
|
if dotfile.key not in [d.key for d in o.dotfiles]:
|
||||||
LOG.warn('{} ignored, not associated to this profile'.format(key))
|
msg = '{} ignored, not associated to this profile'
|
||||||
continue
|
LOG.warn(msg.format(key))
|
||||||
profiles = o.conf.get_profiles_by_dotfile_key(k)
|
continue
|
||||||
pkeys = ','.join([p.key for p in profiles])
|
profiles = o.conf.get_profiles_by_dotfile_key(k)
|
||||||
if o.dry:
|
pkeys = ','.join([p.key for p in profiles])
|
||||||
LOG.dry('would remove {} from {}'.format(dotfile, pkeys))
|
if o.dry:
|
||||||
continue
|
LOG.dry('would remove {} from {}'.format(dotfile, pkeys))
|
||||||
msg = 'Remove \"{}\" from all these profiles: {}'.format(k, pkeys)
|
continue
|
||||||
if o.safe and not LOG.ask(msg):
|
msg = 'Remove \"{}\" from all these profiles: {}'.format(k, pkeys)
|
||||||
return False
|
if o.safe and not LOG.ask(msg):
|
||||||
if o.debug:
|
|
||||||
LOG.dbg('remove dotfile: {}'.format(dotfile))
|
|
||||||
|
|
||||||
for profile in profiles:
|
|
||||||
if not o.conf.del_dotfile_from_profile(dotfile, profile):
|
|
||||||
return False
|
return False
|
||||||
if not o.conf.del_dotfile(dotfile):
|
if o.debug:
|
||||||
return False
|
LOG.dbg('remove dotfile: {}'.format(dotfile))
|
||||||
|
|
||||||
# remove dotfile from dotpath
|
for profile in profiles:
|
||||||
dtpath = os.path.join(o.dotpath, dotfile.src)
|
if not o.conf.del_dotfile_from_profile(dotfile, profile):
|
||||||
remove(dtpath)
|
return False
|
||||||
removed.append(dotfile.key)
|
if not o.conf.del_dotfile(dotfile):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# remove dotfile from dotpath
|
||||||
|
dtpath = os.path.join(o.dotpath, dotfile.src)
|
||||||
|
remove(dtpath)
|
||||||
|
removed.append(dotfile.key)
|
||||||
|
|
||||||
if o.dry:
|
if o.dry:
|
||||||
LOG.dry('new config file would be:')
|
LOG.dry('new config file would be:')
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ USAGE = """
|
|||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
dotdrop install [-VbtfndDa] [-c <path>] [-p <profile>] [<key>...]
|
dotdrop install [-VbtfndDa] [-c <path>] [-p <profile>] [<key>...]
|
||||||
dotdrop import [-Vbdf] [-c <path>] [-p <profile>]
|
dotdrop import [-Vbdf] [-c <path>] [-p <profile>] [-s <path>]
|
||||||
[-l <link>] <path>...
|
[-l <link>] <path>...
|
||||||
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
|
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
|
||||||
[-C <file>...] [-i <pattern>...]
|
[-C <file>...] [-i <pattern>...]
|
||||||
@@ -72,6 +72,7 @@ Options:
|
|||||||
-i --ignore=<pattern> Pattern to ignore.
|
-i --ignore=<pattern> Pattern to ignore.
|
||||||
-l --link=<link> Link option (nolink|link|link_children).
|
-l --link=<link> Link option (nolink|link|link_children).
|
||||||
-p --profile=<profile> Specify the profile to use [default: {}].
|
-p --profile=<profile> Specify the profile to use [default: {}].
|
||||||
|
-s --as=<path> Import as a different path from actual path.
|
||||||
-b --no-banner Do not display the banner.
|
-b --no-banner Do not display the banner.
|
||||||
-d --dry Dry run.
|
-d --dry Dry run.
|
||||||
-D --showdiff Show a diff before overwriting.
|
-D --showdiff Show a diff before overwriting.
|
||||||
@@ -237,6 +238,7 @@ class Options(AttrMonitor):
|
|||||||
self.compare_ignore = uniq_list(self.compare_ignore)
|
self.compare_ignore = uniq_list(self.compare_ignore)
|
||||||
# "import" specifics
|
# "import" specifics
|
||||||
self.import_path = self.args['<path>']
|
self.import_path = self.args['<path>']
|
||||||
|
self.import_as = self.args['--as']
|
||||||
# "update" specifics
|
# "update" specifics
|
||||||
self.update_path = self.args['<path>']
|
self.update_path = self.args['<path>']
|
||||||
self.update_iskey = self.args['--key']
|
self.update_iskey = self.args['--key']
|
||||||
|
|||||||
@@ -61,12 +61,21 @@ class Updater:
|
|||||||
if not os.path.lexists(path):
|
if not os.path.lexists(path):
|
||||||
self.log.err('\"{}\" does not exist!'.format(path))
|
self.log.err('\"{}\" does not exist!'.format(path))
|
||||||
return False
|
return False
|
||||||
dotfile = self.dotfile_dst_getter(path)
|
dotfiles = self.dotfile_dst_getter(path)
|
||||||
if not dotfile:
|
if not dotfiles:
|
||||||
return False
|
return False
|
||||||
if self.debug:
|
for dotfile in dotfiles:
|
||||||
self.log.dbg('updating {} from path \"{}\"'.format(dotfile, path))
|
if not dotfile:
|
||||||
return self._update(path, dotfile)
|
msg = 'invalid dotfile for update: {}'
|
||||||
|
self.log.err(msg.format(dotfile.key))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
msg = 'updating {} from path \"{}\"'
|
||||||
|
self.log.dbg(msg.format(dotfile, path))
|
||||||
|
if not self._update(path, dotfile):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
def update_key(self, key):
|
def update_key(self, key):
|
||||||
"""update the dotfile referenced by key"""
|
"""update the dotfile referenced by key"""
|
||||||
|
|||||||
122
tests-ng/import-as.sh
Executable file
122
tests-ng/import-as.sh
Executable file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||||
|
# Copyright (c) 2019, deadc0de6
|
||||||
|
#
|
||||||
|
# test basic import
|
||||||
|
#
|
||||||
|
|
||||||
|
# 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 "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
|
||||||
|
|
||||||
|
################################################################
|
||||||
|
# this is the test
|
||||||
|
################################################################
|
||||||
|
|
||||||
|
# the dotfile source
|
||||||
|
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
|
||||||
|
mkdir -p ${tmps}/dotfiles
|
||||||
|
# the dotfile destination
|
||||||
|
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
|
||||||
|
#echo "dotfile destination: ${tmpd}"
|
||||||
|
|
||||||
|
# create the dotfile
|
||||||
|
mkdir -p ${tmpd}/adir
|
||||||
|
echo "adir/file1" > ${tmpd}/adir/file1
|
||||||
|
echo "adir/fil2" > ${tmpd}/adir/file2
|
||||||
|
echo "file3" > ${tmpd}/file3
|
||||||
|
|
||||||
|
# create the config file
|
||||||
|
cfg="${tmps}/config.yaml"
|
||||||
|
|
||||||
|
cat > ${cfg} << _EOF
|
||||||
|
config:
|
||||||
|
backup: true
|
||||||
|
create: true
|
||||||
|
dotpath: dotfiles
|
||||||
|
dotfiles:
|
||||||
|
profiles:
|
||||||
|
_EOF
|
||||||
|
#cat ${cfg}
|
||||||
|
|
||||||
|
# import
|
||||||
|
cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V ${tmpd}/adir
|
||||||
|
cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V ${tmpd}/file3
|
||||||
|
|
||||||
|
echo "import --as dotfiles"
|
||||||
|
cd ${ddpath} | ${bin} import -c ${cfg} -p p2 -V ${tmpd}/adir --as ~/config/adir
|
||||||
|
cd ${ddpath} | ${bin} import -c ${cfg} -p p2 -V ${tmpd}/file3 --as ~/config2/file3
|
||||||
|
|
||||||
|
cat ${cfg}
|
||||||
|
|
||||||
|
set +e
|
||||||
|
cd ${ddpath} | ${bin} import -c ${cfg} -p p2 -V ${tmpd}/adir --as ~/config/should_not && echo "dual dst imported" && exit 1
|
||||||
|
set -e
|
||||||
|
cat ${cfg} | grep should_not && echo "dual dst imported" && exit 1
|
||||||
|
|
||||||
|
cat ${cfg}
|
||||||
|
|
||||||
|
echo "ensure exists and is not link"
|
||||||
|
[ ! -d ${tmps}/dotfiles/${tmpd}/adir ] && echo "not a directory" && exit 1
|
||||||
|
[ ! -e ${tmps}/dotfiles/${tmpd}/adir/file1 ] && echo "not exist" && exit 1
|
||||||
|
[ ! -e ${tmps}/dotfiles/${tmpd}/adir/file2 ] && echo "not exist" && exit 1
|
||||||
|
[ ! -e ${tmps}/dotfiles/${tmpd}/file3 ] && echo "not a file" && exit 1
|
||||||
|
|
||||||
|
echo "ensure --as are correctly imported"
|
||||||
|
[ ! -d ${tmps}/dotfiles/config/adir ] && echo "not a directory" && exit 1
|
||||||
|
[ ! -e ${tmps}/dotfiles/config/adir/file1 ] && echo "not exist" && exit 1
|
||||||
|
[ ! -e ${tmps}/dotfiles/config/adir/file2 ] && echo "not exist" && exit 1
|
||||||
|
[ ! -e ${tmps}/dotfiles/config2/file3 ] && echo "not a file" && exit 1
|
||||||
|
|
||||||
|
cat ${cfg} | grep ${tmpd}/adir >/dev/null 2>&1
|
||||||
|
cat ${cfg} | grep ${tmpd}/file3 >/dev/null 2>&1
|
||||||
|
|
||||||
|
cat ${cfg} | grep config/adir >/dev/null 2>&1
|
||||||
|
cat ${cfg} | grep config2/file3 >/dev/null 2>&1
|
||||||
|
|
||||||
|
nb=`cat ${cfg} | grep d_adir | wc -l`
|
||||||
|
[ "${nb}" != "2" ] && echo 'bad config1' && exit 1
|
||||||
|
nb=`cat ${cfg} | grep f_file3 | wc -l`
|
||||||
|
[ "${nb}" != "2" ] && echo 'bad config2' && exit 1
|
||||||
|
|
||||||
|
cat ${cfg} | grep "src: config/adir" || exit 1
|
||||||
|
cat ${cfg} | grep "src: config2/file3" || exit 1
|
||||||
|
|
||||||
|
cat ${cfg}
|
||||||
|
|
||||||
|
## CLEANING
|
||||||
|
rm -rf ${tmps} ${tmpd}
|
||||||
|
|
||||||
|
echo "OK"
|
||||||
|
exit 0
|
||||||
@@ -129,6 +129,7 @@ def _fake_args():
|
|||||||
args['--show-patch'] = False
|
args['--show-patch'] = False
|
||||||
args['--force-actions'] = False
|
args['--force-actions'] = False
|
||||||
args['--grepable'] = False
|
args['--grepable'] = False
|
||||||
|
args['--as'] = None
|
||||||
# cmds
|
# cmds
|
||||||
args['profiles'] = False
|
args['profiles'] = False
|
||||||
args['files'] = False
|
args['files'] = False
|
||||||
|
|||||||
Reference in New Issue
Block a user