aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Jon Bernard <jbernard@tuxion.com> 2016-01-01 08:54:14 -0500
committerGravatar Jon Bernard <jbernard@tuxion.com> 2016-01-01 08:55:33 -0500
commit3b7a6968d9be240c4ad4ec3d92bb99bc7fd888ef (patch)
tree02ca2d8958c2aa234d5c5a22344f1608f5c383b6
parent916aaeee70af4a08181edad00875e3cb21c5917d (diff)
downloaddotfiles-3b7a6968d9be240c4ad4ec3d92bb99bc7fd888ef.tar.gz
dotfiles-3b7a6968d9be240c4ad4ec3d92bb99bc7fd888ef.tar.bz2
dotfiles-3b7a6968d9be240c4ad4ec3d92bb99bc7fd888ef.zip
Implement dotfile state management
-rw-r--r--dotfiles/cli.py6
-rw-r--r--dotfiles/dotfile.py43
-rw-r--r--dotfiles/repository.py4
-rw-r--r--tests/test_dotfile.py56
-rw-r--r--tests/test_repository.py28
5 files changed, 110 insertions, 27 deletions
diff --git a/dotfiles/cli.py b/dotfiles/cli.py
index d5d49ee..5ca6771 100644
--- a/dotfiles/cli.py
+++ b/dotfiles/cli.py
@@ -29,8 +29,9 @@ def main(ctx, repository):
@main.command()
+@click.option('--color', is_flag=True, help='Enables colored output.')
@pass_repo
-def check(repo):
+def check(repo, color):
"""Shows any broken or unsyned dotfiles."""
list = repo.check()
if list:
@@ -39,8 +40,9 @@ def check(repo):
@main.command()
+@click.option('--color', is_flag=True, help='Enables colored output.')
@pass_repo
-def status(repo):
+def status(repo, color):
"""Shows the status of each dotfile."""
click.echo_via_pager(repo.status())
diff --git a/dotfiles/dotfile.py b/dotfiles/dotfile.py
index 0b15dbd..1a86f63 100644
--- a/dotfiles/dotfile.py
+++ b/dotfiles/dotfile.py
@@ -23,22 +23,25 @@ class Dotfile(object):
called, and not in the layers above.
"""
+ states = {
+ 'error': {'text': '(error)', 'color': 'red'},
+ 'missing': {'text': '(missing)', 'color': 'yellow'},
+ 'conflict': {'text': '(conflict)', 'color': 'yellow'},
+ 'ok': {'text': '(ok)', 'color': 'green'},
+ }
+
def __init__(self, name, target):
self.name = name
self.target = target
- self.state = '(unknown)'
+ self._set_state()
def __str__(self):
- short_name, short_target = self._truncate_paths()
- return '%s -> %s %s' % (short_name, short_target, self.state)
+ short_name, _ = self._truncate_paths()
+ return '%-18s %-s' % (short_name, self.state['text'])
def __repr__(self):
return '<Dotfile %r>' % self.name
- def _truncate_paths(self):
- discard = len(str(self.name.common(self.target))) + 1
- return (str(self.name)[discard:], str(self.target)[discard:])
-
def add(self):
if self.target.check(exists=1):
raise OSError(errno.EEXIST, self.target)
@@ -54,5 +57,27 @@ class Dotfile(object):
def sync(self):
self.name.mksymlinkto(self.target)
- def invalid(self):
- return self.state != '(ok)'
+ def is_ok(self):
+ return self.state == self.states['ok']
+
+ def _set_state(self):
+
+ # only for testing, cli should never reach this state
+ if self.target.check(exists=0):
+ self.state = self.states['error']
+
+ # no $HOME symlink
+ elif self.name.check(exists=0):
+ self.state = self.states['missing']
+
+ # if name exists but isn't a link to the target
+ elif self.name.check(link=0) or not self.name.samefile(self.target):
+ self.state = self.states['conflict']
+
+ # all good
+ else:
+ self.state = self.states['ok']
+
+ def _truncate_paths(self):
+ discard = len(str(self.name.common(self.target))) + 1
+ return (str(self.name)[discard:], str(self.target)[discard:])
diff --git a/dotfiles/repository.py b/dotfiles/repository.py
index 0c3a4f3..f394ed1 100644
--- a/dotfiles/repository.py
+++ b/dotfiles/repository.py
@@ -68,12 +68,14 @@ class Repository(object):
dotfiles.append(Dotfile(name, target))
return sorted(dotfiles, key=attrgetter('name'))
+ # TODO: pass dotfile objects to CLI instead of string
+
def _contents(self, all=True):
"""Convert loaded contents to human readable form."""
contents = ''
dotfiles = self._load()
for dotfile in dotfiles:
- if all or dotfile.invalid():
+ if all or not dotfile.is_ok():
contents += '\n%s' % dotfile
return contents.lstrip()
diff --git a/tests/test_dotfile.py b/tests/test_dotfile.py
index 7b99db1..29908ba 100644
--- a/tests/test_dotfile.py
+++ b/tests/test_dotfile.py
@@ -73,7 +73,53 @@ def test_sync(tmpdir, times):
assert name.samefile(target)
-def test_valid(tmpdir):
+def test_state_error(tmpdir):
+
+ repo = tmpdir.ensure("Dotfiles", dir=1)
+ name = tmpdir.join(".vimrc")
+ target = repo.join("vimrc")
+
+ dotfile = Dotfile(name, target)
+
+ assert Dotfile.states['error'] == dotfile.state
+
+
+def test_state_missing(tmpdir):
+
+ repo = tmpdir.ensure("Dotfiles", dir=1)
+ name = tmpdir.join(".vimrc")
+ target = repo.ensure("vimrc")
+
+ dotfile = Dotfile(name, target)
+
+ assert Dotfile.states['missing'] == dotfile.state
+
+
+def test_state_conflict(tmpdir):
+
+ repo = tmpdir.ensure("Dotfiles", dir=1)
+ name = tmpdir.ensure(".vimrc")
+ target = repo.ensure("vimrc")
+
+ dotfile = Dotfile(name, target)
+
+ assert Dotfile.states['conflict'] == dotfile.state
+
+
+def test_state_ok(tmpdir):
+
+ repo = tmpdir.join("Dotfiles", dir=1)
+ name = tmpdir.join(".vimrc")
+ target = repo.ensure("vimrc")
+
+ name.mksymlinkto(target)
+
+ dotfile = Dotfile(name, target)
+
+ assert Dotfile.states['ok'] == dotfile.state
+
+
+def test_is_ok(tmpdir):
repo = tmpdir.join("Dotfiles", dir=1)
name = tmpdir.join(".vimrc")
@@ -82,8 +128,8 @@ def test_valid(tmpdir):
dotfile = Dotfile(name, target)
- assert '(unknown)' == dotfile.state
- assert True == dotfile.invalid()
+ assert Dotfile.states['ok'] == dotfile.state
+ assert True == dotfile.is_ok()
- dotfile.state = '(ok)'
- assert False == dotfile.invalid()
+ dotfile.state = Dotfile.states['conflict']
+ assert False == dotfile.is_ok()
diff --git a/tests/test_repository.py b/tests/test_repository.py
index 8e6b48b..c8212e6 100644
--- a/tests/test_repository.py
+++ b/tests/test_repository.py
@@ -65,8 +65,8 @@ def test_empty_status(tmpdir):
def test_status_manual(tmpdir, monkeypatch):
repodir = tmpdir.join("Dotfiles", dir=1)
- name = tmpdir.join(".vimrc")
target = repodir.ensure("vimrc")
+ name = tmpdir.ensure(".vimrc")
dotfile = Dotfile(name, target)
@@ -74,9 +74,10 @@ def test_status_manual(tmpdir, monkeypatch):
monkeypatch.setattr(Repository, "_load",
lambda self: [dotfile, dotfile, dotfile])
- expected_status = (".vimrc -> Dotfiles/vimrc (unknown)\n"
- ".vimrc -> Dotfiles/vimrc (unknown)\n"
- ".vimrc -> Dotfiles/vimrc (unknown)")
+ dotfile_state = Dotfile.states['conflict']['text']
+ expected_status = ("{0:<18} {1}\n"
+ "{0:<18} {1}\n"
+ "{0:<18} {1}".format(name.basename, dotfile_state))
assert expected_status == repo.status()
@@ -91,9 +92,12 @@ def test_status_discover(tmpdir):
repo = Repository(repodir, tmpdir)
- expected_status = (".bashrc -> Dotfiles/bashrc (unknown)\n"
- ".inputrc -> Dotfiles/inputrc (unknown)\n"
- ".vimrc -> Dotfiles/vimrc (unknown)")
+ expected_status = ("{1:<18} {0}\n"
+ "{2:<18} {0}\n"
+ "{3:<18} {0}".format(Dotfile.states['ok']['text'],
+ '.bashrc',
+ '.inputrc',
+ '.vimrc'))
assert expected_status == repo.status()
@@ -106,14 +110,18 @@ def test_check(tmpdir, monkeypatch):
dotfile_b = Dotfile(tmpdir.join('.bbb'), repodir.join('bbb'))
dotfile_c = Dotfile(tmpdir.join('.ccc'), repodir.join('ccc'))
- dotfile_b.state = '(ok)'
+ dotfile_b.state = Dotfile.states['ok']
repo = Repository(tmpdir)
monkeypatch.setattr(Repository, "_load",
lambda self: [dotfile_a, dotfile_b, dotfile_c])
- assert ('.aaa -> repo/aaa (unknown)\n'
- '.ccc -> repo/ccc (unknown)') == repo.check()
+ expected_status = ("{1:<18} {0}\n"
+ "{2:<18} {0}".format(Dotfile.states['error']['text'],
+ dotfile_a.name.basename,
+ dotfile_c.name.basename))
+
+ assert expected_status == repo.check()
def test_sync():