1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-11 12:34:16 +00:00

refactor link default config variables

This commit is contained in:
deadc0de6
2019-03-29 14:43:08 +01:00
parent 018be3be40
commit 3e58857d43
9 changed files with 362 additions and 62 deletions

View File

@@ -5,7 +5,7 @@ config:
banner: true banner: true
longkey: false longkey: false
keepdot: false keepdot: false
link_import_default: false link_import_default: nolink
link_install_default: nolink link_dotfile_default: nolink
dotfiles: dotfiles:
profiles: profiles:

View File

@@ -32,16 +32,10 @@ class Cfg:
key_ignoreempty = 'ignoreempty' key_ignoreempty = 'ignoreempty'
key_showdiff = 'showdiff' key_showdiff = 'showdiff'
key_imp_link = 'link_import_default' key_imp_link = 'link_import_default'
key_inst_link = 'link_install_default' key_dotfile_link = 'link_dotfile_default'
key_workdir = 'workdir' key_workdir = 'workdir'
key_include_vars = 'import_variables' key_include_vars = 'import_variables'
# below entries will be automatically transformed
# to their new counterpart
key_deprecated = {
'link_by_default': key_imp_link,
}
# actions keys # actions keys
key_actions = 'actions' key_actions = 'actions'
key_actions_pre = 'pre' key_actions_pre = 'pre'
@@ -89,8 +83,8 @@ class Cfg:
default_keepdot = False default_keepdot = False
default_showdiff = False default_showdiff = False
default_ignoreempty = False default_ignoreempty = False
default_link_imp = False default_link_imp = lnk_nolink
default_link_inst = 'nolink' default_link = lnk_nolink
default_workdir = '~/.config/dotdrop' default_workdir = '~/.config/dotdrop'
def __init__(self, cfgpath, profile=None, debug=False): def __init__(self, cfgpath, profile=None, debug=False):
@@ -106,6 +100,7 @@ class Cfg:
# make sure to have an absolute path to config file # make sure to have an absolute path to config file
self.cfgpath = os.path.abspath(cfgpath) self.cfgpath = os.path.abspath(cfgpath)
self.debug = debug self.debug = debug
self._modified = False
# init the logger # init the logger
self.log = Logger() self.log = Logger()
@@ -196,9 +191,13 @@ class Cfg:
return False return False
return True return True
def _get_def_inst_link(self): def _get_def_link(self):
"""get dotfile link entry when not specified""" """get dotfile link entry when not specified"""
string = self.lnk_settings[self.key_inst_link].lower() string = self.lnk_settings[self.key_dotfile_link].lower()
return self._string_to_linktype(string)
def _string_to_linktype(self, string):
"""translate string to linktype"""
if string == self.lnk_parent.lower(): if string == self.lnk_parent.lower():
return LinkTypes.PARENT return LinkTypes.PARENT
elif string == self.lnk_children.lower(): elif string == self.lnk_children.lower():
@@ -209,7 +208,8 @@ class Cfg:
"""parse config file""" """parse config file"""
# parse the settings # parse the settings
self.lnk_settings = self.content[self.key_settings] self.lnk_settings = self.content[self.key_settings]
self._complete_settings() if not self._complete_settings():
return False
# parse the profiles # parse the profiles
self.lnk_profiles = self.content[self.key_profiles] self.lnk_profiles = self.content[self.key_profiles]
@@ -278,8 +278,9 @@ class Cfg:
if not self.content[self.key_dotfiles]: if not self.content[self.key_dotfiles]:
# ensures the dotfiles entry is a dict # ensures the dotfiles entry is a dict
self.content[self.key_dotfiles] = {} self.content[self.key_dotfiles] = {}
def_link_val = self._get_def_inst_link()
for k, v in self.content[self.key_dotfiles].items(): for k in self.content[self.key_dotfiles].keys():
v = self.content[self.key_dotfiles][k]
src = os.path.normpath(v[self.key_dotfiles_src]) src = os.path.normpath(v[self.key_dotfiles_src])
dst = os.path.normpath(v[self.key_dotfiles_dst]) dst = os.path.normpath(v[self.key_dotfiles_dst])
@@ -291,17 +292,21 @@ class Cfg:
self.log.err(msg.format(k)) self.log.err(msg.format(k))
return False return False
# Otherwise, get link type # fix it
link = def_link_val v = self._fix_dotfile_link(k, v)
if self.key_dotfiles_link in v and v[self.key_dotfiles_link]: self.content[self.key_dotfiles][k] = v
link = LinkTypes.PARENT
if self.key_dotfiles_link_children in v \
and v[self.key_dotfiles_link_children]:
link = LinkTypes.CHILDREN
# get link type
link = self._get_def_link()
if self.key_dotfiles_link in v:
link = self._string_to_linktype(v[self.key_dotfiles_link])
# get ignore empty
noempty = v[self.key_dotfiles_noempty] if \ noempty = v[self.key_dotfiles_noempty] if \
self.key_dotfiles_noempty \ self.key_dotfiles_noempty \
in v else self.lnk_settings[self.key_ignoreempty] in v else self.lnk_settings[self.key_ignoreempty]
# parse actions
itsactions = v[self.key_dotfiles_actions] if \ itsactions = v[self.key_dotfiles_actions] if \
self.key_dotfiles_actions in v else [] self.key_dotfiles_actions in v else []
actions = self._parse_actions(itsactions) actions = self._parse_actions(itsactions)
@@ -563,7 +568,7 @@ class Cfg:
def _complete_settings(self): def _complete_settings(self):
"""set settings defaults if not present""" """set settings defaults if not present"""
self._deprecated() self._fix_deprecated()
if self.key_dotpath not in self.lnk_settings: if self.key_dotpath not in self.lnk_settings:
self.lnk_settings[self.key_dotpath] = self.default_dotpath self.lnk_settings[self.key_dotpath] = self.default_dotpath
if self.key_backup not in self.lnk_settings: if self.key_backup not in self.lnk_settings:
@@ -576,32 +581,99 @@ class Cfg:
self.lnk_settings[self.key_long] = self.default_longkey self.lnk_settings[self.key_long] = self.default_longkey
if self.key_keepdot not in self.lnk_settings: if self.key_keepdot not in self.lnk_settings:
self.lnk_settings[self.key_keepdot] = self.default_keepdot self.lnk_settings[self.key_keepdot] = self.default_keepdot
if self.key_imp_link not in self.lnk_settings:
self.lnk_settings[self.key_imp_link] = self.default_link_imp
if self.key_workdir not in self.lnk_settings: if self.key_workdir not in self.lnk_settings:
self.lnk_settings[self.key_workdir] = self.default_workdir self.lnk_settings[self.key_workdir] = self.default_workdir
if self.key_showdiff not in self.lnk_settings: if self.key_showdiff not in self.lnk_settings:
self.lnk_settings[self.key_showdiff] = self.default_showdiff self.lnk_settings[self.key_showdiff] = self.default_showdiff
if self.key_ignoreempty not in self.lnk_settings: if self.key_ignoreempty not in self.lnk_settings:
self.lnk_settings[self.key_ignoreempty] = self.default_ignoreempty self.lnk_settings[self.key_ignoreempty] = self.default_ignoreempty
if self.key_inst_link not in self.lnk_settings:
self.lnk_settings[self.key_inst_link] = self.default_link_inst
def _deprecated(self): if self.key_dotfile_link not in self.lnk_settings:
self.lnk_settings[self.key_dotfile_link] = self.default_link
else:
key = self.lnk_settings[self.key_dotfile_link]
if key != self.lnk_parent and \
key != self.lnk_children and \
key != self.lnk_nolink:
self.log.err('bad value for {}'.format(self.key_dotfile_link))
return False
if self.key_imp_link not in self.lnk_settings:
self.lnk_settings[self.key_imp_link] = self.default_link_imp
elif self.lnk_settings[self.key_imp_link] == self.lnk_children:
msg = '\"{}\" not supported in {}'.format(self.lnk_children,
self.key_imp_link)
self.log.err(msg)
return False
else:
key = self.lnk_settings[self.key_imp_link]
if key != self.lnk_parent and \
key != self.lnk_children and \
key != self.lnk_nolink:
self.log.err('bad value for {}'.format(self.key_dotfile_link))
return False
return True
def _fix_deprecated(self):
"""fix deprecated entries""" """fix deprecated entries"""
for k, v in self.key_deprecated.items(): # link_by_default
if k in self.lnk_settings: key = 'link_by_default'
# replace newkey = self.key_imp_link
entry = self.lnk_settings[k] if key in self.lnk_settings:
self.lnk_settings[v] = entry if self.lnk_settings[key]:
del self.lnk_settings[k] self.lnk_settings[newkey] = self.lnk_parent
else:
self.lnk_settings[newkey] = self.lnk_nolink
del self.lnk_settings[key]
self._modified = True
def _fix_dotfile_link(self, key, entry):
"""fix deprecated link usage in dotfile entry"""
v = entry
if self.key_dotfiles_link not in v \
and self.key_dotfiles_link_children not in v:
# nothing defined
return v
new = self.lnk_nolink
if self.key_dotfiles_link in v \
and type(v[self.key_dotfiles_link]) is bool:
# patch link: <bool>
if v[self.key_dotfiles_link]:
new = self.lnk_parent
else:
new = self.lnk_nolink
self._modified = True
if self.debug:
self.log.dbg('link updated for {} to {}'.format(key, new))
elif self.key_dotfiles_link_children in v \
and type(v[self.key_dotfiles_link_children]) is bool:
# patch link_children: <bool>
if v[self.key_dotfiles_link_children]:
new = self.lnk_children
else:
new = self.lnk_nolink
del v[self.key_dotfiles_link_children]
self._modified = True
if self.debug:
self.log.dbg('link updated for {} to {}'.format(key, new))
else:
# no change
new = v[self.key_dotfiles_link]
v[self.key_dotfiles_link] = new
return v
def _save(self, content, path): def _save(self, content, path):
"""writes the config to file""" """writes the config to file"""
ret = False ret = False
with open(path, 'w') as f: with open(path, 'w') as f:
ret = yaml.dump(content, f, ret = yaml.dump(content, f,
default_flow_style=False, indent=2) default_flow_style=False,
indent=2)
if ret:
self._modified = False
return ret return ret
def _norm_key_elem(self, elem): def _norm_key_elem(self, elem):
@@ -729,10 +801,7 @@ class Cfg:
} }
# set the link flag # set the link flag
if link == LinkTypes.PARENT: dots[dotfile.key][self.key_dotfiles_link] = link.name
dots[dotfile.key][self.key_dotfiles_link] = True
elif link == LinkTypes.CHILDREN:
dots[dotfile.key][self.key_dotfiles_link_children] = True
# link it to this profile in the yaml file # link it to this profile in the yaml file
pro = self.content[self.key_profiles][profile] pro = self.content[self.key_profiles][profile]
@@ -759,7 +828,13 @@ class Cfg:
def get_settings(self): def get_settings(self):
"""return all defined settings""" """return all defined settings"""
return self.lnk_settings.copy() settings = self.lnk_settings.copy()
# patch link entries
key = self.key_imp_link
settings[key] = self._string_to_linktype(settings[key])
key = self.key_dotfile_link
settings[key] = self._string_to_linktype(settings[key])
return settings
def get_variables(self, profile, debug=False): def get_variables(self, profile, debug=False):
"""return the variables for this profile""" """return the variables for this profile"""
@@ -865,6 +940,10 @@ class Cfg:
self.lnk_settings[self.key_workdir] = workdir self.lnk_settings[self.key_workdir] = workdir
return ret return ret
def is_modified(self):
"""need the db to be saved"""
return self._modified
def save(self): def save(self):
"""save the config to file""" """save the config to file"""
# temporary reset paths # temporary reset paths

View File

@@ -239,7 +239,7 @@ def cmd_importer(o):
# create a new dotfile # create a new dotfile
dotfile = Dotfile('', dst, src) dotfile = Dotfile('', dst, src)
linktype = LinkTypes(o.link) linktype = LinkTypes(o.import_link)
if o.debug: if o.debug:
LOG.dbg('new dotfile: {}'.format(dotfile)) LOG.dbg('new dotfile: {}'.format(dotfile))
@@ -453,6 +453,10 @@ def main():
LOG.err('interrupted') LOG.err('interrupted')
ret = False ret = False
if ret and o.conf.is_modified():
LOG.log('config file updated')
o.conf.save()
return ret return ret

View File

@@ -63,7 +63,7 @@ Options:
-t --temp Install to a temporary directory for review. -t --temp Install to a temporary directory for review.
-T --template Only template dotfiles. -T --template Only template dotfiles.
-D --showdiff Show a diff before overwriting. -D --showdiff Show a diff before overwriting.
-l --inv-link Invert "link_import_default" when importing. -l --inv-link Invert "link_import_default".
-P --show-patch Provide a one-liner to manually patch template. -P --show-patch Provide a one-liner to manually patch template.
-f --force Do not warn if exists. -f --force Do not warn if exists.
-k --key Treat <path> as a dotfile key. -k --key Treat <path> as a dotfile key.
@@ -160,6 +160,8 @@ class Options(AttrMonitor):
self.conf = Cfg(self.confpath, profile=profile, debug=self.debug) self.conf = Cfg(self.confpath, profile=profile, debug=self.debug)
# transform the configs in attribute # transform the configs in attribute
for k, v in self.conf.get_settings().items(): for k, v in self.conf.get_settings().items():
if self.debug:
self.log.dbg('setting: {}={}'.format(k, v))
setattr(self, k, v) setattr(self, k, v)
def _apply_args(self): def _apply_args(self):
@@ -176,16 +178,16 @@ class Options(AttrMonitor):
# adapt attributes based on arguments # adapt attributes based on arguments
self.dry = self.args['--dry'] self.dry = self.args['--dry']
self.safe = not self.args['--force'] self.safe = not self.args['--force']
self.link = LinkTypes.NOLINK
if self.link_import_default:
self.link = LinkTypes.PARENT
# import link default value
self.import_link = self.link_import_default
if self.args['--inv-link']: if self.args['--inv-link']:
# Only invert link type from NOLINK to PARENT and vice-versa if self.import_link == LinkTypes.NOLINK:
if self.link == LinkTypes.NOLINK: self.import_link = LinkTypes.PARENT
self.link = LinkTypes.PARENT elif self.import_link == LinkTypes.PARENT:
elif self.link == LinkTypes.PARENT: self.import_link = LinkTypes.NOLINK
self.link = LinkTypes.NOLINK elif self.import_link == LinkTypes.CHILDREN:
self.import_link = LinkTypes.NOLINK
# "listfiles" specifics # "listfiles" specifics
self.listfiles_templateonly = self.args['--template'] self.listfiles_templateonly = self.args['--template']

142
tests-ng/deprecated-link.sh Executable file
View File

@@ -0,0 +1,142 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6
#
# test migration from link/link_children to single entry
# 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
################################################################
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests'`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests'`
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: false
create: true
dotpath: dotfiles
link_by_default: true
dotfiles:
f_link:
dst: ${tmpd}/abc
src: abc
link: true
f_link1:
dst: ${tmpd}/abc
src: abc
link: true
f_nolink:
dst: ${tmpd}/abc
src: abc
link: false
f_nolink1:
dst: ${tmpd}/abc
src: abc
link: false
f_children:
dst: ${tmpd}/abc
src: abc
link_children: true
f_children2:
dst: ${tmpd}/abc
src: abc
link_children: true
f_children3:
dst: ${tmpd}/abc
src: abc
link_children: false
profiles:
p1:
dotfiles:
- f_link
- f_nolink
- f_nolink1
- f_children
- f_children2
- f_children3
_EOF
cat ${cfg}
# create the dotfiles
echo "test" > ${tmps}/dotfiles/abc
echo "test" > ${tmpd}/abc
# compare
cd ${ddpath} | ${bin} compare -c ${cfg} -p p1
# install
#cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V
cat ${cfg}
# TODO test settings
# TODO change to safe_dump
# TODO yaml order
# fail if find some of these entries
set +e
grep 'link_children: true' ${cfg} >/dev/null && exit 1
grep 'link_children: false' ${cfg} >/dev/null && exit 1
grep 'link: true' ${cfg} >/dev/null && exit 1
grep 'link: false' ${cfg} >/dev/null && exit 1
grep 'link_by_default: true' ${cfg} >/dev/null && exit 1
grep 'link_by_default: false' ${cfg} >/dev/null && exit 1
set -e
# test values have been correctly updated
dotfiles=`cd ${ddpath} | ${bin} listfiles -c ${cfg} -p p1 | grep -v '^ '`
echo "${dotfiles}" | grep '^f_link ' | grep ', link: parent)'
echo "${dotfiles}" | grep '^f_nolink ' | grep ', link: nolink)'
echo "${dotfiles}" | grep '^f_nolink1 ' | grep ', link: nolink)'
echo "${dotfiles}" | grep '^f_children ' | grep ', link: children)'
echo "${dotfiles}" | grep '^f_children2 ' | grep ', link: children)'
echo "${dotfiles}" | grep '^f_children3 ' | grep ', link: nolink)'
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

View File

@@ -2,7 +2,7 @@
# author: deadc0de6 (https://github.com/deadc0de6) # author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2017, deadc0de6 # Copyright (c) 2017, deadc0de6
# #
# test link_install_default # test link_dotfile_default
# returns 1 in case of error # returns 1 in case of error
# #
@@ -54,8 +54,8 @@ tmpd=`mktemp -d --suffix='-dotdrop-tests'`
# create the dotfile # create the dotfile
mkdir -p ${tmps}/dotfiles/abc mkdir -p ${tmps}/dotfiles/abc
echo "test link_install_default 1" > ${tmps}/dotfiles/abc/file1 echo "test link_dotfile_default 1" > ${tmps}/dotfiles/abc/file1
echo "test link_install_default 2" > ${tmps}/dotfiles/abc/file2 echo "test link_dotfile_default 2" > ${tmps}/dotfiles/abc/file2
# create a shell script # create a shell script
# create the config file # create the config file
@@ -66,7 +66,7 @@ config:
backup: true backup: true
create: true create: true
dotpath: dotfiles dotpath: dotfiles
link_install_default: nolink link_dotfile_default: nolink
dotfiles: dotfiles:
d_abc: d_abc:
dst: ${tmpd}/abc dst: ${tmpd}/abc
@@ -96,7 +96,7 @@ config:
backup: true backup: true
create: true create: true
dotpath: dotfiles dotpath: dotfiles
link_install_default: link link_dotfile_default: link
dotfiles: dotfiles:
d_abc: d_abc:
dst: ${tmpd}/abc dst: ${tmpd}/abc
@@ -125,7 +125,7 @@ config:
backup: true backup: true
create: true create: true
dotpath: dotfiles dotpath: dotfiles
link_install_default: link_children link_dotfile_default: link_children
dotfiles: dotfiles:
d_abc: d_abc:
dst: ${tmpd}/abc dst: ${tmpd}/abc

View File

@@ -118,14 +118,13 @@ def load_options(confpath, profile):
args['--cfg'] = confpath args['--cfg'] = confpath
args['--profile'] = profile args['--profile'] = profile
# and get the options # and get the options
# TODO need to patch options
o = Options(args=args) o = Options(args=args)
o.profile = profile o.profile = profile
o.dry = False o.dry = False
o.profile = profile o.profile = profile
o.safe = True o.safe = True
o.install_diff = True o.install_diff = True
o.link = LinkTypes.NOLINK.value o.import_link = LinkTypes.NOLINK
o.install_showdiff = True o.install_showdiff = True
o.debug = True o.debug = True
if ENV_NODEBUG in os.environ: if ENV_NODEBUG in os.environ:

View File

@@ -6,11 +6,15 @@ basic unittest for the config parser
import unittest import unittest
from unittest.mock import patch
import os import os
import yaml import yaml
from dotdrop.config import Cfg from dotdrop.config import Cfg
from tests.helpers import get_tempdir, clean, create_fake_config from dotdrop.options import Options
from dotdrop.linktypes import LinkTypes
from tests.helpers import get_tempdir, clean, \
create_fake_config, _fake_args
class TestConfig(unittest.TestCase): class TestConfig(unittest.TestCase):
@@ -45,6 +49,76 @@ class TestConfig(unittest.TestCase):
self.assertTrue(conf._is_valid()) self.assertTrue(conf._is_valid())
self.assertTrue(conf.dump() != '') self.assertTrue(conf.dump() != '')
def test_def_link(self):
self._test_link_import('nolink', LinkTypes.NOLINK, False)
self._test_link_import('link', LinkTypes.PARENT, False)
self._test_link_import('nolink', LinkTypes.PARENT, True)
self._test_link_import('link', LinkTypes.NOLINK, True)
self._test_link_import_fail('whatever')
self._test_link_import_fail('link_children')
@patch('dotdrop.config.open', create=True)
@patch('dotdrop.config.os.path.exists', create=True)
def _test_link_import(self, cfgstring, expected,
invert, mock_exists, mock_open):
data = '''
config:
backup: true
create: true
dotpath: dotfiles
banner: true
longkey: false
keepdot: false
link_import_default: {}
link_dotfile_default: nolink
dotfiles:
profiles:
'''.format(cfgstring)
mock_open.side_effect = [
unittest.mock.mock_open(read_data=data).return_value
]
mock_exists.return_value = True
args = _fake_args()
args['--profile'] = 'p1'
args['--cfg'] = 'mocked'
if invert:
args['--inv-link'] = True
o = Options(args=args)
self.assertTrue(o.import_link == expected)
@patch('dotdrop.config.open', create=True)
@patch('dotdrop.config.os.path.exists', create=True)
def _test_link_import_fail(self, value, mock_exists, mock_open):
data = '''
config:
backup: true
create: true
dotpath: dotfiles
banner: true
longkey: false
keepdot: false
link_import_default: {}
link_dotfile_default: nolink
dotfiles:
profiles:
'''.format(value)
mock_open.side_effect = [
unittest.mock.mock_open(read_data=data).return_value
]
mock_exists.return_value = True
args = _fake_args()
args['--profile'] = 'p1'
args['--cfg'] = 'mocked'
with self.assertRaisesRegex(ValueError, 'config is not valid'):
o = Options(args=args)
print(o.import_link)
def test_include(self): def test_include(self):
tmp = get_tempdir() tmp = get_tempdir()
self.assertTrue(os.path.exists(tmp)) self.assertTrue(os.path.exists(tmp))

View File

@@ -112,11 +112,11 @@ class TestImport(unittest.TestCase):
o.import_path = dfiles o.import_path = dfiles
cmd_importer(o) cmd_importer(o)
# import symlink # import symlink
o.link = LinkTypes.PARENT o.import_link = LinkTypes.PARENT
sfiles = [dotfile6, dotfile7] sfiles = [dotfile6, dotfile7]
o.import_path = sfiles o.import_path = sfiles
cmd_importer(o) cmd_importer(o)
o.link = LinkTypes.NOLINK o.import_link = LinkTypes.NOLINK
# reload the config # reload the config
o = load_options(confpath, profile) o = load_options(confpath, profile)