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

Merge branch 'master' of github.com:deadc0de6/dotdrop into master

This commit is contained in:
deadc0de6
2020-10-11 20:29:44 +02:00
19 changed files with 496 additions and 98 deletions

View File

@@ -31,6 +31,7 @@ Entry | Description | Default
`longkey` | use long keys for dotfiles when importing (see [Import dotfiles](usage.md#import-dotfiles)) | false `longkey` | use long keys for dotfiles when importing (see [Import dotfiles](usage.md#import-dotfiles)) | false
`minversion` | (*for internal use, do not modify*) provides the minimal dotdrop version to use | - `minversion` | (*for internal use, do not modify*) provides the minimal dotdrop version to use | -
`showdiff` | on install show a diff before asking to overwrite (see `--showdiff`) | false `showdiff` | on install show a diff before asking to overwrite (see `--showdiff`) | false
`template_dotfile_default` | disable templating on all dotfiles when set to false | true
`upignore` | list of patterns to ignore when updating, apply to all dotfiles (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) | - `upignore` | list of patterns to ignore when updating, apply to all dotfiles (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) | -
`workdir` | path to the directory where templates are installed before being symlinked when using `link:link` or `link:link_children` (absolute path or relative to the config file location) | `~/.config/dotdrop` `workdir` | path to the directory where templates are installed before being symlinked when using `link:link` or `link:link_children` (absolute path or relative to the config file location) | `~/.config/dotdrop`
<s>link_by_default</s> | when importing a dotfile set `link` to that value per default | false <s>link_by_default</s> | when importing a dotfile set `link` to that value per default | false
@@ -48,6 +49,7 @@ Entry | Description
`cmpignore` | list of patterns to ignore when comparing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) `cmpignore` | list of patterns to ignore when comparing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns))
`ignoreempty` | if true empty template will not be deployed (defaults to value of `ignoreempty`) `ignoreempty` | if true empty template will not be deployed (defaults to value of `ignoreempty`)
`instignore` | list of patterns to ignore when installing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) `instignore` | list of patterns to ignore when installing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns))
`template` | if false disable template for this dotfile (defaults to value of `template_dotfile_default`)
`trans_read` | transformation key to apply when installing this dotfile (must be defined in the **trans_read** entry below, see [transformations](config-details.md#entry-transformations)) `trans_read` | transformation key to apply when installing this dotfile (must be defined in the **trans_read** entry below, see [transformations](config-details.md#entry-transformations))
`trans_write` | transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below, see [transformations](config-details.md#entry-transformations)) `trans_write` | transformation key to apply when updating this dotfile (must be defined in the **trans_write** entry below, see [transformations](config-details.md#entry-transformations))
`upignore` | list of patterns to ignore when updating (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) `upignore` | list of patterns to ignore when updating (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns))
@@ -69,6 +71,7 @@ Entry | Description
- "<ignore-pattern>" - "<ignore-pattern>"
actions: actions:
- <action-key> - <action-key>
template: (true|false)
trans_read: <transformation-key> trans_read: <transformation-key>
trans_write: <transformation-key> trans_write: <transformation-key>
``` ```

View File

@@ -13,8 +13,8 @@ Note that if the dotfile is using template directives, it will be symlinked into
`~/.config/dotdrop` instead of directly into your *dotpath* `~/.config/dotdrop` instead of directly into your *dotpath*
(see [Templating symlinked dotfiles](#templating-symlinked-dotfiles)) (see [Templating symlinked dotfiles](#templating-symlinked-dotfiles))
Although the config entries `link_on_import` and `link_dotfile_default` can be set to `link_children`, it Although the config entries `link_on_import` and `link_dotfile_default` can be set to the value `link_children`,
is not recommended since operations on a dotfile that is not a directory with the option `link_children` it is not recommended since operations on a dotfile that is not a directory with the option `link_children`
will fail. will fail.
## Symlink a dotfile ## Symlink a dotfile

View File

@@ -4,6 +4,19 @@ Dotdrop leverage the power of [jinja2](https://palletsprojects.com/p/jinja/) to
templating of dotfiles. See [jinja2 template doc](https://jinja.palletsprojects.com/en/2.11.x/templates/) templating of dotfiles. See [jinja2 template doc](https://jinja.palletsprojects.com/en/2.11.x/templates/)
or the below sections for more information on how to template your dotfiles. or the below sections for more information on how to template your dotfiles.
## Templating or not templating
The dotfile config entry [template](config-format.md#dotfiles-entry)
and the global config entry [template_dotfile_default](config-format.md#config-entry)
allow to control if a dotfile is being process by the templating engine.
Obviously if the dotfile uses template directives, it needs to be templated. However if it
is not, disabling templating will speed up its installation (since it won't have to be
processed by the engine).
For dotfiles being symlinked (`link` or `link_children`), see
[the dedicated doc](howto/symlink-dotfiles.md#templating-symlinked-dotfiles)
## Delimiters ## Delimiters
Dotdrop uses different delimiters than Dotdrop uses different delimiters than

View File

@@ -57,6 +57,7 @@ class CfgYaml:
key_dotfile_link = 'link' key_dotfile_link = 'link'
key_dotfile_actions = 'actions' key_dotfile_actions = 'actions'
key_dotfile_noempty = 'ignoreempty' key_dotfile_noempty = 'ignoreempty'
key_dotfile_template = 'template'
# profile # profile
key_profile_dotfiles = 'dotfiles' key_profile_dotfiles = 'dotfiles'
@@ -82,6 +83,7 @@ class CfgYaml:
key_settings_noempty = Settings.key_ignoreempty key_settings_noempty = Settings.key_ignoreempty
key_settings_minversion = Settings.key_minversion key_settings_minversion = Settings.key_minversion
key_imp_link = Settings.key_link_on_import key_imp_link = Settings.key_link_on_import
key_settings_template = Settings.key_template_dotfile_default
# link values # link values
lnk_nolink = LinkTypes.NOLINK.name.lower() lnk_nolink = LinkTypes.NOLINK.name.lower()
@@ -610,6 +612,11 @@ class CfgYaml:
if self.key_dotfile_noempty not in v: if self.key_dotfile_noempty not in v:
val = self.settings.get(self.key_settings_noempty, False) val = self.settings.get(self.key_settings_noempty, False)
v[self.key_dotfile_noempty] = val v[self.key_dotfile_noempty] = val
# apply template if undefined
if self.key_dotfile_template not in v:
val = self.settings.get(self.key_settings_template, True)
v[self.key_dotfile_template] = val
return new return new
def _add_variables(self, new, shell=False, template=True, prio=False): def _add_variables(self, new, shell=False, template=True, prio=False):

View File

@@ -129,11 +129,13 @@ def cmd_install(o):
LOG.dbg(dotfile.prt()) LOG.dbg(dotfile.prt())
if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK:
r, err = inst.link(t, dotfile.src, dotfile.dst, r, err = inst.link(t, dotfile.src, dotfile.dst,
actionexec=pre_actions_exec) actionexec=pre_actions_exec,
template=dotfile.template)
elif hasattr(dotfile, 'link') and \ elif hasattr(dotfile, 'link') and \
dotfile.link == LinkTypes.LINK_CHILDREN: dotfile.link == LinkTypes.LINK_CHILDREN:
r, err = inst.link_children(t, dotfile.src, dotfile.dst, r, err = inst.link_children(t, dotfile.src, dotfile.dst,
actionexec=pre_actions_exec) actionexec=pre_actions_exec,
template=dotfile.template)
else: else:
src = dotfile.src src = dotfile.src
tmp = None tmp = None
@@ -147,7 +149,8 @@ def cmd_install(o):
r, err = inst.install(t, src, dotfile.dst, r, err = inst.install(t, src, dotfile.dst,
actionexec=pre_actions_exec, actionexec=pre_actions_exec,
noempty=dotfile.noempty, noempty=dotfile.noempty,
ignore=ignores) ignore=ignores,
template=dotfile.template)
if tmp: if tmp:
tmp = os.path.join(o.dotpath, tmp) tmp = os.path.join(o.dotpath, tmp)
if os.path.exists(tmp): if os.path.exists(tmp):
@@ -264,7 +267,8 @@ def cmd_compare(o, tmp):
continue continue
# install dotfile to temporary dir and compare # install dotfile to temporary dir and compare
ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst) ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst,
template=dotfile.template)
if not ret: if not ret:
# failed to install to tmp # failed to install to tmp
line = '=> compare {}: error' line = '=> compare {}: error'
@@ -737,9 +741,14 @@ def main():
LOG.err('interrupted') LOG.err('interrupted')
ret = False ret = False
if o.debug:
LOG.dbg('done executing command')
if ret and o.conf.save(): if ret and o.conf.save():
LOG.log('config file updated') LOG.log('config file updated')
if o.debug:
LOG.dbg('return {}'.format(ret))
return ret return ret

View File

@@ -16,12 +16,13 @@ class Dotfile(DictParser):
key_noempty = 'ignoreempty' key_noempty = 'ignoreempty'
key_trans_r = 'trans_read' key_trans_r = 'trans_read'
key_trans_w = 'trans_write' key_trans_w = 'trans_write'
key_template = 'template'
def __init__(self, key, dst, src, def __init__(self, key, dst, src,
actions=[], trans_r=None, trans_w=None, actions=[], trans_r=None, trans_w=None,
link=LinkTypes.NOLINK, noempty=False, link=LinkTypes.NOLINK, noempty=False,
cmpignore=[], upignore=[], cmpignore=[], upignore=[],
instignore=[]): instignore=[], template=True):
""" """
constructor constructor
@key: dotfile key @key: dotfile key
@@ -35,6 +36,7 @@ class Dotfile(DictParser):
@upignore: patterns to ignore when updating @upignore: patterns to ignore when updating
@cmpignore: patterns to ignore when comparing @cmpignore: patterns to ignore when comparing
@instignore: patterns to ignore when installing @instignore: patterns to ignore when installing
@template: template this dotfile
""" """
self.actions = actions self.actions = actions
self.dst = dst self.dst = dst
@@ -47,6 +49,7 @@ class Dotfile(DictParser):
self.upignore = upignore self.upignore = upignore
self.cmpignore = cmpignore self.cmpignore = cmpignore
self.instignore = instignore self.instignore = instignore
self.template = template
if self.link != LinkTypes.NOLINK and \ if self.link != LinkTypes.NOLINK and \
( (
@@ -91,6 +94,7 @@ class Dotfile(DictParser):
value['noempty'] = value.get(cls.key_noempty, False) value['noempty'] = value.get(cls.key_noempty, False)
value['trans_r'] = value.get(cls.key_trans_r) value['trans_r'] = value.get(cls.key_trans_r)
value['trans_w'] = value.get(cls.key_trans_w) value['trans_w'] = value.get(cls.key_trans_w)
value['template'] = value.get(cls.key_template, True)
# remove old entries # remove old entries
value.pop(cls.key_noempty, None) value.pop(cls.key_noempty, None)
value.pop(cls.key_trans_r, None) value.pop(cls.key_trans_r, None)
@@ -104,8 +108,12 @@ class Dotfile(DictParser):
return hash(self.dst) ^ hash(self.src) ^ hash(self.key) return hash(self.dst) ^ hash(self.src) ^ hash(self.key)
def __str__(self): def __str__(self):
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"' msg = 'key:\"{}\"'.format(self.key)
return msg.format(self.key, self.src, self.dst, str(self.link)) msg += ', src:\"{}\"'.format(self.src)
msg += ', dst:\"{}\"'.format(self.dst)
msg += ', link:\"{}\"'.format(str(self.link))
msg += ', template:{}'.format(self.template)
return msg
def prt(self): def prt(self):
"""extended dotfile to str""" """extended dotfile to str"""
@@ -114,6 +122,7 @@ class Dotfile(DictParser):
out += '\n{}src: \"{}\"'.format(indent, self.src) out += '\n{}src: \"{}\"'.format(indent, self.src)
out += '\n{}dst: \"{}\"'.format(indent, self.dst) out += '\n{}dst: \"{}\"'.format(indent, self.dst)
out += '\n{}link: \"{}\"'.format(indent, str(self.link)) out += '\n{}link: \"{}\"'.format(indent, str(self.link))
out += '\n{}template: \"{}\"'.format(indent, str(self.template))
out += '\n{}pre-action:'.format(indent) out += '\n{}pre-action:'.format(indent)
some = self.get_pre_actions() some = self.get_pre_actions()

View File

@@ -7,6 +7,7 @@ handle the installation of dotfiles
import os import os
import errno import errno
import shutil
# local imports # local imports
from dotdrop.logger import Logger from dotdrop.logger import Logger
@@ -65,7 +66,7 @@ class Installer:
def install(self, templater, src, dst, def install(self, templater, src, dst,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[]): ignore=[], template=True):
""" """
install src to dst using a template install src to dst using a template
@templater: the templater object @templater: the templater object
@@ -74,6 +75,7 @@ class Installer:
@actionexec: action executor callback @actionexec: action executor callback
@noempty: render empty template flag @noempty: render empty template flag
@ignore: pattern to ignore when installing @ignore: pattern to ignore when installing
@template: template this dotfile
return return
- True, None : success - True, None : success
@@ -103,22 +105,25 @@ class Installer:
self.log.dbg('install {} to {}'.format(src, dst)) self.log.dbg('install {} to {}'.format(src, dst))
self.log.dbg('is a directory \"{}\": {}'.format(src, isdir)) self.log.dbg('is a directory \"{}\": {}'.format(src, isdir))
if isdir: if isdir:
b, e = self._handle_dir(templater, src, dst, b, e = self._install_dir(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, ignore=ignore) noempty=noempty, ignore=ignore,
template=template)
return self._log_install(b, e) return self._log_install(b, e)
b, e = self._handle_file(templater, src, dst, b, e = self._install_file(templater, src, dst,
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, ignore=ignore) noempty=noempty, ignore=ignore,
template=template)
return self._log_install(b, e) return self._log_install(b, e)
def link(self, templater, src, dst, actionexec=None): def link(self, templater, src, dst, actionexec=None, template=True):
""" """
set src as the link target of dst set src as the link target of dst
@templater: the templater @templater: the templater
@src: dotfile source path in dotpath @src: dotfile source path in dotpath
@dst: dotfile destination path in the FS @dst: dotfile destination path in the FS
@actionexec: action executor callback @actionexec: action executor callback
@template: template this dotfile
return return
- True, None : success - True, None : success
@@ -140,28 +145,32 @@ class Installer:
dst = os.path.normpath(os.path.expanduser(dst)) dst = os.path.normpath(os.path.expanduser(dst))
if self.totemp: if self.totemp:
# ignore actions # ignore actions
b, e = self.install(templater, src, dst, actionexec=None) b, e = self.install(templater, src, dst, actionexec=None,
template=template)
return self._log_install(b, e) return self._log_install(b, e)
if Templategen.is_template(src): if template and Templategen.is_template(src):
if self.debug: if self.debug:
self.log.dbg('dotfile is a template') self.log.dbg('dotfile is a template')
self.log.dbg('install to {} and symlink'.format(self.workdir)) self.log.dbg('install to {} and symlink'.format(self.workdir))
tmp = self._pivot_path(dst, self.workdir, striphome=True) tmp = self._pivot_path(dst, self.workdir, striphome=True)
i, err = self.install(templater, src, tmp, actionexec=actionexec) i, err = self.install(templater, src, tmp, actionexec=actionexec,
template=template)
if not i and not os.path.exists(tmp): if not i and not os.path.exists(tmp):
return self._log_install(i, err) return self._log_install(i, err)
src = tmp src = tmp
b, e = self._link(src, dst, actionexec=actionexec) b, e = self._link(src, dst, actionexec=actionexec)
return self._log_install(b, e) return self._log_install(b, e)
def link_children(self, templater, src, dst, actionexec=None): def link_children(self, templater, src, dst, actionexec=None,
template=True):
""" """
link all dotfiles in a given directory link all files under a given directory
@templater: the templater @templater: the templater
@src: dotfile source path in dotpath @src: dotfile source path in dotpath
@dst: dotfile destination path in the FS @dst: dotfile destination path in the FS
@actionexec: action executor callback @actionexec: action executor callback
@template: template this dotfile
return return
- True, None: success - True, None: success
@@ -221,13 +230,14 @@ class Installer:
if self.debug: if self.debug:
self.log.dbg('symlink child {} to {}'.format(src, dst)) self.log.dbg('symlink child {} to {}'.format(src, dst))
if Templategen.is_template(src): if template and Templategen.is_template(src):
if self.debug: if self.debug:
self.log.dbg('dotfile is a template') self.log.dbg('dotfile is a template')
self.log.dbg('install to {} and symlink' self.log.dbg('install to {} and symlink'
.format(self.workdir)) .format(self.workdir))
tmp = self._pivot_path(dst, self.workdir, striphome=True) tmp = self._pivot_path(dst, self.workdir, striphome=True)
r, e = self.install(templater, src, tmp, actionexec=actionexec) r, e = self.install(templater, src, tmp, actionexec=actionexec,
template=template)
if not r and e and not os.path.exists(tmp): if not r and e and not os.path.exists(tmp):
continue continue
src = tmp src = tmp
@@ -264,7 +274,7 @@ class Installer:
self.log.dry('would remove {} and link to {}'.format(dst, src)) self.log.dry('would remove {} and link to {}'.format(dst, src))
return True, None return True, None
if self.showdiff: if self.showdiff:
self._diff_before_write(src, dst) self._diff_before_write(src, dst, quiet=False)
msg = 'Remove "{}" for link creation?'.format(dst) msg = 'Remove "{}" for link creation?'.format(dst)
if self.safe and not self.log.ask(msg): if self.safe and not self.log.ask(msg):
err = 'ignoring "{}", link was not created'.format(dst) err = 'ignoring "{}", link was not created'.format(dst)
@@ -306,14 +316,16 @@ class Installer:
tmp['_dotfile_sub_abs_dst'] = dst tmp['_dotfile_sub_abs_dst'] = dst
return tmp return tmp
def _handle_file(self, templater, src, dst, def _install_file(self, templater, src, dst,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[]): ignore=[], template=True):
"""install src to dst when is a file""" """install src to dst when is a file"""
if self.debug: if self.debug:
self.log.dbg('generate template for {}'.format(src)) self.log.dbg('deploy file: {}'.format(src))
self.log.dbg('ignore empty: {}'.format(noempty)) self.log.dbg('ignore empty: {}'.format(noempty))
self.log.dbg('ignore pattern: {}'.format(ignore)) self.log.dbg('ignore pattern: {}'.format(ignore))
self.log.dbg('template: {}'.format(template))
self.log.dbg('no empty: {}'.format(noempty))
if utils.must_ignore([src, dst], ignore, debug=self.debug): if utils.must_ignore([src, dst], ignore, debug=self.debug):
if self.debug: if self.debug:
@@ -324,42 +336,55 @@ class Installer:
# symlink loop # symlink loop
err = 'dotfile points to itself: {}'.format(dst) err = 'dotfile points to itself: {}'.format(dst)
return False, err return False, err
saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst))
try:
content = templater.generate(src)
except UndefinedException as e:
return False, str(e)
finally:
templater.restore_vars(saved)
if noempty and utils.content_empty(content):
if self.debug:
self.log.dbg('ignoring empty template: {}'.format(src))
return False, None
if content is None:
err = 'empty template {}'.format(src)
return False, err
if not os.path.exists(src): if not os.path.exists(src):
err = 'source dotfile does not exist: {}'.format(src) err = 'source dotfile does not exist: {}'.format(src)
return False, err return False, err
st = os.stat(src)
ret, err = self._write(src, dst, content, # handle the file
st.st_mode, actionexec=actionexec) content = None
if template:
# template the file
saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst))
try:
content = templater.generate(src)
except UndefinedException as e:
return False, str(e)
finally:
templater.restore_vars(saved)
if noempty and utils.content_empty(content):
if self.debug:
self.log.dbg('ignoring empty template: {}'.format(src))
return False, None
if content is None:
err = 'empty template {}'.format(src)
return False, err
ret, err = self._write(src, dst,
content=content,
actionexec=actionexec,
template=template)
# build return values
if ret < 0: if ret < 0:
# error
return False, err return False, err
if ret > 0: if ret > 0:
# already exists
if self.debug: if self.debug:
self.log.dbg('ignoring {}'.format(dst)) self.log.dbg('ignoring {}'.format(dst))
return False, None return False, None
if ret == 0: if ret == 0:
# success
if not self.dry and not self.comparing: if not self.dry and not self.comparing:
self.log.sub('copied {} to {}'.format(src, dst)) self.log.sub('copied {} to {}'.format(src, dst))
return True, None return True, None
# error
err = 'installing {} to {}'.format(src, dst) err = 'installing {} to {}'.format(src, dst)
return False, err return False, err
def _handle_dir(self, templater, src, dst, def _install_dir(self, templater, src, dst,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[]): ignore=[], template=True):
"""install src to dst when is a directory""" """install src to dst when is a directory"""
if self.debug: if self.debug:
self.log.dbg('install dir {}'.format(src)) self.log.dbg('install dir {}'.format(src))
@@ -374,11 +399,12 @@ class Installer:
f = os.path.join(src, entry) f = os.path.join(src, entry)
if not os.path.isdir(f): if not os.path.isdir(f):
# is file # is file
res, err = self._handle_file(templater, f, res, err = self._install_file(templater, f,
os.path.join(dst, entry), os.path.join(dst, entry),
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore) ignore=ignore,
template=template)
if not res and err: if not res and err:
# error occured # error occured
ret = res, err ret = res, err
@@ -388,11 +414,12 @@ class Installer:
ret = True, None ret = True, None
else: else:
# is directory # is directory
res, err = self._handle_dir(templater, f, res, err = self._install_dir(templater, f,
os.path.join(dst, entry), os.path.join(dst, entry),
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore) ignore=ignore,
template=template)
if not res and err: if not res and err:
# error occured # error occured
ret = res, err ret = res, err
@@ -403,22 +430,31 @@ class Installer:
return ret return ret
def _fake_diff(self, dst, content): def _fake_diff(self, dst, content):
"""fake diff by comparing file content with content""" """
fake diff by comparing file content with content
returns True if same
"""
cur = '' cur = ''
with open(dst, 'br') as f: with open(dst, 'br') as f:
cur = f.read() cur = f.read()
return cur == content return cur == content
def _write(self, src, dst, content, rights, actionexec=None): def _write(self, src, dst, content=None,
"""write content to file actionexec=None, template=True):
"""
copy dotfile / write content to file
return 0, None: for success, return 0, None: for success,
1, None: when already exists 1, None: when already exists
-1, err: when error""" -1, err: when error
content is always empty if template is False
and is to be ignored
"""
overwrite = not self.safe overwrite = not self.safe
if self.dry: if self.dry:
self.log.dry('would install {}'.format(dst)) self.log.dry('would install {}'.format(dst))
return 0, None return 0, None
if os.path.lexists(dst): if os.path.lexists(dst):
rights = os.stat(src).st_mode
samerights = False samerights = False
try: try:
samerights = os.stat(dst).st_mode == rights samerights = os.stat(dst).st_mode == rights
@@ -427,15 +463,26 @@ class Installer:
# broken symlink # broken symlink
err = 'broken symlink {}'.format(dst) err = 'broken symlink {}'.format(dst)
return -1, err return -1, err
if self.diff and self._fake_diff(dst, content) and samerights: diff = None
if self.debug: if self.diff:
self.log.dbg('{} is the same'.format(dst)) diff = self._diff_before_write(src, dst,
return 1, None content=content,
quiet=True)
if not diff and samerights:
if self.debug:
self.log.dbg('{} is the same'.format(dst))
return 1, None
if self.safe: if self.safe:
if self.debug: if self.debug:
self.log.dbg('change detected for {}'.format(dst)) self.log.dbg('change detected for {}'.format(dst))
if self.showdiff: if self.showdiff:
self._diff_before_write(src, dst, content=content) if diff is None:
# get diff
diff = self._diff_before_write(src, dst,
content=content,
quiet=True)
if diff:
self._print_diff(src, dst, diff)
if not self.log.ask('Overwrite \"{}\"'.format(dst)): if not self.log.ask('Overwrite \"{}\"'.format(dst)):
self.log.warn('ignoring {}'.format(dst)) self.log.warn('ignoring {}'.format(dst))
return 1, None return 1, None
@@ -450,24 +497,39 @@ class Installer:
if not r: if not r:
return -1, e return -1, e
if self.debug: if self.debug:
self.log.dbg('writes content to \"{}\"'.format(dst)) self.log.dbg('install dotfile 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 os.path.lexists(dst):
if not self.log.ask('Overwrite \"{}\"'.format(dst)): if not self.log.ask('Overwrite \"{}\"'.format(dst)):
self.log.warn('ignoring {}'.format(dst)) self.log.warn('ignoring {}'.format(dst))
return 1, None return 1, None
# write the file
try: if template:
with open(dst, 'wb') as f: # write content the file
f.write(content) try:
except NotADirectoryError as e: with open(dst, 'wb') as f:
err = 'opening dest file: {}'.format(e) f.write(content)
return -1, err shutil.copymode(src, dst)
os.chmod(dst, rights) except NotADirectoryError as e:
err = 'opening dest file: {}'.format(e)
return -1, err
except Exception as e:
return -1, str(e)
else:
# copy file
try:
shutil.copyfile(src, dst)
shutil.copymode(src, dst)
except Exception as e:
return -1, str(e)
return 0, None return 0, None
def _diff_before_write(self, src, dst, content=None): def _diff_before_write(self, src, dst, content=None, quiet=False):
"""diff before writing when using --showdiff - not efficient""" """
diff before writing
using a temp file if content is not None
returns diff string ('' if same)
"""
tmp = None tmp = None
if content: if content:
tmp = utils.write_to_tmpfile(content) tmp = utils.write_to_tmpfile(content)
@@ -477,9 +539,12 @@ class Installer:
if tmp: if tmp:
utils.remove(tmp, quiet=True) utils.remove(tmp, quiet=True)
# fake the output for readability if not quiet and diff:
if not diff: self._print_diff(src, dst, diff)
return return diff
def _print_diff(self, src, dst, diff):
"""show diff to user"""
self.log.log('diff \"{}\" VS \"{}\"'.format(dst, src)) self.log.log('diff \"{}\" VS \"{}\"'.format(dst, src))
self.log.emph(diff) self.log.emph(diff)
@@ -530,12 +595,13 @@ class Installer:
self.action_executed = True self.action_executed = True
return ret, err return ret, err
def _install_to_temp(self, templater, src, dst, tmpdir): def _install_to_temp(self, templater, src, dst, tmpdir, template=True):
"""install a dotfile to a tempdir""" """install a dotfile to a tempdir"""
tmpdst = self._pivot_path(dst, tmpdir) tmpdst = self._pivot_path(dst, tmpdir)
return self.install(templater, src, tmpdst), tmpdst r = self.install(templater, src, tmpdst, template=template)
return r, tmpdst
def install_to_temp(self, templater, tmpdir, src, dst): def install_to_temp(self, templater, tmpdir, src, dst, template=True):
"""install a dotfile to a tempdir""" """install a dotfile to a tempdir"""
ret = False ret = False
tmpdst = '' tmpdst = ''
@@ -553,7 +619,8 @@ class Installer:
if self.debug: if self.debug:
self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst)) self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst))
# install the dotfile to a temp directory for comparing # install the dotfile to a temp directory for comparing
r, tmpdst = self._install_to_temp(templater, src, dst, tmpdir) r, tmpdst = self._install_to_temp(templater, src, dst, tmpdir,
template=template)
ret, err = r ret, err = r
if self.debug: if self.debug:
self.log.dbg('tmp installed in {}'.format(tmpdst)) self.log.dbg('tmp installed in {}'.format(tmpdst))

View File

@@ -34,6 +34,7 @@ class Settings(DictParser):
key_func_file = 'func_file' key_func_file = 'func_file'
key_filter_file = 'filter_file' key_filter_file = 'filter_file'
key_diff_command = 'diff_command' key_diff_command = 'diff_command'
key_template_dotfile_default = 'template_dotfile_default'
# import keys # import keys
key_import_actions = 'import_actions' key_import_actions = 'import_actions'
@@ -49,7 +50,8 @@ class Settings(DictParser):
upignore=[], cmpignore=[], instignore=[], upignore=[], cmpignore=[], instignore=[],
workdir='~/.config/dotdrop', showdiff=False, workdir='~/.config/dotdrop', showdiff=False,
minversion=None, func_file=[], filter_file=[], minversion=None, func_file=[], filter_file=[],
diff_command='diff -r -u {0} {1}'): diff_command='diff -r -u {0} {1}',
template_dotfile_default=True):
self.backup = backup self.backup = backup
self.banner = banner self.banner = banner
self.create = create self.create = create
@@ -72,6 +74,7 @@ class Settings(DictParser):
self.func_file = func_file self.func_file = func_file
self.filter_file = filter_file self.filter_file = filter_file
self.diff_command = diff_command self.diff_command = diff_command
self.template_dotfile_default = template_dotfile_default
def _serialize_seq(self, name, dic): def _serialize_seq(self, name, dic):
"""serialize attribute 'name' into 'dic'""" """serialize attribute 'name' into 'dic'"""
@@ -94,6 +97,7 @@ class Settings(DictParser):
self.key_workdir: self.workdir, self.key_workdir: self.workdir,
self.key_minversion: self.minversion, self.key_minversion: self.minversion,
self.key_diff_command: self.diff_command, self.key_diff_command: self.diff_command,
self.key_template_dotfile_default: self.template_dotfile_default,
} }
self._serialize_seq(self.key_default_actions, dic) self._serialize_seq(self.key_default_actions, dic)
self._serialize_seq(self.key_import_actions, dic) self._serialize_seq(self.key_import_actions, dic)

View File

@@ -71,7 +71,7 @@ def shell(cmd, debug=False):
def diff(original, modified, raw=True, def diff(original, modified, raw=True,
diff_cmd='', debug=False): diff_cmd='', debug=False):
"""compare two files""" """compare two files, returns '' if same"""
if not diff_cmd: if not diff_cmd:
diff_cmd = 'diff -r -u {0} {1}' diff_cmd = 'diff -r -u {0} {1}'

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -12,7 +12,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

286
tests-ng/notemplate.sh Executable file
View File

@@ -0,0 +1,286 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test notemplate
# returns 1 in case of error
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
#echo "dotfile source: ${tmps}"
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# create the config file
cfg="${tmps}/config.yaml"
# globally
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
template_dotfile_default: false
dotfiles:
f_f1:
dst: ${tmpd}/f1
src: f1
d_d1:
dst: ${tmpd}/dir1
src: dir1
d_d2:
dst: ${tmpd}/dir2
src: dir2
link: link
d_d3:
dst: ${tmpd}/dir3
src: dir3
link: link_children
f_fl:
dst: ${tmpd}/fl
src: fl
link: link
f_fn:
dst: ${tmpd}/fn
src: fn
template: true
profiles:
p1:
dotfiles:
- f_f1
- d_d1
- d_d2
- d_d3
- f_fl
- f_fn
_EOF
#cat ${cfg}
# create the dotfile
echo "before" > ${tmps}/dotfiles/f1
echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/f1
echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/f1
echo "after" >> ${tmps}/dotfiles/f1
# create the directory
mkdir -p ${tmps}/dotfiles/dir1/d1
echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir1/d1/f2
# create the linked directory
mkdir -p ${tmps}/dotfiles/dir2/d1
echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir2/d1/f2
# create the link_children directory
mkdir -p ${tmps}/dotfiles/dir3/{s1,s2,s3}
echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir3/s1/f1
echo "{{@@ header() @@}}" > ${tmps}/dotfiles/dir3/s2/f2
# create the linked dotfile
echo "{{@@ header() @@}}" > ${tmps}/dotfiles/fl
# create the normal dotfile
echo "before" > ${tmps}/dotfiles/fn
echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/fn
echo "after" >> ${tmps}/dotfiles/fn
# install
cd ${ddpath} | ${bin} install -f --showdiff -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -V
# simple file
echo "doing globally"
echo "* test simple file"
[ ! -e ${tmpd}/f1 ] && echo 'not installed1' && exit 1
grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1)
grep 'should not be stripped' ${tmpd}/f1 || (echo "comment stripped" && exit 1)
# directory
echo "* test directory"
[ ! -d ${tmpd}/dir1 ] && echo 'not installed1' && exit 1
[ ! -d ${tmpd}/dir1/d1 ] && echo 'not installed2' && exit 1
[ ! -e ${tmpd}/dir1/d1/f2 ] && echo 'not installed3' && exit 1
grep 'header' ${tmpd}/dir1/d1/f2 || (echo "header stripped" && exit 1)
# linked directory
echo "* test linked directory"
[ ! -h ${tmpd}/dir2 ] && echo 'not installed1' && exit 1
[ ! -d ${tmpd}/dir2/d1 ] && echo 'not installed2' && exit 1
[ ! -e ${tmpd}/dir2/d1/f2 ] && echo 'not installed3' && exit 1
grep 'header' ${tmpd}/dir2/d1/f2 || (echo "header stripped" && exit 1)
# children_link directory
echo "* test link_children directory"
[ ! -d ${tmpd}/dir3 ] && echo 'not installed1' && exit 1
[ ! -h ${tmpd}/dir3/s1 ] && echo 'not installed2' && exit 1
[ ! -h ${tmpd}/dir3/s2 ] && echo 'not installed3' && exit 1
[ ! -h ${tmpd}/dir3/s3 ] && echo 'not installed4' && exit 1
[ ! -e ${tmpd}/dir3/s1/f1 ] && echo 'not installed5' && exit 1
[ ! -e ${tmpd}/dir3/s2/f2 ] && echo 'not installed6' && exit 1
grep 'header' ${tmpd}/dir3/s1/f1 || (echo "header stripped" && exit 1)
grep 'header' ${tmpd}/dir3/s2/f2 || (echo "header stripped" && exit 1)
# linked file
echo "* test linked file"
[ ! -h ${tmpd}/fl ] && echo 'not installed' && exit 1
grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1)
# normal dotfile
echo "* normal dotfile"
[ ! -e ${tmpd}/fn ] && echo 'not installed' && exit 1
grep 'should not be stripped' ${tmpd}/fn && echo "no templated" && exit 1
# test backup done
echo "before" > ${tmps}/dotfiles/f1
cd ${ddpath} | ${bin} install -f --showdiff -c ${cfg} -p p1 -V
[ ! -e ${tmpd}/f1.dotdropbak ] && echo "backup not done" && exit 1
# re-create the dotfile
echo "before" > ${tmps}/dotfiles/f1
echo "{#@@ should not be stripped @@#}" >> ${tmps}/dotfiles/f1
echo "{{@@ header() @@}}" >> ${tmps}/dotfiles/f1
echo "after" >> ${tmps}/dotfiles/f1
# through the dotfile
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
template_dotfile_default: true
dotfiles:
f_f1:
dst: ${tmpd}/f1
src: f1
template: false
d_d1:
dst: ${tmpd}/dir1
src: dir1
template: false
d_d2:
dst: ${tmpd}/dir2
src: dir2
link: link
template: false
d_d3:
dst: ${tmpd}/dir3
src: dir3
link: link_children
template: false
f_fl:
dst: ${tmpd}/fl
src: fl
link: link
template: false
f_fn:
dst: ${tmpd}/fn
src: fn
profiles:
p1:
dotfiles:
- f_f1
- d_d1
- d_d2
- d_d3
- f_fl
- f_fn
_EOF
#cat ${cfg}
# clean destination
rm -rf ${tmpd}/*
# install
cd ${ddpath} | ${bin} install -f --showdiff -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -V
# simple file
echo "doing specifically"
echo "* test simple file"
[ ! -e ${tmpd}/f1 ] && echo 'not installed1' && exit 1
grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1)
grep 'should not be stripped' ${tmpd}/f1 || (echo "comment stripped" && exit 1)
# directory
echo "* test directory"
[ ! -d ${tmpd}/dir1 ] && echo 'not installed1' && exit 1
[ ! -d ${tmpd}/dir1/d1 ] && echo 'not installed2' && exit 1
[ ! -e ${tmpd}/dir1/d1/f2 ] && echo 'not installed3' && exit 1
grep 'header' ${tmpd}/dir1/d1/f2 || (echo "header stripped" && exit 1)
# linked directory
echo "* test linked directory"
[ ! -h ${tmpd}/dir2 ] && echo 'not installed1' && exit 1
[ ! -d ${tmpd}/dir2/d1 ] && echo 'not installed2' && exit 1
[ ! -e ${tmpd}/dir2/d1/f2 ] && echo 'not installed3' && exit 1
grep 'header' ${tmpd}/dir2/d1/f2 || (echo "header stripped" && exit 1)
# children_link directory
echo "* test link_children directory"
[ ! -d ${tmpd}/dir3 ] && echo 'not installed1' && exit 1
[ ! -h ${tmpd}/dir3/s1 ] && echo 'not installed2' && exit 1
[ ! -h ${tmpd}/dir3/s2 ] && echo 'not installed3' && exit 1
[ ! -h ${tmpd}/dir3/s3 ] && echo 'not installed4' && exit 1
[ ! -e ${tmpd}/dir3/s1/f1 ] && echo 'not installed5' && exit 1
[ ! -e ${tmpd}/dir3/s2/f2 ] && echo 'not installed6' && exit 1
grep 'header' ${tmpd}/dir3/s1/f1 || (echo "header stripped" && exit 1)
grep 'header' ${tmpd}/dir3/s2/f2 || (echo "header stripped" && exit 1)
# linked file
echo "* test linked file"
[ ! -h ${tmpd}/fl ] && echo 'not installed' && exit 1
grep 'header' ${tmpd}/f1 || (echo "header stripped" && exit 1)
# normal dotfile
echo "* normal dotfile"
[ ! -e ${tmpd}/fn ] && echo 'not installed' && exit 1
grep 'should not be stripped' ${tmpd}/fn && echo "no templated" && exit 1
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"

View File

@@ -7,7 +7,7 @@
# #
# exit on first error # exit on first error
#set -e set -e
# all this crap to get current path # all this crap to get current path
rl="readlink -f" rl="readlink -f"
@@ -99,7 +99,7 @@ cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc
# check files haven't been updated # check files haven't been updated
grep 'b' ${dt}/a/c/acfile >/dev/null grep 'b' ${dt}/a/c/acfile >/dev/null
[ -e ${dt}/a/newfile ] && exit 1 [ -e ${dt}/a/newfile ] && echo "should not have been updated" && exit 1
## CLEANING ## CLEANING
rm -rf ${tmps} ${tmpd} rm -rf ${tmps} ${tmpd}