Beispiel #1
0
 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)]
Beispiel #2
0
    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
Beispiel #3
0
 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)]
Beispiel #4
0
 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)]
Beispiel #5
0
    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
Beispiel #6
0
 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)]
Beispiel #7
0
    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
Beispiel #8
0
 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)
Beispiel #9
0
 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
Beispiel #10
0
    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)
Beispiel #11
0
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
Beispiel #12
0
 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
Beispiel #13
0
    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
Beispiel #14
0
 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
Beispiel #15
0
    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
Beispiel #16
0
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
Beispiel #17
0
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
Beispiel #18
0
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
Beispiel #19
0
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
Beispiel #20
0
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
Beispiel #21
0
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
Beispiel #22
0
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
Beispiel #23
0
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
Beispiel #24
0
    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
Beispiel #25
0
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
Beispiel #26
0
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
Beispiel #27
0
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