diff options
-rw-r--r-- | dotfiles/cli.py | 275 | ||||
-rw-r--r-- | tests/test_cli.py | 20 |
2 files changed, 43 insertions, 252 deletions
diff --git a/dotfiles/cli.py b/dotfiles/cli.py index 19bf234..d5d49ee 100644 --- a/dotfiles/cli.py +++ b/dotfiles/cli.py @@ -1,254 +1,51 @@ -import os -try: - import ConfigParser as configparser -except ImportError: - import configparser -from optparse import OptionParser, OptionGroup +import sys +import click -from .utils import compare_path, realpath_expanduser -from .core import Dotfiles as Repository from . import __version__ +from .repository import Repository -CONFIG_FILE = '.dotfilesrc' +pass_repo = click.make_pass_decorator(Repository) -# Users can define configuration at several different levels to overlay -# specific configuration for a particular repository. These settings are -# accumulated and passed to the Repository constructor once parsing has -# completed. -repo_settings = { - 'path': Repository.defaults['path'], - 'prefix': Repository.defaults['prefix'], - 'ignore': Repository.defaults['ignore'], - 'homedir': Repository.defaults['homedir'], - 'packages': Repository.defaults['packages'], - 'externals': Repository.defaults['externals'], -} +@click.group() +@click.option('-r', '--repository', type=click.Path(), default='~/Dotfiles', + help='Sets the repository folder location.') +@click.pass_context +def main(ctx, repository): + """Dotfiles is a tool to make managing your dotfile symlinks in $HOME easy, + allowing you to keep all your dotfiles in a single directory. + The default repository is ~/Dotfiles unless specified otherwise and will be + created on demand. If you prefer a different location, you can put your + repository wherever you like using the --repository flag or using the + ~/.dotfilesrc configuration file with a contents of: -def missing_default_repo(): - """Print a helpful message when the default repository is missing. - - For a first-time user, this is the first message they're likely to see, so - it should be as helpful as possible. - """ - - print(""" -If this is your first time running dotfiles, you must first create -a repository. By default, dotfiles will look for '{0}'. -Something like: - - $ mkdir {0} - -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 -'~/{1}' and place the path to your repository in there. The contents would -look like: - + \b [dotfiles] - repository = {0} - -Type 'dotfiles -h' to see detailed usage information.""".format - (repo_settings['path'], CONFIG_FILE)) - - -def add_global_flags(parser): - 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="overwrite colliding dotfiles (use with --sync)") - - parser.add_option("-R", "--repo", - type="string", dest="repository", - help="set repository location (default: %s)" % ( - repo_settings['path'])) - - parser.add_option("-p", "--prefix", - type="string", dest="prefix", - help="set prefix character (default: %s)" % ( - None if not repo_settings['prefix'] else - repo_settings['prefix'])) - - parser.add_option("-C", "--config", - type="string", dest="config_file", - help="set configuration file (default: ~/%s)" % ( - CONFIG_FILE)) - - parser.add_option("-H", "--home", - type="string", dest="homedir", - help="set home directory location (default: %s)" % ( - repo_settings['homedir'])) - - parser.add_option("-d", "--dry-run", - action="store_true", default=False, - help="don't modify anything, just print commands") - - parser.add_option("-n", "--no-dot-prefix", - action="store_true", default=False, - help="don't prefix symlinks in target directory " + - "with a '.'") - - -def add_action_group(parser): - 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 for broken and unsynced dotfiles") - - 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") - - action_group.add_option("-m", "--move", - action="store_const", dest="action", const="move", - help="move (rename) dotfiles repository") - - parser.add_option_group(action_group) - - -def parse_args(): - - parser = OptionParser(usage="%prog ACTION [OPTION...] [FILE...]") - - add_global_flags(parser) - add_action_group(parser) - - (opts, args) = parser.parse_args() - - if opts.show_version: - print('dotfiles v%s' % __version__) - exit(0) - - if not opts.action: - print("Error: An action is required. Type 'dotfiles -h' to see " - "detailed usage information.") - exit(-1) - - return (opts, args) - - -def parse_config(config_file): - parser = configparser.SafeConfigParser() - parser.read(config_file) - - opts = dict() - - for entry in ('repository', 'prefix'): - try: - opts[entry] = parser.get('dotfiles', entry) - except configparser.NoOptionError: - pass - except configparser.NoSectionError: - break - - for entry in ('ignore', 'externals', 'packages'): - try: - opts[entry] = eval(parser.get('dotfiles', entry)) - except configparser.NoOptionError: - pass - except configparser.NoSectionError: - break - - return opts - - -def dispatch(repo, opts, args): - - # TODO: handle/pass dry_run - - if opts.action in ['list', 'check']: - getattr(repo, opts.action)() - - elif opts.action in ['add', 'remove']: - getattr(repo, opts.action)(args) - - elif opts.action == 'sync': - getattr(repo, opts.action)(files=args, force=opts.force) - - elif opts.action == 'move': - if len(args) > 1: - print("Error: Move cannot handle multiple targets.") - exit(-1) - repo.move(args[0]) - - else: - print("Error: Something truly terrible has happened.") - exit(-1) - - -def check_repository_exists(): - if not os.path.exists(repo_settings['path']): - print('Error: Could not find dotfiles repository \"%s\"' % ( - repo_settings['path'])) - if compare_path(repo_settings['path'], Repository.defaults['path']): - missing_default_repo() - exit(-1) - - -def update_settings(opts, key): - global repo_settings - - value = opts.get(key) - if value: - repo_settings[key].update(value) - - -def main(): - - global repo_settings - - (cli_opts, args) = parse_args() - - repo_settings['homedir'] = realpath_expanduser( - cli_opts.homedir or repo_settings['homedir']) - - config_opts = parse_config(cli_opts.config_file or - realpath_expanduser('~/%s' % CONFIG_FILE)) - - repo_settings['path'] = realpath_expanduser( - cli_opts.repository or - config_opts.get('repository') or - repo_settings['path']) - - check_repository_exists() + repository = ~/Dotfiles + """ + ctx.obj = Repository(repository) - update_settings(config_opts, 'ignore') - update_settings(config_opts, 'externals') - update_settings(config_opts, 'packages') - repo_config_file = os.path.join(repo_settings['path'], CONFIG_FILE) - repo_config_opts = parse_config(repo_config_file) +@main.command() +@pass_repo +def check(repo): + """Shows any broken or unsyned dotfiles.""" + list = repo.check() + if list: + click.echo_via_pager(list) + sys.exit(1) - repo_settings['prefix'] = (cli_opts.prefix or - repo_config_opts.get('prefix') or - config_opts.get('prefix') or - repo_settings['prefix']) - repo_settings['no_dot_prefix'] = cli_opts.no_dot_prefix - update_settings(repo_config_opts, 'ignore') - update_settings(repo_config_opts, 'externals') - update_settings(repo_config_opts, 'packages') +@main.command() +@pass_repo +def status(repo): + """Shows the status of each dotfile.""" + click.echo_via_pager(repo.status()) - repo = Repository(**repo_settings) - dispatch(repo, cli_opts, args) +@main.command() +def version(): + """Shows the current version number.""" + click.echo("dotfiles v%s" % __version__) diff --git a/tests/test_cli.py b/tests/test_cli.py index f2cd979..7f08010 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,16 +1,10 @@ -from dotfiles.cli import dispatch +from click.testing import CliRunner +from dotfiles import __version__ +from dotfiles.cli import version -def test_dispatch(): - """Test that the force option is handed on to the sync method.""" - class MockDotfiles: - def sync(self, files=None, force=False): - assert force - - class MockNamespace: - def __init__(self): - self.action = 'sync' - self.force = True - - dispatch(MockDotfiles(), MockNamespace(), []) +def test_version(): + runner = CliRunner() + result = runner.invoke(version) + assert ('dotfiles v%s\n' % __version__) == result.output |