1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-04 14:31:46 +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
longkey: false
keepdot: false
link_import_default: false
link_install_default: nolink
link_import_default: nolink
link_dotfile_default: nolink
dotfiles:
profiles:

View File

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

View File

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

View File

@@ -63,7 +63,7 @@ Options:
-t --temp Install to a temporary directory for review.
-T --template Only template dotfiles.
-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.
-f --force Do not warn if exists.
-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)
# transform the configs in attribute
for k, v in self.conf.get_settings().items():
if self.debug:
self.log.dbg('setting: {}={}'.format(k, v))
setattr(self, k, v)
def _apply_args(self):
@@ -176,16 +178,16 @@ class Options(AttrMonitor):
# adapt attributes based on arguments
self.dry = self.args['--dry']
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']:
# Only invert link type from NOLINK to PARENT and vice-versa
if self.link == LinkTypes.NOLINK:
self.link = LinkTypes.PARENT
elif self.link == LinkTypes.PARENT:
self.link = LinkTypes.NOLINK
if self.import_link == LinkTypes.NOLINK:
self.import_link = LinkTypes.PARENT
elif self.import_link == LinkTypes.PARENT:
self.import_link = LinkTypes.NOLINK
elif self.import_link == LinkTypes.CHILDREN:
self.import_link = LinkTypes.NOLINK
# "listfiles" specifics
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)
# Copyright (c) 2017, deadc0de6
#
# test link_install_default
# test link_dotfile_default
# returns 1 in case of error
#
@@ -54,8 +54,8 @@ tmpd=`mktemp -d --suffix='-dotdrop-tests'`
# create the dotfile
mkdir -p ${tmps}/dotfiles/abc
echo "test link_install_default 1" > ${tmps}/dotfiles/abc/file1
echo "test link_install_default 2" > ${tmps}/dotfiles/abc/file2
echo "test link_dotfile_default 1" > ${tmps}/dotfiles/abc/file1
echo "test link_dotfile_default 2" > ${tmps}/dotfiles/abc/file2
# create a shell script
# create the config file
@@ -66,7 +66,7 @@ config:
backup: true
create: true
dotpath: dotfiles
link_install_default: nolink
link_dotfile_default: nolink
dotfiles:
d_abc:
dst: ${tmpd}/abc
@@ -96,7 +96,7 @@ config:
backup: true
create: true
dotpath: dotfiles
link_install_default: link
link_dotfile_default: link
dotfiles:
d_abc:
dst: ${tmpd}/abc
@@ -125,7 +125,7 @@ config:
backup: true
create: true
dotpath: dotfiles
link_install_default: link_children
link_dotfile_default: link_children
dotfiles:
d_abc:
dst: ${tmpd}/abc

View File

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

View File

@@ -6,11 +6,15 @@ basic unittest for the config parser
import unittest
from unittest.mock import patch
import os
import yaml
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):
@@ -45,6 +49,76 @@ class TestConfig(unittest.TestCase):
self.assertTrue(conf._is_valid())
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):
tmp = get_tempdir()
self.assertTrue(os.path.exists(tmp))

View File

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