summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.rst69
-rwxr-xr-xbin/dotfiles2
-rw-r--r--dotfiles/__init__.py5
-rw-r--r--dotfiles/cli.py97
-rw-r--r--dotfiles/core.py41
-rw-r--r--setup.py14
6 files changed, 184 insertions, 44 deletions
diff --git a/README.rst b/README.rst
index f8b9f9d..132ad2b 100644
--- a/README.rst
+++ b/README.rst
@@ -2,14 +2,33 @@ Dotfile management made easy
============================
``dotfiles`` is a tool to make managing your dotfile symlinks in ``$HOME``
-easy, allowing you to keep all your dotfiles in a single directory.
+easy, allowing you to keep all your dotfiles in a single directory.
Hosting is left to you. Yes, I've seen `<http://dotfiles.org>`_ but I don't
-like that model. If you're advanced enough to need dotfile management, then you
-probably already know how you want to host them. Using whatever VCS you
+like that model. If you're advanced enough to need dotfile management, then
+you probably already know how you want to host them. Using whatever VCS you
prefer, or even rsync, you can easily distribute your dotfiles repository
across multiple hosts.
+Interface
+---------
+
+``-a, --add <file...>``
+ Add dotfile(s) to the repository.
+
+``-c, --check``
+ Check for missing or unmanged dotfiles.
+
+``-l, --list``
+ List currently managed dotfiles, one per line.
+
+``-r, --remove <file...>``
+ Remove dotfile(s) from the repository.
+
+``-s, --sync``
+ Update dotfile symlinks. You can overwrite unmanaged files with ``-f`` or
+ ``--force``.
+
Installation
------------
@@ -49,7 +68,7 @@ To make it available to all your hosts: ::
$ git commit -m "Added vimrc, welcome aboard!"
$ git push
-You get the idea.
+You get the idea. Type ``dotfiles --help`` to see the available options.
Configuration
-------------
@@ -67,6 +86,48 @@ example configuration file might look like: ::
'.bzr.log': '/dev/null',
'.uml': '/tmp'}
+Prefixes
+--------
+
+Dotfiles are stored in the repository with no prefix by default. So,
+``~/.bashrc`` will link to ``~/Dotfiles/bashrc``. If your files already have a
+prefix, ``.`` is common, but I've also seen ``_``, then you can specify this
+in the configuration file and ``dotfiles`` will do the right thing. An example
+configuration in ``~/.dotfilesrc`` might look like: ::
+
+ [dotfiles]
+ prefix = .
+
+Externals
+---------
+
+You may want to link some dotfiles to external locations. For example, ``bzr``
+writes debug information to ``~/.bzr.log`` and there is no easy way to disable
+it. For that, I link ``~/.bzr.log`` to ``/dev/null``. Since ``/dev/null`` is
+not within the repository, this is called an external. You can have as many of
+these as you like. The list of externals is specified in the configuration
+file: ::
+
+ [dotfiles]
+ externals = {
+ '.bzr.log': '/dev/null',
+ '.adobe': '/tmp',
+ '.macromedia': '/tmp'}
+
+Ignores
+-------
+
+If you're using a VCS to manage your repository of dotfiles, you'll want to
+tell ``dotfiles`` to ignore VCS-related files. For example, I use ``git``, so
+I have the following in my ``~/.dotfilesrc``: ::
+
+ [dotfiles]
+ ignore = [
+ '.git',
+ '.gitignore']
+
+Any file you list in ``ignore`` will be skipped.
+
License
-------
diff --git a/bin/dotfiles b/bin/dotfiles
index 1cd5264..5f4e1c9 100755
--- a/bin/dotfiles
+++ b/bin/dotfiles
@@ -13,7 +13,7 @@ if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dotfiles'))
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY)
-import dotfiles
+import dotfiles.cli
if __name__ == '__main__':
dotfiles.cli.main()
diff --git a/dotfiles/__init__.py b/dotfiles/__init__.py
index 0390200..fcb882b 100644
--- a/dotfiles/__init__.py
+++ b/dotfiles/__init__.py
@@ -1,8 +1,3 @@
# -*- coding: utf-8 -*-
-import cli
from core import *
-
-__version__ = '0.2.0'
-
-__all__ = ['cli', 'core']
diff --git a/dotfiles/cli.py b/dotfiles/cli.py
index fadca25..6618719 100644
--- a/dotfiles/cli.py
+++ b/dotfiles/cli.py
@@ -1,31 +1,67 @@
# -*- coding: utf-8 -*-
+"""
+dotfiles.cli
+
+This module provides the CLI interface to dotfiles.
+"""
+
import os
from . import core
import ConfigParser
from optparse import OptionParser, OptionGroup
+USAGE = "Usage: %prog ACTION [OPTION...] [FILE...]"
+
+NO_REPO_MESSAGE = """Could not find dotfiles repository \"%s\"
+
+If this is your first time running dotfiles, you must first create a
+repository. By default, dotfiles will look for '~/Dotfiles'. Something like:
+
+ $ mkdir ~/Dotfiles
+
+is all you need to do. If you don't like the default, you can put your
+repository wherever you like. You have two choices once you've created your
+repository. You can specify the path to the repository on the command line
+using the '-R' flag. Alternatively, you can create a configuration file at
+'~/.dotfilesrc' and place the path to your repository in there. The contents
+would look like:
+
+ [dotfiles]
+ repository = ~/.my-dotfiles-repo
+
+You can see more information by typing 'dotfiles -h'"""
+
+
def method_list(object):
return [method for method in dir(object)
if callable(getattr(object, method))]
def parse_args():
- parser = OptionParser(usage="Usage: %prog ACTION [OPTION...] [FILE...]")
+ parser = OptionParser(usage=USAGE)
parser.set_defaults(config=os.path.expanduser("~/.dotfilesrc"))
- parser.set_defaults(repo=os.path.expanduser("~/Dotfiles"))
- parser.set_defaults(prefix='')
parser.set_defaults(ignore=[])
parser.set_defaults(externals={})
- parser.add_option("-C", "--config", type="string", dest="config",
- help="set configuration file location (default is ~/.dotfilesrc)")
+ parser.add_option("-v", "--version", action="store_true",
+ dest="show_version", default=False,
+ help="show version number and exit")
+
+ parser.add_option("-f", "--force", action="store_true", dest="force",
+ default=False, help="ignore unmanaged dotfiles (use with --sync)")
parser.add_option("-R", "--repo", type="string", dest="repo",
help="set repository location (default is ~/Dotfiles)")
+ parser.add_option("-p", "--prefix", type="string", dest="prefix",
+ help="set prefix character (default is None)")
+
+ parser.add_option("-C", "--config", type="string", dest="config",
+ help="set configuration file location (default is ~/.dotfilesrc)")
+
action_group = OptionGroup(parser, "Actions")
action_group.add_option("-a", "--add", action="store_const", dest="action",
@@ -49,14 +85,9 @@ def parse_args():
(opts, args) = parser.parse_args()
- if not os.path.exists(opts.repo):
- parser.error("Could not find dotfiles repository \"%s\"" % opts.repo)
-
- if not opts.action:
- parser.error("An action is required.")
-
- if opts.action not in method_list(core.Dotfiles):
- parser.error("No such action \"%s\"" % opts.action)
+ # Skip checking if the repository exists here. The user may have specified
+ # a command line argument or a configuration file, which will be examined
+ # next.
return (opts, args)
@@ -65,6 +96,10 @@ def main():
(opts, args) = parse_args()
+ if opts.show_version:
+ print 'dotfiles v%s' % core.__version__
+ exit(0)
+
config_defaults = {
'repository': opts.repo,
'prefix': opts.prefix,
@@ -74,16 +109,40 @@ def main():
parser = ConfigParser.SafeConfigParser(config_defaults)
if opts.config:
-
parser.read(opts.config)
if 'dotfiles' in parser.sections():
- opts.repo = os.path.expanduser(parser.get('dotfiles', 'repository'))
- opts.prefix = parser.get('dotfiles', 'prefix')
- opts.ignore = eval(parser.get('dotfiles', 'ignore'))
- opts.externals = eval(parser.get('dotfiles', 'externals'))
+
+ if not opts.repo:
+ if parser.get('dotfiles', 'repository'):
+ opts.repo = os.path.expanduser(parser.get('dotfiles', 'repository'))
+ else:
+ opts.repo = os.path.expanduser("~/Dotfiles")
+
+ if not opts.prefix:
+ if parser.get('dotfiles', 'prefix'):
+ opts.prefix = parser.get('dotfiles', 'prefix')
+ else:
+ opts.prefix = ''
+
+ if not opts.ignore and parser.get('dotfiles', 'ignore'):
+ opts.ignore = eval(parser.get('dotfiles', 'ignore'))
+
+ if not opts.externals and parser.get('dotfiles', 'externals'):
+ opts.externals = eval(parser.get('dotfiles', 'externals'))
+
+ if not os.path.exists(opts.repo):
+ print "%s\n" % USAGE
+ print NO_REPO_MESSAGE % opts.repo
+ exit(-1)
+
+ if not opts.action:
+ print "%s\n" % USAGE
+ print "Error: An action is required."
+ exit(-1)
getattr(core.Dotfiles(location=opts.repo,
prefix=opts.prefix,
ignore=opts.ignore,
- externals=opts.externals), opts.action)(files=args)
+ externals=opts.externals,
+ force=opts.force), opts.action)(files=args)
diff --git a/dotfiles/core.py b/dotfiles/core.py
index b04892f..c2e368a 100644
--- a/dotfiles/core.py
+++ b/dotfiles/core.py
@@ -1,9 +1,21 @@
# -*- coding: utf-8 -*-
+"""
+dotfiles.core
+~~~~~~~~~~~~~
+
+This module provides the basic functionality of dotfiles.
+"""
+
import os
import shutil
+__version__ = '0.3.0'
+__author__ = "Jon Bernard"
+__license__ = "GPL"
+
+
class Dotfile(object):
def __init__(self, name, target):
@@ -17,11 +29,17 @@ class Dotfile(object):
if not os.path.lexists(self.name):
self.status = 'missing'
elif os.path.realpath(self.name) != self.target:
- self.status = 'unmanged'
+ self.status = 'unmanaged'
- def sync(self):
+ def sync(self, force):
if self.status == 'missing':
os.symlink(self.target, self.name)
+ elif self.status == 'unmanaged':
+ if not force:
+ print "Skipping \"%s\", use --force to override" % self.basename
+ return
+ os.remove(self.name)
+ os.symlink(self.target, self.name)
def add(self):
if self.status == 'missing':
@@ -30,7 +48,6 @@ class Dotfile(object):
if self.status == '':
print "Skipping \"%s\", already managed" % self.basename
return
- print "Adding \"%s\"" % self.basename
shutil.move(self.name, self.target)
os.symlink(self.target, self.name)
@@ -47,20 +64,20 @@ class Dotfile(object):
class Dotfiles(object):
- def __init__(self, location, prefix, ignore, externals):
+ def __init__(self, location, prefix, ignore, externals, force):
self.location = location
+ self.prefix = prefix
+ self.force = force
self.dotfiles = []
- contents = [x for x in os.listdir(self.location)
- if x not in ignore]
+ contents = [x for x in os.listdir(self.location) if x not in ignore]
for file in contents:
- self.dotfiles.append(Dotfile(file,
+ self.dotfiles.append(Dotfile(file[len(prefix):],
os.path.join(self.location, file)))
for file in externals.keys():
self.dotfiles.append(Dotfile(file, externals[file]))
def list(self, **kwargs):
- for dotfile in sorted(self.dotfiles,
- key=lambda dotfile: dotfile.name):
+ for dotfile in sorted(self.dotfiles, key=lambda dotfile: dotfile.name):
if dotfile.status or kwargs.get('verbose', True):
print dotfile
@@ -69,14 +86,14 @@ class Dotfiles(object):
def sync(self, **kwargs):
for dotfile in self.dotfiles:
- dotfile.sync()
+ dotfile.sync(self.force)
def add(self, **kwargs):
for file in kwargs.get('files', None):
if os.path.basename(file).startswith('.'):
Dotfile(file,
os.path.join(self.location,
- os.path.basename(file).strip('.'))).add()
+ self.prefix + os.path.basename(file).strip('.'))).add()
else:
print "Skipping \"%s\", not a dotfile" % file
@@ -85,6 +102,6 @@ class Dotfiles(object):
if os.path.basename(file).startswith('.'):
Dotfile(file,
os.path.join(self.location,
- os.path.basename(file).strip('.'))).remove()
+ self.prefix + os.path.basename(file).strip('.'))).remove()
else:
print "Skipping \"%s\", not a dotfile" % file
diff --git a/setup.py b/setup.py
index a14a6c6..f41e668 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,16 @@
-from distutils.core import setup
-import dotfiles
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+try:
+ from setuptools import setup
+except ImportError:
+ from distutils.core import setup
+
+from dotfiles.core import __version__
+
setup(name='dotfiles',
- version=dotfiles.__version__,
+ version=__version__,
description='Easily manage your dotfiles',
long_description=open('README.rst').read(),
author='Jon Bernard',