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

merge options

This commit is contained in:
deadc0de6
2019-02-12 12:55:59 +01:00
18 changed files with 579 additions and 459 deletions

View File

@@ -2,7 +2,7 @@
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
handle the comparison of dotfiles and local deployment
handle the comparison of two dotfiles
"""
import os
@@ -17,7 +17,7 @@ class Comparator:
def __init__(self, diffopts='', debug=False):
"""constructor
@diffopts: cli switches to pass to unix diff
@diffopts: switches to pass to unix diff
@debug: enable debug
"""
self.diffopts = diffopts
@@ -61,12 +61,14 @@ class Comparator:
self.log.dbg('compare {} and {}'.format(left, right))
ret = []
comp = filecmp.dircmp(left, right)
# handle files only in deployed file
for i in comp.left_only:
if utils.must_ignore([os.path.join(left, i)],
ignore, debug=self.debug):
continue
ret.append('=> \"{}\" does not exist on local\n'.format(i))
# handle files only in dotpath file
for i in comp.right_only:
if utils.must_ignore([os.path.join(right, i)],
@@ -107,6 +109,5 @@ class Comparator:
opts=self.diffopts, debug=self.debug)
if header:
lshort = os.path.basename(left)
rshort = os.path.basename(right)
diff = '=> diff \"{}\":\n{}'.format(lshort, diff)
return diff

View File

@@ -14,7 +14,7 @@ from dotdrop.dotfile import Dotfile
from dotdrop.templategen import Templategen
from dotdrop.logger import Logger
from dotdrop.action import Action, Transform
from dotdrop.utils import *
from dotdrop.utils import strip_home, shell
from dotdrop.linktypes import LinkTypes
@@ -126,7 +126,8 @@ class Cfg:
def eval_dotfiles(self, profile, variables, debug=False):
"""resolve dotfiles src/dst/actions templating for this profile"""
t = Templategen(variables=variables)
for d in self.get_dotfiles(profile):
dotfiles = self._get_dotfiles(profile)
for d in dotfiles:
# src and dst path
d.src = t.generate_string(d.src)
d.dst = t.generate_string(d.dst)
@@ -138,6 +139,7 @@ class Cfg:
if self.key_actions_post in d.actions:
for action in d.actions[self.key_actions_post]:
action.action = t.generate_string(action.action)
return dotfiles
def _load_file(self):
"""load the yaml file"""
@@ -335,14 +337,24 @@ class Cfg:
# make sure we have an absolute dotpath
self.curdotpath = self.lnk_settings[self.key_dotpath]
self.lnk_settings[self.key_dotpath] = self.abs_or_rel(self.curdotpath)
self.lnk_settings[self.key_dotpath] = \
self._abs_path(self.curdotpath)
# make sure we have an absolute workdir
self.curworkdir = self.lnk_settings[self.key_workdir]
self.lnk_settings[self.key_workdir] = self.abs_or_rel(self.curworkdir)
self.lnk_settings[self.key_workdir] = \
self._abs_path(self.curworkdir)
return True
def _abs_path(self, path):
"""return absolute path of path relative to the confpath"""
path = os.path.expanduser(path)
if not os.path.isabs(path):
d = os.path.dirname(self.cfgpath)
return os.path.join(d, path)
return path
def _get_included_dotfiles(self, profile):
"""find all dotfiles for a specific profile
when using the include keyword"""
@@ -427,15 +439,6 @@ class Cfg:
if self.key_ignoreempty not in self.lnk_settings:
self.lnk_settings[self.key_ignoreempty] = self.default_ignoreempty
def abs_or_rel(self, path):
"""path is either absolute or relative to the config path"""
path = os.path.expanduser(path)
if not os.path.isabs(path):
absconf = os.path.join(os.path.dirname(
self.cfgpath), path)
return absconf
return path
def _save(self, content, path):
"""writes the config to file"""
ret = False
@@ -501,37 +504,6 @@ class Cfg:
cnt += 1
return key
def short_to_long(self):
"""transform all short keys to long keys"""
if not self.content[self.key_dotfiles]:
return
match = {}
new = {}
# handle the entries in dotfiles
for oldkey, v in self.content[self.key_dotfiles].items():
path = v[self.key_dotfiles_dst]
path = os.path.expanduser(path)
newkey = self._get_long_key(path)
new[newkey] = v
match[oldkey] = newkey
# replace with new keys
self.content[self.key_dotfiles] = new
# handle the entries in profiles
for k, v in self.lnk_profiles.items():
if self.key_profiles_dots not in v:
continue
if not v[self.key_profiles_dots]:
continue
new = []
for oldkey in v[self.key_profiles_dots]:
if oldkey == self.key_all:
continue
newkey = match[oldkey]
new.append(newkey)
# replace with new keys
v[self.key_profiles_dots] = new
def _dotfile_exists(self, dotfile):
"""return True and the existing dotfile key
if it already exists, False and a new unique key otherwise"""
@@ -609,7 +581,7 @@ class Cfg:
return True, dotfile
def get_dotfiles(self, profile):
def _get_dotfiles(self, profile):
"""return a list of dotfiles for a specific profile"""
if profile not in self.prodots:
return []

View File

@@ -7,11 +7,9 @@ entry point
import os
import sys
import socket
from docopt import docopt
# local imports
from dotdrop.version import __version__ as VERSION
from dotdrop.options import Options
from dotdrop.logger import Logger
from dotdrop.templategen import Templategen
from dotdrop.installer import Installer
@@ -23,91 +21,43 @@ from dotdrop.utils import get_tmpdir, remove, strip_home, run
from dotdrop.linktypes import LinkTypes
LOG = Logger()
ENV_PROFILE = 'DOTDROP_PROFILE'
ENV_NOBANNER = 'DOTDROP_NOBANNER'
PROFILE = socket.gethostname()
if ENV_PROFILE in os.environ:
PROFILE = os.environ[ENV_PROFILE]
TRANS_SUFFIX = 'trans'
BANNER = """ _ _ _
__| | ___ | |_ __| |_ __ ___ _ __
/ _` |/ _ \| __/ _` | '__/ _ \| '_ |
\__,_|\___/ \__\__,_|_| \___/| .__/ v{}
|_|""".format(VERSION)
USAGE = """
{}
Usage:
dotdrop install [-tfndVbD] [-c <path>] [-p <profile>] [<key>...]
dotdrop import [-ldVb] [-c <path>] [-p <profile>] <path>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [-C <file>...] [-i <pattern>...]
dotdrop update [-fdVbkP] [-c <path>] [-p <profile>]
[-i <pattern>...] [<path>...]
dotdrop listfiles [-VTb] [-c <path>] [-p <profile>]
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
dotdrop list [-Vb] [-c <path>]
dotdrop --help
dotdrop --version
Options:
-p --profile=<profile> Specify the profile to use [default: {}].
-c --cfg=<path> Path to the config [default: config.yaml].
-C --file=<path> Path of dotfile to compare.
-i --ignore=<pattern> Pattern to ignore.
-o --dopts=<opts> Diff options [default: ].
-n --nodiff Do not diff when installing.
-t --temp Install to a temporary directory for review.
-T --template Only template dotfiles.
-D --showdiff Show a diff before overwriting.
-l --inv-link Invert the value of "link_by_default" when importing.
-P --show-patch Provide a one-liner to manually patch template.
-f --force Do not warn if exists.
-k --key Treat <path> as a dotfile key.
-V --verbose Be verbose.
-d --dry Dry run.
-b --no-banner Do not display the banner.
-v --version Show version.
-h --help Show this screen.
""".format(BANNER, PROFILE)
###########################################################
# entry point
###########################################################
def cmd_install(opts, conf, temporary=False, keys=[]):
def cmd_install(o):
"""install dotfiles for this profile"""
dotfiles = conf.get_dotfiles(opts['profile'])
if keys:
dotfiles = o.dotfiles
if o.install_keys:
# filtered dotfiles to install
dotfiles = [d for d in dotfiles if d.key in set(keys)]
dotfiles = [d for d in dotfiles if d.key in set(o.install_keys)]
if not dotfiles:
msg = 'no dotfile to install for this profile (\"{}\")'
LOG.warn(msg.format(opts['profile']))
LOG.warn(msg.format(o.profile))
return False
t = Templategen(base=opts['dotpath'], variables=opts['variables'],
debug=opts['debug'])
t = Templategen(base=o.dotpath, variables=o.variables,
debug=o.debug)
tmpdir = None
if temporary:
if o.install_temporary:
tmpdir = get_tmpdir()
inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], safe=opts['safe'],
base=opts['dotpath'], workdir=opts['workdir'],
diff=opts['installdiff'], debug=opts['debug'],
totemp=tmpdir, showdiff=opts['showdiff'])
inst = Installer(create=o.create, backup=o.backup,
dry=o.dry, safe=o.safe,
base=o.dotpath, workdir=o.workdir,
diff=o.install_diff, debug=o.debug,
totemp=tmpdir,
showdiff=o.install_showdiff)
installed = []
for dotfile in dotfiles:
preactions = []
if not temporary and dotfile.actions \
if not o.install_temporary and dotfile.actions \
and Cfg.key_actions_pre in dotfile.actions:
for action in dotfile.actions[Cfg.key_actions_pre]:
preactions.append(action)
if opts['debug']:
if o.debug:
LOG.dbg('installing {}'.format(dotfile))
if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.PARENTS:
r = inst.link(t, dotfile.src, dotfile.dst, actions=preactions)
@@ -117,59 +67,60 @@ def cmd_install(opts, conf, temporary=False, keys=[]):
src = dotfile.src
tmp = None
if dotfile.trans_r:
tmp = apply_trans(opts, dotfile)
tmp = apply_trans(o.dotpath, dotfile, debug=o.debug)
if not tmp:
continue
src = tmp
r = inst.install(t, src, dotfile.dst, actions=preactions,
noempty=dotfile.noempty)
if tmp:
tmp = os.path.join(opts['dotpath'], tmp)
tmp = os.path.join(o.dotpath, tmp)
if os.path.exists(tmp):
remove(tmp)
if len(r) > 0:
if not temporary and Cfg.key_actions_post in dotfile.actions:
if not o.install_temporary and \
Cfg.key_actions_post in dotfile.actions:
actions = dotfile.actions[Cfg.key_actions_post]
# execute action
for action in actions:
if opts['dry']:
if o.dry:
LOG.dry('would execute action: {}'.format(action))
else:
if opts['debug']:
if o.debug:
LOG.dbg('executing post action {}'.format(action))
action.execute()
installed.extend(r)
if temporary:
if o.install_temporary:
LOG.log('\nInstalled to tmp \"{}\".'.format(tmpdir))
LOG.log('\n{} dotfile(s) installed.'.format(len(installed)))
return True
def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
def cmd_compare(o, tmp):
"""compare dotfiles and return True if all identical"""
dotfiles = conf.get_dotfiles(opts['profile'])
dotfiles = o.dotfiles
if dotfiles == []:
msg = 'no dotfile defined for this profile (\"{}\")'
LOG.warn(msg.format(opts['profile']))
LOG.warn(msg.format(o.profile))
return True
# compare only specific files
same = True
selected = dotfiles
if focus:
selected = _select(focus, dotfiles)
if o.compare_focus:
selected = _select(o.compare_focus, dotfiles)
if len(selected) < 1:
return False
t = Templategen(base=opts['dotpath'], variables=opts['variables'],
debug=opts['debug'])
inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], base=opts['dotpath'],
workdir=opts['workdir'], debug=opts['debug'])
comp = Comparator(diffopts=opts['dopts'], debug=opts['debug'])
t = Templategen(base=o.dotpath, variables=o.variables,
debug=o.debug)
inst = Installer(create=o.create, backup=o.backup,
dry=o.dry, base=o.dotpath,
workdir=o.workdir, debug=o.debug)
comp = Comparator(diffopts=o.compare_dopts, debug=o.debug)
for dotfile in selected:
if opts['debug']:
if o.debug:
LOG.dbg('comparing {}'.format(dotfile))
src = dotfile.src
if not os.path.lexists(os.path.expanduser(dotfile.dst)):
@@ -181,7 +132,7 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
tmpsrc = None
if dotfile.trans_r:
# apply transformation
tmpsrc = apply_trans(opts, dotfile)
tmpsrc = apply_trans(o.dotpath, dotfile, debug=o.debug)
if not tmpsrc:
# could not apply trans
same = False
@@ -193,15 +144,15 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
# failed to install to tmp
same = False
continue
ignores = list(set(ignore + dotfile.cmpignore))
ignores = list(set(o.compare_ignore + dotfile.cmpignore))
diff = comp.compare(insttmp, dotfile.dst, ignore=ignores)
if tmpsrc:
# clean tmp transformed dotfile if any
tmpsrc = os.path.join(opts['dotpath'], tmpsrc)
tmpsrc = os.path.join(o.dotpath, tmpsrc)
if os.path.exists(tmpsrc):
remove(tmpsrc)
if diff == '':
if opts['debug']:
if o.debug:
line = '=> compare {}: diffing with \"{}\"'
LOG.dbg(line.format(dotfile.key, dotfile.dst))
LOG.dbg('same file')
@@ -214,16 +165,20 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
return same
def cmd_update(opts, conf, paths, iskey=False, ignore=[], showpatch=False):
def cmd_update(o):
"""update the dotfile(s) from path(s) or key(s)"""
ret = True
updater = Updater(conf, opts['dotpath'], opts['profile'],
opts['variables'], opts['dry'], opts['safe'],
iskey=iskey, debug=opts['debug'], ignore=[],
showpatch=showpatch)
paths = o.update_path
iskey = o.update_iskey
ignore = o.update_ignore
showpatch = o.update_showpatch
updater = Updater(o.dotpath, o.dotfiles, o.variables,
dry=o.dry, safe=o.safe, debug=o.debug,
ignore=ignore, showpatch=showpatch)
if not iskey:
# update paths
if opts['debug']:
if o.debug:
LOG.dbg('update by paths: {}'.format(paths))
for path in paths:
if not updater.update_path(path):
@@ -233,8 +188,8 @@ def cmd_update(opts, conf, paths, iskey=False, ignore=[], showpatch=False):
keys = paths
if not keys:
# if not provided, take all keys
keys = [d.key for d in conf.get_dotfiles(opts['profile'])]
if opts['debug']:
keys = [d.key for d in o.dotfiles]
if o.debug:
LOG.dbg('update by keys: {}'.format(keys))
for key in keys:
if not updater.update_key(key):
@@ -242,12 +197,13 @@ def cmd_update(opts, conf, paths, iskey=False, ignore=[], showpatch=False):
return ret
def cmd_importer(opts, conf, paths):
def cmd_importer(o):
"""import dotfile(s) from paths"""
ret = True
cnt = 0
paths = o.import_path
for path in paths:
if opts['debug']:
if o.debug:
LOG.dbg('trying to import {}'.format(path))
if not os.path.lexists(path):
LOG.err('\"{}\" does not exist, ignored!'.format(path))
@@ -257,37 +213,37 @@ def cmd_importer(opts, conf, paths):
dst = os.path.abspath(dst)
src = strip_home(dst)
strip = '.' + os.sep
if opts['keepdot']:
if o.keepdot:
strip = os.sep
src = src.lstrip(strip)
# create a new dotfile
dotfile = Dotfile('', dst, src)
linktype = LinkTypes(opts['link'])
linktype = LinkTypes(o.link)
if opts['debug']:
if o.debug:
LOG.dbg('new dotfile: {}'.format(dotfile))
# prepare hierarchy for dotfile
srcf = os.path.join(opts['dotpath'], src)
srcf = os.path.join(o.dotpath, src)
if not os.path.exists(srcf):
cmd = ['mkdir', '-p', '{}'.format(os.path.dirname(srcf))]
if opts['dry']:
if o.dry:
LOG.dry('would run: {}'.format(' '.join(cmd)))
else:
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True)
r, _ = run(cmd, raw=False, debug=o.debug, checkerr=True)
if not r:
LOG.err('importing \"{}\" failed!'.format(path))
ret = False
continue
cmd = ['cp', '-R', '-L', dst, srcf]
if opts['dry']:
if o.dry:
LOG.dry('would run: {}'.format(' '.join(cmd)))
if linktype == LinkTypes.PARENTS:
LOG.dry('would symlink {} to {}'.format(srcf, dst))
else:
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True)
r, _ = run(cmd, raw=False, debug=o.debug, checkerr=True)
if not r:
LOG.err('importing \"{}\" failed!'.format(path))
ret = False
@@ -295,42 +251,42 @@ def cmd_importer(opts, conf, paths):
if linktype == LinkTypes.PARENTS:
remove(dst)
os.symlink(srcf, dst)
retconf, dotfile = conf.new(dotfile, opts['profile'],
link=linktype, debug=opts['debug'])
retconf, dotfile = o.conf.new(dotfile, o.profile,
link=linktype, debug=o.debug)
if retconf:
LOG.sub('\"{}\" imported'.format(path))
cnt += 1
else:
LOG.warn('\"{}\" ignored'.format(path))
if opts['dry']:
if o.dry:
LOG.dry('new config file would be:')
LOG.raw(conf.dump())
LOG.raw(o.conf.dump())
else:
conf.save()
o.conf.save()
LOG.log('\n{} file(s) imported.'.format(cnt))
return ret
def cmd_list_profiles(conf):
def cmd_list_profiles(o):
"""list all profiles"""
LOG.log('Available profile(s):')
for p in conf.get_profiles():
for p in o.profiles:
LOG.sub(p)
LOG.log('')
def cmd_list_files(opts, conf, templateonly=False):
def cmd_list_files(o):
"""list all dotfiles for a specific profile"""
if not opts['profile'] in conf.get_profiles():
LOG.warn('unknown profile \"{}\"'.format(opts['profile']))
if o.profile not in o.profiles:
LOG.warn('unknown profile \"{}\"'.format(o.profile))
return
what = 'Dotfile(s)'
if templateonly:
if o.listfiles_templateonly:
what = 'Template(s)'
LOG.emph('{} for profile \"{}\"\n'.format(what, opts['profile']))
for dotfile in conf.get_dotfiles(opts['profile']):
if templateonly:
src = os.path.join(opts['dotpath'], dotfile.src)
LOG.emph('{} for profile \"{}\"\n'.format(what, o.profile))
for dotfile in o.dotfiles:
if o.listfiles_templateonly:
src = os.path.join(o.dotpath, dotfile.src)
if not Templategen.is_template(src):
continue
LOG.log('{} (src: \"{}\", link: {})'.format(dotfile.key, dotfile.src,
@@ -339,18 +295,18 @@ def cmd_list_files(opts, conf, templateonly=False):
LOG.log('')
def cmd_detail(opts, conf, keys=None):
def cmd_detail(o):
"""list details on all files for all dotfile entries"""
if not opts['profile'] in conf.get_profiles():
LOG.warn('unknown profile \"{}\"'.format(opts['profile']))
if o.profile not in o.profiles:
LOG.warn('unknown profile \"{}\"'.format(o.profile))
return
dotfiles = conf.get_dotfiles(opts['profile'])
if keys:
dotfiles = o.dotfiles
if o.detail_keys:
# filtered dotfiles to install
dotfiles = [d for d in dotfiles if d.key in set(keys)]
LOG.emph('dotfiles details for profile \"{}\":\n'.format(opts['profile']))
dotfiles = [d for d in dotfiles if d.key in set(o.details_keys)]
LOG.emph('dotfiles details for profile \"{}\":\n'.format(o.profile))
for d in dotfiles:
_detail(opts['dotpath'], d)
_detail(o.dotpath, d)
LOG.log('')
@@ -379,12 +335,6 @@ def _detail(dotpath, dotfile):
LOG.sub('{} (template:{})'.format(p, template))
def _header():
"""print the header"""
LOG.log(BANNER)
LOG.log('')
def _select(selections, dotfiles):
selected = []
for selection in selections:
@@ -400,16 +350,16 @@ def _select(selections, dotfiles):
return selected
def apply_trans(opts, dotfile):
def apply_trans(dotpath, dotfile, debug=False):
"""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)
trans = dotfile.trans_r
if opts['debug']:
if debug:
LOG.dbg('executing transformation {}'.format(trans))
s = os.path.join(opts['dotpath'], src)
temp = os.path.join(opts['dotpath'], new_src)
s = os.path.join(dotpath, src)
temp = os.path.join(dotpath, new_src)
if not trans.transform(s, temp):
msg = 'transformation \"{}\" failed for {}'
LOG.err(msg.format(trans.key, dotfile.key))
@@ -426,110 +376,64 @@ def apply_trans(opts, dotfile):
def main():
"""entry point"""
ret = True
args = docopt(USAGE, version=VERSION)
try:
conf = Cfg(os.path.expanduser(args['--cfg']),
debug=args['--verbose'])
o = Options()
except ValueError as e:
LOG.err('Config format error: {}'.format(str(e)))
return False
opts = conf.get_settings()
opts['dry'] = args['--dry']
opts['profile'] = args['--profile']
opts['safe'] = not args['--force']
opts['debug'] = args['--verbose']
opts['installdiff'] = not args['--nodiff']
opts['link'] = LinkTypes.NOLINK
if opts['link_by_default']:
opts['link'] = LinkTypes.PARENTS
# Only invert link type from NOLINK to PARENTS and vice-versa
if args['--inv-link'] and opts['link'] == LinkTypes.NOLINK:
opts['link'] = LinkTypes.PARENTS
if args['--inv-link'] and opts['link'] == LinkTypes.PARENTS:
opts['link'] = LinkTypes.NOLINK
opts['variables'] = conf.get_variables(opts['profile'],
debug=opts['debug'])
opts['showdiff'] = opts['showdiff'] or args['--showdiff']
if opts['debug']:
LOG.dbg('config file: {}'.format(args['--cfg']))
LOG.dbg('options:\n{}'.format(opts))
LOG.dbg('configs:\n{}'.format(conf.dump()))
# resolve dynamic paths
conf.eval_dotfiles(opts['profile'], opts['variables'],
debug=opts['debug'])
if ENV_NOBANNER not in os.environ \
and opts['banner'] \
and not args['--no-banner']:
_header()
ret = True
try:
if args['list']:
if o.cmd_list:
# list existing profiles
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: list')
cmd_list_profiles(conf)
cmd_list_profiles(o)
elif args['listfiles']:
elif o.cmd_listfiles:
# list files for selected profile
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: listfiles')
cmd_list_files(opts, conf, templateonly=args['--template'])
cmd_list_files(o)
elif args['install']:
elif o.cmd_install:
# install the dotfiles stored in dotdrop
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: install')
ret = cmd_install(opts, conf, temporary=args['--temp'],
keys=args['<key>'])
ret = cmd_install(o)
elif args['compare']:
elif o.cmd_compare:
# compare local dotfiles with dotfiles stored in dotdrop
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: compare')
tmp = get_tmpdir()
opts['dopts'] = args['--dopts']
ret = cmd_compare(opts, conf, tmp, focus=args['--file'],
ignore=args['--ignore'])
ret = cmd_compare(o, tmp)
# clean tmp directory
remove(tmp)
elif args['import']:
elif o.cmd_import:
# import dotfile(s)
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: import')
ret = cmd_importer(opts, conf, args['<path>'])
ret = cmd_importer(o)
elif args['update']:
elif o.cmd_update:
# update a dotfile
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: update')
iskey = args['--key']
ret = cmd_update(opts, conf, args['<path>'],
iskey=iskey, ignore=args['--ignore'],
showpatch=args['--show-patch'])
ret = cmd_update(o)
elif args['detail']:
elif o.cmd_detail:
# detail files
if opts['debug']:
if o.debug:
LOG.dbg('running cmd: update')
cmd_detail(opts, conf, keys=args['<key>'])
cmd_detail(o)
except KeyboardInterrupt:
LOG.err('interrupted')
ret = False
if opts['debug']:
LOG.dbg('configs:\n{}'.format(conf.dump()))
return ret

View File

@@ -12,8 +12,8 @@ class Dotfile:
def __init__(self, key, dst, src,
actions={}, trans_r=None, trans_w=None,
link=LinkTypes.NOLINK, cmpignore=[], noempty=False,
upignore=[]):
link=LinkTypes.NOLINK, cmpignore=[],
noempty=False, upignore=[]):
"""constructor
@key: dotfile key
@dst: dotfile dst (in user's home usually)

View File

@@ -2,7 +2,7 @@
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
provides logging functions
provide logging functions
"""
import sys

194
dotdrop/options.py Normal file
View File

@@ -0,0 +1,194 @@
"""
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
stores all options to use across dotdrop
"""
import os
import socket
from docopt import docopt
# local imports
from dotdrop.version import __version__ as VERSION
from dotdrop.linktypes import LinkTypes
from dotdrop.logger import Logger
from dotdrop.config import Cfg
PROFILE = socket.gethostname()
ENV_PROFILE = 'DOTDROP_PROFILE'
ENV_NOBANNER = 'DOTDROP_NOBANNER'
if ENV_PROFILE in os.environ:
PROFILE = os.environ[ENV_PROFILE]
BANNER = """ _ _ _
__| | ___ | |_ __| |_ __ ___ _ __
/ _` |/ _ \| __/ _` | '__/ _ \| '_ |
\__,_|\___/ \__\__,_|_| \___/| .__/ v{}
|_|""".format(VERSION)
USAGE = """
{}
Usage:
dotdrop install [-VbtfndD] [-c <path>] [-p <profile>] [<key>...]
dotdrop import [-Vbld] [-c <path>] [-p <profile>] <path>...
dotdrop compare [-Vb] [-c <path>] [-p <profile>]
[-o <opts>] [-C <file>...] [-i <pattern>...]
dotdrop update [-VbfdkP] [-c <path>] [-p <profile>]
[-i <pattern>...] [<path>...]
dotdrop listfiles [-VbT] [-c <path>] [-p <profile>]
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
dotdrop list [-Vb] [-c <path>]
dotdrop --help
dotdrop --version
Options:
-p --profile=<profile> Specify the profile to use [default: {}].
-c --cfg=<path> Path to the config [default: config.yaml].
-C --file=<path> Path of dotfile to compare.
-i --ignore=<pattern> Pattern to ignore.
-o --dopts=<opts> Diff options [default: ].
-n --nodiff Do not diff when installing.
-t --temp Install to a temporary directory for review.
-T --template Only template dotfiles.
-D --showdiff Show a diff before overwriting.
-l --inv-link Invert the value of "link_by_default" when importing.
-P --show-patch Provide a one-liner to manually patch template.
-f --force Do not warn if exists.
-k --key Treat <path> as a dotfile key.
-V --verbose Be verbose.
-d --dry Dry run.
-b --no-banner Do not display the banner.
-v --version Show version.
-h --help Show this screen.
""".format(BANNER, PROFILE)
class AttrMonitor:
_set_attr_err = False
def __setattr__(self, key, value):
"""monitor attribute setting"""
if not hasattr(self, key) and self._set_attr_err:
self._attr_change(key)
super(AttrMonitor, self).__setattr__(key, value)
def _attr_set(self, attr):
"""do something when unexistent attr is set"""
pass
class Options(AttrMonitor):
def __init__(self, args=None):
"""constructor
@args: argument dictionary (if None use sys)
"""
self.args = args
if not args:
self.args = docopt(USAGE, version=VERSION)
self.log = Logger()
self.debug = self.args['--verbose']
self.confpath = os.path.expanduser(self.args['--cfg'])
self.log.dbg('config file: {}'.format(self.confpath))
self._read_config()
self._apply_args()
self._fill_attr()
if ENV_NOBANNER not in os.environ \
and self.banner \
and not self.args['--no-banner']:
self._header()
self._print_attr()
# start monitoring for bad attribute
self._set_attr_err = True
def _header(self):
"""print the header"""
self.log.log(BANNER)
self.log.log('')
def _read_config(self):
"""read the config file"""
self.conf = Cfg(self.confpath, debug=self.debug)
# transform the configs in attribute
for k, v in self.conf.get_settings().items():
setattr(self, k, v)
def _apply_args(self):
"""apply cli args as attribute"""
# the commands
self.cmd_list = self.args['list']
self.cmd_listfiles = self.args['listfiles']
self.cmd_install = self.args['install']
self.cmd_compare = self.args['compare']
self.cmd_import = self.args['import']
self.cmd_update = self.args['update']
self.cmd_detail = self.args['detail']
# adapt attributes based on arguments
self.dry = self.args['--dry']
self.profile = self.args['--profile']
self.safe = not self.args['--force']
self.link = LinkTypes.NOLINK
if self.link_by_default:
self.link = LinkTypes.PARENTS
if self.args['--inv-link']:
# Only invert link type from NOLINK to PARENTS and vice-versa
if self.link == LinkTypes.NOLINK:
self.link = LinkTypes.PARENTS
elif self.link == LinkTypes.PARENTS:
self.link = LinkTypes.NOLINK
# "listfiles" specifics
self.listfiles_templateonly = self.args['--template']
# "install" specifics
self.install_temporary = self.args['--temp']
self.install_keys = self.args['<key>']
self.install_diff = not self.args['--nodiff']
self.install_showdiff = self.showdiff or self.args['--showdiff']
# "compare" specifics
self.compare_dopts = self.args['--dopts']
self.compare_focus = self.args['--file']
self.compare_ignore = self.args['--ignore']
# "import" specifics
self.import_path = self.args['<path>']
# "update" specifics
self.update_path = self.args['<path>']
self.update_iskey = self.args['--key']
self.update_ignore = self.args['--ignore']
self.update_showpatch = self.args['--show-patch']
# "detail" specifics
self.detail_keys = self.args['<key>']
def _fill_attr(self):
"""create attributes from conf"""
# variables
self.variables = self.conf.get_variables(self.profile,
debug=self.debug).copy()
# the dotfiles
self.dotfiles = self.conf.eval_dotfiles(self.profile, self.variables,
debug=self.debug).copy()
# the profiles
self.profiles = self.conf.get_profiles()
def _print_attr(self):
"""print all of this class attributes"""
if not self.debug:
return
self.log.dbg('options:')
for att in dir(self):
if att.startswith('_'):
continue
val = getattr(self, att)
if callable(val):
continue
self.log.dbg('- {}: \"{}\"'.format(att, val))
def _attr_set(self, attr):
"""error when some inexistent attr is set"""
raise Exception('bad option: {}'.format(attr))

View File

@@ -20,27 +20,23 @@ TILD = '~'
class Updater:
def __init__(self, conf, dotpath, profile, variables, dry, safe,
iskey=False, debug=False, ignore=[], showpatch=False):
def __init__(self, dotpath, dotfiles, variables, dry=False, safe=True,
debug=False, ignore=[], showpatch=False):
"""constructor
@conf: configuration
@dotpath: path where dotfiles are stored
@profile: profile selected
@dotfiles: dotfiles for this profile
@variables: dictionary of variables for the templates
@dry: simulate
@safe: ask for overwrite if True
@iskey: will the update be called on keys or path
@debug: enable debug
@ignore: pattern to ignore when updating
@showpatch: show patch if dotfile to update is a template
"""
self.conf = conf
self.dotpath = dotpath
self.profile = profile
self.dotfiles = dotfiles
self.variables = variables
self.dry = dry
self.safe = safe
self.iskey = iskey
self.debug = debug
self.ignore = ignore
self.showpatch = showpatch
@@ -78,22 +74,22 @@ class Updater:
if self.debug:
self.log.dbg('ignore pattern(s): {}'.format(self.ignores))
left = os.path.expanduser(path)
right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src)
right = os.path.expanduser(right)
path = os.path.expanduser(path)
dtpath = os.path.join(self.dotpath, dotfile.src)
dtpath = os.path.expanduser(dtpath)
if self._ignore([left, right]):
if self._ignore([path, dtpath]):
return True
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)
path = new_path
if os.path.isdir(path):
ret = self._handle_dir(path, dtpath)
else:
ret = self._handle_file(left, right)
ret = self._handle_file(path, dtpath)
# clean temporary files
if new_path and os.path.exists(new_path):
utils.remove(new_path)
@@ -128,7 +124,7 @@ class Updater:
def _get_dotfile_by_key(self, key):
"""get the dotfile matching this key"""
dotfiles = self.conf.get_dotfiles(self.profile)
dotfiles = self.dotfiles
subs = [d for d in dotfiles if d.key == key]
if not subs:
self.log.err('key \"{}\" not found!'.format(key))
@@ -141,7 +137,7 @@ class Updater:
def _get_dotfile_by_path(self, path):
"""get the dotfile matching this path"""
dotfiles = self.conf.get_dotfiles(self.profile)
dotfiles = self.dotfiles
subs = [d for d in dotfiles if d.dst == path]
if not subs:
self.log.err('\"{}\" is not managed!'.format(path))
@@ -154,11 +150,13 @@ class Updater:
def _is_template(self, path):
if not Templategen.is_template(path):
if self.debug:
self.log.dbg('{} is NO template'.format(path))
return False
self.log.warn('{} uses template, update manually'.format(path))
return True
def _show_patch(self, tpath, fpath):
def _show_patch(self, fpath, tpath):
"""provide a way to manually patch the template"""
content = self._resolve_template(tpath)
tmp = utils.write_to_tmpfile(content)
@@ -172,49 +170,49 @@ class Updater:
debug=self.debug)
return t.generate(tpath)
def _handle_file(self, left, right, compare=True):
"""sync left (deployed file) and right (dotdrop dotfile)"""
if self._ignore([left, right]):
def _handle_file(self, path, dtpath, compare=True):
"""sync path (deployed file) and dtpath (dotdrop dotfile path)"""
if self._ignore([path, dtpath]):
return True
if self.debug:
self.log.dbg('update for file {} and {}'.format(left, right))
if self._is_template(right):
self.log.dbg('update for file {} and {}'.format(path, dtpath))
if self._is_template(dtpath):
# dotfile is a template
if self.debug:
self.log.dbg('{} is a template'.format(right))
self.log.dbg('{} is a template'.format(dtpath))
if self.showpatch:
self._show_patch(right, left)
self._show_patch(path, dtpath)
return False
if compare and filecmp.cmp(left, right, shallow=True):
if compare and filecmp.cmp(path, dtpath, shallow=True):
# no difference
if self.debug:
self.log.dbg('identical files: {} and {}'.format(left, right))
self.log.dbg('identical files: {} and {}'.format(path, dtpath))
return True
if not self._overwrite(left, right):
if not self._overwrite(path, dtpath):
return False
try:
if self.dry:
self.log.dry('would cp {} {}'.format(left, right))
self.log.dry('would cp {} {}'.format(path, dtpath))
else:
if self.debug:
self.log.dbg('cp {} {}'.format(left, right))
shutil.copyfile(left, right)
self.log.dbg('cp {} {}'.format(path, dtpath))
shutil.copyfile(path, dtpath)
except IOError as e:
self.log.warn('{} update failed, do manually: {}'.format(left, e))
self.log.warn('{} update failed, do manually: {}'.format(path, e))
return False
return True
def _handle_dir(self, left, right):
"""sync left (deployed dir) and right (dotdrop dir)"""
def _handle_dir(self, path, dtpath):
"""sync path (deployed dir) and dtpath (dotdrop dir path)"""
if self.debug:
self.log.dbg('handle update for dir {} to {}'.format(left, right))
self.log.dbg('handle update for dir {} to {}'.format(path, dtpath))
# paths must be absolute (no tildes)
left = os.path.expanduser(left)
right = os.path.expanduser(right)
if self._ignore([left, right]):
path = os.path.expanduser(path)
dtpath = os.path.expanduser(dtpath)
if self._ignore([path, dtpath]):
return True
# find the differences
diff = filecmp.dircmp(left, right, ignore=None)
diff = filecmp.dircmp(path, dtpath, ignore=None)
# handle directories diff
return self._merge_dirs(diff)

View File

@@ -10,8 +10,6 @@ import tempfile
import os
import uuid
import shlex
import functools
import operator
import fnmatch
from shutil import rmtree

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env python3
"""
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
transform all short dotfile keys
from the short format to the long
format
For example ~/.config/awesome/rc.lua
short format: f_rc.lua
long format: f_config_awesome_rc.lua
You need to have dotdrop installed for this script
to work (either with setup.py or pypi).
"""
from docopt import docopt
import sys
import os
from dotdrop.config import Cfg
USAGE = """
short-to-long-key.py
Usage:
short-to-long-key.py <config.yaml>
short-to-long-key.py --help
Options:
-h --help Show this screen.
"""
def main():
args = docopt(USAGE)
path = os.path.expanduser(args['<config.yaml>'])
try:
conf = Cfg(path)
except ValueError as e:
print('error: {}'.format(str(e)))
return False
conf.short_to_long()
print(conf.dump())
if __name__ == '__main__':
if main():
sys.exit(0)
sys.exit(1)

View File

@@ -2,3 +2,4 @@ pycodestyle; python_version >= '3.0'
nose; python_version >= '3.0'
coverage; python_version >= '3.0'
coveralls; python_version >= '3.0'
pyflakes; python_version >= '3.0'

View File

@@ -12,6 +12,10 @@ pycodestyle --ignore=W605 dotdrop/
pycodestyle tests/
pycodestyle scripts/
# pyflakes tests
pyflakes dotdrop/
pyflakes tests/
# retrieve the nosetests binary
set +e
nosebin="nosetests"

View File

@@ -10,9 +10,9 @@ import string
import random
import tempfile
from dotdrop.config import Cfg
from dotdrop.utils import *
from dotdrop.options import Options
from dotdrop.linktypes import LinkTypes
from dotdrop.utils import strip_home
TMPSUFFIX = '.dotdrop'
@@ -81,20 +81,56 @@ def create_dir(path):
return path
def load_config(confpath, profile):
def _fake_args():
args = {}
args['--verbose'] = False
args['--no-banner'] = False
args['--dry'] = False
args['--force'] = False
args['--nodiff'] = False
args['--showdiff'] = True
args['--inv-link'] = False
args['--template'] = False
args['--temp'] = False
args['<key>'] = []
args['--dopts'] = ''
args['--file'] = []
args['--ignore'] = []
args['<path>'] = []
args['--key'] = False
args['--ignore'] = []
args['--show-patch'] = False
# cmds
args['list'] = False
args['listfiles'] = False
args['install'] = False
args['compare'] = False
args['import'] = False
args['update'] = False
args['detail'] = False
return args
def load_options(confpath, profile):
"""Load the config file from path"""
conf = Cfg(confpath)
opts = conf.get_settings()
opts['dry'] = False
opts['profile'] = profile
opts['safe'] = True
opts['installdiff'] = True
opts['link'] = LinkTypes.NOLINK.value
opts['showdiff'] = True
opts['debug'] = True
opts['dopts'] = ''
opts['variables'] = {}
return conf, opts
# create the fake args (bypass docopt)
args = _fake_args()
args['--cfg'] = confpath
args['--profile'] = profile
# and get the options
# TODO need to patch options
o = Options(args=args)
o.profile = profile
o.dry = False
o.profile = profile
o.safe = True
o.install_diff = True
o.link = LinkTypes.NOLINK.value
o.install_showdiff = True
o.debug = True
o.compare_dopts = ''
o.variables = {}
return o
def get_path_strip_version(path):

View File

@@ -16,7 +16,7 @@ from dotdrop.templategen import Templategen
# from tests.helpers import *
from tests.helpers import create_dir, get_string, get_tempdir, clean, \
create_random_file, create_fake_config, load_config, edit_content
create_random_file, create_fake_config, load_options, edit_content
class TestCompare(unittest.TestCase):
@@ -26,15 +26,16 @@ class TestCompare(unittest.TestCase):
CONFIG_DOTPATH = 'dotfiles'
CONFIG_NAME = 'config.yaml'
def compare(self, opts, conf, tmp, nbdotfiles):
dotfiles = conf.get_dotfiles(opts['profile'])
def compare(self, o, tmp, nbdotfiles):
dotfiles = o.dotfiles
self.assertTrue(len(dotfiles) == nbdotfiles)
t = Templategen(base=opts['dotpath'], debug=True)
inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], base=opts['dotpath'], debug=True)
t = Templategen(base=o.dotpath, debug=True)
inst = Installer(create=o.create, backup=o.backup,
dry=o.dry, base=o.dotpath, debug=True)
comp = Comparator()
results = {}
for dotfile in dotfiles:
path = os.path.expanduser(dotfile.dst)
ret, insttmp = inst.install_to_temp(t, tmp, dotfile.src,
dotfile.dst)
if not ret:
@@ -42,10 +43,6 @@ class TestCompare(unittest.TestCase):
continue
diff = comp.compare(insttmp, dotfile.dst,
ignore=['whatever', 'whatelse'])
print('XXXX diff for {} and {}:\n{}'.format(dotfile.src,
dotfile.dst,
diff))
path = os.path.expanduser(dotfile.dst)
results[path] = diff == ''
return results
@@ -110,32 +107,33 @@ class TestCompare(unittest.TestCase):
backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile)
opts['longkey'] = True
o = load_options(confpath, profile)
o.longkey = True
dfiles = [d1, d2, d3, d4, d5, d9]
# import the files
cmd_importer(opts, conf, dfiles)
conf, opts = load_config(confpath, profile)
o.import_path = dfiles
cmd_importer(o)
o = load_options(confpath, profile)
# compare the files
expected = {d1: True, d2: True, d3: True, d4: True,
d5: True, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# modify file
edit_content(d1, get_string(20))
expected = {d1: False, d2: True, d3: True, d4: True,
d5: True, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# modify binary file
edit_content(d4, bytes(get_string(20), 'ascii'), binary=True)
expected = {d1: False, d2: True, d3: True, d4: False,
d5: True, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# add file in directory
@@ -143,7 +141,7 @@ class TestCompare(unittest.TestCase):
self.assertTrue(os.path.exists(d7))
expected = {d1: False, d2: True, d3: True, d4: False,
d5: False, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# modify all files
@@ -151,21 +149,23 @@ class TestCompare(unittest.TestCase):
edit_content(d3, get_string(21))
expected = {d1: False, d2: False, d3: False, d4: False,
d5: False, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# edit sub file
edit_content(d9f1, get_string(12))
expected = {d1: False, d2: False, d3: False, d4: False,
d5: False, d9: False}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# test compare from dotdrop
self.assertFalse(cmd_compare(opts, conf, tmp))
self.assertFalse(cmd_compare(o, tmp))
# test focus
self.assertFalse(cmd_compare(opts, conf, tmp, focus=d4))
self.assertFalse(cmd_compare(opts, conf, tmp, focus='/tmp/fake'))
o.compare_focus = d4
self.assertFalse(cmd_compare(o, tmp))
o.compare_focus = '/tmp/fake'
self.assertFalse(cmd_compare(o, tmp))
def main():

View File

@@ -8,11 +8,9 @@ basic unittest for the config parser
import unittest
import os
import yaml
import tempfile
import shutil
from dotdrop.config import Cfg
from tests.helpers import *
from tests.helpers import get_tempdir, clean, create_fake_config
class TestConfig(unittest.TestCase):
@@ -88,16 +86,15 @@ class TestConfig(unittest.TestCase):
self.assertTrue(conf is not None)
# test profile
opts = conf.get_settings()
profiles = conf.get_profiles()
self.assertTrue(pf1key in profiles)
self.assertTrue(pf2key in profiles)
# test dotfiles
dotfiles = conf.get_dotfiles(pf1key)
dotfiles = conf._get_dotfiles(pf1key)
self.assertTrue(df1key in [x.key for x in dotfiles])
self.assertTrue(df2key in [x.key for x in dotfiles])
dotfiles = conf.get_dotfiles(pf2key)
dotfiles = conf._get_dotfiles(pf2key)
self.assertTrue(df1key in [x.key for x in dotfiles])
self.assertFalse(df2key in [x.key for x in dotfiles])

View File

@@ -12,11 +12,13 @@ import yaml
from dotdrop.dotdrop import cmd_importer
from dotdrop.dotdrop import cmd_list_profiles
from dotdrop.dotdrop import cmd_list_files
from dotdrop.dotdrop import _header
from dotdrop.dotdrop import cmd_update
from dotdrop.config import Cfg
from dotdrop.linktypes import LinkTypes
from tests.helpers import *
from tests.helpers import get_path_strip_version, edit_content, \
load_options, create_random_file, \
clean, get_string, get_dotfile_from_yaml, \
get_tempdir, create_fake_config, create_dir
class TestImport(unittest.TestCase):
@@ -34,11 +36,11 @@ class TestImport(unittest.TestCase):
content = yaml.load(f)
return content
def assert_file(self, path, conf, profile):
def assert_file(self, path, o, profile):
"""Make sure path has been inserted in conf for profile"""
strip = get_path_strip_version(path)
self.assertTrue(strip in [x.src for x in conf.get_dotfiles(profile)])
dsts = [os.path.expanduser(x.dst) for x in conf.get_dotfiles(profile)]
self.assertTrue(strip in [x.src for x in o.dotfiles])
dsts = [os.path.expanduser(x.dst) for x in o.dotfiles]
self.assertTrue(path in dsts)
def assert_in_yaml(self, path, dic, link=False):
@@ -69,7 +71,7 @@ class TestImport(unittest.TestCase):
backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile)
o = load_options(confpath, profile)
# create some random dotfiles
dotfile1, content1 = create_random_file(src)
@@ -107,25 +109,27 @@ class TestImport(unittest.TestCase):
# import the dotfiles
dfiles = [dotfile1, dotfile2, dotfile3, dotfile4, dotfile5]
cmd_importer(opts, conf, dfiles)
o.import_path = dfiles
cmd_importer(o)
# import symlink
opts[Cfg.key_dotfiles_link] = True
o.link = LinkTypes.PARENTS
sfiles = [dotfile6, dotfile7]
cmd_importer(opts, conf, sfiles)
opts[Cfg.key_dotfiles_link] = False
o.import_path = sfiles
cmd_importer(o)
o.link = LinkTypes.NOLINK
# reload the config
conf, opts = load_config(confpath, profile)
o = load_options(confpath, profile)
# test dotfiles in config class
self.assertTrue(profile in conf.get_profiles())
self.assert_file(dotfile1, conf, profile)
self.assert_file(dotfile2, conf, profile)
self.assert_file(dotfile3, conf, profile)
self.assert_file(dotfile4, conf, profile)
self.assert_file(dotfile5, conf, profile)
self.assert_file(dotfile6, conf, profile)
self.assert_file(dotfile7, conf, profile)
self.assertTrue(profile in o.profiles)
self.assert_file(dotfile1, o, profile)
self.assert_file(dotfile2, o, profile)
self.assert_file(dotfile3, o, profile)
self.assert_file(dotfile4, o, profile)
self.assert_file(dotfile5, o, profile)
self.assert_file(dotfile6, o, profile)
self.assert_file(dotfile7, o, profile)
# test dotfiles in yaml file
y = self.load_yaml(confpath)
@@ -193,15 +197,15 @@ class TestImport(unittest.TestCase):
self.assertTrue(os.path.islink(dotfile7))
self.assertTrue(os.path.realpath(dotfile7) == indt7)
cmd_list_profiles(conf)
cmd_list_files(opts, conf)
_header()
cmd_list_profiles(o)
cmd_list_files(o)
# fake test update
editcontent = 'edited'
edit_content(dotfile1, editcontent)
opts['safe'] = False
cmd_update(opts, conf, [dotfile1])
o.safe = False
o.update_path = [dotfile1]
cmd_update(o)
c2 = open(indt1, 'r').read()
self.assertTrue(editcontent == c2)

View File

@@ -11,7 +11,7 @@ import filecmp
from dotdrop.config import Cfg
from tests.helpers import create_dir, get_string, get_tempdir, clean, \
create_random_file, load_config
create_random_file, load_options
from dotdrop.dotfile import Dotfile
from dotdrop.installer import Installer
from dotdrop.action import Action
@@ -181,12 +181,12 @@ exec bspwm
self.assertTrue(conf is not None)
# install them
conf, opts = load_config(confpath, profile)
opts['safe'] = False
opts['debug'] = True
opts['showdiff'] = True
opts['variables'] = {}
cmd_install(opts, conf)
o = load_options(confpath, profile)
o.safe = False
o.debug = True
o.install_showdiff = True
o.variables = {}
cmd_install(o)
# now compare the generated files
self.assertTrue(os.path.exists(dst1))
@@ -233,7 +233,7 @@ exec bspwm
self.assertTrue(tempcontent == header())
def test_link_children(self):
"""test the link children"""
# create source dir
src_dir = get_tempdir()
self.assertTrue(os.path.exists(src_dir))
@@ -257,6 +257,7 @@ exec bspwm
self.assertEqual(os.path.realpath(dst), src)
def test_fails_without_src(self):
"""test fails without src"""
src = '/some/non/existant/file'
installer = Installer()
@@ -272,7 +273,7 @@ exec bspwm
.format(src))
def test_fails_when_src_file(self):
"""test fails when src file"""
# create source dir
src_dir = get_tempdir()
self.assertTrue(os.path.exists(src_dir))
@@ -296,6 +297,7 @@ exec bspwm
.format(src))
def test_creates_dst(self):
"""test creates dst"""
src_dir = get_tempdir()
self.assertTrue(os.path.exists(src_dir))
self.addCleanup(clean, src_dir)
@@ -316,7 +318,7 @@ exec bspwm
self.assertTrue(os.path.exists(dst_dir))
def test_prompts_to_replace_dst(self):
"""test prompts to replace dst"""
# create source dir
src_dir = get_tempdir()
self.assertTrue(os.path.exists(src_dir))
@@ -354,7 +356,7 @@ exec bspwm
@patch('dotdrop.installer.Templategen')
def test_runs_templater(self, mocked_templategen):
"""test runs templater"""
# create source dir
src_dir = get_tempdir()
self.assertTrue(os.path.exists(src_dir))

View File

@@ -7,15 +7,15 @@ basic unittest for the compare function
import unittest
import os
import yaml
from dotdrop.config import Cfg
from dotdrop.dotdrop import cmd_list_profiles
from dotdrop.dotdrop import cmd_list_files
from dotdrop.dotdrop import cmd_detail
from dotdrop.dotdrop import cmd_importer
from tests.helpers import *
from tests.helpers import create_dir, get_string, get_tempdir, \
create_random_file, load_options, \
create_fake_config, clean
class TestListings(unittest.TestCase):
@@ -74,22 +74,26 @@ class TestListings(unittest.TestCase):
backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile)
o = load_options(confpath, profile)
dfiles = [d1, d2, d3, d4, d5]
# import the files
cmd_importer(opts, conf, dfiles)
conf, opts = load_config(confpath, profile)
o.import_path = dfiles
cmd_importer(o)
o = load_options(confpath, profile)
# listfiles
cmd_list_profiles(conf)
cmd_list_profiles(o)
# list files
cmd_list_files(opts, conf, templateonly=False)
cmd_list_files(opts, conf, templateonly=True)
o.listfiles_templateonly = False
cmd_list_files(o)
o.listfiles_templateonly = True
cmd_list_files(o)
# details
cmd_detail(opts, conf, keys=None)
o.detail_keys = None
cmd_detail(o)
def main():

View File

@@ -10,9 +10,10 @@ import os
from dotdrop.dotdrop import cmd_update
from dotdrop.dotdrop import cmd_importer
from dotdrop.action import Transform
from tests.helpers import create_dir, get_string, get_tempdir, clean, \
create_random_file, create_fake_config, load_config, edit_content
create_random_file, create_fake_config, load_options, edit_content
class TestUpdate(unittest.TestCase):
@@ -53,6 +54,31 @@ class TestUpdate(unittest.TestCase):
self.assertTrue(os.path.exists(d2))
self.addCleanup(clean, d2)
# template
d3t, c3t = create_random_file(fold_config)
self.assertTrue(os.path.exists(d3t))
self.addCleanup(clean, d3t)
# sub dirs
dsubstmp = get_tempdir()
self.assertTrue(os.path.exists(dsubstmp))
self.addCleanup(clean, dsubstmp)
dirsubs = os.path.basename(dsubstmp)
dir1string = 'somedir'
dir1 = os.path.join(dsubstmp, dir1string)
create_dir(dir1)
dir1sub1str = 'sub1'
sub1 = os.path.join(dir1, dir1sub1str)
create_dir(sub1)
dir1sub2str = 'sub2'
sub2 = os.path.join(dir1, dir1sub2str)
create_dir(sub2)
f1s1, f1s1c1 = create_random_file(sub1)
self.assertTrue(os.path.exists(f1s1))
f1s2, f1s2c1 = create_random_file(sub2)
self.assertTrue(os.path.exists(f1s2))
# create the directory to test
dpath = os.path.join(fold_config, get_string(5))
dir1 = create_dir(dpath)
@@ -67,12 +93,47 @@ class TestUpdate(unittest.TestCase):
backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile)
dfiles = [d1, dir1, d2]
o = load_options(confpath, profile)
o.debug = True
o.update_showpatch = True
dfiles = [d1, dir1, d2, d3t, dsubstmp]
# import the files
cmd_importer(opts, conf, dfiles)
conf, opts = load_config(confpath, profile)
o.import_path = dfiles
cmd_importer(o)
# get new config
o = load_options(confpath, profile)
o.safe = False
o.debug = True
o.update_showpatch = True
trans = Transform('trans', 'cp -r {0} {1}')
d3tb = os.path.basename(d3t)
for dotfile in o.dotfiles:
if os.path.basename(dotfile.dst) == d3tb:
# patch the template
src = os.path.join(o.dotpath, dotfile.src)
src = os.path.expanduser(src)
edit_content(src, '{{@@ profile @@}}')
if os.path.basename(dotfile.dst) == dirsubs:
# retrieve the path of the sub in the dotpath
d1indotpath = os.path.join(o.dotpath, dotfile.src)
d1indotpath = os.path.expanduser(d1indotpath)
dotfile.trans_w = trans
# update template
o.update_path = [d3t]
self.assertFalse(cmd_update(o))
# update sub dirs
gone = os.path.join(d1indotpath, dir1string)
gone = os.path.join(gone, dir1sub1str)
self.assertTrue(os.path.exists(gone))
clean(sub1) # dir1sub1str
self.assertTrue(os.path.exists(gone))
o.update_path = [dsubstmp]
cmd_update(o)
self.assertFalse(os.path.exists(gone))
# edit the files
edit_content(d1, 'newcontent')
@@ -87,9 +148,8 @@ class TestUpdate(unittest.TestCase):
create_random_file(dpath)
# update it
opts['safe'] = False
opts['debug'] = True
cmd_update(opts, conf, [d1, dir1])
o.update_path = [d1, dir1]
cmd_update(o)
# test content
newcontent = open(d1, 'r').read()
@@ -100,7 +160,7 @@ class TestUpdate(unittest.TestCase):
edit_content(d2, 'newcontentbykey')
# update it by key
dfiles = conf.get_dotfiles(profile)
dfiles = o.dotfiles
d2key = ''
for ds in dfiles:
t = os.path.expanduser(ds.dst)
@@ -108,9 +168,9 @@ class TestUpdate(unittest.TestCase):
d2key = ds.key
break
self.assertTrue(d2key != '')
opts['safe'] = False
opts['debug'] = True
cmd_update(opts, conf, [d2key], iskey=True)
o.update_path = [d2key]
o.update_iskey = True
cmd_update(o)
# test content
newcontent = open(d2, 'r').read()