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

wrap conf and args into a new Options class

This commit is contained in:
deadc0de6
2019-02-10 20:59:14 +01:00
parent 88c730157d
commit c82cc56dcf
10 changed files with 435 additions and 313 deletions

View File

@@ -138,6 +138,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 self.get_dotfiles(profile)
def _load_file(self):
"""load the yaml file"""
@@ -335,14 +336,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 +438,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

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,42 @@ 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.installdiff, debug=o.debug,
totemp=tmpdir, showdiff=o.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 +66,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 +131,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 +143,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 +164,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,
o.dry, o.safe, iskey=iskey, 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 +187,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 +196,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 +212,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 +250,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 +294,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 +334,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 +349,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 +375,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

169
dotdrop/options.py Normal file
View File

@@ -0,0 +1,169 @@
"""
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 [-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)
class Options:
def __init__(self, args=None):
"""constructor
@key: action key
@action: action string
"""
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()
if self.debug:
self._print_attr()
def _header():
"""print the header"""
self.log.log(BANNER)
self.log.log('')
def _read_config(self):
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):
# 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.installdiff = not self.args['--nodiff']
self.showdiff = self.showdiff or self.args['--showdiff']
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>']
# "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):
# 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()
self.profiles = self.conf.get_profiles()
def _print_attr(self):
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))

View File

@@ -20,12 +20,11 @@ TILD = '~'
class Updater:
def __init__(self, conf, dotpath, profile, variables, dry, safe,
def __init__(self, dotpath, dotfiles, variables, dry, safe,
iskey=False, 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
@@ -34,9 +33,8 @@ class Updater:
@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
@@ -79,7 +77,7 @@ class Updater:
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.join(self.dotpath, dotfile.src)
right = os.path.expanduser(right)
if self._ignore([left, right]):
@@ -128,7 +126,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 +139,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))

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 *
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.installdiff = True
o.link = LinkTypes.NOLINK.value
o.showdiff = True
o.debug = True
o.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,12 +26,12 @@ 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:
@@ -98,50 +98,53 @@ 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]
# 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}
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}
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}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# add file in directory
d7, _ = create_random_file(d5)
self.assertTrue(os.path.exists(d7))
expected = {d1: False, d2: True, d3: True, d4: False, d5: False}
results = self.compare(opts, conf, tmp, len(dfiles))
results = self.compare(o, tmp, len(dfiles))
self.assertTrue(results == expected)
# modify all files
edit_content(d2, get_string(20))
edit_content(d3, get_string(21))
expected = {d1: False, d2: False, d3: False, d4: False, d5: 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.focus = d4
self.assertFalse(cmd_compare(o, tmp))
o.focus = '/tmp/fake'
self.assertFalse(cmd_compare(o, tmp))
def main():

View File

@@ -12,7 +12,6 @@ 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
@@ -34,11 +33,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 +68,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 +106,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 +194,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.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

@@ -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

@@ -12,7 +12,7 @@ from dotdrop.dotdrop import cmd_update
from dotdrop.dotdrop import cmd_importer
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):
@@ -67,12 +67,13 @@ class TestUpdate(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, dir1, d2]
# 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)
# edit the files
edit_content(d1, 'newcontent')
@@ -87,9 +88,10 @@ class TestUpdate(unittest.TestCase):
create_random_file(dpath)
# update it
opts['safe'] = False
opts['debug'] = True
cmd_update(opts, conf, [d1, dir1])
o.safe = False
o.debug = True
o.update_path = [d1, dir1]
cmd_update(o)
# test content
newcontent = open(d1, 'r').read()
@@ -100,7 +102,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 +110,11 @@ 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.safe = False
o.debug = True
o.update_path = [d2key]
o.iskey = True
cmd_update(o)
# test content
newcontent = open(d2, 'r').read()