diff --git a/README.md b/README.md index a00f1c5..30b4bc6 100644 --- a/README.md +++ b/README.md @@ -177,11 +177,12 @@ the following entries: * `dotpath`: path to the folder containing the dotfiles to be managed by dotdrop (absolute path or relative to the config file location) * **dotfiles** entry: a list of dotfiles in the form - + * When `link` is true, dotdrop will create a link instead of copying. Template generation (as in [template](#template)) is not supported when `link` is true. ``` : dst: src: + link: # Optional ``` * **profiles** entry: a list of profiles with a sublist diff --git a/dotdrop/config.py b/dotdrop/config.py index 9757dad..597fdf8 100644 --- a/dotdrop/config.py +++ b/dotdrop/config.py @@ -62,8 +62,8 @@ class Cfg: for k, v in self.content[self.key_dotfiles].items(): src = v[self.key_dotfiles_src] dst = v[self.key_dotfiles_dst] - link = v[self.key_dotfiles_link] if self.key_dotfiles_link in v - else False + link = v[self.key_dotfiles_link] if self.key_dotfiles_link \ + in v else False self.dotfiles[k] = Dotfile(k, dst, src, link) # contains a list of dotfiles defined for each profile for k, v in self.profiles.items(): diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index dad1ca1..a6096d4 100755 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -66,7 +66,10 @@ def install(opts, conf): diff=opts['installdiff']) installed = [] for dotfile in dotfiles: - r = inst.install(t, opts['profile'], dotfile.src, dotfile.dst) + if hasattr(dotfile, "link") and dotfile.link: + r = inst.link(dotfile.src, dotfile.dst) + else: + r = inst.install(t, opts['profile'], dotfile.src, dotfile.dst) installed.extend(r) LOG.log('\n%u dotfile(s) installed.' % (len(installed))) return True diff --git a/dotdrop/installer.py b/dotdrop/installer.py index 658cb53..35502ec 100644 --- a/dotdrop/installer.py +++ b/dotdrop/installer.py @@ -32,6 +32,32 @@ class Installer: return self._handle_dir(templater, profile, src, dst) return self._handle_file(templater, profile, src, dst) + 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 "{}", link exists'.format(dst)) + return [] + if self.dry: + self.log.dry('would remove {} and link it to {}' + .format(dst, src)) + return [] + if self.safe and not self.log.ask('Remove "{}" for link creation?' + .format(dst)): + self.log.warn('ignoring "{}", link was not created' + .format(dst)) + return [] + utils.remove(dst) + if self.dry: + self.log.dry('would link {} to {}'.format(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 _handle_file(self, templater, profile, src, dst): '''Install a file using templater for "profile"''' content = templater.generate(src, profile) @@ -47,7 +73,7 @@ class Installer: self.log.sub('ignoring \"%s\", same content' % (dst)) return [] if ret == 0: - if not self.quiet: + if not self.quiet and not self.dry: self.log.sub('copied %s to %s' % (src, dst)) return [(src, dst)] return [] diff --git a/dotdrop/utils.py b/dotdrop/utils.py index e48e7bc..5d78efe 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -6,7 +6,10 @@ utilities import subprocess import tempfile +import os from logger import Logger +from shutil import rmtree + LOG = Logger() @@ -29,3 +32,15 @@ def diff(src, dst, log=False, raw=True): def get_tmpdir(): return tempfile.mkdtemp(prefix='dotdrop-') + + +def remove(path): + ''' Remove a file / directory / symlink ''' + if not os.path.exists(path): + raise Exception("ERROR in remove: File not found: {}".format(path)) + if os.path.islink(path) or os.path.isfile(path): + os.unlink(path) + elif os.path.isdir(path): + rmtree(path) + else: + raise Exception("Unsupported file type for deletion: {}".format(path))