diff --git a/.gitignore b/.gitignore index 0d20b64..bf45d22 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +*.egg-info *.pyc +build/ +dist/ diff --git a/README.md b/README.md index f73d473..1e5a5fe 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,17 @@ submodule; be sure to commit your changes before running `./install`, otherwise the old version of Dotbot will be checked out by the install script. If using a subrepo, run `git fetch && git checkout origin/master` in the Dotbot directory. +If you prefer, you can install Dotbot from [PyPI] and call it as a command-line +program: + +```bash +pip install dotbot +touch install.conf.yaml +``` + +In this case, rather than running `./install`, you can invoke Dotbot with +`dotbot -c `. + ### Full Example Here's an example of a complete configuration. @@ -366,12 +377,22 @@ Contributing Do you have a feature request, bug report, or patch? Great! See [CONTRIBUTING.md][contributing] for information on what you can do about that. +Packaging +--------- + +1. Update version information. + +2. Build the package using ``python setup.py sdist bdist_wheel``. + +3. Sign and upload the package using ``twine upload -s dist/*``. + License ------- Copyright (c) 2014-2018 Anish Athalye. Released under the MIT License. See [LICENSE.md][license] for details. +[PyPI]: https://pypi.org/project/dotbot/ [init-dotfiles]: https://github.com/Vaelatern/init-dotfiles [dotfiles-template]: https://github.com/anishathalye/dotfiles_template [inspiration]: https://github.com/anishathalye/dotbot/wiki/Users diff --git a/dotbot/__init__.py b/dotbot/__init__.py index 1d03464..20ec150 100644 --- a/dotbot/__init__.py +++ b/dotbot/__init__.py @@ -1,2 +1,4 @@ from .cli import main from .plugin import Plugin + +__version__ = '1.12.1' diff --git a/dotbot/cli.py b/dotbot/cli.py index 8febb26..0674cbe 100644 --- a/dotbot/cli.py +++ b/dotbot/cli.py @@ -7,25 +7,29 @@ from .messenger import Messenger from .messenger import Level from .util import module +import dotbot +import yaml + def add_options(parser): - parser.add_argument('-Q', '--super-quiet', dest='super_quiet', action='store_true', + parser.add_argument('-Q', '--super-quiet', action='store_true', help='suppress almost all output') - parser.add_argument('-q', '--quiet', dest='quiet', action='store_true', + parser.add_argument('-q', '--quiet', action='store_true', help='suppress most output') - parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', + parser.add_argument('-v', '--verbose', action='store_true', help='enable verbose output') parser.add_argument('-d', '--base-directory', - dest='base_directory', help='execute commands from within BASEDIR', - metavar='BASEDIR', required=True) - parser.add_argument('-c', '--config-file', dest='config_file', - help='run commands given in CONFIGFILE', metavar='CONFIGFILE', - required=True) + help='execute commands from within BASEDIR', + metavar='BASEDIR') + parser.add_argument('-c', '--config-file', + help='run commands given in CONFIGFILE', metavar='CONFIGFILE') parser.add_argument('-p', '--plugin', action='append', dest='plugins', default=[], help='load PLUGIN as a plugin', metavar='PLUGIN') - parser.add_argument('--disable-built-in-plugins', dest='disable_built_in_plugins', + parser.add_argument('--disable-built-in-plugins', action='store_true', help='disable built-in plugins') parser.add_argument('--plugin-dir', action='append', dest='plugin_dirs', default=[], metavar='PLUGIN_DIR', help='load all plugins in PLUGIN_DIR') + parser.add_argument('--version', action='store_true', + help='show program\'s version number and exit') def read_config(config_file): reader = ConfigReader(config_file) @@ -37,6 +41,9 @@ def main(): parser = ArgumentParser() add_options(parser) options = parser.parse_args() + if options.version: + print('Dotbot version %s (yaml: %s)' % (dotbot.__version__, yaml.__version__)) + exit(0) if options.super_quiet: log.set_level(Level.WARNING) if options.quiet: @@ -55,11 +62,19 @@ def main(): for plugin_path in plugin_paths: abspath = os.path.abspath(plugin_path) module.load(abspath) + if not options.config_file: + log.error('No configuration file specified') + exit(1) tasks = read_config(options.config_file) if not isinstance(tasks, list): raise ReadingError('Configuration file must be a list of tasks') - os.chdir(options.base_directory) - dispatcher = Dispatcher(options.base_directory) + if options.base_directory: + base_directory = options.base_directory + else: + # default to directory of config file + base_directory = os.path.dirname(os.path.realpath(options.config_file)) + os.chdir(base_directory) + dispatcher = Dispatcher(base_directory) success = dispatcher.dispatch(tasks) if success: log.info('\n==> All tasks executed successfully') diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..3c6e79c --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[bdist_wheel] +universal=1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..93ba954 --- /dev/null +++ b/setup.py @@ -0,0 +1,82 @@ +from setuptools import setup +from codecs import open # For a consistent encoding +from os import path +import re + + +here = path.dirname(__file__) + + +with open(path.join(here, 'README.md'), encoding='utf-8') as f: + long_description = f.read() + + +def read(*names, **kwargs): + with open( + path.join(here, *names), + encoding=kwargs.get("encoding", "utf8") + ) as fp: + return fp.read() + + +def find_version(*file_paths): + version_file = read(*file_paths) + version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", + version_file, re.M) + if version_match: + return version_match.group(1) + raise RuntimeError("Unable to find version string.") + + +setup( + name='dotbot', + + version=find_version('dotbot', '__init__.py'), + + description='A tool that bootstraps your dotfiles', + long_description=long_description, + long_description_content_type='text/markdown', + + url='https://github.com/anishathalye/dotbot', + + author='Anish Athalye', + author_email='me@anishathalye.com', + + license='MIT', + + classifiers=[ + 'Development Status :: 5 - Production/Stable', + + 'Intended Audience :: Developers', + + 'License :: OSI Approved :: MIT License', + + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.2', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + + 'Topic :: Utilities', + ], + + keywords='dotfiles', + + packages=['dotbot'], + + install_requires=[ + 'PyYAML>=3.12,<4', + ], + + # To provide executable scripts, use entry points in preference to the + # "scripts" keyword. Entry points provide cross-platform support and allow + # pip to create the appropriate form of executable for the target platform. + entry_points={ + 'console_scripts': [ + 'dotbot=dotbot:main', + ], + }, +) diff --git a/test/test-lib.bash b/test/test-lib.bash index 7b5e9ca..e4d9a4e 100644 --- a/test/test-lib.bash +++ b/test/test-lib.bash @@ -48,14 +48,14 @@ initialize() { run_dotbot() { ( cat > "${DOTFILES}/${INSTALL_CONF}" - ${DOTBOT_EXEC} -d "${DOTFILES}" -c "${DOTFILES}/${INSTALL_CONF}" "${@}" + ${DOTBOT_EXEC} -c "${DOTFILES}/${INSTALL_CONF}" "${@}" ) } run_dotbot_json() { ( cat > "${DOTFILES}/${INSTALL_CONF_JSON}" - ${DOTBOT_EXEC} -d "${DOTFILES}" -c "${DOTFILES}/${INSTALL_CONF_JSON}" "${@}" + ${DOTBOT_EXEC} -c "${DOTFILES}/${INSTALL_CONF_JSON}" "${@}" ) }