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:
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user