1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-04 20:19:46 +00:00
Files
dotdrop/dotdrop/dotdrop.py
2023-10-22 14:46:02 +02:00

970 lines
31 KiB
Python

"""
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2017, deadc0de6
entry point
"""
import os
import sys
import time
import fnmatch
from concurrent import futures
# local imports
from dotdrop.options import Options
from dotdrop.logger import Logger
from dotdrop.templategen import Templategen
from dotdrop.installer import Installer
from dotdrop.uninstaller import Uninstaller
from dotdrop.updater import Updater
from dotdrop.comparator import Comparator
from dotdrop.importer import Importer
from dotdrop.utils import get_tmpdir, removepath, \
uniq_list, ignores_to_absolute, dependencies_met, \
adapt_workers, check_version, pivot_path
from dotdrop.linktypes import LinkTypes
from dotdrop.exceptions import YamlException, \
UndefinedException, UnmetDependency, \
ConfigException, OptionsException
LOG = Logger()
TRANS_SUFFIX = 'trans'
###########################################################
# entry point
###########################################################
def action_executor(opts, actions, defactions, templater, post=False):
"""closure for action execution"""
def execute():
"""
execute actions and return
True, None if ok
False, errstring if issue
"""
actiontype = 'pre' if not post else 'post'
# execute default actions
for action in defactions:
if opts.dry:
LOG.dry(f'would execute def-{actiontype}-action: {action}')
continue
LOG.dbg(f'executing def-{actiontype}-action: {action}')
ret = action.execute(templater=templater, debug=opts.debug)
if not ret:
err = f'def-{actiontype}-action \"{action.key}\" failed'
LOG.err(err)
return False, err
# execute actions
for action in actions:
if opts.dry:
err = f'would execute {actiontype}-action: {action}'
LOG.dry(err)
continue
LOG.dbg(f'executing {actiontype}-action: {action}')
ret = action.execute(templater=templater, debug=opts.debug)
if not ret:
err = f'{actiontype}-action \"{action.key}\" failed'
LOG.err(err)
return False, err
return True, None
return execute
def _dotfile_update(opts, path, key=False):
"""
update a dotfile pointed by path
if key is false or by key (in path)
"""
updater = Updater(opts.dotpath, opts.variables, opts.conf, opts.profile,
dry=opts.dry, safe=opts.safe, debug=opts.debug,
ignore=opts.update_ignore,
showpatch=opts.update_showpatch,
ignore_missing_in_dotdrop=opts.ignore_missing_in_dotdrop)
if key:
return updater.update_key(path)
return updater.update_path(path)
def _dotfile_compare(opts, dotfile, tmp):
"""
compare a dotfile
returns True if same
"""
templ = _get_templater(opts)
ignore_missing_in_dotdrop = opts.ignore_missing_in_dotdrop or \
dotfile.ignore_missing_in_dotdrop
inst = Installer(create=opts.create, backup=opts.backup,
dry=opts.dry, base=opts.dotpath,
workdir=opts.workdir, debug=opts.debug,
backup_suffix=opts.install_backup_suffix,
diff_cmd=opts.diff_command)
comp = Comparator(diff_cmd=opts.diff_command, debug=opts.debug,
ignore_missing_in_dotdrop=ignore_missing_in_dotdrop)
# add dotfile variables
newvars = dotfile.get_dotfile_variables()
templ.add_tmp_vars(newvars=newvars)
# dotfiles does not exist / not installed
LOG.dbg(f'comparing {dotfile}')
src = dotfile.src
if not os.path.lexists(os.path.expanduser(dotfile.dst)):
line = f'=> compare {dotfile.key}: \"{dotfile.dst}\" '
line += 'does not exist on destination'
LOG.log(line)
return False
# apply transformation
tmpsrc = None
if dotfile.trans_install:
LOG.dbg('applying transformation before comparing')
tmpsrc = apply_install_trans(opts.dotpath, dotfile,
templ, debug=opts.debug)
if not tmpsrc:
# could not apply trans
return False
src = tmpsrc
# is a symlink pointing to itself
asrc = os.path.join(opts.dotpath, os.path.expanduser(src))
adst = os.path.expanduser(dotfile.dst)
if os.path.samefile(asrc, adst):
line = f'=> compare {dotfile.key}: diffing with \"{dotfile.dst}\"'
LOG.dbg(line)
LOG.dbg('points to itself')
return True
ignores = list(set(opts.compare_ignore + dotfile.cmpignore))
ignores = ignores_to_absolute(ignores, [dotfile.dst, dotfile.src],
debug=opts.debug)
insttmp = None
if dotfile.template and \
Templategen.path_is_template(src,
debug=opts.debug):
# install dotfile to temporary dir for compare
ret, err, insttmp = inst.install_to_temp(templ, tmp, src, dotfile.dst,
is_template=True,
chmod=dotfile.chmod,
set_create=True)
if not ret:
# failed to install to tmp
line = f'=> compare {dotfile.key} error: {err}'
LOG.log(line)
LOG.err(err)
return False
src = insttmp
# compare
# need to be executed before cleaning
diff = comp.compare(src, dotfile.dst, ignore=ignores, mode=dotfile.chmod)
# clean tmp transformed dotfile if any
if tmpsrc:
tmpsrc = os.path.join(opts.dotpath, tmpsrc)
if os.path.exists(tmpsrc):
removepath(tmpsrc, LOG)
# clean tmp template dotfile if any
if insttmp and os.path.exists(insttmp):
removepath(insttmp, LOG)
if diff != '':
# print diff results
if opts.compare_fileonly:
line = f'=> differ: \"{dotfile.key}\" \"{dotfile.dst}\"'
LOG.log(line)
else:
line = f'=> compare {dotfile.key}: diffing with \"{dotfile.dst}\"'
LOG.log(line)
LOG.emph(diff)
return False
# no difference
line = f'=> compare {dotfile.key}: diffing with \"{dotfile.dst}\"'
LOG.dbg(line)
LOG.dbg('same file')
return True
def _dotfile_install(opts, dotfile, tmpdir=None):
"""
install a dotfile
returns <success, dotfile key, err>
"""
# installer
inst = _get_install_installer(opts, tmpdir=tmpdir)
# templater
templ = _get_templater(opts)
# add dotfile variables
newvars = dotfile.get_dotfile_variables()
templ.add_tmp_vars(newvars=newvars)
preactions = []
if not opts.install_temporary:
preactions.extend(dotfile.get_pre_actions())
defactions = opts.install_default_actions_pre
pre_actions_exec = action_executor(opts, preactions, defactions,
templ, post=False)
LOG.dbg(f'installing dotfile: \"{dotfile.key}\"')
LOG.dbg(dotfile.prt())
ignores = list(set(opts.install_ignore + dotfile.instignore))
ignores = ignores_to_absolute(ignores, [dotfile.dst, dotfile.src],
debug=opts.debug)
is_template = dotfile.template and Templategen.path_is_template(
dotfile.src,
)
if hasattr(dotfile, 'link') and dotfile.link in (
LinkTypes.LINK, LinkTypes.LINK_CHILDREN,
LinkTypes.RELATIVE, LinkTypes.ABSOLUTE
):
# nolink|relative|absolute|link_children
ret, err = inst.install(templ, dotfile.src, dotfile.dst,
dotfile.link,
actionexec=pre_actions_exec,
is_template=is_template,
ignore=ignores,
chmod=dotfile.chmod,
force_chmod=opts.install_force_chmod)
else:
# nolink
src = dotfile.src
tmp = None
if dotfile.trans_install:
tmp = apply_install_trans(opts.dotpath, dotfile,
templ, debug=opts.debug)
if not tmp:
return False, dotfile.key, None
src = tmp
# make sure to re-evaluate if is template
is_template = dotfile.template and Templategen.path_is_template(src)
ret, err = inst.install(templ, src, dotfile.dst,
LinkTypes.NOLINK,
actionexec=pre_actions_exec,
noempty=dotfile.noempty,
ignore=ignores,
is_template=is_template,
chmod=dotfile.chmod,
force_chmod=opts.install_force_chmod)
if tmp:
tmp = os.path.join(opts.dotpath, tmp)
if os.path.exists(tmp):
removepath(tmp, LOG)
# check result of installation
if ret:
# dotfile was installed
if not opts.install_temporary:
defactions = opts.install_default_actions_post
postactions = dotfile.get_post_actions()
post_actions_exec = action_executor(opts, postactions, defactions,
templ, post=True)
post_actions_exec()
else:
# dotfile was NOT installed
if opts.install_force_action:
# pre-actions
LOG.dbg('force pre action execution ...')
pre_actions_exec()
# post-actions
LOG.dbg('force post action execution ...')
defactions = opts.install_default_actions_post
postactions = dotfile.get_post_actions()
post_actions_exec = action_executor(opts, postactions, defactions,
templ, post=True)
post_actions_exec()
return ret, dotfile.key, err
def cmd_install(opts):
"""install dotfiles for this profile"""
dotfiles = opts.dotfiles
prof = opts.conf.get_profile()
adapt_workers(opts, LOG)
pro_pre_actions = prof.get_pre_actions() if prof else []
pro_post_actions = prof.get_post_actions() if prof else []
if opts.install_keys:
# filtered dotfiles to install
uniq = uniq_list(opts.install_keys)
dotfiles = [d for d in dotfiles if d.key in uniq]
if not dotfiles:
msg = f'no dotfile to install for this profile (\"{opts.profile}\")'
LOG.warn(msg)
return False
lfs = [k.key for k in dotfiles]
LOG.dbg(f'dotfiles registered for install: {lfs}')
# the installer
tmpdir = None
if opts.install_temporary:
tmpdir = get_tmpdir()
installed = []
# clear the workdir
if opts.install_clear_workdir and not opts.dry:
LOG.dbg(f'clearing the workdir under {opts.workdir}')
for root, _, files in os.walk(opts.workdir):
for file in files:
fpath = os.path.join(root, file)
removepath(fpath, logger=LOG)
# execute profile pre-action
LOG.dbg(f'run {len(pro_pre_actions)} profile pre actions')
templ = _get_templater(opts)
ret, _ = action_executor(opts, pro_pre_actions, [], templ, post=False)()
if not ret:
return False
# install each dotfile
if opts.workers > 1:
# in parallel
LOG.dbg(f'run with {opts.workers} workers')
ex = futures.ThreadPoolExecutor(max_workers=opts.workers)
wait_for = []
for dotfile in dotfiles:
j = ex.submit(_dotfile_install, opts, dotfile, tmpdir=tmpdir)
wait_for.append(j)
# check result
for fut in futures.as_completed(wait_for):
tmpret, key, err = fut.result()
# check result
if tmpret:
installed.append(key)
elif err:
LOG.err(f'installing \"{key}\" failed: {err}')
else:
# sequentially
for dotfile in dotfiles:
tmpret, key, err = _dotfile_install(opts, dotfile, tmpdir=tmpdir)
# check result
if tmpret:
installed.append(key)
elif err:
LOG.err(f'installing \"{key}\" failed: {err}')
# execute profile post-action
if len(installed) > 0 or opts.install_force_action:
msg = f'run {len(pro_post_actions)} profile post actions'
LOG.dbg(msg)
ret, _ = action_executor(opts, pro_post_actions,
[], templ, post=False)()
if not ret:
return False
insts = ','.join(installed)
LOG.dbg(f'install done: installed \"{insts}\"')
if opts.install_temporary:
LOG.log(f'\ninstalled to tmp \"{tmpdir}\".')
LOG.log(f'\n{len(installed)} dotfile(s) installed.')
return True
def _workdir_enum(opts):
workdir_files = []
for root, _, files in os.walk(opts.workdir):
for file in files:
fpath = os.path.join(root, file)
workdir_files.append(fpath)
for dotfile in opts.dotfiles:
src = os.path.join(opts.dotpath, dotfile.src)
if dotfile.link == LinkTypes.NOLINK:
# ignore not link files
continue
if not Templategen.path_is_template(src):
# ignore not template
continue
newpath = pivot_path(dotfile.dst, opts.workdir,
striphome=True, logger=None)
if os.path.isdir(newpath):
# recursive
pattern = f'{newpath}/*'
files = workdir_files.copy()
for file in files:
if fnmatch.fnmatch(file, pattern):
workdir_files.remove(file)
# only checks children
children = [f.path for f in os.scandir(newpath)]
for child in children:
if child in workdir_files:
workdir_files.remove(child)
else:
if newpath in workdir_files:
workdir_files.remove(newpath)
for wfile in workdir_files:
line = f'=> \"{wfile}\" does not exist in dotdrop'
LOG.log(line)
return len(workdir_files)
def cmd_compare(opts, tmp):
"""compare dotfiles and return True if all identical"""
dotfiles = opts.dotfiles
if not dotfiles:
msg = f'no dotfile defined for this profile (\"{opts.profile}\")'
LOG.warn(msg)
return True
# compare only specific files
selected = dotfiles
if opts.compare_focus:
selected = _select(opts.compare_focus, dotfiles)
if len(selected) < 1:
LOG.log('\nno dotfile to compare')
return False
same = True
cnt = 0
if opts.workers > 1:
# in parallel
LOG.dbg(f'run with {opts.workers} workers')
ex = futures.ThreadPoolExecutor(max_workers=opts.workers)
wait_for = []
for dotfile in selected:
if not dotfile.src and not dotfile.dst:
# ignore fake dotfile
continue
j = ex.submit(_dotfile_compare, opts, dotfile, tmp)
wait_for.append(j)
# check result
for fut in futures.as_completed(wait_for):
if not fut.result():
same = False
cnt += 1
else:
# sequentially
for dotfile in selected:
if not dotfile.src and not dotfile.dst:
# ignore fake dotfile
continue
if not _dotfile_compare(opts, dotfile, tmp):
same = False
cnt += 1
if opts.compare_workdir and _workdir_enum(opts) > 0:
same = False
LOG.log(f'\n{cnt} dotfile(s) compared.')
return same
def cmd_update(opts):
"""update the dotfile(s) from path(s) or key(s)"""
cnt = 0
paths = opts.update_path
iskey = opts.update_iskey
if opts.profile not in [p.key for p in opts.profiles]:
LOG.err(f'no such profile \"{opts.profile}\"')
return False
adapt_workers(opts, LOG)
if not paths:
# update the entire profile
if iskey:
LOG.dbg(f'update by keys: {paths}')
paths = [d.key for d in opts.dotfiles]
else:
LOG.dbg(f'update by paths: {paths}')
paths = [d.dst for d in opts.dotfiles]
msg = f'Update all dotfiles for profile \"{opts.profile}\"'
if opts.safe and not LOG.ask(msg):
LOG.log(f'\n{cnt} file(s) updated.')
return False
# check there's something to do
if not paths:
LOG.log('\nno dotfile to update')
return True
LOG.dbg(f'dotfile to update: {paths}')
# update each dotfile
if opts.workers > 1:
# in parallel
LOG.dbg(f'run with {opts.workers} workers')
ex = futures.ThreadPoolExecutor(max_workers=opts.workers)
wait_for = []
for path in paths:
j = ex.submit(_dotfile_update, opts, path, key=iskey)
wait_for.append(j)
# check result
for fut in futures.as_completed(wait_for):
if fut.result():
cnt += 1
else:
# sequentially
for path in paths:
if _dotfile_update(opts, path, key=iskey):
cnt += 1
LOG.log(f'\n{cnt} file(s) updated.')
return cnt == len(paths)
def cmd_importer(opts):
"""import dotfile(s) from paths"""
ret = True
cnt = 0
paths = opts.import_path
importer = Importer(opts.profile, opts.conf,
opts.dotpath, opts.diff_command,
opts.variables,
dry=opts.dry, safe=opts.safe,
debug=opts.debug,
keepdot=opts.keepdot,
ignore=opts.import_ignore)
for path in paths:
tmpret = importer.import_path(path,
import_as=opts.import_as,
import_link=opts.import_link,
import_mode=opts.import_mode,
trans_install=opts.import_trans_install,
trans_update=opts.import_trans_update)
if tmpret < 0:
ret = False
elif tmpret > 0:
cnt += 1
if opts.dry:
LOG.dry('new config file would be:')
LOG.raw(opts.conf.dump())
else:
opts.conf.save()
LOG.log(f'\n{cnt} file(s) imported.')
return ret
def cmd_list_profiles(opts):
"""list all profiles"""
LOG.emph('Available profile(s):\n')
for profile in opts.profiles:
if opts.profiles_grepable:
fmt = f'{profile.key}'
LOG.raw(fmt)
else:
LOG.sub(profile.key, end='')
LOG.log(f' ({len(profile.dotfiles)} dotfiles)')
LOG.log('')
def cmd_files(opts):
"""list all dotfiles for a specific profile"""
if opts.profile not in [p.key for p in opts.profiles]:
LOG.warn(f'unknown profile \"{opts.profile}\"')
return
what = 'Dotfile(s)'
if opts.files_templateonly:
what = 'Template(s)'
LOG.emph(f'{what} for profile \"{opts.profile}\":\n')
for dotfile in opts.dotfiles:
if opts.files_templateonly:
src = os.path.join(opts.dotpath, dotfile.src)
if not Templategen.path_is_template(src):
continue
if opts.files_grepable:
fmt = f'{dotfile.key},'
fmt += f'dst:{dotfile.dst},'
fmt += f'src:{dotfile.src},'
fmt += f'link:{dotfile.link.name.lower()}'
if dotfile.chmod:
fmt += f',chmod:{dotfile.chmod:o}'
else:
fmt += ',chmod:None'
LOG.raw(fmt)
else:
LOG.log(f'{dotfile.key}', bold=True)
LOG.sub(f'dst: {dotfile.dst}')
LOG.sub(f'src: {dotfile.src}')
LOG.sub(f'link: {dotfile.link.name.lower()}')
if dotfile.chmod:
LOG.sub(f'chmod: {dotfile.chmod:o}')
LOG.log('')
def cmd_detail(opts):
"""list details on all files for all dotfile entries"""
if opts.profile not in [p.key for p in opts.profiles]:
LOG.warn(f'unknown profile \"{opts.profile}\"')
return
dotfiles = opts.dotfiles
if opts.detail_keys:
# filtered dotfiles to install
uniq = uniq_list(opts.details_keys)
dotfiles = [d for d in dotfiles if d.key in uniq]
LOG.emph(f'dotfiles details for profile \"{opts.profile}\":\n')
for dotfile in dotfiles:
_detail(opts.dotpath, dotfile)
LOG.log('')
def cmd_uninstall(opts):
"""uninstall"""
dotfiles = opts.dotfiles
keys = opts.uninstall_key
if keys:
# update only specific keys for this profile
dotfiles = []
for key in uniq_list(keys):
dotfile = opts.conf.get_dotfile(key)
if dotfile:
dotfiles.append(dotfile)
if not dotfiles:
msg = f'no dotfile to uninstall for this profile (\"{opts.profile}\")'
LOG.warn(msg)
return False
if opts.debug:
lfs = [k.key for k in dotfiles]
LOG.dbg(f'dotfiles registered for uninstall: {lfs}')
uninst = Uninstaller(base=opts.dotpath,
workdir=opts.workdir,
dry=opts.dry,
safe=opts.safe,
debug=opts.debug,
backup_suffix=opts.install_backup_suffix)
uninstalled = 0
for dotf in dotfiles:
res, msg = uninst.uninstall(dotf.src,
dotf.dst,
dotf.link)
if not res:
LOG.err(msg)
continue
uninstalled += 1
LOG.log(f'\n{uninstalled} dotfile(s) uninstalled.')
return True
def cmd_remove(opts):
"""remove dotfile from dotpath and from config"""
paths = opts.remove_path
iskey = opts.remove_iskey
if not paths:
LOG.log('no dotfile to remove')
return False
pathss = ','.join(paths)
LOG.dbg(f'dotfile(s) to remove: {pathss}')
removed = []
for key in paths:
if not iskey:
# by path
dotfiles = opts.conf.get_dotfile_by_dst(key)
if not dotfiles:
LOG.warn(f'{key} ignored, does not exist')
continue
else:
# by key
dotfile = opts.conf.get_dotfile(key)
if not dotfile:
LOG.warn(f'{key} ignored, does not exist')
continue
dotfiles = [dotfile]
for dotfile in dotfiles:
k = dotfile.key
# ignore if uses any type of link
if dotfile.link != LinkTypes.NOLINK:
msg = f'{k} uses symlink, remove manually'
LOG.warn(msg)
continue
LOG.dbg(f'removing {key}')
# make sure is part of the profile
if dotfile.key not in [d.key for d in opts.dotfiles]:
msg = f'{key} ignored, not associated to this profile'
LOG.warn(msg)
continue
profiles = opts.conf.get_profiles_by_dotfile_key(k)
pkeys = ','.join([p.key for p in profiles])
if opts.dry:
LOG.dry(f'would remove {dotfile} from {pkeys}')
continue
msg = f'Remove \"{k}\" from all these profiles: {pkeys}'
if opts.safe and not LOG.ask(msg):
return False
LOG.dbg(f'remove dotfile: {dotfile}')
for profile in profiles:
if not opts.conf.del_dotfile_from_profile(dotfile, profile):
return False
if not opts.conf.del_dotfile(dotfile):
return False
# remove dotfile from dotpath
dtpath = os.path.join(opts.dotpath, dotfile.src)
removepath(dtpath, LOG)
# remove empty directory
parent = os.path.dirname(dtpath)
# remove any empty parent up to dotpath
while parent != opts.dotpath:
if os.path.isdir(parent) and not os.listdir(parent):
msg = f'Remove empty dir \"{parent}\"'
if opts.safe and not LOG.ask(msg):
break
removepath(parent, LOG)
parent = os.path.dirname(parent)
removed.append(dotfile)
if opts.dry:
LOG.dry('new config file would be:')
LOG.raw(opts.conf.dump())
else:
opts.conf.save()
if removed:
LOG.log('\nFollowing dotfile(s) are not tracked anymore:')
entries = [f'- \"{r.dst}\" (was tracked as \"{r.key}\")'
for r in removed]
LOG.log('\n'.join(entries))
else:
LOG.log('\nno dotfile removed')
return True
###########################################################
# helpers
###########################################################
def _get_install_installer(opts, tmpdir=None):
"""get an installer instance for cmd_install"""
inst = Installer(create=opts.create, backup=opts.backup,
dry=opts.dry, safe=opts.safe,
base=opts.dotpath, workdir=opts.workdir,
diff=opts.install_diff, debug=opts.debug,
totemp=tmpdir,
showdiff=opts.install_showdiff,
backup_suffix=opts.install_backup_suffix,
diff_cmd=opts.diff_command,
remove_existing_in_dir=opts.install_remove_existing)
return inst
def _get_templater(opts):
"""get an templater instance"""
templ = Templategen(base=opts.dotpath, variables=opts.variables,
func_file=opts.func_file, filter_file=opts.filter_file,
debug=opts.debug)
return templ
def _detail(dotpath, dotfile):
"""display details on all files under a dotfile entry"""
entry = f'{dotfile.key}'
attribs = []
attribs.append(f'dst: \"{dotfile.dst}\"')
attribs.append(f'link: \"{dotfile.link.name.lower()}\"')
attribs.append(f'chmod: \"{dotfile.chmod}\"')
attrs = ', '.join(attribs)
LOG.log(f'{entry} ({attrs})')
path = os.path.join(dotpath, os.path.expanduser(dotfile.src))
if not os.path.isdir(path):
template = 'no'
if dotfile.template and Templategen.path_is_template(path):
template = 'yes'
LOG.sub(f'{path} (template:{template})')
else:
for root, _, files in os.walk(path):
for file in files:
fpath = os.path.join(root, file)
template = 'no'
if dotfile.template and Templategen.path_is_template(fpath):
template = 'yes'
LOG.sub(f'{fpath} (template:{template})')
def _select(selections, dotfiles):
selected = []
for selection in selections:
dotfile = next(
(x for x in dotfiles
if os.path.expanduser(x.dst) == os.path.expanduser(selection)),
None
)
if dotfile:
selected.append(dotfile)
else:
LOG.err(f'no dotfile matches \"{selection}\"')
return selected
def apply_install_trans(dotpath, dotfile, templater, debug=False):
"""
apply the install transformation to the dotfile
return None if fails and new source if succeed
"""
src = dotfile.src
new_src = f'{src}.{TRANS_SUFFIX}'
trans = dotfile.trans_install
LOG.dbg(f'executing install transformation: {trans}')
srcpath = os.path.join(dotpath, src)
temp = os.path.join(dotpath, new_src)
if not trans.transform(srcpath, temp, templater=templater, debug=debug):
msg = f'install transformation \"{trans.key}\"'
msg += f'failed for {dotfile.key}'
LOG.err(msg)
if new_src and os.path.exists(new_src):
removepath(new_src, LOG)
return None
return new_src
###########################################################
# main
###########################################################
def _exec_command(opts):
"""execute command"""
ret = True
command = ''
try:
if opts.cmd_profiles:
# list existing profiles
command = 'profiles'
LOG.dbg(f'running cmd: {command}')
cmd_list_profiles(opts)
elif opts.cmd_files:
# list files for selected profile
command = 'files'
LOG.dbg(f'running cmd: {command}')
cmd_files(opts)
elif opts.cmd_install:
# install the dotfiles stored in dotdrop
command = 'install'
LOG.dbg(f'running cmd: {command}')
ret = cmd_install(opts)
elif opts.cmd_compare:
# compare local dotfiles with dotfiles stored in dotdrop
command = 'compare'
LOG.dbg(f'running cmd: {command}')
tmp = get_tmpdir()
ret = cmd_compare(opts, tmp)
# clean tmp directory
removepath(tmp, LOG)
elif opts.cmd_import:
# import dotfile(s)
command = 'import'
LOG.dbg(f'running cmd: {command}')
ret = cmd_importer(opts)
elif opts.cmd_update:
# update a dotfile
command = 'update'
LOG.dbg(f'running cmd: {command}')
ret = cmd_update(opts)
elif opts.cmd_detail:
# detail files
command = 'detail'
LOG.dbg(f'running cmd: {command}')
cmd_detail(opts)
elif opts.cmd_remove:
# remove dotfile
command = 'remove'
LOG.dbg(f'running cmd: {command}')
cmd_remove(opts)
elif opts.cmd_uninstall:
# uninstall dotfile
command = 'uninstall'
LOG.dbg(f'running cmd: {command}')
cmd_uninstall(opts)
except UndefinedException as exc:
LOG.err(exc)
ret = False
except KeyboardInterrupt:
LOG.err('interrupted')
ret = False
return ret, command
def main():
"""entry point"""
# check dependencies are met
try:
dependencies_met()
except UnmetDependency as exc:
LOG.err(exc)
return False
time0 = time.time()
try:
opts = Options()
except YamlException as exc:
LOG.err(f'error (yaml): {exc}')
return False
except ConfigException as exc:
LOG.err(f'error (config): {exc}')
return False
except UndefinedException as exc:
LOG.err(f'error (deps): {exc}')
return False
except OptionsException as exc:
LOG.err(f'error (options): {exc}')
return False
if opts.debug:
LOG.debug = opts.debug
LOG.dbg('\n\n')
options_time = time.time() - time0
if opts.check_version:
check_version()
time0 = time.time()
ret, command = _exec_command(opts)
cmd_time = time.time() - time0
LOG.dbg(f'done executing command \"{command}\"')
LOG.dbg(f'options loaded in {options_time}')
LOG.dbg(f'command executed in {cmd_time}')
if ret and opts.conf.save():
LOG.log('config file updated')
LOG.dbg(f'return {ret}')
return ret
if __name__ == '__main__':
if main():
sys.exit(0)
sys.exit(1)