mirror of
https://github.com/deadc0de6/dotdrop.git
synced 2026-02-15 16:15:05 +00:00
adding "remove" option for #47 and fix a few issues/bugs
This commit is contained in:
@@ -38,6 +38,7 @@ _dotdrop ()
|
||||
'import'
|
||||
'compare'
|
||||
'update'
|
||||
'remove'
|
||||
'listfiles'
|
||||
'detail'
|
||||
'list'
|
||||
@@ -59,6 +60,9 @@ _dotdrop ()
|
||||
update)
|
||||
_dotdrop-update
|
||||
;;
|
||||
remove)
|
||||
_dotdrop-remove
|
||||
;;
|
||||
listfiles)
|
||||
_dotdrop-listfiles
|
||||
;;
|
||||
@@ -97,7 +101,7 @@ _dotdrop-install ()
|
||||
'(-D)-D' \
|
||||
'(--showdiff)--showdiff' \
|
||||
'(-a)-a' \
|
||||
'(--force-action)--force-action' \
|
||||
'(--force-actions)--force-actions' \
|
||||
'(-c=-)-c=-' \
|
||||
'(--cfg=-)--cfg=-' \
|
||||
'(-p=-)-p=-' \
|
||||
@@ -193,6 +197,35 @@ _dotdrop-update ()
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdrop-remove ()
|
||||
{
|
||||
local context state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
if [[ $words[$CURRENT] == -* ]] ; then
|
||||
_arguments -C \
|
||||
':command:->command' \
|
||||
'(-V)-V' \
|
||||
'(--verbose)--verbose' \
|
||||
'(-b)-b' \
|
||||
'(--no-banner)--no-banner' \
|
||||
'(-f)-f' \
|
||||
'(--force)--force' \
|
||||
'(-d)-d' \
|
||||
'(--dry)--dry' \
|
||||
'(-k)-k' \
|
||||
'(--key)--key' \
|
||||
'(-c=-)-c=-' \
|
||||
'(--cfg=-)--cfg=-' \
|
||||
'(-p=-)-p=-' \
|
||||
'(--profile=-)--profile=-' \
|
||||
|
||||
else
|
||||
myargs=('<path>')
|
||||
_message_next_arg
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdrop-listfiles ()
|
||||
{
|
||||
local context state state_descr line
|
||||
|
||||
@@ -38,6 +38,7 @@ _dotdrop.sh ()
|
||||
'import'
|
||||
'compare'
|
||||
'update'
|
||||
'remove'
|
||||
'listfiles'
|
||||
'detail'
|
||||
'list'
|
||||
@@ -59,6 +60,9 @@ _dotdrop.sh ()
|
||||
update)
|
||||
_dotdrop.sh-update
|
||||
;;
|
||||
remove)
|
||||
_dotdrop.sh-remove
|
||||
;;
|
||||
listfiles)
|
||||
_dotdrop.sh-listfiles
|
||||
;;
|
||||
@@ -97,7 +101,7 @@ _dotdrop.sh-install ()
|
||||
'(-D)-D' \
|
||||
'(--showdiff)--showdiff' \
|
||||
'(-a)-a' \
|
||||
'(--force-action)--force-action' \
|
||||
'(--force-actions)--force-actions' \
|
||||
'(-c=-)-c=-' \
|
||||
'(--cfg=-)--cfg=-' \
|
||||
'(-p=-)-p=-' \
|
||||
@@ -193,6 +197,35 @@ _dotdrop.sh-update ()
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdrop.sh-remove ()
|
||||
{
|
||||
local context state state_descr line
|
||||
typeset -A opt_args
|
||||
|
||||
if [[ $words[$CURRENT] == -* ]] ; then
|
||||
_arguments -C \
|
||||
':command:->command' \
|
||||
'(-V)-V' \
|
||||
'(--verbose)--verbose' \
|
||||
'(-b)-b' \
|
||||
'(--no-banner)--no-banner' \
|
||||
'(-f)-f' \
|
||||
'(--force)--force' \
|
||||
'(-d)-d' \
|
||||
'(--dry)--dry' \
|
||||
'(-k)-k' \
|
||||
'(--key)--key' \
|
||||
'(-c=-)-c=-' \
|
||||
'(--cfg=-)--cfg=-' \
|
||||
'(-p=-)-p=-' \
|
||||
'(--profile=-)--profile=-' \
|
||||
|
||||
else
|
||||
myargs=('<path>')
|
||||
_message_next_arg
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdrop.sh-listfiles ()
|
||||
{
|
||||
local context state state_descr line
|
||||
|
||||
@@ -5,7 +5,7 @@ _dotdrop()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [ $COMP_CWORD -eq 1 ]; then
|
||||
COMPREPLY=( $( compgen -W '-h --help -v --version install import compare update listfiles detail list' -- $cur) )
|
||||
COMPREPLY=( $( compgen -W '-h --help -v --version install import compare update remove listfiles detail list' -- $cur) )
|
||||
else
|
||||
case ${COMP_WORDS[1]} in
|
||||
install)
|
||||
@@ -19,6 +19,9 @@ _dotdrop()
|
||||
;;
|
||||
update)
|
||||
_dotdrop_update
|
||||
;;
|
||||
remove)
|
||||
_dotdrop_remove
|
||||
;;
|
||||
listfiles)
|
||||
_dotdrop_listfiles
|
||||
@@ -40,7 +43,7 @@ _dotdrop_install()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [ $COMP_CWORD -ge 2 ]; then
|
||||
COMPREPLY=( $( compgen -fW '-V --verbose -b --no-banner -t --temp -f --force -n --nodiff -d --dry -D --showdiff -a --force-action -c= --cfg= -p= --profile= ' -- $cur) )
|
||||
COMPREPLY=( $( compgen -fW '-V --verbose -b --no-banner -t --temp -f --force -n --nodiff -d --dry -D --showdiff -a --force-actions -c= --cfg= -p= --profile= ' -- $cur) )
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -74,6 +77,16 @@ _dotdrop_update()
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdrop_remove()
|
||||
{
|
||||
local cur
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [ $COMP_CWORD -ge 2 ]; then
|
||||
COMPREPLY=( $( compgen -fW '-V --verbose -b --no-banner -f --force -d --dry -k --key -c= --cfg= -p= --profile= ' -- $cur) )
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdrop_listfiles()
|
||||
{
|
||||
local cur
|
||||
|
||||
@@ -5,7 +5,7 @@ _dotdropsh()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [ $COMP_CWORD -eq 1 ]; then
|
||||
COMPREPLY=( $( compgen -W '-h --help -v --version install import compare update listfiles detail list' -- $cur) )
|
||||
COMPREPLY=( $( compgen -W '-h --help -v --version install import compare update remove listfiles detail list' -- $cur) )
|
||||
else
|
||||
case ${COMP_WORDS[1]} in
|
||||
install)
|
||||
@@ -19,6 +19,9 @@ _dotdropsh()
|
||||
;;
|
||||
update)
|
||||
_dotdropsh_update
|
||||
;;
|
||||
remove)
|
||||
_dotdropsh_remove
|
||||
;;
|
||||
listfiles)
|
||||
_dotdropsh_listfiles
|
||||
@@ -40,7 +43,7 @@ _dotdropsh_install()
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [ $COMP_CWORD -ge 2 ]; then
|
||||
COMPREPLY=( $( compgen -fW '-V --verbose -b --no-banner -t --temp -f --force -n --nodiff -d --dry -D --showdiff -a --force-action -c= --cfg= -p= --profile= ' -- $cur) )
|
||||
COMPREPLY=( $( compgen -fW '-V --verbose -b --no-banner -t --temp -f --force -n --nodiff -d --dry -D --showdiff -a --force-actions -c= --cfg= -p= --profile= ' -- $cur) )
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -74,6 +77,16 @@ _dotdropsh_update()
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdropsh_remove()
|
||||
{
|
||||
local cur
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
|
||||
if [ $COMP_CWORD -ge 2 ]; then
|
||||
COMPREPLY=( $( compgen -fW '-V --verbose -b --no-banner -f --force -d --dry -k --key -c= --cfg= -p= --profile= ' -- $cur) )
|
||||
fi
|
||||
}
|
||||
|
||||
_dotdropsh_listfiles()
|
||||
{
|
||||
local cur
|
||||
|
||||
@@ -19,6 +19,9 @@ from dotdrop.logger import Logger
|
||||
from dotdrop.utils import strip_home
|
||||
|
||||
|
||||
TILD = '~'
|
||||
|
||||
|
||||
class CfgAggregator:
|
||||
|
||||
file_prefix = 'f'
|
||||
@@ -99,9 +102,9 @@ class CfgAggregator:
|
||||
|
||||
# patch trans_w/trans_r in dotfiles
|
||||
self._patch_keys_to_objs(self.dotfiles,
|
||||
"trans_r", self.get_trans_r)
|
||||
"trans_r", self._get_trans_r)
|
||||
self._patch_keys_to_objs(self.dotfiles,
|
||||
"trans_w", self.get_trans_w)
|
||||
"trans_w", self._get_trans_w)
|
||||
|
||||
def _patch_keys_to_objs(self, containers, keys, get_by_key):
|
||||
"""
|
||||
@@ -128,6 +131,14 @@ class CfgAggregator:
|
||||
self.log.dbg('patching {}.{} with {}'.format(c, keys, objects))
|
||||
setattr(c, keys, objects)
|
||||
|
||||
def del_dotfile(self, dotfile):
|
||||
"""remove this dotfile from the config"""
|
||||
return self.cfgyaml.del_dotfile(dotfile.key)
|
||||
|
||||
def del_dotfile_from_profile(self, dotfile, profile):
|
||||
"""remove this dotfile from this profile"""
|
||||
return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key)
|
||||
|
||||
def new(self, src, dst, link, profile_key):
|
||||
"""
|
||||
import a new dotfile
|
||||
@@ -136,10 +147,9 @@ class CfgAggregator:
|
||||
@link: LinkType
|
||||
@profile_key: to which profile
|
||||
"""
|
||||
home = os.path.expanduser('~')
|
||||
dst = dst.replace(home, '~', 1)
|
||||
dst = self.path_to_dotfile_dst(dst)
|
||||
|
||||
dotfile = self._get_dotfile_by_dst(dst)
|
||||
dotfile = self.get_dotfile_by_dst(dst)
|
||||
if not dotfile:
|
||||
# get a new dotfile with a unique key
|
||||
key = self._get_new_dotfile_key(dst)
|
||||
@@ -228,8 +238,22 @@ class CfgAggregator:
|
||||
cnt += 1
|
||||
return newkey
|
||||
|
||||
def _get_dotfile_by_dst(self, dst):
|
||||
def path_to_dotfile_dst(self, path):
|
||||
"""normalize the path to match dotfile dst"""
|
||||
path = os.path.expanduser(path)
|
||||
path = os.path.expandvars(path)
|
||||
path = os.path.abspath(path)
|
||||
home = os.path.expanduser(TILD) + os.sep
|
||||
|
||||
# normalize the path
|
||||
if path.startswith(home):
|
||||
path = path[len(home):]
|
||||
path = os.path.join(TILD, path)
|
||||
return path
|
||||
|
||||
def get_dotfile_by_dst(self, dst):
|
||||
"""get a dotfile by dst"""
|
||||
dst = self.path_to_dotfile_dst(dst)
|
||||
try:
|
||||
return next(d for d in self.dotfiles if d.dst == dst)
|
||||
except StopIteration:
|
||||
@@ -262,12 +286,24 @@ class CfgAggregator:
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def get_profiles_by_dotfile_key(self, key):
|
||||
"""return all profiles having this dotfile"""
|
||||
res = []
|
||||
for p in self.profiles:
|
||||
keys = [d.key for d in p.dotfiles]
|
||||
if key in keys:
|
||||
res.append(p)
|
||||
return res
|
||||
|
||||
def get_dotfiles(self, profile=None):
|
||||
"""return dotfiles dict for this profile key"""
|
||||
if not profile:
|
||||
return self.dotfiles
|
||||
try:
|
||||
return next(x.dotfiles for x in self.profiles if x.key == profile)
|
||||
pro = self.get_profile(profile)
|
||||
if not pro:
|
||||
return []
|
||||
return pro.dotfiles
|
||||
except StopIteration:
|
||||
return []
|
||||
|
||||
@@ -278,7 +314,7 @@ class CfgAggregator:
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def get_action(self, key):
|
||||
def _get_action(self, key):
|
||||
"""return action by key"""
|
||||
try:
|
||||
return next(x for x in self.actions if x.key == key)
|
||||
@@ -293,19 +329,19 @@ class CfgAggregator:
|
||||
key, *args = fields
|
||||
if self.debug:
|
||||
self.log.dbg('action with parm: {} and {}'.format(key, args))
|
||||
action = self.get_action(key).copy(args)
|
||||
action = self._get_action(key).copy(args)
|
||||
else:
|
||||
action = self.get_action(key)
|
||||
action = self._get_action(key)
|
||||
return action
|
||||
|
||||
def get_trans_r(self, key):
|
||||
def _get_trans_r(self, key):
|
||||
"""return the trans_r with this key"""
|
||||
try:
|
||||
return next(x for x in self.trans_r if x.key == key)
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def get_trans_w(self, key):
|
||||
def _get_trans_w(self, key):
|
||||
"""return the trans_w with this key"""
|
||||
try:
|
||||
return next(x for x in self.trans_w if x.key == key)
|
||||
|
||||
@@ -8,6 +8,7 @@ handle lower level of the config file
|
||||
import os
|
||||
import yaml
|
||||
import glob
|
||||
from copy import deepcopy
|
||||
|
||||
# local imports
|
||||
from dotdrop.settings import Settings
|
||||
@@ -97,9 +98,10 @@ class CfgYaml:
|
||||
|
||||
def _parse_main_yaml(self, dic):
|
||||
"""parse the different blocks"""
|
||||
self.ori_settings = self._get_entry(self.yaml_dict, self.key_settings)
|
||||
self.ori_settings = self._get_entry(dic, self.key_settings)
|
||||
self.settings = Settings(None).serialize().get(self.key_settings)
|
||||
self.settings.update(self.ori_settings)
|
||||
|
||||
# resolve settings paths
|
||||
p = self._resolve_path(self.settings[self.key_settings_dotpath])
|
||||
self.settings[self.key_settings_dotpath] = p
|
||||
@@ -109,50 +111,60 @@ class CfgYaml:
|
||||
self.log.dbg('settings: {}'.format(self.settings))
|
||||
|
||||
# dotfiles
|
||||
self.dotfiles = self._get_entry(self.yaml_dict, self.key_dotfiles)
|
||||
self.ori_dotfiles = self._get_entry(dic, self.key_dotfiles)
|
||||
self.dotfiles = deepcopy(self.ori_dotfiles)
|
||||
keys = self.dotfiles.keys()
|
||||
if len(keys) != len(list(set(keys))):
|
||||
dups = [x for x in keys if x not in list(set(keys))]
|
||||
raise Exception('duplicate dotfile keys found: {}'.format(dups))
|
||||
self.dotfiles = self._norm_dotfiles(self.dotfiles)
|
||||
if self.debug:
|
||||
self.log.dbg('dotfiles: {}'.format(self.dotfiles))
|
||||
|
||||
# profiles
|
||||
self.profiles = self._get_entry(self.yaml_dict, self.key_profiles)
|
||||
self.ori_profiles = self._get_entry(dic, self.key_profiles)
|
||||
self.profiles = deepcopy(self.ori_profiles)
|
||||
if self.debug:
|
||||
self.log.dbg('profiles: {}'.format(self.profiles))
|
||||
|
||||
# actions
|
||||
self.actions = self._get_entry(self.yaml_dict, self.key_actions,
|
||||
self.ori_actions = self._get_entry(dic, self.key_actions,
|
||||
mandatory=False)
|
||||
self.actions = deepcopy(self.ori_actions)
|
||||
self.actions = self._norm_actions(self.actions)
|
||||
if self.debug:
|
||||
self.log.dbg('actions: {}'.format(self.actions))
|
||||
|
||||
# trans_r
|
||||
if self.old_key_trans_r in self.yaml_dict:
|
||||
key = self.key_trans_r
|
||||
if self.old_key_trans_r in dic:
|
||||
self.log.warn('\"trans\" is deprecated, please use \"trans_read\"')
|
||||
self.yaml_dict[self.key_trans_r] = self.yaml_dict.pop(
|
||||
self.old_key_trans_r
|
||||
)
|
||||
self.dirty = True
|
||||
self.trans_r = self._get_entry(self.yaml_dict, self.key_trans_r,
|
||||
mandatory=False)
|
||||
key = self.old_key_trans_r
|
||||
self.ori_trans_r = self._get_entry(dic, key, mandatory=False)
|
||||
self.trans_r = deepcopy(self.ori_trans_r)
|
||||
if self.debug:
|
||||
self.log.dbg('trans_r: {}'.format(self.trans_r))
|
||||
|
||||
# trans_w
|
||||
self.trans_w = self._get_entry(self.yaml_dict, self.key_trans_w,
|
||||
self.ori_trans_w = self._get_entry(dic, self.key_trans_w,
|
||||
mandatory=False)
|
||||
self.trans_w = deepcopy(self.ori_trans_w)
|
||||
if self.debug:
|
||||
self.log.dbg('trans_w: {}'.format(self.trans_w))
|
||||
|
||||
# variables
|
||||
self.variables = self._get_entry(self.yaml_dict, self.key_variables,
|
||||
self.ori_variables = self._get_entry(dic,
|
||||
self.key_variables,
|
||||
mandatory=False)
|
||||
self.variables = deepcopy(self.ori_variables)
|
||||
if self.debug:
|
||||
self.log.dbg('variables: {}'.format(self.variables))
|
||||
|
||||
# dynvariables
|
||||
self.dvariables = self._get_entry(self.yaml_dict, self.key_dvariables,
|
||||
self.ori_dvariables = self._get_entry(dic,
|
||||
self.key_dvariables,
|
||||
mandatory=False)
|
||||
self.dvariables = deepcopy(self.ori_dvariables)
|
||||
if self.debug:
|
||||
self.log.dbg('dvariables: {}'.format(self.dvariables))
|
||||
|
||||
@@ -493,7 +505,7 @@ class CfgYaml:
|
||||
values[self.key_profiles_dotfiles] = uniq_list(current)
|
||||
if self.debug:
|
||||
dfs = values[self.key_profiles_dotfiles]
|
||||
self.log.dbg('profile dfs after include: {}'.format(dfs))
|
||||
self.log.dbg('{} dfs after include: {}'.format(profile, dfs))
|
||||
return values.get(self.key_profiles_dotfiles, [])
|
||||
|
||||
def _resolve_path(self, path):
|
||||
@@ -538,21 +550,21 @@ class CfgYaml:
|
||||
"""merge low into high"""
|
||||
# won't work in python3.4
|
||||
# return {**low, **high}
|
||||
new = low.copy()
|
||||
new = deepcopy(low)
|
||||
new.update(high)
|
||||
return new
|
||||
|
||||
def _get_entry(self, yaml_dict, key, mandatory=True):
|
||||
def _get_entry(self, dic, key, mandatory=True):
|
||||
"""return entry from yaml dictionary"""
|
||||
if key not in yaml_dict:
|
||||
if key not in dic:
|
||||
if mandatory:
|
||||
raise Exception('invalid config: no {} found'.format(key))
|
||||
yaml_dict[key] = {}
|
||||
return yaml_dict[key]
|
||||
if mandatory and not yaml_dict[key]:
|
||||
dic[key] = {}
|
||||
return dic[key]
|
||||
if mandatory and not dic[key]:
|
||||
# ensure is not none
|
||||
yaml_dict[key] = {}
|
||||
return yaml_dict[key]
|
||||
dic[key] = {}
|
||||
return dic[key]
|
||||
|
||||
def _load_yaml(self, path):
|
||||
"""load a yaml file to a dict"""
|
||||
@@ -609,6 +621,40 @@ class CfgYaml:
|
||||
self.yaml_dict[self.key_dotfiles][key] = df_dict
|
||||
self.dirty = True
|
||||
|
||||
def del_dotfile(self, key):
|
||||
"""remove this dotfile from config"""
|
||||
if key not in self.yaml_dict[self.key_dotfiles]:
|
||||
self.log.err('key not in dotfiles: {}'.format(key))
|
||||
return False
|
||||
if self.debug:
|
||||
self.log.dbg('remove dotfile: {}'.format(key))
|
||||
del self.yaml_dict[self.key_dotfiles][key]
|
||||
if self.debug:
|
||||
dfs = self.yaml_dict[self.key_dotfiles]
|
||||
self.log.dbg('new dotfiles: {}'.format(dfs))
|
||||
self.dirty = True
|
||||
return True
|
||||
|
||||
def del_dotfile_from_profile(self, df_key, pro_key):
|
||||
"""remove this dotfile from that profile"""
|
||||
if df_key not in self.dotfiles.keys():
|
||||
self.log.err('key not in dotfiles: {}'.format(df_key))
|
||||
return False
|
||||
if pro_key not in self.profiles.keys():
|
||||
self.log.err('key not in profiles: {}'.format(pro_key))
|
||||
return False
|
||||
profiles = self.yaml_dict[self.key_profiles][pro_key]
|
||||
if self.debug:
|
||||
dfs = profiles[self.key_profiles_dotfiles]
|
||||
self.log.dbg('{} profile dotfiles: {}'.format(pro_key, dfs))
|
||||
self.log.dbg('remove {} from profile {}'.format(df_key, pro_key))
|
||||
profiles[self.key_profiles_dotfiles].remove(df_key)
|
||||
if self.debug:
|
||||
dfs = profiles[self.key_profiles_dotfiles]
|
||||
self.log.dbg('{} profile dotfiles: {}'.format(pro_key, dfs))
|
||||
self.dirty = True
|
||||
return True
|
||||
|
||||
def _fix_deprecated(self, yamldict):
|
||||
"""fix deprecated entries"""
|
||||
self._fix_deprecated_link_by_default(yamldict)
|
||||
@@ -671,9 +717,9 @@ class CfgYaml:
|
||||
newv = v
|
||||
if isinstance(v, dict):
|
||||
newv = self._clear_none(v)
|
||||
if v is None:
|
||||
if newv is None:
|
||||
continue
|
||||
if not v:
|
||||
if not newv:
|
||||
continue
|
||||
new[k] = newv
|
||||
return new
|
||||
|
||||
@@ -268,7 +268,10 @@ def cmd_update(o):
|
||||
if o.debug:
|
||||
LOG.dbg('dotfile to update: {}'.format(paths))
|
||||
|
||||
updater = Updater(o.dotpath, o.dotfiles, o.variables,
|
||||
updater = Updater(o.dotpath, o.variables,
|
||||
o.conf.get_dotfile,
|
||||
o.conf.get_dotfile_by_dst,
|
||||
o.conf.path_to_dotfile_dst,
|
||||
dry=o.dry, safe=o.safe, debug=o.debug,
|
||||
ignore=ignore, showpatch=showpatch)
|
||||
if not iskey:
|
||||
@@ -403,6 +406,67 @@ def cmd_detail(o):
|
||||
LOG.log('')
|
||||
|
||||
|
||||
def cmd_remove(o):
|
||||
"""remove dotfile from dotpath and from config"""
|
||||
paths = o.remove_path
|
||||
iskey = o.remove_iskey
|
||||
|
||||
if not paths:
|
||||
LOG.log('no dotfile to remove')
|
||||
return False
|
||||
if o.debug:
|
||||
LOG.dbg('dotfile to remove: {}'.format(paths))
|
||||
|
||||
removed = []
|
||||
for key in paths:
|
||||
if o.debug:
|
||||
LOG.dbg('removing {}'.format(key))
|
||||
if not iskey:
|
||||
# by path
|
||||
dotfile = o.conf.get_dotfile_by_dst(key)
|
||||
if not dotfile:
|
||||
LOG.warn('{} ignored, does not exist'.format(key))
|
||||
continue
|
||||
k = dotfile.key
|
||||
else:
|
||||
# by key
|
||||
dotfile = o.conf.get_dotfile(key)
|
||||
k = key
|
||||
# make sure is part of the profile
|
||||
if dotfile.key not in [d.key for d in o.dotfiles]:
|
||||
LOG.warn('{} ignored, not associated to this profile'.format(key))
|
||||
continue
|
||||
profiles = o.conf.get_profiles_by_dotfile_key(k)
|
||||
pkeys = ','.join([p.key for p in profiles])
|
||||
if o.dry:
|
||||
LOG.dry('would remove {} from {}'.format(dotfile, pkeys))
|
||||
continue
|
||||
msg = 'Remove dotfile from all these profiles: {}'.format(pkeys)
|
||||
if o.safe and not LOG.ask(msg):
|
||||
return False
|
||||
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
|
||||
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:
|
||||
LOG.dry('new config file would be:')
|
||||
LOG.raw(o.conf.dump())
|
||||
else:
|
||||
o.conf.save()
|
||||
LOG.log('\ndotfile(s) removed: {}'.format(','.join(removed)))
|
||||
return True
|
||||
|
||||
|
||||
###########################################################
|
||||
# helpers
|
||||
###########################################################
|
||||
@@ -444,8 +508,10 @@ def _select(selections, dotfiles):
|
||||
|
||||
|
||||
def apply_trans(dotpath, dotfile, debug=False):
|
||||
"""apply the read transformation to the dotfile
|
||||
return None if fails and new source if succeed"""
|
||||
"""
|
||||
apply the read transformation to the dotfile
|
||||
return None if fails and new source if succeed
|
||||
"""
|
||||
src = dotfile.src
|
||||
new_src = '{}.{}'.format(src, TRANS_SUFFIX)
|
||||
for trans in dotfile.trans_r:
|
||||
@@ -523,6 +589,12 @@ def main():
|
||||
LOG.dbg('running cmd: detail')
|
||||
cmd_detail(o)
|
||||
|
||||
elif o.cmd_remove:
|
||||
# remove dotfile
|
||||
if o.debug:
|
||||
LOG.dbg('running cmd: remove')
|
||||
cmd_remove(o)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
LOG.err('interrupted')
|
||||
ret = False
|
||||
|
||||
@@ -57,6 +57,7 @@ Usage:
|
||||
[-o <opts>] [-C <file>...] [-i <pattern>...]
|
||||
dotdrop update [-VbfdkP] [-c <path>] [-p <profile>]
|
||||
[-i <pattern>...] [<path>...]
|
||||
dotdrop remove [-Vbfdk] [-c <path>] [-p <profile>] [<path>...]
|
||||
dotdrop listfiles [-VbT] [-c <path>] [-p <profile>]
|
||||
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
|
||||
dotdrop list [-Vb] [-c <path>]
|
||||
@@ -193,6 +194,7 @@ class Options(AttrMonitor):
|
||||
self.cmd_import = self.args['import']
|
||||
self.cmd_update = self.args['update']
|
||||
self.cmd_detail = self.args['detail']
|
||||
self.cmd_remove = self.args['remove']
|
||||
|
||||
# adapt attributes based on arguments
|
||||
self.dry = self.args['--dry']
|
||||
@@ -236,6 +238,9 @@ class Options(AttrMonitor):
|
||||
self.update_showpatch = self.args['--show-patch']
|
||||
# "detail" specifics
|
||||
self.detail_keys = self.args['<key>']
|
||||
# "remove" specifics
|
||||
self.remove_path = self.args['<path>']
|
||||
self.remove_iskey = self.args['--key']
|
||||
|
||||
def _fill_attr(self):
|
||||
"""create attributes from conf"""
|
||||
|
||||
@@ -20,12 +20,17 @@ TILD = '~'
|
||||
|
||||
class Updater:
|
||||
|
||||
def __init__(self, dotpath, dotfiles, variables, dry=False, safe=True,
|
||||
def __init__(self, dotpath, variables,
|
||||
dotfile_key_getter, dotfile_dst_getter,
|
||||
dotfile_path_normalizer,
|
||||
dry=False, safe=True,
|
||||
debug=False, ignore=[], showpatch=False):
|
||||
"""constructor
|
||||
@dotpath: path where dotfiles are stored
|
||||
@dotfiles: dotfiles for this profile
|
||||
@variables: dictionary of variables for the templates
|
||||
@dotfile_key_getter: func to get a dotfile by key
|
||||
@dotfile_dst_getter: func to get a dotfile by dst
|
||||
@dotfile_path_normalizer: func to normalize dotfile dst
|
||||
@dry: simulate
|
||||
@safe: ask for overwrite if True
|
||||
@debug: enable debug
|
||||
@@ -33,8 +38,10 @@ class Updater:
|
||||
@showpatch: show patch if dotfile to update is a template
|
||||
"""
|
||||
self.dotpath = dotpath
|
||||
self.dotfiles = dotfiles
|
||||
self.variables = variables
|
||||
self.dotfile_key_getter = dotfile_key_getter
|
||||
self.dotfile_dst_getter = dotfile_dst_getter
|
||||
self.dotfile_path_normalizer = dotfile_path_normalizer
|
||||
self.dry = dry
|
||||
self.safe = safe
|
||||
self.debug = debug
|
||||
@@ -48,8 +55,7 @@ class Updater:
|
||||
if not os.path.lexists(path):
|
||||
self.log.err('\"{}\" does not exist!'.format(path))
|
||||
return False
|
||||
path = self._normalize(path)
|
||||
dotfile = self._get_dotfile_by_path(path)
|
||||
dotfile = self.dotfile_dst_getter(path)
|
||||
if not dotfile:
|
||||
return False
|
||||
if self.debug:
|
||||
@@ -58,12 +64,12 @@ class Updater:
|
||||
|
||||
def update_key(self, key):
|
||||
"""update the dotfile referenced by key"""
|
||||
dotfile = self._get_dotfile_by_key(key)
|
||||
dotfile = self.dotfile_key_getter(key)
|
||||
if not dotfile:
|
||||
return False
|
||||
if self.debug:
|
||||
self.log.dbg('updating {} from key \"{}\"'.format(dotfile, key))
|
||||
path = self._normalize(dotfile.dst)
|
||||
path = self.dotfile_path_normalizer(dotfile.dst)
|
||||
return self._update(path, dotfile)
|
||||
|
||||
def _update(self, path, dotfile):
|
||||
@@ -111,45 +117,6 @@ class Updater:
|
||||
return None
|
||||
return tmp
|
||||
|
||||
def _normalize(self, path):
|
||||
"""normalize the path to match dotfile"""
|
||||
path = os.path.expanduser(path)
|
||||
path = os.path.expandvars(path)
|
||||
path = os.path.abspath(path)
|
||||
home = os.path.expanduser(TILD) + os.sep
|
||||
|
||||
# normalize the path
|
||||
if path.startswith(home):
|
||||
path = path[len(home):]
|
||||
path = os.path.join(TILD, path)
|
||||
return path
|
||||
|
||||
def _get_dotfile_by_key(self, key):
|
||||
"""get the dotfile matching this key"""
|
||||
dotfiles = self.dotfiles
|
||||
subs = [d for d in dotfiles if d.key == key]
|
||||
if not subs:
|
||||
self.log.err('key \"{}\" not found!'.format(key))
|
||||
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):
|
||||
"""get the dotfile matching this path"""
|
||||
dotfiles = self.dotfiles
|
||||
subs = [d for d in dotfiles if d.dst == path]
|
||||
if not subs:
|
||||
self.log.err('\"{}\" is not managed!'.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 _is_template(self, path):
|
||||
if not Templategen.is_template(path):
|
||||
if self.debug:
|
||||
|
||||
179
tests-ng/remove.sh
Executable file
179
tests-ng/remove.sh
Executable file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env bash
|
||||
# author: deadc0de6 (https://github.com/deadc0de6)
|
||||
# Copyright (c) 2019, deadc0de6
|
||||
#
|
||||
# test remove
|
||||
# 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 -e "\e[96m\e[1m==> RUNNING $(basename $BASH_SOURCE) <==\e[0m"
|
||||
|
||||
################################################################
|
||||
# this is the test
|
||||
################################################################
|
||||
|
||||
# dotdrop directory
|
||||
tmps=`mktemp -d --suffix='-dotdrop-tests'`
|
||||
mkdir -p ${tmps}/dotfiles
|
||||
# the dotfile to be imported
|
||||
tmpd=`mktemp -d --suffix='-dotdrop-tests'`
|
||||
|
||||
# create the config file
|
||||
cfg="${tmps}/config.yaml"
|
||||
cat > ${cfg} << _EOF
|
||||
config:
|
||||
backup: true
|
||||
create: true
|
||||
dotpath: dotfiles
|
||||
dotfiles:
|
||||
f_abc:
|
||||
dst: ${tmpd}/abc
|
||||
src: abc
|
||||
f_def:
|
||||
dst: ${tmpd}/def
|
||||
src: def
|
||||
f_last:
|
||||
dst: ${tmpd}/last
|
||||
src: last
|
||||
profiles:
|
||||
p1:
|
||||
dotfiles:
|
||||
- f_abc
|
||||
- f_def
|
||||
p2:
|
||||
dotfiles:
|
||||
- f_def
|
||||
last:
|
||||
dotfiles:
|
||||
- f_last
|
||||
_EOF
|
||||
cfgbak="${tmps}/config.yaml.bak"
|
||||
cp ${cfg} ${cfgbak}
|
||||
|
||||
# create the dotfile
|
||||
echo "abc" > ${tmps}/dotfiles/abc
|
||||
echo "abc" > ${tmpd}/abc
|
||||
|
||||
echo "def" > ${tmps}/dotfiles/def
|
||||
echo "def" > ${tmpd}/def
|
||||
|
||||
# remove with bad profile
|
||||
cd ${ddpath} | ${bin} remove -f -k -p empty -c ${cfg} f_abc -V
|
||||
[ ! -e ${tmps}/dotfiles/abc ] && echo "dotfile in dotpath deleted" && exit 1
|
||||
[ ! -e ${tmpd}/abc ] && echo "source dotfile deleted" && exit 1
|
||||
[ ! -e ${tmps}/dotfiles/def ] && echo "dotfile in dotpath deleted" && exit 1
|
||||
[ ! -e ${tmpd}/def ] && echo "source dotfile deleted" && exit 1
|
||||
# ensure config not altered
|
||||
diff ${cfg} ${cfgbak}
|
||||
|
||||
# remove by key
|
||||
echo "[+] remove f_abc by key"
|
||||
cd ${ddpath} | ${bin} remove -p p1 -f -k -c ${cfg} f_abc -V
|
||||
cat ${cfg}
|
||||
echo "[+] remove f_def by key"
|
||||
cd ${ddpath} | ${bin} remove -p p2 -f -k -c ${cfg} f_def -V
|
||||
cat ${cfg}
|
||||
|
||||
# checks
|
||||
[ -e ${tmps}/dotfiles/abc ] && echo "dotfile in dotpath not deleted" && exit 1
|
||||
[ ! -e ${tmpd}/abc ] && echo "source dotfile deleted" && exit 1
|
||||
|
||||
[ -e ${tmps}/dotfiles/def ] && echo "dotfile in dotpath not deleted" && exit 1
|
||||
[ ! -e ${tmpd}/def ] && echo "source dotfile deleted" && exit 1
|
||||
|
||||
echo "[+] ========="
|
||||
|
||||
# create the config file
|
||||
cfg="${tmps}/config.yaml"
|
||||
cat > ${cfg} << _EOF
|
||||
config:
|
||||
backup: true
|
||||
create: true
|
||||
dotpath: dotfiles
|
||||
dotfiles:
|
||||
f_abc:
|
||||
dst: ${tmpd}/abc
|
||||
src: abc
|
||||
f_def:
|
||||
dst: ${tmpd}/def
|
||||
src: def
|
||||
f_last:
|
||||
dst: ${tmpd}/last
|
||||
src: last
|
||||
profiles:
|
||||
p1:
|
||||
dotfiles:
|
||||
- f_abc
|
||||
- f_def
|
||||
p2:
|
||||
dotfiles:
|
||||
- f_def
|
||||
last:
|
||||
dotfiles:
|
||||
- f_last
|
||||
_EOF
|
||||
cat ${cfg}
|
||||
|
||||
# create the dotfile
|
||||
echo "abc" > ${tmps}/dotfiles/abc
|
||||
echo "abc" > ${tmpd}/abc
|
||||
|
||||
echo "def" > ${tmps}/dotfiles/def
|
||||
echo "def" > ${tmpd}/def
|
||||
|
||||
# remove by key
|
||||
echo "[+] remove f_abc by path"
|
||||
cd ${ddpath} | ${bin} remove -p p1 -f -c ${cfg} ${tmpd}/abc -V
|
||||
cat ${cfg}
|
||||
echo "[+] remove f_def by path"
|
||||
cd ${ddpath} | ${bin} remove -p p2 -f -c ${cfg} ${tmpd}/def -V
|
||||
cat ${cfg}
|
||||
|
||||
# checks
|
||||
[ -e ${tmps}/dotfiles/abc ] && echo "(2) dotfile in dotpath not deleted" && exit 1
|
||||
[ ! -e ${tmpd}/abc ] && echo "(2) source dotfile deleted" && exit 1
|
||||
|
||||
[ -e ${tmps}/dotfiles/def ] && echo "(2) dotfile in dotpath not deleted" && exit 1
|
||||
[ ! -e ${tmpd}/def ] && echo "(2) source dotfile deleted" && exit 1
|
||||
|
||||
|
||||
cat ${cfg}
|
||||
|
||||
## CLEANING
|
||||
rm -rf ${tmps} ${tmpd}
|
||||
|
||||
echo "OK"
|
||||
exit 0
|
||||
@@ -136,6 +136,7 @@ def _fake_args():
|
||||
args['import'] = False
|
||||
args['update'] = False
|
||||
args['detail'] = False
|
||||
args['remove'] = False
|
||||
return args
|
||||
|
||||
|
||||
@@ -172,8 +173,13 @@ def get_dotfile_from_yaml(dic, path):
|
||||
# path is not the file in dotpath but on the FS
|
||||
dotfiles = dic['dotfiles']
|
||||
# src = get_path_strip_version(path)
|
||||
dotfile = [d for d in dotfiles.values() if d['dst'] == path][0]
|
||||
return dotfile
|
||||
home = os.path.expanduser('~')
|
||||
if path.startswith(home):
|
||||
path = path.replace(home, '~')
|
||||
dotfile = [d for d in dotfiles.values() if d['dst'] == path]
|
||||
if dotfile:
|
||||
return dotfile[0]
|
||||
return None
|
||||
|
||||
|
||||
def yaml_dashed_list(items, indent=0):
|
||||
@@ -261,6 +267,11 @@ def file_in_yaml(yaml_file, path, link=False):
|
||||
in_dst = path in (os.path.expanduser(x['dst']) for x in dotfiles)
|
||||
|
||||
if link:
|
||||
has_link = 'link' in get_dotfile_from_yaml(yaml_conf, path)
|
||||
df = get_dotfile_from_yaml(yaml_conf, path)
|
||||
has_link = False
|
||||
if df:
|
||||
has_link = 'link' in df
|
||||
else:
|
||||
return False
|
||||
return in_src and in_dst and has_link
|
||||
return in_src and in_dst
|
||||
|
||||
@@ -352,7 +352,7 @@ class TestImport(unittest.TestCase):
|
||||
self.assertFalse(any(a.endswith('ing') for a in actions))
|
||||
|
||||
# testing transformations
|
||||
transformations = y['trans_read'].keys()
|
||||
transformations = y['trans'].keys()
|
||||
self.assertTrue(all(t.endswith('ed') for t in transformations))
|
||||
self.assertFalse(any(t.endswith('ing') for t in transformations))
|
||||
transformations = y['trans_write'].keys()
|
||||
@@ -394,7 +394,7 @@ class TestImport(unittest.TestCase):
|
||||
self.assertFalse(any(action.endswith('ed') for action in actions))
|
||||
|
||||
# testing transformations
|
||||
transformations = y['trans_read'].keys()
|
||||
transformations = y['trans'].keys()
|
||||
self.assertTrue(all(t.endswith('ing') for t in transformations))
|
||||
self.assertFalse(any(t.endswith('ed') for t in transformations))
|
||||
transformations = y['trans_write'].keys()
|
||||
|
||||
@@ -486,7 +486,6 @@ exec bspwm
|
||||
# ensure dst is link
|
||||
self.assertTrue(os.path.islink(dst))
|
||||
# ensure dst not directly linked to src
|
||||
# TODO: maybe check that its actually linked to template folder
|
||||
self.assertNotEqual(os.path.realpath(dst), src)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user