aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Jon Bernard <jbernard@tuxion.com> 2016-01-21 22:49:19 -0500
committerGravatar Jon Bernard <jbernard@tuxion.com> 2016-01-21 22:49:19 -0500
commite6c935b715ce1fa385e33aa06375796e5e7980db (patch)
treea249fe237f0ce7f6fad42ecb20fe566968c1c4cf
parentee75886843538f510f9e39fedae6a6f952193e83 (diff)
downloaddotfiles-e6c935b715ce1fa385e33aa06375796e5e7980db.tar.gz
dotfiles-e6c935b715ce1fa385e33aa06375796e5e7980db.tar.bz2
dotfiles-e6c935b715ce1fa385e33aa06375796e5e7980db.zip
Allow globs in ignore patterns
-rw-r--r--dotfiles/cli.py21
-rw-r--r--dotfiles/repository.py110
-rw-r--r--tests/test_dotfile.py2
-rw-r--r--tests/test_repository.py8
4 files changed, 76 insertions, 65 deletions
diff --git a/dotfiles/cli.py b/dotfiles/cli.py
index fc6e0d3..13a1bad 100644
--- a/dotfiles/cli.py
+++ b/dotfiles/cli.py
@@ -5,9 +5,10 @@ from .repository import Repository
from .exceptions import DotfileException
-DEFAULT_REPODIR = os.path.expanduser('~/Dotfiles')
-DEFAULT_IGNORE = ['.git', '.hg']
-DEFAULT_DOT = True
+# Defaults
+REPOSITORY_PATH = os.path.expanduser('~/Dotfiles')
+IGNORE_PATTERNS = ['.git', '.hg', '*~']
+PRESERVE_LEADING_DOT = False
def confirm(method, files, repo):
@@ -17,7 +18,7 @@ def confirm(method, files, repo):
# user has specified specific files, so we are not assuming all
return files
- # no files provided, so we assume all files after confimration
+ # no files provided, so we assume all files after confirmation
message = 'Are you sure you want to %s all dotfiles?' % method
click.confirm(message, abort=True)
return str(repo).split()
@@ -40,9 +41,9 @@ pass_repo = click.make_pass_decorator(Repository)
@click.group(context_settings=dict(help_option_names=['-h', '--help']))
@click.option('-r', '--repo', type=click.Path(), show_default=True,
- default=DEFAULT_REPODIR, envvar='DOTFILES_REPO')
-@click.option('--dot/--no-dot', default=True, show_default=DEFAULT_DOT,
- envvar='DOTFILES_DOT')
+ default=REPOSITORY_PATH, envvar='DOTFILES_REPO')
+@click.option('--dot/--dot', show_default=True,
+ default=PRESERVE_LEADING_DOT, envvar='DOTFILES_DOT')
@click.version_option()
@click.pass_context
def cli(ctx, repo, dot):
@@ -52,11 +53,13 @@ def cli(ctx, repo, dot):
The following environment variables are recognized at runtime:
\b
- DOTFILES_DOT: Set this to 'False' to remove the leading dot.
DOTFILES_REPO: Set this to the location of your repository.
DOTFILES_COLOR: Set this to 'True' to enable color output.
+ DOTFILES_DOT: Set this to 'True' to preserve the leading dot.
"""
- ctx.obj = Repository(repodir=repo, ignore=DEFAULT_IGNORE, dot=dot)
+ ctx.obj = Repository(path=repo,
+ ignore_patterns=IGNORE_PATTERNS,
+ preserve_leading_dot=dot)
@cli.command()
diff --git a/dotfiles/repository.py b/dotfiles/repository.py
index 86b975c..b36d77d 100644
--- a/dotfiles/repository.py
+++ b/dotfiles/repository.py
@@ -1,5 +1,5 @@
-import py
-from click import echo
+import click
+import py.path
from operator import attrgetter
from .dotfile import Dotfile
@@ -10,67 +10,84 @@ from .exceptions import NotRootedInHome, InRepository, IsDirectory
class Repository(object):
"""A repository is a directory that contains dotfiles.
- :param repodir: the location of the repository directory
- :param homedir: the location of the home directory (primarily for testing)
- :param ignore: a list of targets to ignore
- :param dot: wether to preserve the target's leading dot
+ :param path: the location of the repository directory
+ :param ignore_patterns: a list of glob patterns to ignore
+ :param preserve_leading_dot: whether to preserve the target's leading dot
+ :param home_directory: the location of the home directory
"""
- homedir = py.path.local('~/', expanduser=True)
+ home_directory = py.path.local('~/', expanduser=True)
- def __init__(self, repodir, homedir=homedir, ignore=[], dot=True):
- self.repodir = py.path.local(repodir).ensure_dir()
- self.homedir = py.path.local(homedir)
- self.ignore = ignore
- self.dot = dot
+ def __init__(self, path,
+ ignore_patterns=[],
+ preserve_leading_dot=False,
+ home_directory=home_directory):
+
+ # create repository directory if not found
+ self.path = py.path.local(path).ensure_dir()
+
+ self.home_directory = home_directory
+ self.ignore_patterns = ignore_patterns
+ self.preserve_leading_dot = preserve_leading_dot
def __str__(self):
"""Return human-readable repository contents."""
return ''.join('%s\n' % item for item in self.contents()).rstrip()
def __repr__(self):
- return '<Repository %r>' % self.repodir
+ return '<Repository %r>' % self.path
- def _target_to_name(self, target):
+ def _ignore(self, path):
+ for pattern in self.ignore_patterns:
+ if path.fnmatch(pattern):
+ return True
+ return False
+
+ def _dotfile_path(self, target):
"""Return the expected symlink for the given repository target."""
- relpath = self.repodir.bestrelpath(target)
- if self.dot:
- return self.homedir.join(relpath)
+ relpath = self.path.bestrelpath(target)
+ if self.preserve_leading_dot:
+ return self.home_directory.join(relpath)
else:
- return self.homedir.join('.%s' % relpath)
+ return self.home_directory.join('.%s' % relpath)
- def _name_to_target(self, name):
+ def _dotfile_target(self, path):
"""Return the expected repository target for the given symlink."""
- relpath = self.homedir.bestrelpath(name)
- if self.dot:
- return self.repodir.join(relpath)
+ relpath = self.home_directory.bestrelpath(path)
+ if self.preserve_leading_dot:
+ return self.path.join(relpath)
else:
- return self.repodir.join(relpath[1:])
+ return self.path.join(relpath[1:])
- def _dotfile(self, name):
+ def _dotfile(self, path):
"""Return a valid dotfile for the given path."""
- target = self._name_to_target(name)
+ target = self._dotfile_target(path)
- if not name.fnmatch('%s/*' % self.homedir):
- raise NotRootedInHome(name)
- if name.fnmatch('%s/*' % self.repodir):
- raise InRepository(name)
- if target.basename in self.ignore:
- raise TargetIgnored(name)
- if name.check(dir=1):
- raise IsDirectory(name)
+ if not path.fnmatch('%s/*' % self.home_directory):
+ raise NotRootedInHome(path)
+ if path.fnmatch('%s/*' % self.path):
+ raise InRepository(path)
+ if self._ignore(target):
+ raise TargetIgnored(path)
+ if path.check(dir=1):
+ raise IsDirectory(path)
- return Dotfile(name, target)
+ return Dotfile(path, target)
def _contents(self, dir):
"""Return all unignored files contained below a directory."""
def filter(node):
- return node.check(dir=0) and node.basename not in self.ignore
+ return node.check(dir=0) and not self._ignore(node)
+
+ return dir.visit(filter, lambda x: not self._ignore(x))
- def recurse(node):
- return node.basename not in self.ignore
+ def contents(self):
+ """Return a list of dotfiles for each file in the repository."""
+ def construct(target):
+ return Dotfile(self._dotfile_path(target), target)
- return dir.visit(filter, recurse)
+ contents = self._contents(self.path)
+ return sorted(map(construct, contents), key=attrgetter('path'))
def dotfiles(self, paths):
"""Return a collection of dotfiles given a list of paths.
@@ -92,19 +109,11 @@ class Repository(object):
try:
return self._dotfile(path)
except DotfileException as err:
- echo(err)
+ click.echo(err)
return None
return [d for d in map(construct, paths) if d is not None]
- def contents(self):
- """Return a list of dotfiles for each file in the repository."""
- def construct(target):
- return Dotfile(self._target_to_name(target), target)
-
- contents = self._contents(self.repodir)
- return sorted(map(construct, contents), key=attrgetter('name'))
-
def prune(self):
"""Remove any empty directories in the repository.
@@ -113,11 +122,8 @@ class Repository(object):
so pruning must take place explicitly after such operations occur.
"""
def filter(node):
- return node.check(dir=1) and node.basename not in self.ignore
-
- def recurse(node):
- return node.basename not in self.ignore
+ return node.check(dir=1) and not self._ignore(node)
- for dir in self.repodir.visit(filter, recurse):
+ for dir in self.path.visit(filter, lambda x: not self._ignore(x)):
if not len(dir.listdir()):
dir.remove()
diff --git a/tests/test_dotfile.py b/tests/test_dotfile.py
index a913743..ff19d9d 100644
--- a/tests/test_dotfile.py
+++ b/tests/test_dotfile.py
@@ -1,5 +1,5 @@
-import py
import pytest
+import py.path
from dotfiles.dotfile import Dotfile
from dotfiles.exceptions import IsSymlink, NotASymlink
diff --git a/tests/test_repository.py b/tests/test_repository.py
index 4cf694f..05328e6 100644
--- a/tests/test_repository.py
+++ b/tests/test_repository.py
@@ -1,5 +1,5 @@
-import py
import pytest
+import py.path
from dotfiles.repository import Repository
from dotfiles.exceptions import NotRootedInHome, InRepository, TargetIgnored, \
@@ -48,9 +48,11 @@ def test_dotfile(repo, home):
with pytest.raises(NotRootedInHome):
Repository(repo, home)._dotfile(py.path.local('/tmp/foo'))
with pytest.raises(TargetIgnored):
- Repository(repo, home, ignore=['.foo'])._dotfile(home.join('.foo'))
+ Repository(repo, home,
+ ignore_patterns=['.foo'])._dotfile(home.join('.foo'))
with pytest.raises(TargetIgnored):
- Repository(repo, home, ignore=['foo'])._dotfile(home.join('.bar/foo'))
+ Repository(repo, home,
+ ignore_patterns=['foo'])._dotfile(home.join('.bar/foo'))
with pytest.raises(IsDirectory):
Repository(repo, home)._dotfile(home.ensure_dir('.config'))