1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-12 20:25:17 +00:00
This commit is contained in:
deadc0de6
2021-04-30 20:01:02 +02:00
parent 5db5d51fb2
commit cb71bf299f
7 changed files with 532 additions and 480 deletions

View File

@@ -3,6 +3,7 @@ author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6 Copyright (c) 2017, deadc0de6
""" """
# pylint: disable=C0415
import sys import sys

View File

@@ -36,6 +36,7 @@ from dotdrop.exceptions import YamlException, UndefinedException
class CfgYaml: class CfgYaml:
"""yaml config file parser"""
# global entries # global entries
key_settings = Settings.key_yaml key_settings = Settings.key_yaml
@@ -437,7 +438,6 @@ class CfgYaml:
self._log.warn(warn) self._log.warn(warn)
self._dirty = False self._dirty = False
self.cfg_updated = False
return True return True
def dump(self): def dump(self):
@@ -464,20 +464,20 @@ class CfgYaml:
self._check_minversion(minversion) self._check_minversion(minversion)
# normalize paths # normalize paths
p = self._norm_path(settings[self.key_settings_dotpath]) paths = self._norm_path(settings[self.key_settings_dotpath])
settings[self.key_settings_dotpath] = p settings[self.key_settings_dotpath] = paths
p = self._norm_path(settings[self.key_settings_workdir]) paths = self._norm_path(settings[self.key_settings_workdir])
settings[self.key_settings_workdir] = p settings[self.key_settings_workdir] = paths
p = [ paths = [
self._norm_path(p) self._norm_path(path)
for p in settings[Settings.key_filter_file] for path in settings[Settings.key_filter_file]
] ]
settings[Settings.key_filter_file] = p settings[Settings.key_filter_file] = paths
p = [ paths = [
self._norm_path(p) self._norm_path(path)
for p in settings[Settings.key_func_file] for path in settings[Settings.key_func_file]
] ]
settings[Settings.key_func_file] = p settings[Settings.key_func_file] = paths
if self._debug: if self._debug:
self._debug_dict('settings block:', settings) self._debug_dict('settings block:', settings)
return settings return settings
@@ -1166,11 +1166,11 @@ class CfgYaml:
new = [] new = []
if not entries: if not entries:
return new return new
for e in entries: for entry in entries:
et = self._template_item(e) newe = self._template_item(entry)
if self._debug and e != et: if self._debug and entry != newe:
self._dbg('resolved: {} -> {}'.format(e, et)) self._dbg('resolved: {} -> {}'.format(entry, newe))
new.append(et) new.append(newe)
return new return new
def _template_dict(self, entries): def _template_dict(self, entries):
@@ -1178,11 +1178,11 @@ class CfgYaml:
new = {} new = {}
if not entries: if not entries:
return new return new
for k, v in entries.items(): for k, val in entries.items():
vt = self._template_item(v) newv = self._template_item(val)
if self._debug and v != vt: if self._debug and val != newv:
self._dbg('resolved: {} -> {}'.format(v, vt)) self._dbg('resolved: {} -> {}'.format(val, newv))
new[k] = vt new[k] = newv
return new return new
def _template_dotfiles_entries(self): def _template_dotfiles_entries(self):
@@ -1226,9 +1226,9 @@ class CfgYaml:
if self.key_all not in pdfs: if self.key_all not in pdfs:
# take a subset of the dotfiles # take a subset of the dotfiles
newdotfiles = {} newdotfiles = {}
for k, v in dotfiles.items(): for k, val in dotfiles.items():
if k in pdfs: if k in pdfs:
newdotfiles[k] = v newdotfiles[k] = val
dotfiles = newdotfiles dotfiles = newdotfiles
for dotfile in dotfiles.values(): for dotfile in dotfiles.values():
@@ -1246,26 +1246,29 @@ class CfgYaml:
var = self._enrich_vars(variables, self._profile) var = self._enrich_vars(variables, self._profile)
# use a separated templategen to handle variables # use a separated templategen to handle variables
# resolved outside the main config # resolved outside the main config
t = Templategen(variables=var, func_files = self.settings[Settings.key_func_file]
func_file=self.settings[Settings.key_func_file], filter_files = self.settings[Settings.key_filter_file]
filter_file=self.settings[Settings.key_filter_file]) templ = Templategen(variables=var,
func_file=func_files,
filter_file=filter_files)
for k in variables.keys(): for k in variables.keys():
val = variables[k] val = variables[k]
while Templategen.var_is_template(val): while Templategen.var_is_template(val):
val = t.generate_string(val) val = templ.generate_string(val)
variables[k] = val variables[k] = val
t.update_variables(variables) templ.update_variables(variables)
if variables is self.variables: if variables is self.variables:
self._redefine_templater() self._redefine_templater()
def _get_profile_included_vars(self): def _get_profile_included_vars(self):
"""resolve profile included variables/dynvariables""" """resolve profile included variables/dynvariables"""
for k, v in self.profiles.items(): for _, val in self.profiles.items():
if self.key_profile_include in v and v[self.key_profile_include]: if self.key_profile_include in val and \
val[self.key_profile_include]:
new = [] new = []
for x in v[self.key_profile_include]: for entry in val[self.key_profile_include]:
new.append(self._tmpl.generate_string(x)) new.append(self._tmpl.generate_string(entry))
v[self.key_profile_include] = new val[self.key_profile_include] = new
# now get the included ones # now get the included ones
pro_var = self._get_profile_included_item(self.key_profile_variables) pro_var = self._get_profile_included_item(self.key_profile_variables)
@@ -1291,7 +1294,8 @@ class CfgYaml:
""" """
if not dic: if not dic:
return return
[dic.pop(k, None) for k in self._profilevarskeys] for k in self._profilevarskeys:
dic.pop(k, None)
def _parse_extended_import_path(self, path_entry): def _parse_extended_import_path(self, path_entry):
"""Parse an import path in a tuple (path, fatal_not_found).""" """Parse an import path in a tuple (path, fatal_not_found)."""
@@ -1369,7 +1373,8 @@ class CfgYaml:
processed_paths = (self._process_path(p) for p in paths) processed_paths = (self._process_path(p) for p in paths)
return list(chain.from_iterable(processed_paths)) return list(chain.from_iterable(processed_paths))
def _merge_dict(self, high, low): @classmethod
def _merge_dict(cls, high, low):
"""merge high and low dict""" """merge high and low dict"""
if not high: if not high:
high = {} high = {}
@@ -1377,7 +1382,8 @@ class CfgYaml:
low = {} low = {}
return {**low, **high} return {**low, **high}
def _get_entry(self, dic, key, mandatory=True): @classmethod
def _get_entry(cls, dic, key, mandatory=True):
"""return copy of entry from yaml dictionary""" """return copy of entry from yaml dictionary"""
if key not in dic: if key not in dic:
if mandatory: if mandatory:
@@ -1393,19 +1399,19 @@ class CfgYaml:
def _clear_none(self, dic): def _clear_none(self, dic):
"""recursively delete all none/empty values in a dictionary.""" """recursively delete all none/empty values in a dictionary."""
new = {} new = {}
for k, v in dic.items(): for k, val in dic.items():
if k == self.key_dotfile_src: if k == self.key_dotfile_src:
# allow empty dotfile src # allow empty dotfile src
new[k] = v new[k] = val
continue continue
if k == self.key_dotfile_dst: if k == self.key_dotfile_dst:
# allow empty dotfile dst # allow empty dotfile dst
new[k] = v new[k] = val
continue continue
newv = v newv = val
if isinstance(v, dict): if isinstance(val, dict):
# recursive travers dict # recursive travers dict
newv = self._clear_none(v) newv = self._clear_none(val)
if not newv: if not newv:
# no empty dict # no empty dict
continue continue
@@ -1418,7 +1424,8 @@ class CfgYaml:
new[k] = newv new[k] = newv
return new return new
def _is_glob(self, path): @classmethod
def _is_glob(cls, path):
"""Quick test if path is a glob.""" """Quick test if path is a glob."""
return '*' in path or '?' in path return '*' in path or '?' in path
@@ -1435,8 +1442,8 @@ class CfgYaml:
return path return path
path = os.path.expanduser(path) path = os.path.expanduser(path)
if not os.path.isabs(path): if not os.path.isabs(path):
d = os.path.dirname(self._path) dirn = os.path.dirname(self._path)
ret = os.path.join(d, path) ret = os.path.join(dirn, path)
if self._debug: if self._debug:
msg = 'normalizing relative to cfg: {} -> {}' msg = 'normalizing relative to cfg: {} -> {}'
self._dbg(msg.format(path, ret)) self._dbg(msg.format(path, ret))
@@ -1446,30 +1453,31 @@ class CfgYaml:
self._dbg('normalizing: {} -> {}'.format(path, ret)) self._dbg('normalizing: {} -> {}'.format(path, ret))
return ret return ret
def _shell_exec_dvars(self, dic, keys=[]): def _shell_exec_dvars(self, dic, keys=None):
"""shell execute dynvariables in-place""" """shell execute dynvariables in-place"""
if not keys: if not keys:
keys = dic.keys() keys = dic.keys()
for k in keys: for k in keys:
v = dic[k] val = dic[k]
ret, out = shell(v, debug=self._debug) ret, out = shell(val, debug=self._debug)
if not ret: if not ret:
err = 'var \"{}: {}\" failed: {}'.format(k, v, out) err = 'var \"{}: {}\" failed: {}'.format(k, val, out)
self._log.err(err) self._log.err(err)
raise YamlException(err) raise YamlException(err)
if self._debug: if self._debug:
self._dbg('{}: `{}` -> {}'.format(k, v, out)) self._dbg('{}: `{}` -> {}'.format(k, val, out))
dic[k] = out dic[k] = out
def _check_minversion(self, minversion): @classmethod
def _check_minversion(cls, minversion):
if not minversion: if not minversion:
return return
try: try:
cur = tuple([int(x) for x in VERSION.split('.')]) cur = ([int(x) for x in VERSION.split('.')])
cfg = tuple([int(x) for x in minversion.split('.')]) cfg = ([int(x) for x in minversion.split('.')])
except Exception: except Exception as exc:
err = 'bad version: \"{}\" VS \"{}\"'.format(VERSION, minversion) err = 'bad version: \"{}\" VS \"{}\"'.format(VERSION, minversion)
raise YamlException(err) raise YamlException(err) from exc
if cur < cfg: if cur < cfg:
err = 'current dotdrop version is too old for that config file.' err = 'current dotdrop version is too old for that config file.'
err += ' Please update.' err += ' Please update.'
@@ -1495,8 +1503,8 @@ class CfgYaml:
self._dbg('{}:'.format(title)) self._dbg('{}:'.format(title))
if not elems: if not elems:
return return
for k, v in elems.items(): for k, val in elems.items():
self._dbg('\t- \"{}\": {}'.format(k, v)) self._dbg('\t- \"{}\": {}'.format(k, val))
def _dbg(self, content): def _dbg(self, content):
pre = os.path.basename(self._path) pre = os.path.basename(self._path)

View File

@@ -22,7 +22,8 @@ from dotdrop.utils import get_tmpdir, removepath, \
uniq_list, patch_ignores, dependencies_met, \ uniq_list, patch_ignores, dependencies_met, \
adapt_workers adapt_workers
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
from dotdrop.exceptions import YamlException, UndefinedException from dotdrop.exceptions import YamlException, \
UndefinedException, UnmetDependency
LOG = Logger() LOG = Logger()
TRANS_SUFFIX = 'trans' TRANS_SUFFIX = 'trans'
@@ -32,7 +33,7 @@ TRANS_SUFFIX = 'trans'
########################################################### ###########################################################
def action_executor(o, actions, defactions, templater, post=False): def action_executor(opts, actions, defactions, templater, post=False):
"""closure for action execution""" """closure for action execution"""
def execute(): def execute():
""" """
@@ -40,70 +41,71 @@ def action_executor(o, actions, defactions, templater, post=False):
True, None if ok True, None if ok
False, errstring if issue False, errstring if issue
""" """
s = 'pre' if not post else 'post' actiontype = 'pre' if not post else 'post'
# execute default actions # execute default actions
for action in defactions: for action in defactions:
if o.dry: if opts.dry:
LOG.dry('would execute def-{}-action: {}'.format(s, LOG.dry('would execute def-{}-action: {}'.format(actiontype,
action)) action))
continue continue
LOG.dbg('executing def-{}-action: {}'.format(s, action)) LOG.dbg('executing def-{}-action: {}'.format(actiontype, action))
ret = action.execute(templater=templater, debug=o.debug) ret = action.execute(templater=templater, debug=opts.debug)
if not ret: if not ret:
err = 'def-{}-action \"{}\" failed'.format(s, action.key) err = 'def-{}-action \"{}\" failed'
LOG.err(err) LOG.err(err.format(actiontype, action.key))
return False, err return False, err
# execute actions # execute actions
for action in actions: for action in actions:
if o.dry: if opts.dry:
LOG.dry('would execute {}-action: {}'.format(s, action)) err = 'would execute {}-action: {}'
LOG.dry(err.format(actiontype, action))
continue continue
LOG.dbg('executing {}-action: {}'.format(s, action)) LOG.dbg('executing {}-action: {}'.format(actiontype, action))
ret = action.execute(templater=templater, debug=o.debug) ret = action.execute(templater=templater, debug=opts.debug)
if not ret: if not ret:
err = '{}-action \"{}\" failed'.format(s, action.key) err = '{}-action \"{}\" failed'.format(actiontype, action.key)
LOG.err(err) LOG.err(err)
return False, err return False, err
return True, None return True, None
return execute return execute
def _dotfile_update(o, path, key=False): def _dotfile_update(opts, path, key=False):
""" """
update a dotfile pointed by path update a dotfile pointed by path
if key is false or by key (in path) if key is false or by key (in path)
""" """
updater = Updater(o.dotpath, o.variables, o.conf, updater = Updater(opts.dotpath, opts.variables, opts.conf,
dry=o.dry, safe=o.safe, debug=o.debug, dry=opts.dry, safe=opts.safe, debug=opts.debug,
ignore=o.update_ignore, ignore=opts.update_ignore,
showpatch=o.update_showpatch, showpatch=opts.update_showpatch,
ignore_missing_in_dotdrop=o.ignore_missing_in_dotdrop) ignore_missing_in_dotdrop=opts.ignore_missing_in_dotdrop)
if key: if key:
return updater.update_key(path) return updater.update_key(path)
return updater.update_path(path) return updater.update_path(path)
def _dotfile_compare(o, dotfile, tmp): def _dotfile_compare(opts, dotfile, tmp):
""" """
compare a dotfile compare a dotfile
returns True if same returns True if same
""" """
t = _get_templater(o) templ = _get_templater(opts)
ignore_missing_in_dotdrop = o.ignore_missing_in_dotdrop or \ ignore_missing_in_dotdrop = opts.ignore_missing_in_dotdrop or \
dotfile.ignore_missing_in_dotdrop dotfile.ignore_missing_in_dotdrop
inst = Installer(create=o.create, backup=o.backup, inst = Installer(create=opts.create, backup=opts.backup,
dry=o.dry, base=o.dotpath, dry=opts.dry, base=opts.dotpath,
workdir=o.workdir, debug=o.debug, workdir=opts.workdir, debug=opts.debug,
backup_suffix=o.install_backup_suffix, backup_suffix=opts.install_backup_suffix,
diff_cmd=o.diff_command) diff_cmd=opts.diff_command)
comp = Comparator(diff_cmd=o.diff_command, debug=o.debug, comp = Comparator(diff_cmd=opts.diff_command, debug=opts.debug,
ignore_missing_in_dotdrop=ignore_missing_in_dotdrop) ignore_missing_in_dotdrop=ignore_missing_in_dotdrop)
# add dotfile variables # add dotfile variables
newvars = dotfile.get_dotfile_variables() newvars = dotfile.get_dotfile_variables()
t.add_tmp_vars(newvars=newvars) templ.add_tmp_vars(newvars=newvars)
# dotfiles does not exist / not installed # dotfiles does not exist / not installed
LOG.dbg('comparing {}'.format(dotfile)) LOG.dbg('comparing {}'.format(dotfile))
@@ -118,14 +120,14 @@ def _dotfile_compare(o, dotfile, tmp):
tmpsrc = None tmpsrc = None
if dotfile.trans_r: if dotfile.trans_r:
LOG.dbg('applying transformation before comparing') LOG.dbg('applying transformation before comparing')
tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug) tmpsrc = apply_trans(opts.dotpath, dotfile, templ, debug=opts.debug)
if not tmpsrc: if not tmpsrc:
# could not apply trans # could not apply trans
return False return False
src = tmpsrc src = tmpsrc
# is a symlink pointing to itself # is a symlink pointing to itself
asrc = os.path.join(o.dotpath, os.path.expanduser(src)) asrc = os.path.join(opts.dotpath, os.path.expanduser(src))
adst = os.path.expanduser(dotfile.dst) adst = os.path.expanduser(dotfile.dst)
if os.path.samefile(asrc, adst): if os.path.samefile(asrc, adst):
line = '=> compare {}: diffing with \"{}\"' line = '=> compare {}: diffing with \"{}\"'
@@ -133,13 +135,13 @@ def _dotfile_compare(o, dotfile, tmp):
LOG.dbg('points to itself') LOG.dbg('points to itself')
return True return True
ignores = list(set(o.compare_ignore + dotfile.cmpignore)) ignores = list(set(opts.compare_ignore + dotfile.cmpignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) ignores = patch_ignores(ignores, dotfile.dst, debug=opts.debug)
insttmp = None insttmp = None
if dotfile.template and Templategen.is_template(src, ignore=ignores): if dotfile.template and Templategen.is_template(src, ignore=ignores):
# install dotfile to temporary dir for compare # install dotfile to temporary dir for compare
ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst, ret, err, insttmp = inst.install_to_temp(templ, tmp, src, dotfile.dst,
is_template=True, is_template=True,
chmod=dotfile.chmod) chmod=dotfile.chmod)
if not ret: if not ret:
@@ -151,28 +153,29 @@ def _dotfile_compare(o, dotfile, tmp):
src = insttmp src = insttmp
# compare # compare
# need to be executed before cleaning
diff = comp.compare(src, dotfile.dst, ignore=ignores) diff = comp.compare(src, dotfile.dst, ignore=ignores)
# clean tmp transformed dotfile if any # clean tmp transformed dotfile if any
if tmpsrc: if tmpsrc:
tmpsrc = os.path.join(o.dotpath, tmpsrc) tmpsrc = os.path.join(opts.dotpath, tmpsrc)
if os.path.exists(tmpsrc): if os.path.exists(tmpsrc):
removepath(tmpsrc, LOG) removepath(tmpsrc, LOG)
# clean tmp template dotfile if any # clean tmp template dotfile if any
if insttmp: if insttmp and os.path.exists(insttmp):
if os.path.exists(insttmp):
removepath(insttmp, LOG) removepath(insttmp, LOG)
if diff != '': if diff != '':
# print diff results # print diff results
line = '=> compare {}: diffing with \"{}\"' line = '=> compare {}: diffing with \"{}\"'
LOG.log(line.format(dotfile.key, dotfile.dst)) LOG.log(line.format(dotfile.key, dotfile.dst))
if o.compare_fileonly: if opts.compare_fileonly:
LOG.raw('<files are different>') LOG.raw('<files are different>')
else: else:
LOG.emph(diff) LOG.emph(diff)
return False return False
# no difference # no difference
line = '=> compare {}: diffing with \"{}\"' line = '=> compare {}: diffing with \"{}\"'
LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg(line.format(dotfile.key, dotfile.dst))
@@ -180,33 +183,33 @@ def _dotfile_compare(o, dotfile, tmp):
return True return True
def _dotfile_install(o, dotfile, tmpdir=None): def _dotfile_install(opts, dotfile, tmpdir=None):
""" """
install a dotfile install a dotfile
returns <success, dotfile key, err> returns <success, dotfile key, err>
""" """
# installer # installer
inst = _get_install_installer(o, tmpdir=tmpdir) inst = _get_install_installer(opts, tmpdir=tmpdir)
# templater # templater
t = _get_templater(o) templ = _get_templater(opts)
# add dotfile variables # add dotfile variables
newvars = dotfile.get_dotfile_variables() newvars = dotfile.get_dotfile_variables()
t.add_tmp_vars(newvars=newvars) templ.add_tmp_vars(newvars=newvars)
preactions = [] preactions = []
if not o.install_temporary: if not opts.install_temporary:
preactions.extend(dotfile.get_pre_actions()) preactions.extend(dotfile.get_pre_actions())
defactions = o.install_default_actions_pre defactions = opts.install_default_actions_pre
pre_actions_exec = action_executor(o, preactions, defactions, pre_actions_exec = action_executor(opts, preactions, defactions,
t, post=False) templ, post=False)
LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key)) LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key))
LOG.dbg(dotfile.prt()) LOG.dbg(dotfile.prt())
ignores = list(set(o.install_ignore + dotfile.instignore)) ignores = list(set(opts.install_ignore + dotfile.instignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) ignores = patch_ignores(ignores, dotfile.dst, debug=opts.debug)
is_template = dotfile.template and Templategen.is_template( is_template = dotfile.template and Templategen.is_template(
dotfile.src, dotfile.src,
@@ -214,29 +217,29 @@ def _dotfile_install(o, dotfile, tmpdir=None):
) )
if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK:
# link # link
r, err = inst.install(t, dotfile.src, dotfile.dst, ret, err = inst.install(templ, dotfile.src, dotfile.dst,
dotfile.link, dotfile.link,
actionexec=pre_actions_exec, actionexec=pre_actions_exec,
is_template=is_template, is_template=is_template,
ignore=ignores, ignore=ignores,
chmod=dotfile.chmod, chmod=dotfile.chmod,
force_chmod=o.install_force_chmod) force_chmod=opts.install_force_chmod)
elif hasattr(dotfile, 'link') and \ elif hasattr(dotfile, 'link') and \
dotfile.link == LinkTypes.LINK_CHILDREN: dotfile.link == LinkTypes.LINK_CHILDREN:
# link_children # link_children
r, err = inst.install(t, dotfile.src, dotfile.dst, ret, err = inst.install(templ, dotfile.src, dotfile.dst,
dotfile.link, dotfile.link,
actionexec=pre_actions_exec, actionexec=pre_actions_exec,
is_template=is_template, is_template=is_template,
chmod=dotfile.chmod, chmod=dotfile.chmod,
ignore=ignores, ignore=ignores,
force_chmod=o.install_force_chmod) force_chmod=opts.install_force_chmod)
else: else:
# nolink # nolink
src = dotfile.src src = dotfile.src
tmp = None tmp = None
if dotfile.trans_r: if dotfile.trans_r:
tmp = apply_trans(o.dotpath, dotfile, t, debug=o.debug) tmp = apply_trans(opts.dotpath, dotfile, templ, debug=opts.debug)
if not tmp: if not tmp:
return False, dotfile.key, None return False, dotfile.key, None
src = tmp src = tmp
@@ -245,92 +248,92 @@ def _dotfile_install(o, dotfile, tmpdir=None):
src, src,
ignore=ignores, ignore=ignores,
) )
r, err = inst.install(t, src, dotfile.dst, ret, err = inst.install(templ, src, dotfile.dst,
LinkTypes.NOLINK, LinkTypes.NOLINK,
actionexec=pre_actions_exec, actionexec=pre_actions_exec,
noempty=dotfile.noempty, noempty=dotfile.noempty,
ignore=ignores, ignore=ignores,
is_template=is_template, is_template=is_template,
chmod=dotfile.chmod, chmod=dotfile.chmod,
force_chmod=o.install_force_chmod) force_chmod=opts.install_force_chmod)
if tmp: if tmp:
tmp = os.path.join(o.dotpath, tmp) tmp = os.path.join(opts.dotpath, tmp)
if os.path.exists(tmp): if os.path.exists(tmp):
removepath(tmp, LOG) removepath(tmp, LOG)
# check result of installation # check result of installation
if r: if ret:
# dotfile was installed # dotfile was installed
if not o.install_temporary: if not opts.install_temporary:
defactions = o.install_default_actions_post defactions = opts.install_default_actions_post
postactions = dotfile.get_post_actions() postactions = dotfile.get_post_actions()
post_actions_exec = action_executor(o, postactions, defactions, post_actions_exec = action_executor(opts, postactions, defactions,
t, post=True) templ, post=True)
post_actions_exec() post_actions_exec()
else: else:
# dotfile was NOT installed # dotfile was NOT installed
if o.install_force_action: if opts.install_force_action:
# pre-actions # pre-actions
LOG.dbg('force pre action execution ...') LOG.dbg('force pre action execution ...')
pre_actions_exec() pre_actions_exec()
# post-actions # post-actions
LOG.dbg('force post action execution ...') LOG.dbg('force post action execution ...')
defactions = o.install_default_actions_post defactions = opts.install_default_actions_post
postactions = dotfile.get_post_actions() postactions = dotfile.get_post_actions()
post_actions_exec = action_executor(o, postactions, defactions, post_actions_exec = action_executor(opts, postactions, defactions,
t, post=True) templ, post=True)
post_actions_exec() post_actions_exec()
return r, dotfile.key, err return ret, dotfile.key, err
def cmd_install(o): def cmd_install(opts):
"""install dotfiles for this profile""" """install dotfiles for this profile"""
dotfiles = o.dotfiles dotfiles = opts.dotfiles
prof = o.conf.get_profile() prof = opts.conf.get_profile()
adapt_workers(o, LOG) adapt_workers(opts, LOG)
pro_pre_actions = prof.get_pre_actions() if prof else [] pro_pre_actions = prof.get_pre_actions() if prof else []
pro_post_actions = prof.get_post_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else []
if o.install_keys: if opts.install_keys:
# filtered dotfiles to install # filtered dotfiles to install
uniq = uniq_list(o.install_keys) uniq = uniq_list(opts.install_keys)
dotfiles = [d for d in dotfiles if d.key in uniq] dotfiles = [d for d in dotfiles if d.key in uniq]
if not dotfiles: if not dotfiles:
msg = 'no dotfile to install for this profile (\"{}\")' msg = 'no dotfile to install for this profile (\"{}\")'
LOG.warn(msg.format(o.profile)) LOG.warn(msg.format(opts.profile))
return False return False
# the installer # the installer
tmpdir = None tmpdir = None
if o.install_temporary: if opts.install_temporary:
tmpdir = get_tmpdir() tmpdir = get_tmpdir()
installed = [] installed = []
# execute profile pre-action # execute profile pre-action
LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions))) LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions)))
t = _get_templater(o) templ = _get_templater(opts)
ret, err = action_executor(o, pro_pre_actions, [], t, post=False)() ret, _ = action_executor(opts, pro_pre_actions, [], templ, post=False)()
if not ret: if not ret:
return False return False
# install each dotfile # install each dotfile
if o.workers > 1: if opts.workers > 1:
# in parallel # in parallel
LOG.dbg('run with {} workers'.format(o.workers)) LOG.dbg('run with {} workers'.format(opts.workers))
ex = futures.ThreadPoolExecutor(max_workers=o.workers) ex = futures.ThreadPoolExecutor(max_workers=opts.workers)
wait_for = [] wait_for = []
for dotfile in dotfiles: for dotfile in dotfiles:
j = ex.submit(_dotfile_install, o, dotfile, tmpdir=tmpdir) j = ex.submit(_dotfile_install, opts, dotfile, tmpdir=tmpdir)
wait_for.append(j) wait_for.append(j)
# check result # check result
for f in futures.as_completed(wait_for): for fut in futures.as_completed(wait_for):
r, key, err = f.result() tmpret, key, err = fut.result()
if r: if tmpret:
installed.append(key) installed.append(key)
elif err: elif err:
LOG.err('installing \"{}\" failed: {}'.format(key, LOG.err('installing \"{}\" failed: {}'.format(key,
@@ -338,42 +341,43 @@ def cmd_install(o):
else: else:
# sequentially # sequentially
for dotfile in dotfiles: for dotfile in dotfiles:
r, key, err = _dotfile_install(o, dotfile, tmpdir=tmpdir) tmpret, key, err = _dotfile_install(opts, dotfile, tmpdir=tmpdir)
# check result # check result
if r: if tmpret:
installed.append(key) installed.append(key)
elif err: elif err:
LOG.err('installing \"{}\" failed: {}'.format(key, LOG.err('installing \"{}\" failed: {}'.format(key,
err)) err))
# execute profile post-action # execute profile post-action
if len(installed) > 0 or o.install_force_action: if len(installed) > 0 or opts.install_force_action:
msg = 'run {} profile post actions' msg = 'run {} profile post actions'
LOG.dbg(msg.format(len(pro_post_actions))) LOG.dbg(msg.format(len(pro_post_actions)))
ret, err = action_executor(o, pro_post_actions, [], t, post=False)() ret, _ = action_executor(opts, pro_post_actions,
[], templ, post=False)()
if not ret: if not ret:
return False return False
LOG.dbg('install done: installed \"{}\"'.format(','.join(installed))) LOG.dbg('install done: installed \"{}\"'.format(','.join(installed)))
if o.install_temporary: if opts.install_temporary:
LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir))
LOG.log('\n{} dotfile(s) installed.'.format(len(installed))) LOG.log('\n{} dotfile(s) installed.'.format(len(installed)))
return True return True
def cmd_compare(o, tmp): def cmd_compare(opts, tmp):
"""compare dotfiles and return True if all identical""" """compare dotfiles and return True if all identical"""
dotfiles = o.dotfiles dotfiles = opts.dotfiles
if not dotfiles: if not dotfiles:
msg = 'no dotfile defined for this profile (\"{}\")' msg = 'no dotfile defined for this profile (\"{}\")'
LOG.warn(msg.format(o.profile)) LOG.warn(msg.format(opts.profile))
return True return True
# compare only specific files # compare only specific files
selected = dotfiles selected = dotfiles
if o.compare_focus: if opts.compare_focus:
selected = _select(o.compare_focus, dotfiles) selected = _select(opts.compare_focus, dotfiles)
if len(selected) < 1: if len(selected) < 1:
LOG.log('\nno dotfile to compare') LOG.log('\nno dotfile to compare')
@@ -381,20 +385,20 @@ def cmd_compare(o, tmp):
same = True same = True
cnt = 0 cnt = 0
if o.workers > 1: if opts.workers > 1:
# in parallel # in parallel
LOG.dbg('run with {} workers'.format(o.workers)) LOG.dbg('run with {} workers'.format(opts.workers))
ex = futures.ThreadPoolExecutor(max_workers=o.workers) ex = futures.ThreadPoolExecutor(max_workers=opts.workers)
wait_for = [] wait_for = []
for dotfile in selected: for dotfile in selected:
j = ex.submit(_dotfile_compare, o, dotfile, tmp)
wait_for.append(j)
# check result
for f in futures.as_completed(wait_for):
if not dotfile.src and not dotfile.dst: if not dotfile.src and not dotfile.dst:
# ignore fake dotfile # ignore fake dotfile
continue continue
if not f.result(): j = ex.submit(_dotfile_compare, opts, dotfile, tmp)
wait_for.append(j)
# check result
for fut in futures.as_completed(wait_for):
if not fut.result():
same = False same = False
cnt += 1 cnt += 1
else: else:
@@ -403,7 +407,7 @@ def cmd_compare(o, tmp):
if not dotfile.src and not dotfile.dst: if not dotfile.src and not dotfile.dst:
# ignore fake dotfile # ignore fake dotfile
continue continue
if not _dotfile_compare(o, dotfile, tmp): if not _dotfile_compare(opts, dotfile, tmp):
same = False same = False
cnt += 1 cnt += 1
@@ -411,31 +415,32 @@ def cmd_compare(o, tmp):
return same return same
def cmd_update(o): def cmd_update(opts):
"""update the dotfile(s) from path(s) or key(s)""" """update the dotfile(s) from path(s) or key(s)"""
cnt = 0 cnt = 0
paths = o.update_path paths = opts.update_path
iskey = o.update_iskey iskey = opts.update_iskey
if o.profile not in [p.key for p in o.profiles]: if opts.profile not in [p.key for p in opts.profiles]:
LOG.err('no such profile \"{}\"'.format(o.profile)) LOG.err('no such profile \"{}\"'.format(opts.profile))
return False return False
adapt_workers(o, LOG) adapt_workers(opts, LOG)
if not paths: if not paths:
# update the entire profile # update the entire profile
if iskey: if iskey:
LOG.dbg('update by keys: {}'.format(paths)) LOG.dbg('update by keys: {}'.format(paths))
paths = [d.key for d in o.dotfiles] paths = [d.key for d in opts.dotfiles]
else: else:
LOG.dbg('update by paths: {}'.format(paths)) LOG.dbg('update by paths: {}'.format(paths))
paths = [d.dst for d in o.dotfiles] paths = [d.dst for d in opts.dotfiles]
msg = 'Update all dotfiles for profile \"{}\"'.format(o.profile) msg = 'Update all dotfiles for profile \"{}\"'.format(opts.profile)
if o.safe and not LOG.ask(msg): if opts.safe and not LOG.ask(msg):
LOG.log('\n{} file(s) updated.'.format(cnt)) LOG.log('\n{} file(s) updated.'.format(cnt))
return False return False
# check there's something to do
if not paths: if not paths:
LOG.log('\nno dotfile to update') LOG.log('\nno dotfile to update')
return True return True
@@ -443,84 +448,87 @@ def cmd_update(o):
LOG.dbg('dotfile to update: {}'.format(paths)) LOG.dbg('dotfile to update: {}'.format(paths))
# update each dotfile # update each dotfile
if o.workers > 1: if opts.workers > 1:
# in parallel # in parallel
LOG.dbg('run with {} workers'.format(o.workers)) LOG.dbg('run with {} workers'.format(opts.workers))
ex = futures.ThreadPoolExecutor(max_workers=o.workers) ex = futures.ThreadPoolExecutor(max_workers=opts.workers)
wait_for = [] wait_for = []
for path in paths: for path in paths:
j = ex.submit(_dotfile_update, o, path, key=iskey) j = ex.submit(_dotfile_update, opts, path, key=iskey)
wait_for.append(j) wait_for.append(j)
# check result # check result
for f in futures.as_completed(wait_for): for fut in futures.as_completed(wait_for):
if f.result(): if fut.result():
cnt += 1 cnt += 1
else: else:
# sequentially # sequentially
for path in paths: for path in paths:
if _dotfile_update(o, path, key=iskey): if _dotfile_update(opts, path, key=iskey):
cnt += 1 cnt += 1
LOG.log('\n{} file(s) updated.'.format(cnt)) LOG.log('\n{} file(s) updated.'.format(cnt))
return cnt == len(paths) return cnt == len(paths)
def cmd_importer(o): def cmd_importer(opts):
"""import dotfile(s) from paths""" """import dotfile(s) from paths"""
ret = True ret = True
cnt = 0 cnt = 0
paths = o.import_path paths = opts.import_path
importer = Importer(o.profile, o.conf, o.dotpath, o.diff_command, importer = Importer(opts.profile, opts.conf,
dry=o.dry, safe=o.safe, debug=o.debug, opts.dotpath, opts.diff_command,
keepdot=o.keepdot, ignore=o.import_ignore) dry=opts.dry, safe=opts.safe,
debug=opts.debug,
keepdot=opts.keepdot,
ignore=opts.import_ignore)
for path in paths: for path in paths:
r = importer.import_path(path, import_as=o.import_as, tmpret = importer.import_path(path, import_as=opts.import_as,
import_link=o.import_link, import_link=opts.import_link,
import_mode=o.import_mode) import_mode=opts.import_mode)
if r < 0: if tmpret < 0:
ret = False ret = False
elif r > 0: elif tmpret > 0:
cnt += 1 cnt += 1
if o.dry: if opts.dry:
LOG.dry('new config file would be:') LOG.dry('new config file would be:')
LOG.raw(o.conf.dump()) LOG.raw(opts.conf.dump())
else: else:
o.conf.save() opts.conf.save()
LOG.log('\n{} file(s) imported.'.format(cnt)) LOG.log('\n{} file(s) imported.'.format(cnt))
return ret return ret
def cmd_list_profiles(o): def cmd_list_profiles(opts):
"""list all profiles""" """list all profiles"""
LOG.emph('Available profile(s):\n') LOG.emph('Available profile(s):\n')
for p in o.profiles: for profile in opts.profiles:
if o.profiles_grepable: if opts.profiles_grepable:
fmt = '{}'.format(p.key) fmt = '{}'.format(profile.key)
LOG.raw(fmt) LOG.raw(fmt)
else: else:
LOG.sub(p.key, end='') LOG.sub(profile.key, end='')
LOG.log(' ({} dotfiles)'.format(len(p.dotfiles))) LOG.log(' ({} dotfiles)'.format(len(profile.dotfiles)))
LOG.log('') LOG.log('')
def cmd_files(o): def cmd_files(opts):
"""list all dotfiles for a specific profile""" """list all dotfiles for a specific profile"""
if o.profile not in [p.key for p in o.profiles]: if opts.profile not in [p.key for p in opts.profiles]:
LOG.warn('unknown profile \"{}\"'.format(o.profile)) LOG.warn('unknown profile \"{}\"'.format(opts.profile))
return return
what = 'Dotfile(s)' what = 'Dotfile(s)'
if o.files_templateonly: if opts.files_templateonly:
what = 'Template(s)' what = 'Template(s)'
LOG.emph('{} for profile \"{}\":\n'.format(what, o.profile)) LOG.emph('{} for profile \"{}\":\n'.format(what, opts.profile))
for dotfile in o.dotfiles: for dotfile in opts.dotfiles:
if o.files_templateonly: if opts.files_templateonly:
src = os.path.join(o.dotpath, dotfile.src) src = os.path.join(opts.dotpath, dotfile.src)
if not Templategen.is_template(src): if not Templategen.is_template(src):
continue continue
if o.files_grepable: if opts.files_grepable:
fmt = '{},dst:{},src:{},link:{}' fmt = '{},dst:{},src:{},link:{}'
fmt = fmt.format(dotfile.key, dotfile.dst, fmt = fmt.format(dotfile.key, dotfile.dst,
dotfile.src, dotfile.link.name.lower()) dotfile.src, dotfile.link.name.lower())
@@ -539,26 +547,26 @@ def cmd_files(o):
LOG.log('') LOG.log('')
def cmd_detail(o): def cmd_detail(opts):
"""list details on all files for all dotfile entries""" """list details on all files for all dotfile entries"""
if o.profile not in [p.key for p in o.profiles]: if opts.profile not in [p.key for p in opts.profiles]:
LOG.warn('unknown profile \"{}\"'.format(o.profile)) LOG.warn('unknown profile \"{}\"'.format(opts.profile))
return return
dotfiles = o.dotfiles dotfiles = opts.dotfiles
if o.detail_keys: if opts.detail_keys:
# filtered dotfiles to install # filtered dotfiles to install
uniq = uniq_list(o.details_keys) uniq = uniq_list(opts.details_keys)
dotfiles = [d for d in dotfiles if d.key in uniq] dotfiles = [d for d in dotfiles if d.key in uniq]
LOG.emph('dotfiles details for profile \"{}\":\n'.format(o.profile)) LOG.emph('dotfiles details for profile \"{}\":\n'.format(opts.profile))
for d in dotfiles: for dotfile in dotfiles:
_detail(o.dotpath, d) _detail(opts.dotpath, dotfile)
LOG.log('') LOG.log('')
def cmd_remove(o): def cmd_remove(opts):
"""remove dotfile from dotpath and from config""" """remove dotfile from dotpath and from config"""
paths = o.remove_path paths = opts.remove_path
iskey = o.remove_iskey iskey = opts.remove_iskey
if not paths: if not paths:
LOG.log('no dotfile to remove') LOG.log('no dotfile to remove')
@@ -569,13 +577,13 @@ def cmd_remove(o):
for key in paths: for key in paths:
if not iskey: if not iskey:
# by path # by path
dotfiles = o.conf.get_dotfile_by_dst(key) dotfiles = opts.conf.get_dotfile_by_dst(key)
if not dotfiles: if not dotfiles:
LOG.warn('{} ignored, does not exist'.format(key)) LOG.warn('{} ignored, does not exist'.format(key))
continue continue
else: else:
# by key # by key
dotfile = o.conf.get_dotfile(key) dotfile = opts.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
@@ -592,46 +600,46 @@ def cmd_remove(o):
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 opts.dotfiles]:
msg = '{} ignored, not associated to this profile' msg = '{} ignored, not associated to this profile'
LOG.warn(msg.format(key)) LOG.warn(msg.format(key))
continue continue
profiles = o.conf.get_profiles_by_dotfile_key(k) profiles = opts.conf.get_profiles_by_dotfile_key(k)
pkeys = ','.join([p.key for p in profiles]) pkeys = ','.join([p.key for p in profiles])
if o.dry: if opts.dry:
LOG.dry('would remove {} from {}'.format(dotfile, pkeys)) LOG.dry('would remove {} from {}'.format(dotfile, pkeys))
continue continue
msg = 'Remove \"{}\" from all these profiles: {}'.format(k, pkeys) msg = 'Remove \"{}\" from all these profiles: {}'.format(k, pkeys)
if o.safe and not LOG.ask(msg): if opts.safe and not LOG.ask(msg):
return False return False
LOG.dbg('remove dotfile: {}'.format(dotfile)) LOG.dbg('remove dotfile: {}'.format(dotfile))
for profile in profiles: for profile in profiles:
if not o.conf.del_dotfile_from_profile(dotfile, profile): if not opts.conf.del_dotfile_from_profile(dotfile, profile):
return False return False
if not o.conf.del_dotfile(dotfile): if not opts.conf.del_dotfile(dotfile):
return False return False
# remove dotfile from dotpath # remove dotfile from dotpath
dtpath = os.path.join(o.dotpath, dotfile.src) dtpath = os.path.join(opts.dotpath, dotfile.src)
removepath(dtpath, LOG) removepath(dtpath, LOG)
# remove empty directory # remove empty directory
parent = os.path.dirname(dtpath) parent = os.path.dirname(dtpath)
# remove any empty parent up to dotpath # remove any empty parent up to dotpath
while parent != o.dotpath: while parent != opts.dotpath:
if os.path.isdir(parent) and not os.listdir(parent): if os.path.isdir(parent) and not os.listdir(parent):
msg = 'Remove empty dir \"{}\"'.format(parent) msg = 'Remove empty dir \"{}\"'.format(parent)
if o.safe and not LOG.ask(msg): if opts.safe and not LOG.ask(msg):
break break
removepath(parent, LOG) removepath(parent, LOG)
parent = os.path.dirname(parent) parent = os.path.dirname(parent)
removed.append(dotfile) removed.append(dotfile)
if o.dry: if opts.dry:
LOG.dry('new config file would be:') LOG.dry('new config file would be:')
LOG.raw(o.conf.dump()) LOG.raw(opts.conf.dump())
else: else:
o.conf.save() opts.conf.save()
if removed: if removed:
LOG.log('\nFollowing dotfile(s) are not tracked anymore:') LOG.log('\nFollowing dotfile(s) are not tracked anymore:')
entries = ['- \"{}\" (was tracked as \"{}\")'.format(r.dst, r.key) entries = ['- \"{}\" (was tracked as \"{}\")'.format(r.dst, r.key)
@@ -647,25 +655,25 @@ def cmd_remove(o):
########################################################### ###########################################################
def _get_install_installer(o, tmpdir=None): def _get_install_installer(opts, tmpdir=None):
"""get an installer instance for cmd_install""" """get an installer instance for cmd_install"""
inst = Installer(create=o.create, backup=o.backup, inst = Installer(create=opts.create, backup=opts.backup,
dry=o.dry, safe=o.safe, dry=opts.dry, safe=opts.safe,
base=o.dotpath, workdir=o.workdir, base=opts.dotpath, workdir=opts.workdir,
diff=o.install_diff, debug=o.debug, diff=opts.install_diff, debug=opts.debug,
totemp=tmpdir, totemp=tmpdir,
showdiff=o.install_showdiff, showdiff=opts.install_showdiff,
backup_suffix=o.install_backup_suffix, backup_suffix=opts.install_backup_suffix,
diff_cmd=o.diff_command) diff_cmd=opts.diff_command)
return inst return inst
def _get_templater(o): def _get_templater(opts):
"""get an templater instance""" """get an templater instance"""
t = Templategen(base=o.dotpath, variables=o.variables, templ = Templategen(base=opts.dotpath, variables=opts.variables,
func_file=o.func_file, filter_file=o.filter_file, func_file=opts.func_file, filter_file=opts.filter_file,
debug=o.debug) debug=opts.debug)
return t return templ
def _detail(dotpath, dotfile): def _detail(dotpath, dotfile):
@@ -684,24 +692,24 @@ def _detail(dotpath, dotfile):
LOG.sub('{} (template:{})'.format(path, template)) LOG.sub('{} (template:{})'.format(path, template))
else: else:
for root, _, files in os.walk(path): for root, _, files in os.walk(path):
for f in files: for file in files:
p = os.path.join(root, f) fpath = os.path.join(root, file)
template = 'no' template = 'no'
if dotfile.template and Templategen.is_template(p): if dotfile.template and Templategen.is_template(fpath):
template = 'yes' template = 'yes'
LOG.sub('{} (template:{})'.format(p, template)) LOG.sub('{} (template:{})'.format(fpath, template))
def _select(selections, dotfiles): def _select(selections, dotfiles):
selected = [] selected = []
for selection in selections: for selection in selections:
df = next( dotfile = next(
(x for x in dotfiles (x for x in dotfiles
if os.path.expanduser(x.dst) == os.path.expanduser(selection)), if os.path.expanduser(x.dst) == os.path.expanduser(selection)),
None None
) )
if df: if dotfile:
selected.append(df) selected.append(dotfile)
else: else:
LOG.err('no dotfile matches \"{}\"'.format(selection)) LOG.err('no dotfile matches \"{}\"'.format(selection))
return selected return selected
@@ -716,9 +724,9 @@ def apply_trans(dotpath, dotfile, templater, debug=False):
new_src = '{}.{}'.format(src, TRANS_SUFFIX) new_src = '{}.{}'.format(src, TRANS_SUFFIX)
trans = dotfile.trans_r trans = dotfile.trans_r
LOG.dbg('executing transformation: {}'.format(trans)) LOG.dbg('executing transformation: {}'.format(trans))
s = os.path.join(dotpath, src) srcpath = os.path.join(dotpath, src)
temp = os.path.join(dotpath, new_src) temp = os.path.join(dotpath, new_src)
if not trans.transform(s, temp, templater=templater, debug=debug): if not trans.transform(srcpath, temp, templater=templater, debug=debug):
msg = 'transformation \"{}\" failed for {}' msg = 'transformation \"{}\" failed for {}'
LOG.err(msg.format(trans.key, dotfile.key)) LOG.err(msg.format(trans.key, dotfile.key))
if new_src and os.path.exists(new_src): if new_src and os.path.exists(new_src):
@@ -731,97 +739,103 @@ def apply_trans(dotpath, dotfile, templater, debug=False):
# main # main
########################################################### ###########################################################
def _exec_command(opts):
"""execute command"""
ret = True
command = ''
try:
if opts.cmd_profiles:
# list existing profiles
command = 'profiles'
LOG.dbg('running cmd: {}'.format(command))
cmd_list_profiles(opts)
elif opts.cmd_files:
# list files for selected profile
command = 'files'
LOG.dbg('running cmd: {}'.format(command))
cmd_files(opts)
elif opts.cmd_install:
# install the dotfiles stored in dotdrop
command = 'install'
LOG.dbg('running cmd: {}'.format(command))
ret = cmd_install(opts)
elif opts.cmd_compare:
# compare local dotfiles with dotfiles stored in dotdrop
command = 'compare'
LOG.dbg('running cmd: {}'.format(command))
tmp = get_tmpdir()
ret = cmd_compare(opts, tmp)
# clean tmp directory
removepath(tmp, LOG)
elif opts.cmd_import:
# import dotfile(s)
command = 'import'
LOG.dbg('running cmd: {}'.format(command))
ret = cmd_importer(opts)
elif opts.cmd_update:
# update a dotfile
command = 'update'
LOG.dbg('running cmd: {}'.format(command))
ret = cmd_update(opts)
elif opts.cmd_detail:
# detail files
command = 'detail'
LOG.dbg('running cmd: {}'.format(command))
cmd_detail(opts)
elif opts.cmd_remove:
# remove dotfile
command = 'remove'
LOG.dbg('running cmd: {}'.format(command))
cmd_remove(opts)
except KeyboardInterrupt:
LOG.err('interrupted')
ret = False
return ret, command
def main(): def main():
"""entry point""" """entry point"""
# check dependencies are met # check dependencies are met
try: try:
dependencies_met() dependencies_met()
except Exception as e: except UnmetDependency as exc:
LOG.err(e) LOG.err(exc)
return False return False
t0 = time.time() time0 = time.time()
try: try:
o = Options() opts = Options()
except YamlException as e: except YamlException as exc:
LOG.err('config error: {}'.format(str(e))) LOG.err('config error: {}'.format(str(exc)))
return False return False
except UndefinedException as e: except UndefinedException as exc:
LOG.err('config error: {}'.format(str(e))) LOG.err('config error: {}'.format(str(exc)))
return False return False
if o.debug: if opts.debug:
LOG.debug = o.debug LOG.debug = opts.debug
LOG.dbg('\n\n') LOG.dbg('\n\n')
options_time = time.time() - t0 options_time = time.time() - time0
ret = True time0 = time.time()
t0 = time.time() ret, command = _exec_command(opts)
command = '' cmd_time = time.time() - time0
try:
if o.cmd_profiles:
# list existing profiles
command = 'profiles'
LOG.dbg('running cmd: {}'.format(command))
cmd_list_profiles(o)
elif o.cmd_files:
# list files for selected profile
command = 'files'
LOG.dbg('running cmd: {}'.format(command))
cmd_files(o)
elif o.cmd_install:
# install the dotfiles stored in dotdrop
command = 'install'
LOG.dbg('running cmd: {}'.format(command))
ret = cmd_install(o)
elif o.cmd_compare:
# compare local dotfiles with dotfiles stored in dotdrop
command = 'compare'
LOG.dbg('running cmd: {}'.format(command))
tmp = get_tmpdir()
ret = cmd_compare(o, tmp)
# clean tmp directory
removepath(tmp, LOG)
elif o.cmd_import:
# import dotfile(s)
command = 'import'
LOG.dbg('running cmd: {}'.format(command))
ret = cmd_importer(o)
elif o.cmd_update:
# update a dotfile
command = 'update'
LOG.dbg('running cmd: {}'.format(command))
ret = cmd_update(o)
elif o.cmd_detail:
# detail files
command = 'detail'
LOG.dbg('running cmd: {}'.format(command))
cmd_detail(o)
elif o.cmd_remove:
# remove dotfile
command = 'remove'
LOG.dbg('running cmd: {}'.format(command))
cmd_remove(o)
except KeyboardInterrupt:
LOG.err('interrupted')
ret = False
cmd_time = time.time() - t0
LOG.dbg('done executing command \"{}\"'.format(command)) LOG.dbg('done executing command \"{}\"'.format(command))
LOG.dbg('options loaded in {}'.format(options_time)) LOG.dbg('options loaded in {}'.format(options_time))
LOG.dbg('command executed in {}'.format(cmd_time)) LOG.dbg('command executed in {}'.format(cmd_time))
if ret and o.conf.save(): if ret and opts.conf.save():
LOG.log('config file updated') LOG.log('config file updated')
LOG.dbg('return {}'.format(ret)) LOG.dbg('return {}'.format(ret))

View File

@@ -12,3 +12,7 @@ class YamlException(Exception):
class UndefinedException(Exception): class UndefinedException(Exception):
"""exception in templating""" """exception in templating"""
class UnmetDependency(Exception):
"""unmet dependency"""

View File

@@ -17,6 +17,7 @@ from dotdrop.exceptions import UndefinedException
class Installer: class Installer:
"""dotfile installer"""
def __init__(self, base='.', create=True, backup=True, def __init__(self, base='.', create=True, backup=True,
dry=False, safe=False, workdir='~/.config/dotdrop', dry=False, safe=False, workdir='~/.config/dotdrop',
@@ -65,7 +66,7 @@ class Installer:
def install(self, templater, src, dst, linktype, def install(self, templater, src, dst, linktype,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[], is_template=True, ignore=None, is_template=True,
chmod=None, force_chmod=False): chmod=None, force_chmod=False):
""" """
install src to dst install src to dst
@@ -93,7 +94,7 @@ class Installer:
return True, None return True, None
msg = 'installing \"{}\" to \"{}\" (link: {})' msg = 'installing \"{}\" to \"{}\" (link: {})'
self.log.dbg(msg.format(src, dst, str(linktype))) self.log.dbg(msg.format(src, dst, str(linktype)))
src, dst, cont, err = self._check_paths(src, dst, chmod) src, dst, cont, err = self._check_paths(src, dst)
if not cont: if not cont:
return self._log_install(cont, err) return self._log_install(cont, err)
@@ -108,10 +109,13 @@ class Installer:
# install to temporary dir # install to temporary dir
# and ignore any actions # and ignore any actions
if self.totemp: if self.totemp:
r, err, _ = self.install_to_temp(templater, self.totemp, ret, err, _ = self.install_to_temp(templater,
src, dst, is_template=is_template, self.totemp,
chmod=chmod, ignore=ignore) src, dst,
return self._log_install(r, err) is_template=is_template,
chmod=chmod,
ignore=ignore)
return self._log_install(ret, err)
isdir = os.path.isdir(src) isdir = os.path.isdir(src)
self.log.dbg('install {} to {}'.format(src, dst)) self.log.dbg('install {} to {}'.format(src, dst))
@@ -120,20 +124,18 @@ class Installer:
if linktype == LinkTypes.NOLINK: if linktype == LinkTypes.NOLINK:
# normal file # normal file
if isdir: if isdir:
r, err = self._copy_dir(templater, src, dst, ret, err = self._copy_dir(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, ignore=ignore, noempty=noempty, ignore=ignore,
is_template=is_template, is_template=is_template)
chmod=chmod)
else: else:
r, err = self._copy_file(templater, src, dst, ret, err = self._copy_file(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, ignore=ignore, noempty=noempty, ignore=ignore,
is_template=is_template, is_template=is_template)
chmod=chmod)
elif linktype == LinkTypes.LINK: elif linktype == LinkTypes.LINK:
# symlink # symlink
r, err = self._link(templater, src, dst, ret, err = self._link(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
is_template=is_template) is_template=is_template)
elif linktype == LinkTypes.LINK_CHILDREN: elif linktype == LinkTypes.LINK_CHILDREN:
@@ -142,17 +144,17 @@ class Installer:
msg = 'symlink children of {} to {}' msg = 'symlink children of {} to {}'
self.log.dbg(msg.format(src, dst)) self.log.dbg(msg.format(src, dst))
err = 'source dotfile is not a directory: {}'.format(src) err = 'source dotfile is not a directory: {}'.format(src)
r = False ret = False
else: else:
r, err = self._link_children(templater, src, dst, ret, err = self._link_children(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
is_template=is_template, is_template=is_template,
ignore=ignore) ignore=ignore)
self.log.dbg('before chmod: {} err:{}'.format(r, err)) self.log.dbg('before chmod: {} err:{}'.format(ret, err))
if self.dry: if self.dry:
return self._log_install(r, err) return self._log_install(ret, err)
# handle chmod # handle chmod
# - on success (r, not err) # - on success (r, not err)
@@ -160,7 +162,7 @@ class Installer:
# but not when # but not when
# - error (not r, err) # - error (not r, err)
# - aborted (not r, err) # - aborted (not r, err)
if (r or (not r and not err)): if (ret or (not ret and not err)):
if not chmod: if not chmod:
chmod = utils.get_file_perm(src) chmod = utils.get_file_perm(src)
dstperms = utils.get_file_perm(dst) dstperms = utils.get_file_perm(dst)
@@ -168,21 +170,21 @@ class Installer:
# apply mode # apply mode
msg = 'chmod {} to {:o}'.format(dst, chmod) msg = 'chmod {} to {:o}'.format(dst, chmod)
if not force_chmod and self.safe and not self.log.ask(msg): if not force_chmod and self.safe and not self.log.ask(msg):
r = False ret = False
err = 'aborted' err = 'aborted'
else: else:
if not self.comparing: if not self.comparing:
self.log.sub('chmod {} to {:o}'.format(dst, chmod)) self.log.sub('chmod {} to {:o}'.format(dst, chmod))
if utils.chmod(dst, chmod, debug=self.debug): if utils.chmod(dst, chmod, debug=self.debug):
r = True ret = True
else: else:
r = False ret = False
err = 'chmod failed' err = 'chmod failed'
return self._log_install(r, err) return self._log_install(ret, err)
def install_to_temp(self, templater, tmpdir, src, dst, def install_to_temp(self, templater, tmpdir, src, dst,
is_template=True, chmod=None, ignore=[]): is_template=True, chmod=None, ignore=None):
""" """
install a dotfile to a tempdir install a dotfile to a tempdir
@@ -198,9 +200,10 @@ class Installer:
- success, error-if-any, dotfile-installed-path - success, error-if-any, dotfile-installed-path
""" """
self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst)) self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst))
src, dst, cont, err = self._check_paths(src, dst, chmod) src, dst, cont, err = self._check_paths(src, dst)
if not cont: if not cont:
return self._log_install(cont, err) self._log_install(cont, err)
return cont, err, None
ret = False ret = False
tmpdst = '' tmpdst = ''
@@ -253,18 +256,18 @@ class Installer:
self.log.dbg('is a template') self.log.dbg('is a template')
self.log.dbg('install to {}'.format(self.workdir)) self.log.dbg('install to {}'.format(self.workdir))
tmp = self._pivot_path(dst, self.workdir, striphome=True) tmp = self._pivot_path(dst, self.workdir, striphome=True)
r, err = self.install(templater, src, tmp, ret, err = self.install(templater, src, tmp,
LinkTypes.NOLINK, LinkTypes.NOLINK,
actionexec=actionexec, actionexec=actionexec,
is_template=is_template) is_template=is_template)
if not r and not os.path.exists(tmp): if not ret and not os.path.exists(tmp):
return r, err return ret, err
src = tmp src = tmp
r, err = self._symlink(src, dst, actionexec=actionexec) ret, err = self._symlink(src, dst, actionexec=actionexec)
return r, err return ret, err
def _link_children(self, templater, src, dst, def _link_children(self, templater, src, dst,
actionexec=None, is_template=True, ignore=[]): actionexec=None, is_template=True, ignore=None):
""" """
install link:link_children install link:link_children
@@ -318,11 +321,11 @@ class Installer:
self.log.dbg('install to {} and symlink' self.log.dbg('install to {} and symlink'
.format(self.workdir)) .format(self.workdir))
tmp = self._pivot_path(subdst, self.workdir, striphome=True) tmp = self._pivot_path(subdst, self.workdir, striphome=True)
r, e = self.install(templater, subsrc, tmp, ret2, err2 = self.install(templater, subsrc, tmp,
LinkTypes.NOLINK, LinkTypes.NOLINK,
actionexec=actionexec, actionexec=actionexec,
is_template=is_template) is_template=is_template)
if not r and e and not os.path.exists(tmp): if not ret2 and err2 and not os.path.exists(tmp):
continue continue
subsrc = tmp subsrc = tmp
@@ -369,8 +372,8 @@ class Installer:
overwrite = True overwrite = True
try: try:
utils.removepath(dst) utils.removepath(dst)
except OSError as e: except OSError as exc:
err = 'something went wrong with {}: {}'.format(src, e) err = 'something went wrong with {}: {}'.format(src, exc)
return False, err return False, err
if self.dry: if self.dry:
self.log.dry('would link {} to {}'.format(dst, src)) self.log.dry('would link {} to {}'.format(dst, src))
@@ -379,9 +382,9 @@ class Installer:
if not self._create_dirs(base): if not self._create_dirs(base):
err = 'error creating directory for {}'.format(dst) err = 'error creating directory for {}'.format(dst)
return False, err return False, err
r, e = self._exec_pre_actions(actionexec) ret, err = self._exec_pre_actions(actionexec)
if not r: if not ret:
return False, e return False, err
# re-check in case action created the file # re-check in case action created the file
if os.path.lexists(dst): if os.path.lexists(dst):
msg = 'Remove "{}" for link creation?'.format(dst) msg = 'Remove "{}" for link creation?'.format(dst)
@@ -389,8 +392,8 @@ class Installer:
return False, 'aborted' return False, 'aborted'
try: try:
utils.removepath(dst) utils.removepath(dst)
except OSError as e: except OSError as exc:
err = 'something went wrong with {}: {}'.format(src, e) err = 'something went wrong with {}: {}'.format(src, exc)
return False, err return False, err
os.symlink(src, dst) os.symlink(src, dst)
if not self.comparing: if not self.comparing:
@@ -399,8 +402,7 @@ class Installer:
def _copy_file(self, templater, src, dst, def _copy_file(self, templater, src, dst,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[], is_template=True, ignore=None, is_template=True):
chmod=None):
""" """
install src to dst when is a file install src to dst when is a file
@@ -441,8 +443,8 @@ class Installer:
saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst)) saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst))
try: try:
content = templater.generate(src) content = templater.generate(src)
except UndefinedException as e: except UndefinedException as exc:
return False, str(e) return False, str(exc)
finally: finally:
templater.restore_vars(saved) templater.restore_vars(saved)
# test is empty # test is empty
@@ -456,8 +458,7 @@ class Installer:
# write the file # write the file
ret, err = self._write(src, dst, ret, err = self._write(src, dst,
content=content, content=content,
actionexec=actionexec, actionexec=actionexec)
chmod=chmod)
if ret and not err: if ret and not err:
if not self.dry and not self.comparing: if not self.dry and not self.comparing:
self.log.sub('install {} to {}'.format(src, dst)) self.log.sub('install {} to {}'.format(src, dst))
@@ -465,7 +466,7 @@ class Installer:
def _copy_dir(self, templater, src, dst, def _copy_dir(self, templater, src, dst,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[], is_template=True, chmod=None): ignore=None, is_template=True):
""" """
install src to dst when is a directory install src to dst when is a directory
@@ -486,17 +487,16 @@ class Installer:
# handle all files in dir # handle all files in dir
for entry in os.listdir(src): for entry in os.listdir(src):
f = os.path.join(src, entry) fpath = os.path.join(src, entry)
self.log.dbg('deploy sub from {}: {}'.format(dst, entry)) self.log.dbg('deploy sub from {}: {}'.format(dst, entry))
if not os.path.isdir(f): if not os.path.isdir(fpath):
# is file # is file
res, err = self._copy_file(templater, f, res, err = self._copy_file(templater, fpath,
os.path.join(dst, entry), os.path.join(dst, entry),
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore, ignore=ignore,
is_template=is_template, is_template=is_template)
chmod=None)
if not res and err: if not res and err:
# error occured # error occured
ret = res, err ret = res, err
@@ -506,13 +506,12 @@ class Installer:
ret = True, None ret = True, None
else: else:
# is directory # is directory
res, err = self._copy_dir(templater, f, res, err = self._copy_dir(templater, fpath,
os.path.join(dst, entry), os.path.join(dst, entry),
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore, ignore=ignore,
is_template=is_template, is_template=is_template)
chmod=None)
if not res and err: if not res and err:
# error occured # error occured
ret = res, err ret = res, err
@@ -522,8 +521,33 @@ class Installer:
ret = True, None ret = True, None
return ret return ret
@classmethod
def _write_content_to_file(cls, content, src, dst):
"""write content to file"""
if content:
# write content the file
try:
with open(dst, 'wb') as file:
file.write(content)
shutil.copymode(src, dst)
except NotADirectoryError as exc:
err = 'opening dest file: {}'.format(exc)
return False, err
except OSError as exc:
return False, str(exc)
except TypeError as exc:
return False, str(exc)
else:
# copy file
try:
shutil.copyfile(src, dst)
shutil.copymode(src, dst)
except OSError as exc:
return False, str(exc)
return True, None
def _write(self, src, dst, content=None, def _write(self, src, dst, content=None,
actionexec=None, chmod=None): actionexec=None):
""" """
copy dotfile / write content to file copy dotfile / write content to file
@@ -541,8 +565,8 @@ class Installer:
if os.path.lexists(dst): if os.path.lexists(dst):
try: try:
os.stat(dst) os.stat(dst)
except OSError as e: except OSError as exc:
if e.errno == errno.ENOENT: if exc.errno == errno.ENOENT:
# broken symlink # broken symlink
err = 'broken symlink {}'.format(dst) err = 'broken symlink {}'.format(dst)
return False, err return False, err
@@ -560,47 +584,38 @@ class Installer:
if not self.log.ask('Overwrite \"{}\"'.format(dst)): if not self.log.ask('Overwrite \"{}\"'.format(dst)):
return False, 'aborted' return False, 'aborted'
overwrite = True overwrite = True
if self.backup and os.path.lexists(dst): if self.backup and os.path.lexists(dst):
self._backup(dst) self._backup(dst)
# create hierarchy
base = os.path.dirname(dst) base = os.path.dirname(dst)
if not self._create_dirs(base): if not self._create_dirs(base):
err = 'creating directory for {}'.format(dst) err = 'creating directory for {}'.format(dst)
return False, err return False, err
r, e = self._exec_pre_actions(actionexec)
if not r: # execute pre actions
return False, e ret, err = self._exec_pre_actions(actionexec)
if not ret:
return False, err
self.log.dbg('install file to \"{}\"'.format(dst)) self.log.dbg('install file to \"{}\"'.format(dst))
# re-check in case action created the file # re-check in case action created the file
if self.safe and not overwrite and os.path.lexists(dst): if self.safe and not overwrite and \
if not self.log.ask('Overwrite \"{}\"'.format(dst)): os.path.lexists(dst) and \
not self.log.ask('Overwrite \"{}\"'.format(dst)):
self.log.warn('ignoring {}'.format(dst)) self.log.warn('ignoring {}'.format(dst))
return False, 'aborted' return False, 'aborted'
if content: # writing to file
# write content the file return self._write_content_to_file(content, src, dst)
try:
with open(dst, 'wb') as f:
f.write(content)
shutil.copymode(src, dst)
except NotADirectoryError as e:
err = 'opening dest file: {}'.format(e)
return False, err
except Exception as e:
return False, str(e)
else:
# copy file
try:
shutil.copyfile(src, dst)
shutil.copymode(src, dst)
except Exception as e:
return False, str(e)
return True, None
######################################################## ########################################################
# helpers # helpers
######################################################## ########################################################
def _get_tmp_file_vars(self, src, dst): @classmethod
def _get_tmp_file_vars(cls, src, dst):
tmp = {} tmp = {}
tmp['_dotfile_sub_abs_src'] = src tmp['_dotfile_sub_abs_src'] = src
tmp['_dotfile_sub_abs_dst'] = dst tmp['_dotfile_sub_abs_dst'] = dst
@@ -615,10 +630,10 @@ class Installer:
if content: if content:
tmp = utils.write_to_tmpfile(content) tmp = utils.write_to_tmpfile(content)
src = tmp src = tmp
r = utils.fastdiff(src, dst) ret = utils.fastdiff(src, dst)
if r: if ret:
self.log.dbg('content differ') self.log.dbg('content differ')
return r return ret
def _show_diff_before_write(self, src, dst, content=None): def _show_diff_before_write(self, src, dst, content=None):
""" """
@@ -691,7 +706,10 @@ class Installer:
return ret, err return ret, err
def _log_install(self, boolean, err): def _log_install(self, boolean, err):
"""log installation process""" """
log installation process
returns success, error-if-any
"""
if not self.debug: if not self.debug:
return boolean, err return boolean, err
if boolean: if boolean:
@@ -703,7 +721,7 @@ class Installer:
self.log.dbg('install: IGNORED') self.log.dbg('install: IGNORED')
return boolean, err return boolean, err
def _check_paths(self, src, dst, chmod): def _check_paths(self, src, dst):
""" """
check and normalize param check and normalize param
returns <src>, <dst>, <continue>, <error> returns <src>, <dst>, <continue>, <error>

View File

@@ -30,9 +30,10 @@ COMMENT_END = '@@#}'
class Templategen: class Templategen:
"""dotfile templater"""
def __init__(self, base='.', variables={}, def __init__(self, base='.', variables=None,
func_file=[], filter_file=[], debug=False): func_file=None, filter_file=None, debug=False):
"""constructor """constructor
@base: directory path where to search for templates @base: directory path where to search for templates
@variables: dictionary of variables for templates @variables: dictionary of variables for templates
@@ -69,13 +70,13 @@ class Templategen:
self.log.dbg('load global functions:') self.log.dbg('load global functions:')
self._load_funcs_to_dic(jhelpers, self.env.globals) self._load_funcs_to_dic(jhelpers, self.env.globals)
if func_file: if func_file:
for f in func_file: for ffile in func_file:
self.log.dbg('load custom functions from {}'.format(f)) self.log.dbg('load custom functions from {}'.format(ffile))
self._load_path_to_dic(f, self.env.globals) self._load_path_to_dic(ffile, self.env.globals)
if filter_file: if filter_file:
for f in filter_file: for ffile in filter_file:
self.log.dbg('load custom filters from {}'.format(f)) self.log.dbg('load custom filters from {}'.format(ffile))
self._load_path_to_dic(f, self.env.filters) self._load_path_to_dic(ffile, self.env.filters)
if self.debug: if self.debug:
self._debug_dict('template additional variables', variables) self._debug_dict('template additional variables', variables)
@@ -89,9 +90,9 @@ class Templategen:
return '' return ''
try: try:
return self._handle_file(src) return self._handle_file(src)
except UndefinedError as e: except UndefinedError as exc:
err = 'undefined variable: {}'.format(e.message) err = 'undefined variable: {}'.format(exc.message)
raise UndefinedException(err) raise UndefinedException(err) from exc
def generate_string(self, string): def generate_string(self, string):
""" """
@@ -103,11 +104,11 @@ class Templategen:
return '' return ''
try: try:
return self.env.from_string(string).render(self.variables) return self.env.from_string(string).render(self.variables)
except UndefinedError as e: except UndefinedError as exc:
err = 'undefined variable: {}'.format(e.message) err = 'undefined variable: {}'.format(exc.message)
raise UndefinedException(err) raise UndefinedException(err) from exc
def add_tmp_vars(self, newvars={}): def add_tmp_vars(self, newvars=None):
"""add vars to the globals, make sure to call restore_vars""" """add vars to the globals, make sure to call restore_vars"""
saved_variables = self.variables.copy() saved_variables = self.variables.copy()
if not newvars: if not newvars:
@@ -139,13 +140,15 @@ class Templategen:
self.log.dbg('load function \"{}\"'.format(name)) self.log.dbg('load function \"{}\"'.format(name))
dic[name] = func dic[name] = func
def _header(self, prepend=''): @classmethod
def _header(cls, prepend=''):
"""add a comment usually in the header of a dotfile""" """add a comment usually in the header of a dotfile"""
return '{}{}'.format(prepend, utils.header()) return '{}{}'.format(prepend, utils.header())
def _handle_file(self, src): def _handle_file(self, src):
"""generate the file content from template""" """generate the file content from template"""
try: try:
# pylint: disable=C0415
import magic import magic
filetype = magic.from_file(src, mime=True) filetype = magic.from_file(src, mime=True)
self.log.dbg('using \"magic\" for filetype identification') self.log.dbg('using \"magic\" for filetype identification')
@@ -162,7 +165,8 @@ class Templategen:
return self._handle_bin_file(src) return self._handle_bin_file(src)
return self._handle_text_file(src) return self._handle_text_file(src)
def _is_text(self, fileoutput): @classmethod
def _is_text(cls, fileoutput):
"""return if `file -b` output is ascii text""" """return if `file -b` output is ascii text"""
out = fileoutput.lower() out = fileoutput.lower()
if out.startswith('text'): if out.startswith('text'):
@@ -179,8 +183,8 @@ class Templategen:
path = os.path.normpath(path) path = os.path.normpath(path)
if not os.path.exists(path): if not os.path.exists(path):
raise TemplateNotFound(path) raise TemplateNotFound(path)
with open(path, 'r') as f: with open(path, 'r') as file:
content = f.read() content = file.read()
return content return content
def _handle_text_file(self, src): def _handle_text_file(self, src):
@@ -199,18 +203,19 @@ class Templategen:
# this is dirty # this is dirty
if not src.startswith(self.base): if not src.startswith(self.base):
src = os.path.join(self.base, src) src = os.path.join(self.base, src)
with open(src, 'rb') as f: with open(src, 'rb') as file:
content = f.read() content = file.read()
return content return content
def _read_bad_encoded_text(self, path): @classmethod
def _read_bad_encoded_text(cls, path):
"""decode non utf-8 data""" """decode non utf-8 data"""
with open(path, 'rb') as f: with open(path, 'rb') as file:
data = f.read() data = file.read()
return data.decode('utf-8', 'replace') return data.decode('utf-8', 'replace')
@staticmethod @staticmethod
def is_template(path, ignore=[]): def is_template(path, ignore=None):
"""recursively check if any file is a template within path""" """recursively check if any file is a template within path"""
path = os.path.expanduser(path) path = os.path.expanduser(path)
@@ -250,10 +255,11 @@ class Templategen:
markers = [BLOCK_START, VAR_START, COMMENT_START] markers = [BLOCK_START, VAR_START, COMMENT_START]
patterns = [re.compile(marker.encode()) for marker in markers] patterns = [re.compile(marker.encode()) for marker in markers]
try: try:
with io.open(path, "r", encoding="utf-8") as f: with io.open(path, "r", encoding="utf-8") as file:
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ) mapf = mmap.mmap(file.fileno(), 0,
access=mmap.ACCESS_READ)
for pattern in patterns: for pattern in patterns:
if pattern.search(m): if pattern.search(mapf):
return True return True
except UnicodeDecodeError: except UnicodeDecodeError:
# is binary so surely no template # is binary so surely no template
@@ -267,5 +273,5 @@ class Templategen:
self.log.dbg('{}:'.format(title)) self.log.dbg('{}:'.format(title))
if not elems: if not elems:
return return
for k, v in elems.items(): for k, val in elems.items():
self.log.dbg(' - \"{}\": {}'.format(k, v)) self.log.dbg(' - \"{}\": {}'.format(k, val))

View File

@@ -18,6 +18,7 @@ from shutil import rmtree, which
# local import # local import
from dotdrop.logger import Logger from dotdrop.logger import Logger
from dotdrop.exceptions import UnmetDependency
LOG = Logger() LOG = Logger()
STAR = '*' STAR = '*'
@@ -316,7 +317,7 @@ def dependencies_met():
err = 'The tool \"{}\" was not found in the PATH!' err = 'The tool \"{}\" was not found in the PATH!'
for dep in deps: for dep in deps:
if not which(dep): if not which(dep):
raise Exception(err.format(dep)) raise UnmetDependency(err.format(dep))
# check python deps # check python deps
err = 'missing python module \"{}\"' err = 'missing python module \"{}\"'