1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-06 20:40:38 +00:00

- add ability to use "write transformation"

- only allow a single transformation per dotfile
- refactoring and bug fixes
This commit is contained in:
deadc0de6
2018-12-06 13:19:57 +01:00
parent 1d53602769
commit 37db5cd68c
9 changed files with 262 additions and 112 deletions

View File

@@ -60,15 +60,15 @@ class Transform(Cmd):
"""execute transformation with {0} and {1}
where {0} is the file to transform and
{1} is the result file"""
if os.path.exists(arg1):
msg = 'transformation destination exists: {}'
self.log.warn(msg.format(arg1))
return False
ret = 1
cmd = self.action.format(arg0, arg1)
if os.path.exists(arg1):
msg = 'transformation \"{}\": destination exists: {}'
self.log.warn(msg.format(cmd, arg1))
return False
self.log.sub('transforming with \"{}\"'.format(cmd))
try:
ret = subprocess.call(cmd, shell=True)
except KeyboardInterrupt:
self.log.warn('action interrupted')
self.log.warn('transformation interrupted')
return ret == 0

View File

@@ -42,7 +42,8 @@ class Cfg:
key_actions_post = 'post'
# transformations keys
key_trans = 'trans'
key_trans_r = 'trans'
key_trans_w = 'trans_write'
# template variables
key_variables = 'variables'
@@ -57,7 +58,8 @@ class Cfg:
key_dotfiles_noempty = 'ignoreempty'
key_dotfiles_cmpignore = 'cmpignore'
key_dotfiles_actions = 'actions'
key_dotfiles_trans = 'trans'
key_dotfiles_trans_r = 'trans'
key_dotfiles_trans_w = 'trans_write'
# profiles keys
key_profiles = 'profiles'
@@ -101,9 +103,13 @@ class Cfg:
# NOT linked inside the yaml dict (self.content)
self.actions = {}
# dict of all transformation objects by trans key
# dict of all read transformation objects by trans key
# NOT linked inside the yaml dict (self.content)
self.trans = {}
self.trans_r = {}
# dict of all write transformation objects by trans key
# NOT linked inside the yaml dict (self.content)
self.trans_w = {}
# represents all dotfiles per profile by profile key
# NOT linked inside the yaml dict (self.content)
@@ -174,11 +180,17 @@ class Cfg:
self.actions[self.key_actions_post] = {}
self.actions[self.key_actions_post][k] = Action(k, v)
# parse all transformations
if self.key_trans in self.content:
if self.content[self.key_trans] is not None:
for k, v in self.content[self.key_trans].items():
self.trans[k] = Transform(k, v)
# parse read transformations
if self.key_trans_r in self.content:
if self.content[self.key_trans_r] is not None:
for k, v in self.content[self.key_trans_r].items():
self.trans_r[k] = Transform(k, v)
# parse write transformations
if self.key_trans_w in self.content:
if self.content[self.key_trans_w] is not None:
for k, v in self.content[self.key_trans_w].items():
self.trans_w[k] = Transform(k, v)
# parse the profiles
self.lnk_profiles = self.content[self.key_profiles]
@@ -213,20 +225,60 @@ class Cfg:
itsactions = v[self.key_dotfiles_actions] if \
self.key_dotfiles_actions in v else []
actions = self._parse_actions(itsactions)
itstrans = v[self.key_dotfiles_trans] if \
self.key_dotfiles_trans in v else []
trans = self._parse_trans(itstrans)
if len(trans) > 0 and link:
# parse read transformation
itstrans_r = v[self.key_dotfiles_trans_r] if \
self.key_dotfiles_trans_r in v else None
trans_r = None
if itstrans_r:
if type(itstrans_r) is list:
msg = 'One transformation allowed per dotfile'
msg += ', error on dotfile \"{}\"'
self.log.err(msg.format(k))
msg = 'Please modify your config file to: \"trans: {}\"'
self.log.err(msg.format(itstrans_r[0]))
return False
trans_r = self._parse_trans(itstrans_r, read=True)
if not trans_r:
msg = 'unknown trans \"{}\" for \"{}\"'
self.log.err(msg.format(itstrans_r, k))
return False
# parse write transformation
itstrans_w = v[self.key_dotfiles_trans_w] if \
self.key_dotfiles_trans_w in v else None
trans_w = None
if itstrans_w:
if type(itstrans_w) is list:
msg = 'One write transformation allowed per dotfile'
msg += ', error on dotfile \"{}\"'
self.log.err(msg.format(k))
msg = 'Please modify your config file: \"trans_write: {}\"'
self.log.err(msg.format(itstrans_w[0]))
return False
trans_w = self._parse_trans(itstrans_w, read=False)
if not trans_w:
msg = 'unknown trans_write \"{}\" for \"{}\"'
self.log.err(msg.format(itstrans_w, k))
return False
# disable transformation when link is true
if link and (trans_r or trans_w):
msg = 'transformations disabled for \"{}\"'.format(dst)
msg += ' because link is True'
self.log.warn(msg)
trans = []
trans_r = None
trans_w = None
# parse ignore pattern
ignores = v[self.key_dotfiles_cmpignore] if \
self.key_dotfiles_cmpignore in v else []
# create new dotfile
self.dotfiles[k] = Dotfile(k, dst, src,
link=link, actions=actions,
trans=trans, cmpignore=ignores,
noempty=noempty)
trans_r=trans_r, trans_w=trans_w,
cmpignore=ignores, noempty=noempty)
# assign dotfiles to each profile
for k, v in self.lnk_profiles.items():
@@ -315,16 +367,14 @@ class Cfg:
res[key].append(action)
return res
def _parse_trans(self, entries):
"""parse transformations specified for an element
where entries are the ones defined for this dotfile"""
res = []
for entry in entries:
if entry not in self.trans.keys():
self.log.warn('unknown trans \"{}\"'.format(entry))
continue
res.append(self.trans[entry])
return res
def _parse_trans(self, trans, read=True):
"""parse transformation key specified for a dotfile"""
transformations = self.trans_r
if not read:
transformations = self.trans_w
if trans not in transformations.keys():
return None
return transformations[trans]
def _complete_settings(self):
"""set settings defaults if not present"""

View File

@@ -113,7 +113,7 @@ def cmd_install(opts, conf, temporary=False, keys=[]):
else:
src = dotfile.src
tmp = None
if dotfile.trans:
if dotfile.trans_r:
tmp = apply_trans(opts, dotfile)
if not tmp:
continue
@@ -173,7 +173,7 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
LOG.emph('\"{}\" does not exist on local\n'.format(dotfile.dst))
tmpsrc = None
if dotfile.trans:
if dotfile.trans_r:
# apply transformation
tmpsrc = apply_trans(opts, dotfile)
if not tmpsrc:
@@ -387,22 +387,18 @@ def _select(selections, dotfiles):
def apply_trans(opts, dotfile):
"""apply the transformation to the dotfile
"""apply the read transformation to the dotfile
return None if fails and new source if succeed"""
src = dotfile.src
new_src = '{}.{}'.format(src, TRANS_SUFFIX)
err = False
for trans in dotfile.trans:
if opts['debug']:
LOG.dbg('executing transformation {}'.format(trans))
s = os.path.join(opts['dotpath'], src)
temp = os.path.join(opts['dotpath'], new_src)
if not trans.transform(s, temp):
msg = 'transformation \"{}\" failed for {}'
LOG.err(msg.format(trans.key, dotfile.key))
err = True
break
if err:
trans = dotfile.trans_r
if opts['debug']:
LOG.dbg('executing transformation {}'.format(trans))
s = os.path.join(opts['dotpath'], src)
temp = os.path.join(opts['dotpath'], new_src)
if not trans.transform(s, temp):
msg = 'transformation \"{}\" failed for {}'
LOG.err(msg.format(trans.key, dotfile.key))
if new_src and os.path.exists(new_src):
remove(new_src)
return None
@@ -422,7 +418,7 @@ def main():
try:
conf = Cfg(os.path.expanduser(args['--cfg']))
except ValueError as e:
LOG.err('error: {}'.format(str(e)))
LOG.err('Config format error: {}'.format(str(e)))
return False
opts = conf.get_settings()

View File

@@ -9,9 +9,8 @@ represents a dotfile in dotdrop
class Dotfile:
def __init__(self, key, dst, src,
actions={}, trans=[],
link=False, cmpignore=[],
noempty=False):
actions={}, trans_r=None, trans_w=None,
link=False, cmpignore=[], noempty=False):
# key of dotfile in the config
self.key = key
# path where to install this dotfile
@@ -22,8 +21,10 @@ class Dotfile:
self.link = link
# list of actions
self.actions = actions
# list of transformations
self.trans = trans
# read transformation
self.trans_r = trans_r
# write transformation
self.trans_w = trans_w
# pattern to ignore when comparing
self.cmpignore = cmpignore
# do not deploy empty file

View File

@@ -56,12 +56,39 @@ class Updater:
def _update(self, path, dotfile):
"""update dotfile from file pointed by path"""
ret = False
new_path = None
left = os.path.expanduser(path)
right = os.path.join(self.conf.abs_dotpath(self.dotpath), dotfile.src)
right = os.path.expanduser(right)
if os.path.isdir(path):
return self._handle_dir(left, right)
return self._handle_file(left, right)
if dotfile.trans_w:
# apply write transformation if any
new_path = self._apply_trans_w(path, dotfile)
if not new_path:
return False
left = new_path
if os.path.isdir(left):
ret = self._handle_dir(left, right)
else:
ret = self._handle_file(left, right)
# clean temporary files
if new_path and os.path.exists(new_path):
utils.remove(new_path)
return ret
def _apply_trans_w(self, path, dotfile):
"""apply write transformation to dotfile"""
trans = dotfile.trans_w
if self.debug:
self.log.dbg('executing write transformation {}'.format(trans))
tmp = utils.get_unique_tmp_name()
if not trans.transform(path, tmp):
msg = 'transformation \"{}\" failed for {}'
self.log.err(msg.format(trans.key, dotfile.key))
if os.path.exists(tmp):
utils.remove(tmp)
return None
return tmp
def _normalize(self, path):
"""normalize the path to match dotfile"""

View File

@@ -8,6 +8,7 @@ utilities
import subprocess
import tempfile
import os
import uuid
import shlex
from shutil import rmtree
@@ -61,6 +62,12 @@ def get_tmpfile():
return path
def get_unique_tmp_name():
"""get a unique file name (not created)"""
unique = str(uuid.uuid4())
return os.path.join(tempfile.gettempdir(), unique)
def remove(path):
"""remove a file/directory/symlink"""
if not os.path.lexists(path):