From 13c925be87ddf946773689ec51fd0251f795a9c4 Mon Sep 17 00:00:00 2001 From: Anish Athalye Date: Wed, 22 Oct 2014 13:27:34 -0400 Subject: [PATCH] Add functionality to forcibly link items This commit introduces an extended configuration syntax for linking files and directories. Currently, this syntax allows for specifying items to be forcibly linked, overwriting existing files or directories if necessary. The extended configuration syntax was proposed by Travers McInerney . --- README.md | 14 +++++++++++++- dotbot/executor/linker.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 486fbd9..5c79337 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ have a defined ordering. ### Link Link commands specify how files and directories should be symbolically linked. +If desired, items can be specified to be forcibly linked, overwriting existing +files if necessary. #### Format @@ -99,13 +101,23 @@ locations. Source locations are specified relative to the base directory (that is specified when running the installer). Source directory names should contain a trailing "/" character. +Link commands support an (optional) extended configuration. In this type of +configuration, instead of specifying source locations directly, targets are +mapped to extended configuration dictionaries. These dictionaries map "path" to +the source path, and specify "force" as true if the file or directory should be +forcibly linked. + ##### Example ```json { "link": { "~/.vimrc": "vimrc", - "~/.vim": "vim/" + "~/.vim": "vim/", + "~/.zshrc": { + "path": "zshrc", + "force": true + } } } ``` diff --git a/dotbot/executor/linker.py b/dotbot/executor/linker.py index 60c76b5..2d14ecc 100644 --- a/dotbot/executor/linker.py +++ b/dotbot/executor/linker.py @@ -1,4 +1,4 @@ -import os +import os, shutil from . import Executor class Linker(Executor): @@ -19,7 +19,15 @@ class Linker(Executor): def _process_links(self, links): success = True for destination, source in links.items(): - success &= self._link(source, destination) + if isinstance(source, dict): + # extended config + path = source['path'] + force = source.get('force', False) + if force: + success &= self._delete(destination) + else: + path = source + success &= self._link(path, destination) if success: self._log.info('All links have been set up') else: @@ -47,6 +55,22 @@ class Linker(Executor): path = os.path.expanduser(path) return os.path.exists(path) + def _delete(self, path): + success = True + if self._exists(path) and not self._is_link(path): + fullpath = os.path.expanduser(path) + try: + if os.path.isdir(fullpath): + shutil.rmtree(fullpath) + else: + os.remove(fullpath) + except OSError: + self._log.warning('Failed to remove %s' % path) + success = False + else: + self._log.lowinfo('Removing %s' % path) + return success + def _link(self, source, link_name): ''' Links link_name to source.