def link(self, src, dst): '''Sets src as the link target of dst''' src = os.path.join(self.base, os.path.expanduser(src)) dst = os.path.join(self.base, os.path.expanduser(dst)) if os.path.exists(dst): if os.path.realpath(dst) == os.path.realpath(src): self.log.sub('ignoring "%s", link exists' % dst) return [] if self.dry: self.log.dry('would remove %s and link it to %s' % (dst, src)) return [] if self.safe and \ not self.log.ask('Remove "%s" for link creation?' % dst): self.log.warn('ignoring "%s", link was not created' % dst) return [] try: utils.remove(dst) except OSError: self.log.err('something went wrong with %s' % src) return [] if self.dry: self.log.dry('would link %s to %s' % (dst, src)) return [] os.symlink(src, dst) self.log.sub('linked %s to %s' % (dst, src)) # Follows original developer's behavior return [(src, dst)]
def _update(self, path, dotfile): """update dotfile from file pointed by path""" ret = False new_path = None self.ignores = list(set(self.ignore + dotfile.upignore)) if self.debug: self.log.dbg('ignore pattern(s): {}'.format(self.ignores)) left = os.path.expanduser(path) right = os.path.join(self.conf.abs_or_rel(self.dotpath), dotfile.src) right = os.path.expanduser(right) if self._ignore([left, right]): return True if dotfile.trans_w: # apply write transformation if any new_path = self._apply_trans_w(path, dotfile) if not new_path: return False left = new_path if os.path.isdir(left): ret = self._handle_dir(left, right) else: ret = self._handle_file(left, right) # clean temporary files if new_path and os.path.exists(new_path): utils.remove(new_path) return ret
def _link(self, src, dst, actions=[]): """set src as a link target of dst""" if os.path.lexists(dst): if os.path.realpath(dst) == os.path.realpath(src): if self.debug: self.log.dbg('ignoring "{}", link exists'.format(dst)) return [] if self.dry: self.log.dry('would remove {} and link to {}'.format(dst, src)) return [] msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not self.log.ask(msg): msg = 'ignoring "{}", link was not created' self.log.warn(msg.format(dst)) return [] try: utils.remove(dst) except OSError as e: self.log.err('something went wrong with {}: {}'.format(src, e)) return [] if self.dry: self.log.dry('would link {} to {}'.format(dst, src)) return [] base = os.path.dirname(dst) if not self._create_dirs(base): self.log.err('creating directory for {}'.format(dst)) return [] self._exec_pre_actions(actions) os.symlink(src, dst) self.log.sub('linked {} to {}'.format(dst, src)) return [(src, dst)]
def _update(self, path, dotfile): """update dotfile from file pointed by path""" ret = False new_path = None ignores = list(set(self.ignore + dotfile.upignore)) self.ignores = patch_ignores(ignores, dotfile.dst, debug=self.debug) if self.debug: self.log.dbg('ignore pattern(s): {}'.format(self.ignores)) path = os.path.expanduser(path) dtpath = os.path.join(self.dotpath, dotfile.src) dtpath = os.path.expanduser(dtpath) if self._ignore([path, dtpath]): self.log.sub('\"{}\" ignored'.format(dotfile.key)) return True # apply write transformation if any new_path = self._apply_trans_w(path, dotfile) if not new_path: return False if os.path.isdir(new_path): ret = self._handle_dir(new_path, dtpath) else: ret = self._handle_file(new_path, dtpath) # clean temporary files if new_path != path and os.path.exists(new_path): remove(new_path) return ret
def link(self, src, dst): """set src as the link target of dst""" src = os.path.join(self.base, os.path.expanduser(src)) dst = os.path.join(self.base, os.path.expanduser(dst)) if os.path.exists(dst): if os.path.realpath(dst) == os.path.realpath(src): self.log.sub('ignoring "{}", link exists'.format(dst)) return [] if self.dry: self.log.dry('would remove {} and link to {}'.format(dst, src)) return [] msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not self.log.ask(msg): msg = 'ignoring "{}", link was not created' self.log.warn(msg.format(dst)) return [] try: utils.remove(dst) except OSError: self.log.err('something went wrong with {}'.format(src)) return [] if self.dry: self.log.dry('would link {} to {}'.format(dst, src)) return [] base = os.path.dirname(dst) if not self._create_dirs(base): self.log.err('creating directory for \"{}\"'.format(dst)) return [] os.symlink(src, dst) self.log.sub('linked {} to {}'.format(dst, src)) return [(src, dst)]
def _link(self, src, dst, actionexec=None): """ set src as a link target of dst return - True, None: success - False, error_msg: error - False, None, ignored """ overwrite = not self.safe if os.path.lexists(dst): if os.path.realpath(dst) == os.path.realpath(src): msg = 'ignoring "{}", link already exists'.format(dst) if self.debug: self.log.dbg(msg) return False, None if self.dry: self.log.dry('would remove {} and link to {}'.format(dst, src)) return True, None if self.showdiff: self._diff_before_write(src, dst) msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not self.log.ask(msg): err = 'ignoring "{}", link was not created'.format(dst) return False, err overwrite = True try: utils.remove(dst) except OSError as e: err = 'something went wrong with {}: {}'.format(src, e) return False, err if self.dry: self.log.dry('would link {} to {}'.format(dst, src)) return True, None base = os.path.dirname(dst) if not self._create_dirs(base): err = 'creating directory for {}'.format(dst) return False, err r, e = self._exec_pre_actions(actionexec) if not r: return False, e # re-check in case action created the file if os.path.lexists(dst): msg = 'Remove "{}" for link creation?'.format(dst) if self.safe and not overwrite and not self.log.ask(msg): err = 'ignoring "{}", link was not created'.format(dst) return False, err try: utils.remove(dst) except OSError as e: err = 'something went wrong with {}: {}'.format(src, e) return False, err os.symlink(src, dst) self.log.sub('linked {} to {}'.format(dst, src)) return True, None
def _diff_before_write(self, src, dst, src_content): """diff before writing when using --showdiff - not efficient""" # create tmp to diff for templates tmpfile = utils.get_tmpfile() with open(tmpfile, 'wb') as f: f.write(src_content) comp = Comparator(debug=self.debug) diff = comp.compare(tmpfile, dst) # fake the output for readability self.log.log('diff \"{}\" VS \"{}\"'.format(src, dst)) self.log.emph(diff) if tmpfile: utils.remove(tmpfile)
def _apply_trans_w(self, path, dotfile): """apply write transformation to dotfile""" trans = dotfile.trans_w if self.debug: self.log.dbg('executing write transformation {}'.format(trans)) tmp = utils.get_unique_tmp_name() if not trans.transform(path, tmp): msg = 'transformation \"{}\" failed for {}' self.log.err(msg.format(trans.key, dotfile.key)) if os.path.exists(tmp): utils.remove(tmp) return None return tmp
def _diff_before_write(self, src, dst, content=None): """diff before writing when using --showdiff - not efficient""" tmp = None if content: tmp = utils.write_to_tmpfile(content) src = tmp diff = utils.diff(src, dst, raw=False) utils.remove(tmp, quiet=True) # fake the output for readability if not diff: return self.log.log('diff \"{}\" VS \"{}\"'.format(src, dst)) self.log.emph(diff)
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 debug: LOG.dbg('executing transformation {}'.format(trans)) 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)) if new_src and os.path.exists(new_src): remove(new_src) return None return new_src
def _apply_trans_w(self, path, dotfile): """apply write transformation to dotfile""" trans = dotfile.get_trans_w() if not trans: return path if self.debug: self.log.dbg('executing write transformation {}'.format(trans)) tmp = get_unique_tmp_name() self.templater.restore_vars(self.tvars) newvars = dotfile.get_dotfile_variables() self.templater.add_tmp_vars(newvars=newvars) if not trans.transform( path, tmp, templater=self.templater, debug=self.debug): msg = 'transformation \"{}\" failed for {}' self.log.err(msg.format(trans.key, dotfile.key)) if os.path.exists(tmp): remove(tmp) return None return tmp
def _diff_before_write(self, src, dst, content=None, quiet=False): """ diff before writing using a temp file if content is not None returns diff string ('' if same) """ tmp = None if content: tmp = utils.write_to_tmpfile(content) src = tmp diff = utils.diff(modified=src, original=dst, raw=False, diff_cmd=self.diff_cmd) if tmp: utils.remove(tmp, logger=self.log) if not quiet and diff: self._print_diff(src, dst, diff) return diff
def _update(self, path, dotfile): """update dotfile from file pointed by path""" ret = False new_path = None left = os.path.expanduser(path) right = os.path.join(self.conf.abs_dotpath(self.dotpath), dotfile.src) right = os.path.expanduser(right) if dotfile.trans_w: # apply write transformation if any new_path = self._apply_trans_w(path, dotfile) if not new_path: return False left = new_path if os.path.isdir(left): ret = self._handle_dir(left, right) else: ret = self._handle_file(left, right) # clean temporary files if new_path and os.path.exists(new_path): utils.remove(new_path) return ret
def _merge_dirs(self, diff): """Synchronize directories recursively.""" left, right = diff.left, diff.right if self.debug: self.log.dbg('sync dir {} to {}'.format(left, right)) if self._ignore([left, right]): return True # create dirs that don't exist in dotdrop for toadd in diff.left_only: exist = os.path.join(left, toadd) if not os.path.isdir(exist): # ignore files for now continue # match to dotdrop dotpath new = os.path.join(right, toadd) if self._ignore([exist, new]): continue if self.dry: self.log.dry('would cp -r {} {}'.format(exist, new)) continue if self.debug: self.log.dbg('cp -r {} {}'.format(exist, new)) # Newly created directory should be copied as is (for efficiency). shutil.copytree(exist, new) # remove dirs that don't exist in deployed version for toremove in diff.right_only: old = os.path.join(right, toremove) if not os.path.isdir(old): # ignore files for now continue if self._ignore([old]): continue if self.dry: self.log.dry('would rm -r {}'.format(old)) continue if self.debug: self.log.dbg('rm -r {}'.format(old)) if not self._confirm_rm_r(old): continue utils.remove(old) # handle files diff # sync files that exist in both but are different fdiff = diff.diff_files fdiff.extend(diff.funny_files) fdiff.extend(diff.common_funny) for f in fdiff: fleft = os.path.join(left, f) fright = os.path.join(right, f) if self._ignore([fleft, fright]): continue if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) continue if self.debug: self.log.dbg('cp {} {}'.format(fleft, fright)) self._handle_file(fleft, fright, compare=False) # copy files that don't exist in dotdrop for toadd in diff.left_only: exist = os.path.join(left, toadd) if os.path.isdir(exist): # ignore dirs, done above continue new = os.path.join(right, toadd) if self._ignore([exist, new]): continue if self.dry: self.log.dry('would cp {} {}'.format(exist, new)) continue if self.debug: self.log.dbg('cp {} {}'.format(exist, new)) shutil.copyfile(exist, new) # remove files that don't exist in deployed version for toremove in diff.right_only: new = os.path.join(right, toremove) if not os.path.exists(new): continue if os.path.isdir(new): # ignore dirs, done above continue if self._ignore([new]): continue if self.dry: self.log.dry('would rm {}'.format(new)) continue if self.debug: self.log.dbg('rm {}'.format(new)) utils.remove(new) # Recursively decent into common subdirectories. for subdir in diff.subdirs.values(): self._merge_dirs(subdir) # Nothing more to do here. return True
def cmd_compare(o, tmp): """compare dotfiles and return True if all identical""" dotfiles = o.dotfiles if not dotfiles: msg = 'no dotfile defined for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return True # compare only specific files same = True selected = dotfiles if o.compare_focus: selected = _select(o.compare_focus, dotfiles) if len(selected) < 1: return False t = Templategen(base=o.dotpath, variables=o.variables, func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) tvars = t.add_tmp_vars() inst = Installer(create=o.create, backup=o.backup, dry=o.dry, base=o.dotpath, workdir=o.workdir, debug=o.debug, backup_suffix=o.install_backup_suffix, diff_cmd=o.diff_command) comp = Comparator(diff_cmd=o.diff_command, debug=o.debug) for dotfile in selected: if not dotfile.src and not dotfile.dst: # ignore fake dotfile continue # add dotfile variables t.restore_vars(tvars) newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) # dotfiles does not exist / not installed if o.debug: LOG.dbg('comparing {}'.format(dotfile)) src = dotfile.src if not os.path.lexists(os.path.expanduser(dotfile.dst)): line = '=> compare {}: \"{}\" does not exist on destination' LOG.log(line.format(dotfile.key, dotfile.dst)) same = False continue # apply transformation tmpsrc = None if dotfile.trans_r: if o.debug: LOG.dbg('applying transformation before comparing') tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmpsrc: # could not apply trans same = False continue src = tmpsrc # is a symlink pointing to itself asrc = os.path.join(o.dotpath, os.path.expanduser(src)) adst = os.path.expanduser(dotfile.dst) if os.path.samefile(asrc, adst): if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('points to itself') continue log_dst = dotfile.dst if log_dst.startswith(os.path.expanduser('~')): log_dst = log_dst.replace(os.path.expanduser('~'), '~', 1) # install dotfile to temporary dir and compare ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst, template=dotfile.template) if not ret: # failed to install to tmp line = '=> compare {}: error' LOG.log(line.format(dotfile.key, err)) LOG.err(err) same = False continue ignores = list(set(o.compare_ignore + dotfile.cmpignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) src_newer = os.path.getmtime(asrc) > os.path.getmtime(adst) if o.debug: LOG.dbg('src newer: {}'.format(src_newer)) diff = '' if o.compare_target == 'local' \ or (o.compare_target == 'smart' and not src_newer): diff = comp.compare(dotfile.dst, insttmp, ignore=ignores) diff_target = dotfile.dst else: diff = comp.compare(insttmp, dotfile.dst, ignore=ignores) diff_target = insttmp # clean tmp transformed dotfile if any if tmpsrc: tmpsrc = os.path.join(o.dotpath, tmpsrc) if os.path.exists(tmpsrc): remove(tmpsrc, LOG) if diff == '': # no difference if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.src_key, log_dst)) LOG.dbg('same file') else: # print diff results line = '=> compare {}: diffing with \"{}\"' if diff_target == dotfile.dst: line = line.format(log_dst, dotfile.src_key) else: line = line.format(dotfile.src_key, log_dst) LOG.log(line) if o.compare_target == 'smart': if src_newer: LOG.sub('{} is newer'.format(dotfile.src_key)) else: LOG.sub('{} is newer'.format(log_dst)) if not o.compare_fileonly: LOG.emph(diff) same = False return same
def main(): """entry point""" try: o = Options() except ValueError as e: LOG.err('Config format error: {}'.format(str(e))) return False ret = True try: if o.cmd_list: # list existing profiles if o.debug: LOG.dbg('running cmd: list') cmd_list_profiles(o) elif o.cmd_listfiles: # list files for selected profile if o.debug: LOG.dbg('running cmd: listfiles') cmd_list_files(o) elif o.cmd_install: # install the dotfiles stored in dotdrop if o.debug: LOG.dbg('running cmd: install') ret = cmd_install(o) elif o.cmd_compare: # compare local dotfiles with dotfiles stored in dotdrop if o.debug: LOG.dbg('running cmd: compare') tmp = get_tmpdir() ret = cmd_compare(o, tmp) # clean tmp directory remove(tmp) elif o.cmd_import: # import dotfile(s) if o.debug: LOG.dbg('running cmd: import') ret = cmd_importer(o) elif o.cmd_update: # update a dotfile if o.debug: LOG.dbg('running cmd: update') ret = cmd_update(o) elif o.cmd_detail: # detail files if o.debug: LOG.dbg('running cmd: update') cmd_detail(o) except KeyboardInterrupt: LOG.err('interrupted') ret = False return ret
def cmd_install(opts, conf, temporary=False, keys=[]): """install dotfiles for this profile""" dotfiles = conf.get_dotfiles(opts['profile']) if keys: # filtered dotfiles to install dotfiles = [d for d in dotfiles if d.key in set(keys)] if not dotfiles: msg = 'no dotfile to install for this profile (\"{}\")' LOG.warn(msg.format(opts['profile'])) return False t = Templategen(base=opts['dotpath'], variables=opts['variables'], debug=opts['debug']) tmpdir = None if 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']) installed = [] for dotfile in dotfiles: preactions = [] if not 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']: LOG.dbg('installing {}'.format(dotfile)) if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.PARENTS: r = inst.link(t, dotfile.src, dotfile.dst, actions=preactions) elif hasattr(dotfile, 'link') and dotfile.link == LinkTypes.CHILDREN: r = inst.linkall(t, dotfile.src, dotfile.dst, actions=preactions) else: src = dotfile.src tmp = None if dotfile.trans_r: tmp = apply_trans(opts, dotfile) 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) if os.path.exists(tmp): remove(tmp) if len(r) > 0: if not 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']: LOG.dry('would execute action: {}'.format(action)) else: if opts['debug']: LOG.dbg('executing post action {}'.format(action)) action.execute() installed.extend(r) if temporary: LOG.log('\nInstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\n{} dotfile(s) installed.'.format(len(installed))) return True
def cmd_install(o): """install dotfiles for this profile""" dotfiles = o.dotfiles if o.install_keys: # filtered dotfiles to install 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(o.profile)) return False t = Templategen(base=o.dotpath, variables=o.variables, debug=o.debug) tmpdir = None if o.install_temporary: tmpdir = get_tmpdir() inst = Installer(create=o.create, backup=o.backup, dry=o.dry, safe=o.safe, base=o.dotpath, workdir=o.workdir, diff=o.install_diff, debug=o.debug, totemp=tmpdir, showdiff=o.install_showdiff, backup_suffix=o.install_backup_suffix) installed = [] for dotfile in dotfiles: preactions = [] 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 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) elif hasattr(dotfile, 'link') and dotfile.link == LinkTypes.CHILDREN: r = inst.link_children(t, dotfile.src, dotfile.dst, actions=preactions) else: src = dotfile.src tmp = None if dotfile.trans_r: 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(o.dotpath, tmp) if os.path.exists(tmp): remove(tmp) if len(r) > 0: 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 o.dry: LOG.dry('would execute action: {}'.format(action)) else: if o.debug: LOG.dbg('executing post action {}'.format(action)) action.execute() installed.extend(r) 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(o, tmp): """compare dotfiles and return True if all identical""" dotfiles = o.dotfiles if not dotfiles: msg = 'no dotfile defined for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return True # compare only specific files same = True selected = dotfiles if o.compare_focus: selected = _select(o.compare_focus, dotfiles) if len(selected) < 1: return False t = Templategen(base=o.dotpath, variables=o.variables, func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) tvars = t.add_tmp_vars() inst = Installer(create=o.create, backup=o.backup, dry=o.dry, base=o.dotpath, workdir=o.workdir, debug=o.debug, backup_suffix=o.install_backup_suffix, diff_cmd=o.diff_command) comp = Comparator(diff_cmd=o.diff_command, debug=o.debug) for dotfile in selected: # add dotfile variables t.restore_vars(tvars) newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) if o.debug: LOG.dbg('comparing {}'.format(dotfile)) src = dotfile.src if not os.path.lexists(os.path.expanduser(dotfile.dst)): line = '=> compare {}: \"{}\" does not exist on destination' LOG.log(line.format(dotfile.key, dotfile.dst)) same = False continue tmpsrc = None if dotfile.trans_r: # apply transformation if o.debug: LOG.dbg('applying transformation before comparing') tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmpsrc: # could not apply trans same = False continue src = tmpsrc # is a symlink pointing to itself asrc = os.path.join(o.dotpath, os.path.expanduser(src)) adst = os.path.expanduser(dotfile.dst) if os.path.samefile(asrc, adst): if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('points to itself') continue # install dotfile to temporary dir ret, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst) if not ret: # failed to install to tmp same = False continue ignores = list(set(o.compare_ignore + dotfile.cmpignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) diff = comp.compare(insttmp, dotfile.dst, ignore=ignores) if tmpsrc: # clean tmp transformed dotfile if any tmpsrc = os.path.join(o.dotpath, tmpsrc) if os.path.exists(tmpsrc): remove(tmpsrc) if diff == '': if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('same file') else: line = '=> compare {}: diffing with \"{}\"' LOG.log(line.format(dotfile.key, dotfile.dst)) LOG.emph(diff) same = False return same
def cmd_compare(o, tmp): """compare dotfiles and return True if all identical""" dotfiles = o.dotfiles if dotfiles == []: msg = 'no dotfile defined for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return True # compare only specific files same = True selected = dotfiles if o.compare_focus: selected = _select(o.compare_focus, dotfiles) if len(selected) < 1: return False 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, backup_suffix=o.install_backup_suffix) comp = Comparator(diffopts=o.compare_dopts, debug=o.debug) for dotfile in selected: if o.debug: LOG.dbg('comparing {}'.format(dotfile)) src = dotfile.src if not os.path.lexists(os.path.expanduser(dotfile.dst)): line = '=> compare {}: \"{}\" does not exist on local' LOG.log(line.format(dotfile.key, dotfile.dst)) same = False continue tmpsrc = None if dotfile.trans_r: # apply transformation tmpsrc = apply_trans(o.dotpath, dotfile, debug=o.debug) if not tmpsrc: # could not apply trans same = False continue src = tmpsrc # install dotfile to temporary dir ret, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst) if not ret: # failed to install to tmp same = False continue 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(o.dotpath, tmpsrc) if os.path.exists(tmpsrc): remove(tmpsrc) if diff == '': if o.debug: line = '=> compare {}: diffing with \"{}\"' LOG.dbg(line.format(dotfile.key, dotfile.dst)) LOG.dbg('same file') else: line = '=> compare {}: diffing with \"{}\"' LOG.log(line.format(dotfile.key, dotfile.dst)) LOG.emph(diff) same = False return same
def cmd_importer(o): """import dotfile(s) from paths""" ret = True cnt = 0 paths = o.import_path for path in paths: if o.debug: LOG.dbg('trying to import {}'.format(path)) if not os.path.exists(path): LOG.err('\"{}\" does not exist, ignored!'.format(path)) ret = False continue dst = path.rstrip(os.sep) dst = os.path.abspath(dst) src = strip_home(dst) strip = '.' + os.sep if o.keepdot: strip = os.sep src = src.lstrip(strip) # create a new dotfile dotfile = Dotfile('', dst, src) linktype = LinkTypes(o.link) if o.debug: LOG.dbg('new dotfile: {}'.format(dotfile)) # prepare hierarchy for dotfile srcf = os.path.join(o.dotpath, src) if not os.path.exists(srcf): cmd = ['mkdir', '-p', '{}'.format(os.path.dirname(srcf))] if o.dry: LOG.dry('would run: {}'.format(' '.join(cmd))) else: 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 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=o.debug, checkerr=True) if not r: LOG.err('importing \"{}\" failed!'.format(path)) ret = False continue if linktype == LinkTypes.PARENTS: remove(dst) os.symlink(srcf, dst) 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 o.dry: LOG.dry('new config file would be:') LOG.raw(o.conf.dump()) else: o.conf.save() LOG.log('\n{} file(s) imported.'.format(cnt)) return ret
def cmd_remove(o): """remove dotfile from dotpath and from config""" paths = o.remove_path iskey = o.remove_iskey if not paths: LOG.log('no dotfile to remove') return False if o.debug: LOG.dbg('dotfile(s) to remove: {}'.format(','.join(paths))) removed = [] for key in paths: if not iskey: # by path dotfiles = o.conf.get_dotfile_by_dst(key) if not dotfiles: LOG.warn('{} ignored, does not exist'.format(key)) continue else: # by key dotfile = o.conf.get_dotfile(key) if not dotfile: LOG.warn('{} ignored, does not exist'.format(key)) continue dotfiles = [dotfile] for dotfile in dotfiles: k = dotfile.key # ignore if uses any type of link if dotfile.link != LinkTypes.NOLINK: LOG.warn('dotfile uses link, remove manually') continue if o.debug: LOG.dbg('removing {}'.format(key)) # make sure is part of the profile if dotfile.key not in [d.key for d in o.dotfiles]: msg = '{} ignored, not associated to this profile' LOG.warn(msg.format(key)) continue profiles = o.conf.get_profiles_by_dotfile_key(k) pkeys = ','.join([p.key for p in profiles]) if o.dry: LOG.dry('would remove {} from {}'.format(dotfile, pkeys)) continue msg = 'Remove \"{}\" from all these profiles: {}'.format(k, pkeys) if o.safe and not LOG.ask(msg): return False if o.debug: LOG.dbg('remove dotfile: {}'.format(dotfile)) for profile in profiles: if not o.conf.del_dotfile_from_profile(dotfile, profile): return False if not o.conf.del_dotfile(dotfile): return False # remove dotfile from dotpath dtpath = os.path.join(o.dotpath, dotfile.src) remove(dtpath) removed.append(dotfile.key) if o.dry: LOG.dry('new config file would be:') LOG.raw(o.conf.dump()) else: o.conf.save() if removed: LOG.log('\ndotfile(s) removed: {}'.format(','.join(removed))) else: LOG.log('\nno dotfile removed') return True
def _handle_dir(self, left, right): """sync left (deployed dir) and right (dotdrop dir)""" if self.debug: self.log.dbg('handle update for dir {} to {}'.format(left, right)) # find the difference diff = filecmp.dircmp(left, right, ignore=None) # handle directories diff # create dirs that don't exist in dotdrop if self.debug: self.log.dbg('handle dirs that do not exist in dotdrop') for toadd in diff.left_only: exist = os.path.join(left, toadd) if not os.path.isdir(exist): # ignore files for now continue # match to dotdrop dotpath new = os.path.join(right, toadd) if self.dry: self.log.dry('would mkdir -p {}'.format(new)) continue if self.debug: self.log.dbg('mkdir -p {}'.format(new)) self._create_dirs(new) # remove dirs that don't exist in deployed version if self.debug: self.log.dbg('remove dirs that do not exist in deployed version') for toremove in diff.right_only: new = os.path.join(right, toremove) if self.dry: self.log.dry('would rm -r {}'.format(new)) continue if self.debug: self.log.dbg('rm -r {}'.format(new)) if not self._confirm_rm_r(new): continue utils.remove(new) # handle files diff # sync files that exist in both but are different if self.debug: self.log.dbg('sync files that exist in both but are different') fdiff = diff.diff_files fdiff.extend(diff.funny_files) fdiff.extend(diff.common_funny) for f in fdiff: fleft = os.path.join(left, f) fright = os.path.join(right, f) if self.dry: self.log.dry('would cp {} {}'.format(fleft, fright)) continue if self.debug: self.log.dbg('cp {} {}'.format(fleft, fright)) self._handle_file(fleft, fright, compare=False) # copy files that don't exist in dotdrop if self.debug: self.log.dbg('copy files not existing in dotdrop') for toadd in diff.left_only: exist = os.path.join(left, toadd) if os.path.isdir(exist): # ignore dirs, done above continue new = os.path.join(right, toadd) if self.dry: self.log.dry('would cp {} {}'.format(exist, new)) continue if self.debug: self.log.dbg('cp {} {}'.format(exist, new)) shutil.copyfile(exist, new) # remove files that don't exist in deployed version if self.debug: self.log.dbg('remove files that do not exist in deployed version') for toremove in diff.right_only: new = os.path.join(right, toremove) if not os.path.exists(new): continue if os.path.isdir(new): # ignore dirs, done above continue if self.dry: self.log.dry('would rm {}'.format(new)) continue if self.debug: self.log.dbg('rm {}'.format(new)) utils.remove(new) return True
def main(): """entry point""" try: o = Options() except YamlException as e: LOG.err('config file error: {}'.format(str(e))) return False if o.debug: LOG.dbg('\n\n') # check dependencies are met try: dependencies_met() except Exception as e: LOG.err(e) return False ret = True try: if o.cmd_profiles: # list existing profiles if o.debug: LOG.dbg('running cmd: profiles') cmd_list_profiles(o) elif o.cmd_files: # list files for selected profile if o.debug: LOG.dbg('running cmd: files') cmd_list_files(o) elif o.cmd_install: # install the dotfiles stored in dotdrop if o.debug: LOG.dbg('running cmd: install') ret = cmd_install(o) elif o.cmd_compare: # compare local dotfiles with dotfiles stored in dotdrop if o.debug: LOG.dbg('running cmd: compare') tmp = get_tmpdir() ret = cmd_compare(o, tmp) # clean tmp directory remove(tmp) elif o.cmd_import: # import dotfile(s) if o.debug: LOG.dbg('running cmd: import') ret = cmd_importer(o) elif o.cmd_update: # update a dotfile if o.debug: LOG.dbg('running cmd: update') ret = cmd_update(o) elif o.cmd_detail: # detail files if o.debug: LOG.dbg('running cmd: detail') cmd_detail(o) elif o.cmd_remove: # remove dotfile if o.debug: LOG.dbg('running cmd: remove') cmd_remove(o) except KeyboardInterrupt: LOG.err('interrupted') ret = False if ret and o.conf.save(): LOG.log('config file updated') return ret
def cmd_install(o): """install dotfiles for this profile""" dotfiles = o.dotfiles prof = o.conf.get_profile() pro_pre_actions = prof.get_pre_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else [] if o.install_keys: # filtered dotfiles to install uniq = uniq_list(o.install_keys) dotfiles = [d for d in dotfiles if d.key in uniq] if not dotfiles: msg = 'no dotfile to install for this profile (\"{}\")' LOG.warn(msg.format(o.profile)) return False t = Templategen(base=o.dotpath, variables=o.variables, func_file=o.func_file, filter_file=o.filter_file, debug=o.debug) tmpdir = None if o.install_temporary: tmpdir = get_tmpdir() inst = Installer(create=o.create, backup=o.backup, dry=o.dry, safe=o.safe, base=o.dotpath, workdir=o.workdir, diff=o.install_diff, debug=o.debug, totemp=tmpdir, showdiff=o.install_showdiff, backup_suffix=o.install_backup_suffix, diff_cmd=o.diff_command) installed = 0 tvars = t.add_tmp_vars() # execute profile pre-action if o.debug: LOG.dbg('run {} profile pre actions'.format(len(pro_pre_actions))) ret, err = action_executor(o, pro_pre_actions, [], t, post=False)() if not ret: return False # install each dotfile for dotfile in dotfiles: # add dotfile variables t.restore_vars(tvars) newvars = dotfile.get_dotfile_variables() t.add_tmp_vars(newvars=newvars) preactions = [] if not o.install_temporary: preactions.extend(dotfile.get_pre_actions()) defactions = o.install_default_actions_pre pre_actions_exec = action_executor(o, preactions, defactions, t, post=False) if o.debug: LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key)) LOG.dbg(dotfile.prt()) if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: r, err = inst.link(t, dotfile.src, dotfile.dst, actionexec=pre_actions_exec) elif hasattr(dotfile, 'link') and \ dotfile.link == LinkTypes.LINK_CHILDREN: r, err = inst.link_children(t, dotfile.src, dotfile.dst, actionexec=pre_actions_exec) else: src = dotfile.src tmp = None if dotfile.trans_r: tmp = apply_trans(o.dotpath, dotfile, t, debug=o.debug) if not tmp: continue src = tmp ignores = list(set(o.install_ignore + dotfile.instignore)) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) r, err = inst.install(t, src, dotfile.dst, actionexec=pre_actions_exec, noempty=dotfile.noempty, ignore=ignores) if tmp: tmp = os.path.join(o.dotpath, tmp) if os.path.exists(tmp): remove(tmp) if r: # dotfile was installed if not o.install_temporary: defactions = o.install_default_actions_post postactions = dotfile.get_post_actions() post_actions_exec = action_executor(o, postactions, defactions, t, post=True) post_actions_exec() installed += 1 elif not r: # dotfile was NOT installed if o.install_force_action: # pre-actions if o.debug: LOG.dbg('force pre action execution ...') pre_actions_exec() # post-actions if o.debug: LOG.dbg('force post action execution ...') postactions = dotfile.get_post_actions() post_actions_exec = action_executor(o, postactions, defactions, t, post=True) post_actions_exec() if err: LOG.err('installing \"{}\" failed: {}'.format( dotfile.key, err)) # execute profile post-action if installed > 0 or o.install_force_action: if o.debug: msg = 'run {} profile post actions' LOG.dbg(msg.format(len(pro_post_actions))) ret, err = action_executor(o, pro_post_actions, [], t, post=False)() if not ret: return False if o.debug: LOG.dbg('install done') if o.install_temporary: LOG.log('\ninstalled to tmp \"{}\".'.format(tmpdir)) LOG.log('\n{} dotfile(s) installed.'.format(installed)) return True
def main(): """entry point""" ret = True args = docopt(USAGE, version=VERSION) try: conf = Cfg(os.path.expanduser(args['--cfg']), debug=args['--verbose']) 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() try: if args['list']: # list existing profiles if opts['debug']: LOG.dbg('running cmd: list') cmd_list_profiles(conf) elif args['listfiles']: # list files for selected profile if opts['debug']: LOG.dbg('running cmd: listfiles') cmd_list_files(opts, conf, templateonly=args['--template']) elif args['install']: # install the dotfiles stored in dotdrop if opts['debug']: LOG.dbg('running cmd: install') ret = cmd_install(opts, conf, temporary=args['--temp'], keys=args['<key>']) elif args['compare']: # compare local dotfiles with dotfiles stored in dotdrop if opts['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']) # clean tmp directory remove(tmp) elif args['import']: # import dotfile(s) if opts['debug']: LOG.dbg('running cmd: import') ret = cmd_importer(opts, conf, args['<path>']) elif args['update']: # update a dotfile if opts['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']) elif args['detail']: # detail files if opts['debug']: LOG.dbg('running cmd: update') cmd_detail(opts, conf, keys=args['<key>']) except KeyboardInterrupt: LOG.err('interrupted') ret = False if opts['debug']: LOG.dbg('configs:\n{}'.format(conf.dump())) return ret