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

View File

@@ -14,7 +14,7 @@ from dotdrop.dotfile import Dotfile
from dotdrop.templategen import Templategen from dotdrop.templategen import Templategen
from dotdrop.logger import Logger from dotdrop.logger import Logger
from dotdrop.action import Action, Transform from dotdrop.action import Action, Transform
from dotdrop.utils import * from dotdrop.utils import strip_home, shell
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
@@ -126,7 +126,8 @@ class Cfg:
def eval_dotfiles(self, profile, variables, debug=False): def eval_dotfiles(self, profile, variables, debug=False):
"""resolve dotfiles src/dst/actions templating for this profile""" """resolve dotfiles src/dst/actions templating for this profile"""
t = Templategen(variables=variables) t = Templategen(variables=variables)
for d in self.get_dotfiles(profile): dotfiles = self._get_dotfiles(profile)
for d in dotfiles:
# src and dst path # src and dst path
d.src = t.generate_string(d.src) d.src = t.generate_string(d.src)
d.dst = t.generate_string(d.dst) d.dst = t.generate_string(d.dst)
@@ -138,6 +139,7 @@ class Cfg:
if self.key_actions_post in d.actions: if self.key_actions_post in d.actions:
for action in d.actions[self.key_actions_post]: for action in d.actions[self.key_actions_post]:
action.action = t.generate_string(action.action) action.action = t.generate_string(action.action)
return dotfiles
def _load_file(self): def _load_file(self):
"""load the yaml file""" """load the yaml file"""
@@ -335,14 +337,24 @@ class Cfg:
# make sure we have an absolute dotpath # make sure we have an absolute dotpath
self.curdotpath = self.lnk_settings[self.key_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 # make sure we have an absolute workdir
self.curworkdir = self.lnk_settings[self.key_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 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): def _get_included_dotfiles(self, profile):
"""find all dotfiles for a specific profile """find all dotfiles for a specific profile
when using the include keyword""" when using the include keyword"""
@@ -427,15 +439,6 @@ class Cfg:
if self.key_ignoreempty not in self.lnk_settings: if self.key_ignoreempty not in self.lnk_settings:
self.lnk_settings[self.key_ignoreempty] = self.default_ignoreempty 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): def _save(self, content, path):
"""writes the config to file""" """writes the config to file"""
ret = False ret = False
@@ -501,37 +504,6 @@ class Cfg:
cnt += 1 cnt += 1
return key 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): def _dotfile_exists(self, dotfile):
"""return True and the existing dotfile key """return True and the existing dotfile key
if it already exists, False and a new unique key otherwise""" if it already exists, False and a new unique key otherwise"""
@@ -609,7 +581,7 @@ class Cfg:
return True, dotfile return True, dotfile
def get_dotfiles(self, profile): def _get_dotfiles(self, profile):
"""return a list of dotfiles for a specific profile""" """return a list of dotfiles for a specific profile"""
if profile not in self.prodots: if profile not in self.prodots:
return [] return []

View File

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

View File

@@ -12,8 +12,8 @@ class Dotfile:
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, cmpignore=[], noempty=False, link=LinkTypes.NOLINK, cmpignore=[],
upignore=[]): noempty=False, upignore=[]):
"""constructor """constructor
@key: dotfile key @key: dotfile key
@dst: dotfile dst (in user's home usually) @dst: dotfile dst (in user's home usually)

View File

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

View File

@@ -10,8 +10,6 @@ import tempfile
import os import os
import uuid import uuid
import shlex import shlex
import functools
import operator
import fnmatch import fnmatch
from shutil import rmtree 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' nose; python_version >= '3.0'
coverage; python_version >= '3.0' coverage; python_version >= '3.0'
coveralls; 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 tests/
pycodestyle scripts/ pycodestyle scripts/
# pyflakes tests
pyflakes dotdrop/
pyflakes tests/
# retrieve the nosetests binary # retrieve the nosetests binary
set +e set +e
nosebin="nosetests" nosebin="nosetests"

View File

@@ -10,9 +10,9 @@ import string
import random import random
import tempfile import tempfile
from dotdrop.config import Cfg from dotdrop.options import Options
from dotdrop.utils import *
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
from dotdrop.utils import strip_home
TMPSUFFIX = '.dotdrop' TMPSUFFIX = '.dotdrop'
@@ -81,20 +81,56 @@ def create_dir(path):
return 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""" """Load the config file from path"""
conf = Cfg(confpath) # create the fake args (bypass docopt)
opts = conf.get_settings() args = _fake_args()
opts['dry'] = False args['--cfg'] = confpath
opts['profile'] = profile args['--profile'] = profile
opts['safe'] = True # and get the options
opts['installdiff'] = True # TODO need to patch options
opts['link'] = LinkTypes.NOLINK.value o = Options(args=args)
opts['showdiff'] = True o.profile = profile
opts['debug'] = True o.dry = False
opts['dopts'] = '' o.profile = profile
opts['variables'] = {} o.safe = True
return conf, opts 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): 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 *
from tests.helpers import create_dir, get_string, get_tempdir, clean, \ 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): class TestCompare(unittest.TestCase):
@@ -26,15 +26,16 @@ class TestCompare(unittest.TestCase):
CONFIG_DOTPATH = 'dotfiles' CONFIG_DOTPATH = 'dotfiles'
CONFIG_NAME = 'config.yaml' CONFIG_NAME = 'config.yaml'
def compare(self, opts, conf, tmp, nbdotfiles): def compare(self, o, tmp, nbdotfiles):
dotfiles = conf.get_dotfiles(opts['profile']) dotfiles = o.dotfiles
self.assertTrue(len(dotfiles) == nbdotfiles) self.assertTrue(len(dotfiles) == nbdotfiles)
t = Templategen(base=opts['dotpath'], debug=True) t = Templategen(base=o.dotpath, debug=True)
inst = Installer(create=opts['create'], backup=opts['backup'], inst = Installer(create=o.create, backup=o.backup,
dry=opts['dry'], base=opts['dotpath'], debug=True) dry=o.dry, base=o.dotpath, debug=True)
comp = Comparator() comp = Comparator()
results = {} results = {}
for dotfile in dotfiles: for dotfile in dotfiles:
path = os.path.expanduser(dotfile.dst)
ret, insttmp = inst.install_to_temp(t, tmp, dotfile.src, ret, insttmp = inst.install_to_temp(t, tmp, dotfile.src,
dotfile.dst) dotfile.dst)
if not ret: if not ret:
@@ -42,10 +43,6 @@ class TestCompare(unittest.TestCase):
continue continue
diff = comp.compare(insttmp, dotfile.dst, diff = comp.compare(insttmp, dotfile.dst,
ignore=['whatever', 'whatelse']) ignore=['whatever', 'whatelse'])
print('XXXX diff for {} and {}:\n{}'.format(dotfile.src,
dotfile.dst,
diff))
path = os.path.expanduser(dotfile.dst)
results[path] = diff == '' results[path] = diff == ''
return results return results
@@ -110,32 +107,33 @@ class TestCompare(unittest.TestCase):
backup=self.CONFIG_BACKUP, backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE) create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath)) self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile) o = load_options(confpath, profile)
opts['longkey'] = True o.longkey = True
dfiles = [d1, d2, d3, d4, d5, d9] dfiles = [d1, d2, d3, d4, d5, d9]
# import the files # import the files
cmd_importer(opts, conf, dfiles) o.import_path = dfiles
conf, opts = load_config(confpath, profile) cmd_importer(o)
o = load_options(confpath, profile)
# compare the files # compare the files
expected = {d1: True, d2: True, d3: True, d4: True, expected = {d1: True, d2: True, d3: True, d4: True,
d5: True, d9: True} d5: True, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles)) results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected) self.assertTrue(results == expected)
# modify file # modify file
edit_content(d1, get_string(20)) edit_content(d1, get_string(20))
expected = {d1: False, d2: True, d3: True, d4: True, expected = {d1: False, d2: True, d3: True, d4: True,
d5: True, d9: True} d5: True, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles)) results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected) self.assertTrue(results == expected)
# modify binary file # modify binary file
edit_content(d4, bytes(get_string(20), 'ascii'), binary=True) edit_content(d4, bytes(get_string(20), 'ascii'), binary=True)
expected = {d1: False, d2: True, d3: True, d4: False, expected = {d1: False, d2: True, d3: True, d4: False,
d5: True, d9: True} d5: True, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles)) results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected) self.assertTrue(results == expected)
# add file in directory # add file in directory
@@ -143,7 +141,7 @@ class TestCompare(unittest.TestCase):
self.assertTrue(os.path.exists(d7)) self.assertTrue(os.path.exists(d7))
expected = {d1: False, d2: True, d3: True, d4: False, expected = {d1: False, d2: True, d3: True, d4: False,
d5: False, d9: True} d5: False, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles)) results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected) self.assertTrue(results == expected)
# modify all files # modify all files
@@ -151,21 +149,23 @@ class TestCompare(unittest.TestCase):
edit_content(d3, get_string(21)) edit_content(d3, get_string(21))
expected = {d1: False, d2: False, d3: False, d4: False, expected = {d1: False, d2: False, d3: False, d4: False,
d5: False, d9: True} d5: False, d9: True}
results = self.compare(opts, conf, tmp, len(dfiles)) results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected) self.assertTrue(results == expected)
# edit sub file # edit sub file
edit_content(d9f1, get_string(12)) edit_content(d9f1, get_string(12))
expected = {d1: False, d2: False, d3: False, d4: False, expected = {d1: False, d2: False, d3: False, d4: False,
d5: False, d9: False} d5: False, d9: False}
results = self.compare(opts, conf, tmp, len(dfiles)) results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected) self.assertTrue(results == expected)
# test compare from dotdrop # test compare from dotdrop
self.assertFalse(cmd_compare(opts, conf, tmp)) self.assertFalse(cmd_compare(o, tmp))
# test focus # test focus
self.assertFalse(cmd_compare(opts, conf, tmp, focus=d4)) o.compare_focus = d4
self.assertFalse(cmd_compare(opts, conf, tmp, focus='/tmp/fake')) self.assertFalse(cmd_compare(o, tmp))
o.compare_focus = '/tmp/fake'
self.assertFalse(cmd_compare(o, tmp))
def main(): def main():

View File

@@ -8,11 +8,9 @@ basic unittest for the config parser
import unittest import unittest
import os import os
import yaml import yaml
import tempfile
import shutil
from dotdrop.config import Cfg from dotdrop.config import Cfg
from tests.helpers import * from tests.helpers import get_tempdir, clean, create_fake_config
class TestConfig(unittest.TestCase): class TestConfig(unittest.TestCase):
@@ -88,16 +86,15 @@ class TestConfig(unittest.TestCase):
self.assertTrue(conf is not None) self.assertTrue(conf is not None)
# test profile # test profile
opts = conf.get_settings()
profiles = conf.get_profiles() profiles = conf.get_profiles()
self.assertTrue(pf1key in profiles) self.assertTrue(pf1key in profiles)
self.assertTrue(pf2key in profiles) self.assertTrue(pf2key in profiles)
# test dotfiles # test dotfiles
dotfiles = conf.get_dotfiles(pf1key) dotfiles = conf._get_dotfiles(pf1key)
self.assertTrue(df1key in [x.key for x in dotfiles]) self.assertTrue(df1key in [x.key for x in dotfiles])
self.assertTrue(df2key 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.assertTrue(df1key in [x.key for x in dotfiles])
self.assertFalse(df2key 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_importer
from dotdrop.dotdrop import cmd_list_profiles from dotdrop.dotdrop import cmd_list_profiles
from dotdrop.dotdrop import cmd_list_files from dotdrop.dotdrop import cmd_list_files
from dotdrop.dotdrop import _header
from dotdrop.dotdrop import cmd_update 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): class TestImport(unittest.TestCase):
@@ -34,11 +36,11 @@ class TestImport(unittest.TestCase):
content = yaml.load(f) content = yaml.load(f)
return content 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""" """Make sure path has been inserted in conf for profile"""
strip = get_path_strip_version(path) strip = get_path_strip_version(path)
self.assertTrue(strip in [x.src 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 conf.get_dotfiles(profile)] dsts = [os.path.expanduser(x.dst) for x in o.dotfiles]
self.assertTrue(path in dsts) self.assertTrue(path in dsts)
def assert_in_yaml(self, path, dic, link=False): def assert_in_yaml(self, path, dic, link=False):
@@ -69,7 +71,7 @@ class TestImport(unittest.TestCase):
backup=self.CONFIG_BACKUP, backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE) create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath)) self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile) o = load_options(confpath, profile)
# create some random dotfiles # create some random dotfiles
dotfile1, content1 = create_random_file(src) dotfile1, content1 = create_random_file(src)
@@ -107,25 +109,27 @@ class TestImport(unittest.TestCase):
# import the dotfiles # import the dotfiles
dfiles = [dotfile1, dotfile2, dotfile3, dotfile4, dotfile5] dfiles = [dotfile1, dotfile2, dotfile3, dotfile4, dotfile5]
cmd_importer(opts, conf, dfiles) o.import_path = dfiles
cmd_importer(o)
# import symlink # import symlink
opts[Cfg.key_dotfiles_link] = True o.link = LinkTypes.PARENTS
sfiles = [dotfile6, dotfile7] sfiles = [dotfile6, dotfile7]
cmd_importer(opts, conf, sfiles) o.import_path = sfiles
opts[Cfg.key_dotfiles_link] = False cmd_importer(o)
o.link = LinkTypes.NOLINK
# reload the config # reload the config
conf, opts = load_config(confpath, profile) o = load_options(confpath, profile)
# test dotfiles in config class # test dotfiles in config class
self.assertTrue(profile in conf.get_profiles()) self.assertTrue(profile in o.profiles)
self.assert_file(dotfile1, conf, profile) self.assert_file(dotfile1, o, profile)
self.assert_file(dotfile2, conf, profile) self.assert_file(dotfile2, o, profile)
self.assert_file(dotfile3, conf, profile) self.assert_file(dotfile3, o, profile)
self.assert_file(dotfile4, conf, profile) self.assert_file(dotfile4, o, profile)
self.assert_file(dotfile5, conf, profile) self.assert_file(dotfile5, o, profile)
self.assert_file(dotfile6, conf, profile) self.assert_file(dotfile6, o, profile)
self.assert_file(dotfile7, conf, profile) self.assert_file(dotfile7, o, profile)
# test dotfiles in yaml file # test dotfiles in yaml file
y = self.load_yaml(confpath) y = self.load_yaml(confpath)
@@ -193,15 +197,15 @@ class TestImport(unittest.TestCase):
self.assertTrue(os.path.islink(dotfile7)) self.assertTrue(os.path.islink(dotfile7))
self.assertTrue(os.path.realpath(dotfile7) == indt7) self.assertTrue(os.path.realpath(dotfile7) == indt7)
cmd_list_profiles(conf) cmd_list_profiles(o)
cmd_list_files(opts, conf) cmd_list_files(o)
_header()
# fake test update # fake test update
editcontent = 'edited' editcontent = 'edited'
edit_content(dotfile1, editcontent) edit_content(dotfile1, editcontent)
opts['safe'] = False o.safe = False
cmd_update(opts, conf, [dotfile1]) o.update_path = [dotfile1]
cmd_update(o)
c2 = open(indt1, 'r').read() c2 = open(indt1, 'r').read()
self.assertTrue(editcontent == c2) self.assertTrue(editcontent == c2)

View File

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

View File

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

View File

@@ -10,9 +10,10 @@ import os
from dotdrop.dotdrop import cmd_update from dotdrop.dotdrop import cmd_update
from dotdrop.dotdrop import cmd_importer from dotdrop.dotdrop import cmd_importer
from dotdrop.action import Transform
from tests.helpers import create_dir, get_string, get_tempdir, clean, \ 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): class TestUpdate(unittest.TestCase):
@@ -53,6 +54,31 @@ class TestUpdate(unittest.TestCase):
self.assertTrue(os.path.exists(d2)) self.assertTrue(os.path.exists(d2))
self.addCleanup(clean, 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 # create the directory to test
dpath = os.path.join(fold_config, get_string(5)) dpath = os.path.join(fold_config, get_string(5))
dir1 = create_dir(dpath) dir1 = create_dir(dpath)
@@ -67,12 +93,47 @@ class TestUpdate(unittest.TestCase):
backup=self.CONFIG_BACKUP, backup=self.CONFIG_BACKUP,
create=self.CONFIG_CREATE) create=self.CONFIG_CREATE)
self.assertTrue(os.path.exists(confpath)) self.assertTrue(os.path.exists(confpath))
conf, opts = load_config(confpath, profile) o = load_options(confpath, profile)
dfiles = [d1, dir1, d2] o.debug = True
o.update_showpatch = True
dfiles = [d1, dir1, d2, d3t, dsubstmp]
# import the files # import the files
cmd_importer(opts, conf, dfiles) o.import_path = dfiles
conf, opts = load_config(confpath, profile) 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 the files
edit_content(d1, 'newcontent') edit_content(d1, 'newcontent')
@@ -87,9 +148,8 @@ class TestUpdate(unittest.TestCase):
create_random_file(dpath) create_random_file(dpath)
# update it # update it
opts['safe'] = False o.update_path = [d1, dir1]
opts['debug'] = True cmd_update(o)
cmd_update(opts, conf, [d1, dir1])
# test content # test content
newcontent = open(d1, 'r').read() newcontent = open(d1, 'r').read()
@@ -100,7 +160,7 @@ class TestUpdate(unittest.TestCase):
edit_content(d2, 'newcontentbykey') edit_content(d2, 'newcontentbykey')
# update it by key # update it by key
dfiles = conf.get_dotfiles(profile) dfiles = o.dotfiles
d2key = '' d2key = ''
for ds in dfiles: for ds in dfiles:
t = os.path.expanduser(ds.dst) t = os.path.expanduser(ds.dst)
@@ -108,9 +168,9 @@ class TestUpdate(unittest.TestCase):
d2key = ds.key d2key = ds.key
break break
self.assertTrue(d2key != '') self.assertTrue(d2key != '')
opts['safe'] = False o.update_path = [d2key]
opts['debug'] = True o.update_iskey = True
cmd_update(opts, conf, [d2key], iskey=True) cmd_update(o)
# test content # test content
newcontent = open(d2, 'r').read() newcontent = open(d2, 'r').read()