aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--dev-requirements.txt4
-rw-r--r--dotfiles/cli.py2
-rw-r--r--dotfiles/dotfile.py51
-rw-r--r--dotfiles/pathutils.py21
-rw-r--r--dotfiles/repository.py15
-rw-r--r--setup.py54
-rw-r--r--tests/conftest.py4
-rw-r--r--tests/test_dotfile.py108
8 files changed, 158 insertions, 101 deletions
diff --git a/dev-requirements.txt b/dev-requirements.txt
new file mode 100644
index 0000000..95bea23
--- /dev/null
+++ b/dev-requirements.txt
@@ -0,0 +1,4 @@
+-e .
+pytest
+pytest-flake8
+pytest-runner
diff --git a/dotfiles/cli.py b/dotfiles/cli.py
index a99d098..9a8fd17 100644
--- a/dotfiles/cli.py
+++ b/dotfiles/cli.py
@@ -142,6 +142,8 @@ def status(repos, all, color):
state_info['missing']['color'] = 'yellow'
state_info['conflict']['color'] = 'magenta'
+ # XXX: could display tree [https://realpython.com/python-pathlib/]
+
for repo in repos:
if len(repos) > 1:
click.secho('%s:' % repo.path)
diff --git a/dotfiles/dotfile.py b/dotfiles/dotfile.py
index e539c68..3932d2a 100644
--- a/dotfiles/dotfile.py
+++ b/dotfiles/dotfile.py
@@ -1,4 +1,5 @@
from click import echo
+from hashlib import md5
from pathlib import Path
from .exceptions import \
@@ -33,7 +34,7 @@ class Dotfile(object):
if debug:
echo('MKDIR %s' % dir)
else:
- dir.mkdir()
+ dir.mkdir(parents=True)
ensure(self.name.parent, debug)
ensure(self.target.parent, debug)
@@ -42,20 +43,20 @@ class Dotfile(object):
"""Create a symlink from name to target, no error checking."""
source = self.name
target = self.target
- if self.name.islink():
+ if self.name.is_symlink():
source = self.target
target = self.name.realpath()
if debug:
echo('LINK %s -> %s' % (source, target))
else:
- source.mksymlinkto(target, absolute=0)
+ source.symlink_to(target)
def _unlink(self, debug):
"""Remove a symlink in the home directory, no error checking."""
if debug:
echo('UNLINK %s' % self.name)
else:
- self.name.remove()
+ self.name.unlink()
def short_name(self, homedir):
"""A shorter, more readable name given a home directory."""
@@ -64,24 +65,29 @@ class Dotfile(object):
def is_present(self):
"""Is this dotfile present in the repository?"""
- return self.name.islink() and (self.name.realpath() == self.target)
+ # return self.name.islink() and (self.name.realpath() == self.target)
+ return self.name.is_symlink() and (self.name.resolve() == self.target)
@property
def state(self):
"""The current state of this dotfile."""
- if self.target.check(exists=0):
+ if not self.target.exists():
# only for testing, cli should never reach this state
return 'error'
- elif self.name.check(exists=0):
+ # elif self.name.check(exists=0):
+ elif not self.name.exists():
# no $HOME file or symlink
return 'missing'
- elif self.name.islink():
+ # elif self.name.islink():
+ elif self.name.is_symlink():
# name exists, is a link, but isn't a link to the target
if not self.name.samefile(self.target):
return 'conflict'
else:
# name exists, is a file, but differs from the target
- if self.name.computehash() != self.target.computehash():
+ # if self.name.computehash() != self.target.computehash():
+ if md5(self.name.read_bytes()).hexdigest() != \
+ md5(self.target.read_bytes()).hexdigest():
return 'conflict'
return 'ok'
@@ -89,42 +95,49 @@ class Dotfile(object):
"""Move a dotfile to it's target and create a symlink."""
if self.is_present():
raise IsSymlink(self.name)
- if self.target.check(exists=1):
+ # if self.target.check(exists=1):
+ if self.target.exists():
raise TargetExists(self.name)
self._ensure_dirs(debug)
- if not self.name.islink():
+ if not self.name.is_symlink():
if debug:
echo('MOVE %s -> %s' % (self.name, self.target))
else:
- self.name.move(self.target)
+ # self.name.move(self.target)
+ self.name.replace(self.target)
self._link(debug)
def remove(self, debug=False):
"""Remove a symlink and move the target back to its name."""
- if self.name.check(link=0):
+ # if self.name.check(link=0):
+ if not self.name.is_symlink():
raise NotASymlink(self.name)
- if self.target.check(exists=0):
+ # if self.target.check(exists=0):
+ if not self.target.is_file():
raise TargetMissing(self.name)
self._unlink(debug)
if debug:
echo('MOVE %s -> %s' % (self.target, self.name))
else:
- self.target.move(self.name)
+ self.target.replace(self.name)
def link(self, debug=False):
"""Create a symlink from name to target."""
- if self.name.check(exists=1):
+ # if self.name.check(exists=1):
+ if self.name.exists():
raise Exists(self.name)
- if self.target.check(exists=0):
+ # if self.target.check(exists=0):
+ if not self.target.exists():
raise TargetMissing(self.name)
self._ensure_dirs(debug)
self._link(debug)
def unlink(self, debug=False):
"""Remove a symlink from name to target."""
- if self.name.check(link=0):
+ # if self.name.check(link=0):
+ if not self.name.is_symlink():
raise NotASymlink(self.name)
- if self.target.check(exists=0):
+ if not self.target.exists():
raise TargetMissing(self.name)
if not self.name.samefile(self.target):
raise RuntimeError
diff --git a/dotfiles/pathutils.py b/dotfiles/pathutils.py
new file mode 100644
index 0000000..0b051b1
--- /dev/null
+++ b/dotfiles/pathutils.py
@@ -0,0 +1,21 @@
+# TODO: docstrings
+
+
+def is_file(path):
+ return path.is_file() and not path.is_symlink()
+
+
+def is_link(path):
+ return path.is_file() and path.is_symlink()
+
+
+def mkdir(path):
+ try:
+ path.mkdir(parents=True)
+ except FileExistsError:
+ pass
+
+
+def touch(path):
+ mkdir(path.parent)
+ path.touch()
diff --git a/dotfiles/repository.py b/dotfiles/repository.py
index 9197c2a..b06d250 100644
--- a/dotfiles/repository.py
+++ b/dotfiles/repository.py
@@ -1,5 +1,12 @@
import os
+# import sys
+# if sys.version_info < (3, 4):
+# from pathlib2 import Path
+# #import pathlib2 as pathlib
+# else:
+# from pathlib import Path
+
from click import echo
from pathlib import Path
from fnmatch import fnmatch
@@ -65,7 +72,7 @@ class Repository(object):
def _ignore(self, path):
for pattern in self.ignore_patterns:
- if fnmatch(path, '*/%s' % pattern):
+ if fnmatch(str(path), '*/%s' % pattern):
return True
return False
@@ -96,9 +103,9 @@ class Repository(object):
target = self._dotfile_target(path)
- if not fnmatch(path, '%s/*' % self.homedir):
+ if not fnmatch(str(path), '%s/*' % self.homedir):
raise NotRootedInHome(path)
- if fnmatch(path, '%s/*' % self.path):
+ if fnmatch(str(path), '%s/*' % self.path):
raise InRepository(path)
if self._ignore(target):
raise TargetIgnored(path)
@@ -162,7 +169,7 @@ class Repository(object):
return self._ignore(path) or path == str(self.path)
dirs = reversed([dir for dir, subdirs, files in
- os.walk(self.path) if not skip(dir)])
+ os.walk(str(self.path)) if not skip(dir)])
for dir in dirs:
if not len(os.listdir(dir)):
diff --git a/setup.py b/setup.py
index 522aa39..5e68f4e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,42 +1,44 @@
-from setuptools import setup, find_packages
-from dotfiles import __version__
+import io
+import re
+from setuptools import setup
+
+with io.open("README.md", "rt", encoding="utf8") as f:
+ readme = f.read()
+
+with io.open("dotfiles/__init__.py", "rt", encoding="utf8") as f:
+ version = re.search(r"__version__ = \'(.*?)\'", f.read()).group(1)
setup(
name='dotfiles',
- version=__version__,
- description='Easily manage your dotfiles',
- long_description=open('README.md', encoding='utf-8').read(),
- long_description_content_type='text/markdown',
- author='Jon Bernard',
- author_email='jbernard@jbernard.io',
- license='ISC',
+ version=version,
url='https://github.com/jbernard/dotfiles',
- packages=find_packages(),
- tests_require=[
- 'pytest',
- 'pytest-flake8',
- ],
- setup_requires=['pytest-runner'],
- entry_points={
- 'console_scripts': [
- 'dotfiles=dotfiles.cli:cli',
- ],
+ project_urls={
+ 'Code': 'https://github.com/jbernard/dotfiles',
+ 'Issues': 'https://github.com/jbernard/dotfiles/issues',
},
- install_requires=['click'],
+ license='ISC',
+ author='Jon Bernard',
+ author_email='jbernard@jbernard.io',
+ description='Easily manage your dotfiles',
+ long_description=readme,
+ long_description_content_type='text/markdown',
+ packages=["dotfiles"],
+ python_requires=">=3.6",
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'License :: OSI Approved :: ISC License (ISCL)'
'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Utilities',
],
- project_urls={
- 'Bug Reports': 'https://github.com/jbernard/dotfiles/issues',
- 'Source': 'https://github.com/jbernard/dotfiles',
+ install_requires=['click'],
+ setup_requires=['pytest-runner'],
+ tests_require=['pytest', 'pytest-flake8'],
+ entry_points={
+ 'console_scripts': [
+ 'dotfiles=dotfiles.cli:cli',
+ ],
},
)
diff --git a/tests/conftest.py b/tests/conftest.py
index f5beb21..41f8995 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,8 +6,8 @@ from dotfiles.repository import Repository
@pytest.fixture(scope='function', params=['', 'home'])
def repo(request, tmpdir):
- path = tmpdir.ensure_dir('repo')
- home = tmpdir.ensure_dir(request.param)
+ path = str(tmpdir.ensure_dir('repo'))
+ home = str(tmpdir.ensure_dir(request.param))
return Repository(path, home)
diff --git a/tests/test_dotfile.py b/tests/test_dotfile.py
index 5672c53..d992bdb 100644
--- a/tests/test_dotfile.py
+++ b/tests/test_dotfile.py
@@ -2,26 +2,27 @@ import pytest
from pathlib import Path
from dotfiles.dotfile import Dotfile
-from dotfiles.exceptions import IsSymlink, NotASymlink, TargetExists, \
- TargetMissing, Exists
+from dotfiles.pathutils import is_file, is_link, touch, mkdir
+from dotfiles.exceptions import TargetExists, IsSymlink, \
+ TargetMissing, NotASymlink, Exists
def _make_dotfile(repo, name, target=None):
- return Dotfile(repo.homedir.join(name),
- repo.path.join(target if target is not None else name))
+ return Dotfile(repo.homedir.joinpath(name),
+ repo.path.joinpath(target if target else name))
@pytest.mark.parametrize('name', ['.a'])
def test_str(repo, name):
dotfile = _make_dotfile(repo, name, '.b')
- assert str(dotfile) == repo.homedir.join(name)
+ assert dotfile.name == repo.homedir / name
@pytest.mark.parametrize('name', ['.foo'])
def test_short_name(repo, name):
dotfile = _make_dotfile(repo, name)
- assert dotfile.name == repo.homedir.join(name)
- assert dotfile.short_name(repo.homedir) == name
+ assert dotfile.name == repo.homedir / name
+ assert dotfile.short_name(repo.homedir) == Path(name)
def test_is_present(repo):
@@ -30,77 +31,81 @@ def test_is_present(repo):
# TODO: more
-# {{{1 state
def test_state(repo):
dotfile = _make_dotfile(repo, '.vimrc', 'vimrc')
assert dotfile.state == 'error'
- dotfile.target.ensure()
- dotfile.name.mksymlinkto(dotfile.target)
+ dotfile.target.touch()
+ dotfile.name.symlink_to(dotfile.target)
assert dotfile.state == 'ok'
- dotfile.name.remove()
+ dotfile.name.unlink()
assert dotfile.state == 'missing'
- dotfile.name.ensure()
+ dotfile.name.touch()
assert dotfile.state == 'ok'
- dotfile.name.write('test content')
+ dotfile.name.write_text('test content')
assert dotfile.state == 'conflict'
- dotfile.target.write('test content')
+ dotfile.target.write_text('test content')
assert dotfile.state == 'ok'
-# {{{1 add
@pytest.mark.parametrize('path', ['.foo', '.foo/bar/baz'])
def test_add(repo, path):
dotfile = _make_dotfile(repo, path)
- dotfile.target.ensure()
- dotfile.name.ensure()
+
+ touch(dotfile.name)
+ touch(dotfile.target)
with pytest.raises(TargetExists):
dotfile.add()
+ dotfile.target.unlink()
- dotfile.target.remove()
dotfile.add()
-
- assert dotfile.target.check(file=1, link=0)
- assert dotfile.name.check(file=1, link=1)
+ assert is_link(dotfile.name)
+ assert is_file(dotfile.target)
assert dotfile.name.samefile(dotfile.target)
with pytest.raises(IsSymlink):
dotfile.add()
-
- assert dotfile.target.check(file=1, link=0)
- assert dotfile.name.check(file=1, link=1)
+ assert is_file(dotfile.target)
assert dotfile.name.samefile(dotfile.target)
-# {{{1 remove
@pytest.mark.parametrize('path', ['.foo', '.foo/bar/baz'])
def test_remove(repo, path):
dotfile = _make_dotfile(repo, path)
- py.path.local(dotfile.name.dirname).ensure_dir()
- dotfile.name.mksymlinkto(dotfile.target)
+
+ # py.path.local(dotfile.name.dirname).ensure_dir()
+ # dotfile.name.mksymlinkto(dotfile.target)
+ # dotfile.name.parent.mkdir(parents=True)
+ mkdir(dotfile.name.parent)
+ dotfile.name.symlink_to(dotfile.target)
with pytest.raises(TargetMissing):
dotfile.remove()
- dotfile.target.ensure()
+ # dotfile.target.ensure()
+ # dotfile.target.touch()
+ touch(dotfile.target)
dotfile.remove()
- assert dotfile.target.check(exists=0)
- assert dotfile.name.check(file=1, link=0)
+ # assert dotfile.target.check(exists=0)
+ # assert dotfile.name.check(file=1, link=0)
+ assert not dotfile.target.exists()
+ assert is_file(dotfile.name)
with pytest.raises(NotASymlink):
dotfile.remove()
- assert dotfile.target.check(exists=0)
- assert dotfile.name.check(file=1, link=0)
+ # assert dotfile.target.check(exists=0)
+ # assert dotfile.name.check(file=1, link=0)
+ assert not dotfile.target.exists()
+ assert is_file(dotfile.name)
-# {{{1 link
@pytest.mark.parametrize('path', ['.foo', '.foo/bar/baz'])
def test_link(repo, path):
dotfile = _make_dotfile(repo, path)
@@ -108,22 +113,21 @@ def test_link(repo, path):
with pytest.raises(TargetMissing):
dotfile.link()
- dotfile.target.ensure()
+ touch(dotfile.target)
dotfile.link()
- assert dotfile.target.check(file=1, link=0)
- assert dotfile.name.check(file=1, link=1)
+ assert is_file(dotfile.target)
+ assert is_link(dotfile.name)
assert dotfile.name.samefile(dotfile.target)
with pytest.raises(Exists):
dotfile.link()
- assert dotfile.target.check(file=1, link=0)
- assert dotfile.name.check(file=1, link=1)
+ assert is_file(dotfile.target)
+ assert is_link(dotfile.name)
assert dotfile.name.samefile(dotfile.target)
-# {{{1 unlink
@pytest.mark.parametrize('path', ['.foo', '.foo/bar/baz'])
def test_unlink(repo, path):
dotfile = _make_dotfile(repo, path)
@@ -131,26 +135,30 @@ def test_unlink(repo, path):
with pytest.raises(NotASymlink):
dotfile.unlink()
- py.path.local(dotfile.name.dirname).ensure_dir()
- dotfile.name.mksymlinkto(dotfile.target)
+ # py.path.local(dotfile.name.dirname).ensure_dir()
+ # dotfile.name.mksymlinkto(dotfile.target)
+ mkdir(dotfile.name.parent)
+ dotfile.name.symlink_to(dotfile.target)
with pytest.raises(TargetMissing):
dotfile.unlink()
- dotfile.target.ensure()
+ # dotfile.target.ensure()
+ touch(dotfile.target)
dotfile.unlink()
- assert dotfile.target.check(file=1, link=0)
- assert dotfile.name.check(exists=0)
+ assert is_file(dotfile.target)
+ assert not dotfile.name.exists()
with pytest.raises(NotASymlink):
dotfile.unlink()
- assert dotfile.target.check(file=1, link=0)
- assert dotfile.name.check(exists=0)
+ assert is_file(dotfile.target)
+ assert not dotfile.name.exists()
-# {{{1 copy
-# @pytest.mark.parametrize('path', ['.foo', '.foo/bar/baz'])
-# def test_unlink(repo, path):
-# dotfile = _make_dotfile(repo, path)
+@pytest.mark.parametrize('path', ['.foo', '.foo/bar/baz'])
+def test_copy(repo, path):
+ pass
+ # dotfile = _make_dotfile(repo, path)
+ # TODO