summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--LICENSE14
-rw-r--r--MANIFEST.in1
-rw-r--r--README.rst80
-rwxr-xr-xbin/dotfiles19
-rw-r--r--dotfiles/__init__.py8
-rw-r--r--dotfiles/cli.py51
-rw-r--r--dotfiles/core.py97
-rw-r--r--setup.py22
8 files changed, 292 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..88f69aa
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+Copyright (C) 2011 Jon Bernard <jbernard@tuxion.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..0c73842
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1 @@
+include README.rst LICENSE
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..6e71871
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,80 @@
+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.
+
+Hosting is left to you. Yes, I've seen `<http://dotfiles.org>`_ and I don't
+believe in 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.
+
+Installation
+------------
+
+To install dotfiles, simply: ::
+
+ $ pip install dotfiles
+
+Or, if you absolutely must: ::
+
+ $ easy_install dotfiles
+
+But, you really shouldn't do that.
+
+If you want to work with the latest version, you can install it from `the
+repository`_::
+
+ $ git clone https://github.com/jbernard/dotfiles
+ $ cd dotfiles
+ $ ./bin/dotfiles --help
+
+Examples
+--------
+
+To install your dotfiles on a new machine, you might do this: ::
+
+ $ git clone https://github.com/me/my-dotfiles Dotfiles
+ $ dotfiles --sync
+
+To add '~/.vimrc' to your repository: ::
+
+ $ dotfiles --add ~/.vimrc (relative paths work also)
+
+To make it available to all your hosts: ::
+
+ $ cd ~/Dotfiles
+ $ git add vimrc
+ $ git commit -m "Added vimrc, welcome aboard!"
+ $ git push
+
+You get the idea.
+
+License
+-------
+
+GPL License. ::
+
+ Copyright (C) 2011 Jon Bernard
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Contribute
+----------
+
+If you'd like to contribute, simply fork `the repository`_, commit your changes
+and send a pull request.
+
+.. _`the repository`: https://github.com/jbernard/dotfiles
diff --git a/bin/dotfiles b/bin/dotfiles
new file mode 100755
index 0000000..1cd5264
--- /dev/null
+++ b/bin/dotfiles
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+# Add project root directory (enable symlink, and trunk execution).
+PROJECT_ROOT_DIRECTORY = os.path.abspath(
+ os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
+
+if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dotfiles'))
+ and PROJECT_ROOT_DIRECTORY not in sys.path):
+ sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
+ os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY)
+
+import dotfiles
+
+if __name__ == '__main__':
+ dotfiles.cli.main()
diff --git a/dotfiles/__init__.py b/dotfiles/__init__.py
new file mode 100644
index 0000000..cc4a822
--- /dev/null
+++ b/dotfiles/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+import cli
+from core import *
+
+__version__ = '0.1.0'
+
+__all__ = ['cli', 'core']
diff --git a/dotfiles/cli.py b/dotfiles/cli.py
new file mode 100644
index 0000000..910dcdc
--- /dev/null
+++ b/dotfiles/cli.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+import os
+from . import core
+from optparse import OptionParser, OptionGroup
+
+
+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.set_defaults(repo=os.path.expanduser("~/Dotfiles"))
+ parser.add_option("-R", "--repo", type="string", dest="repo",
+ help="set repository location (default is ~/Dotfiles)")
+
+ action_group = OptionGroup(parser, "Actions")
+ action_group.add_option("-a", "--add", action="store_const", dest="action",
+ const="add", help="add dotfile(s) to the repository")
+ action_group.add_option("-c", "--check", action="store_const",
+ dest="action", const="check", help="check dotfiles repository")
+ action_group.add_option("-l", "--list", action="store_const",
+ dest="action", const="list",
+ help="list currently managed dotfiles")
+ action_group.add_option("-r", "--remove", action="store_const",
+ dest="action", const="remove",
+ help="remove dotfile(s) from the repository")
+ action_group.add_option("-s", "--sync", action="store_const",
+ dest="action", const="sync", help="update dotfile symlinks")
+ parser.add_option_group(action_group)
+
+ (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)
+
+ return (opts, args)
+
+
+def main():
+ (opts, args) = parse_args()
+ getattr(core.Dotfiles(location=opts.repo), opts.action)(files=args)
diff --git a/dotfiles/core.py b/dotfiles/core.py
new file mode 100644
index 0000000..3a60865
--- /dev/null
+++ b/dotfiles/core.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+
+import os
+import shutil
+
+
+class Dotfile(object):
+
+ def __init__(self, name, target):
+ if name.startswith('/'):
+ self.name = name
+ else:
+ self.name = os.path.expanduser('~/.%s' % name.strip('.'))
+ self.basename = os.path.basename(self.name)
+ self.target = target.rstrip('/')
+ self.status = ''
+ if not os.path.lexists(self.name):
+ self.status = 'missing'
+ elif os.path.realpath(self.name) != self.target:
+ self.status = 'unmanged'
+
+ def sync(self):
+ if self.status == 'missing':
+ os.symlink(self.target, self.name)
+
+ def add(self):
+ if self.status == 'missing':
+ print "Skipping \"%s\", file not found" % self.basename
+ return
+ 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)
+
+ def remove(self):
+ if self.status != '':
+ print "Skipping \"%s\", file is %s" % (self.basename, self.status)
+ return
+ os.remove(self.name)
+ shutil.move(self.target, self.name)
+
+ def __str__(self):
+ return '%-18s %-s' % (self.name.split('/')[-1], self.status)
+
+
+class Dotfiles(object):
+
+ IGNORES = ['.metadata', '.git', '.gitignore']
+
+ EXTRAS = {'adobe': '/tmp',
+ 'bzr.log': '/dev/null',
+ 'macromedia': '/tmp',
+ 'uml': '/tmp'}
+
+ def __init__(self, location):
+ self.location = location
+ self.dotfiles = []
+ contents = [x for x in os.listdir(self.location)
+ if x not in Dotfiles.IGNORES]
+ for file in contents:
+ self.dotfiles.append(Dotfile(file,
+ os.path.join(self.location, file)))
+ for file in self.EXTRAS.keys():
+ self.dotfiles.append(Dotfile(file, self.EXTRAS[file]))
+
+ def list(self, **kwargs):
+ for dotfile in sorted(self.dotfiles,
+ key=lambda dotfile: dotfile.name):
+ if dotfile.status or kwargs.get('verbose', True):
+ print dotfile
+
+ def check(self, **kwargs):
+ self.list(verbose=False)
+
+ def sync(self, **kwargs):
+ for dotfile in self.dotfiles:
+ dotfile.sync()
+
+ 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()
+ else:
+ print "Skipping \"%s\", not a dotfile" % file
+
+ def remove(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('.'))).remove()
+ else:
+ print "Skipping \"%s\", not a dotfile" % file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..a14a6c6
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+from distutils.core import setup
+import dotfiles
+
+setup(name='dotfiles',
+ version=dotfiles.__version__,
+ description='Easily manage your dotfiles',
+ long_description=open('README.rst').read(),
+ author='Jon Bernard',
+ author_email='jbernard@tuxion.com',
+ url='https://github.com/jbernard/dotfiles',
+ packages=['dotfiles'],
+ license='GPL',
+ classifiers=(
+ 'Development Status :: 4 - Beta',
+ 'Environment :: Console',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: GNU General Public License (GPL)',
+ 'Natural Language :: English',
+ 'Programming Language :: Python',
+ ),
+ scripts=['bin/dotfiles'],
+)