diff --git a/.travis.yml b/.travis.yml index 57a5877..2e19a14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,15 @@ language: python +python: + - "2.7" + - "pypy" + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "nightly" + - "pypy3" sudo: false -.mixins: - - &xenial - dist: xenial - -jobs: - include: - - python: "2.7" - - python: "pypy" - - python: "3.2" - - python: "3.3" - - python: "3.4" - - python: "3.5" - - python: "3.6" - - <<: *xenial - python: "3.7" - - <<: *xenial - python: "3.8-dev" - - python: "nightly" - - python: "pypy3" - script: - ./test/test_travis diff --git a/README.md b/README.md index b5909a7..9b6c5a2 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,18 @@ -Dotbot -====== +# Dotbot [![Build Status](https://travis-ci.org/anishathalye/dotbot.svg?branch=master)](https://travis-ci.org/anishathalye/dotbot) Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles && ./install`, even on a freshly installed system! +- [Rationale](#rationale) +- [Getting Started](#getting-started) +- [Configuration](#configuration) +- [Directives](#directives) ([Link](#link), [Create](#create), [Shell](#shell), [Clean](#clean), [Defaults](#defaults)) +- [Plugins](#plugins) +- [Wiki][wiki] + --- -[![Build Status](https://travis-ci.org/anishathalye/dotbot.svg?branch=master)](https://travis-ci.org/anishathalye/dotbot) +## Rationale Dotbot is a tool that bootstraps your dotfiles (it's a [Dot]files [bo]o[t]strapper, get it?). It does *less* than you think, because version @@ -20,8 +26,7 @@ Dotbot is VCS-agnostic -- it doesn't make any attempt to manage your dotfiles. If you want an in-depth tutorial about organizing your dotfiles, see this [blog post][managing-dotfiles-post]. -Get Running in 5 Minutes ------------------------- +## Getting Started ### Starting Fresh? @@ -40,6 +45,7 @@ If you're using **Git**, you can add Dotbot as a submodule: cd ~/.dotfiles # replace with the path to your dotfiles git init # initialize repository if needed git submodule add https://github.com/anishathalye/dotbot +git config -f .gitmodules submodule.dotbot.ignore dirty # ignore dirty commits in the submodule cp dotbot/tools/git-submodule/install . touch install.conf.yaml ``` @@ -104,6 +110,10 @@ The conventional name for the configuration file is `install.conf.yaml`. ~/.vim: vim ~/.vimrc: vimrc +- create: + - ~/downloads + - ~/.vim/undo-history + - shell: - [git submodule update --init --recursive, Installing submodules] ``` @@ -113,14 +123,13 @@ in JSON (which is a subset of YAML). [Here][json-equivalent] is the JSON [equivalent][json2yaml] of the YAML configuration given above. JSON configuration files are conventionally named `install.conf.json`. -Configuration -------------- +## Configuration Dotbot uses YAML or JSON-formatted configuration files to let you specify how to set up your dotfiles. Currently, Dotbot knows how to [link](#link) files and -folders, execute [shell](#shell) commands, and [clean](#clean) directories of -broken symbolic links. Dotbot also supports user [plugins](#plugins) for custom -commands. +folders, [create](#create) folders, execute [shell](#shell) commands, and +[clean](#clean) directories of broken symbolic links. Dotbot also supports user +[plugins](#plugins) for custom commands. **Ideally, bootstrap configurations should be idempotent. That is, the installer should be able to be run multiple times without causing any @@ -140,6 +149,8 @@ configuration file is not behaving as you expect, try inspecting the Also, note that `~` in YAML is the same as `null` in JSON. If you want a single character string containing a tilde, make sure to enclose it in quotes: `'~'` +## Directives + ### Link Link commands specify how files and directories should be symbolically linked. @@ -182,6 +193,9 @@ Available extended configuration parameters: ~/.zshrc: force: true path: zshrc + ~/.hammerspoon: + if: '[ `uname` = Darwin ]' + path: hammerspoon ``` If the source location is omitted or set to `null`, Dotbot will use the @@ -218,6 +232,25 @@ the following config files equivalent: relink: true ``` +### Create + +Create commands specify empty directories to be created. This can be useful +for scaffolding out folders or parent folder structure required for various +apps, plugins, shell commands, etc. + +#### Format + +Create commands are specified as an array of directories to be created. + +#### Example + +```yaml +- create: + - ~/projects + - ~/downloads + - ~/.vim/undo-history +``` + ### Shell Shell commands specify shell commands to be run. Shell commands are run in the @@ -242,8 +275,8 @@ command itself. ```yaml - shell: - - mkdir -p ~/src - - [mkdir -p ~/downloads, Creating downloads directory] + - chsh -s $(which zsh) + - [chsh -s $(which zsh), Making zsh the default shell] - command: read var && echo Your variable is $var stdin: true @@ -347,14 +380,12 @@ Wiki Check out the [Dotbot wiki][wiki] for more information, tips and tricks, user-contributed plugins, and more. -Contributing ------------- +## 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 ---------- +## Packaging 1. Update version information. @@ -362,8 +393,7 @@ Packaging 3. Sign and upload the package using ``twine upload -s dist/*``. -License -------- +## License Copyright (c) 2014-2019 Anish Athalye. Released under the MIT License. See [LICENSE.md][license] for details. diff --git a/bin/dotbot b/bin/dotbot index a2ffe9a..6232a50 100755 --- a/bin/dotbot +++ b/bin/dotbot @@ -7,9 +7,9 @@ # is useful because we don't know the name of the python binary. ''':' # begin python string; this line is interpreted by the shell as `:` -which python >/dev/null 2>&1 && exec python "$0" "$@" -which python3 >/dev/null 2>&1 && exec python3 "$0" "$@" -which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" +command -v python >/dev/null 2>&1 && exec python "$0" "$@" +command -v python3 >/dev/null 2>&1 && exec python3 "$0" "$@" +command -v python2 >/dev/null 2>&1 && exec python2 "$0" "$@" >&2 echo "error: cannot find python" exit 1 ''' diff --git a/dotbot/__init__.py b/dotbot/__init__.py index 0391a42..e1fafd9 100644 --- a/dotbot/__init__.py +++ b/dotbot/__init__.py @@ -1,4 +1,4 @@ from .cli import main from .plugin import Plugin -__version__ = '1.15.0' +__version__ = '1.16.0' diff --git a/dotbot/cli.py b/dotbot/cli.py index 85f2a3c..eb10f90 100644 --- a/dotbot/cli.py +++ b/dotbot/cli.py @@ -56,7 +56,7 @@ def main(): log.use_color(False) plugin_directories = list(options.plugin_dirs) if not options.disable_built_in_plugins: - from .plugins import Clean, Link, Shell, Plugins + from .plugins import Clean, Create, Link, Shell, Plugins plugin_paths = [] for directory in plugin_directories: for plugin_path in glob.glob(os.path.join(directory, '*.py')): diff --git a/dotbot/plugins/__init__.py b/dotbot/plugins/__init__.py index 2d3e204..cc5bc59 100644 --- a/dotbot/plugins/__init__.py +++ b/dotbot/plugins/__init__.py @@ -1,4 +1,5 @@ from .clean import Clean +from .create import Create from .link import Link from .shell import Shell from .plugins import Plugins diff --git a/dotbot/plugins/create.py b/dotbot/plugins/create.py new file mode 100644 index 0000000..dc119da --- /dev/null +++ b/dotbot/plugins/create.py @@ -0,0 +1,53 @@ +import os +import glob +import shutil +import dotbot +import subprocess + + +class Create(dotbot.Plugin): + ''' + Create empty paths. + ''' + + _directive = 'create' + + def can_handle(self, directive): + return directive == self._directive + + def handle(self, directive, data): + if directive != self._directive: + raise ValueError('Create cannot handle directive %s' % directive) + return self._process_paths(data) + + def _process_paths(self, paths): + success = True + for path in paths: + path = os.path.expandvars(os.path.expanduser(path)) + success &= self._create(path) + if success: + self._log.info('All paths have been set up') + else: + self._log.error('Some paths were not successfully set up') + return success + + def _exists(self, path): + ''' + Returns true if the path exists. + ''' + path = os.path.expanduser(path) + return os.path.exists(path) + + def _create(self, path): + success = True + if not self._exists(path): + self._log.debug('Trying to create path %s' % path) + try: + self._log.lowinfo('Creating path %s' % path) + os.makedirs(path) + except OSError: + self._log.warning('Failed to create path %s' % path) + success = False + else: + self._log.lowinfo('Path exists %s' % path) + return success diff --git a/lib/pyyaml b/lib/pyyaml index 7e026bf..0f64cbf 160000 --- a/lib/pyyaml +++ b/lib/pyyaml @@ -1 +1 @@ -Subproject commit 7e026bfee9cc0bddeb1bbca0c4a0bcd826c2bfdf +Subproject commit 0f64cbfa54b0b22dc7b776b7b98a7cd657e84d78 diff --git a/setup.py b/setup.py index 975deb3..6fe1d14 100644 --- a/setup.py +++ b/setup.py @@ -73,7 +73,7 @@ setup( ], install_requires=[ - 'PyYAML>=3.12,<4', + 'PyYAML>=5.1.2,<6', ], # To provide executable scripts, use entry points in preference to the diff --git a/test/Vagrantfile b/test/Vagrantfile index 05d6747..347c402 100644 --- a/test/Vagrantfile +++ b/test/Vagrantfile @@ -1,5 +1,5 @@ Vagrant.configure(2) do |config| - config.vm.box = 'debian/stretch64' + config.vm.box = 'debian/buster64' # sync by copying for isolation config.vm.synced_folder "..", "/dotbot", type: "rsync" diff --git a/test/tests/create.bash b/test/tests/create.bash new file mode 100644 index 0000000..c5f084f --- /dev/null +++ b/test/tests/create.bash @@ -0,0 +1,23 @@ +test_description='create folders' +. '../test-lib.bash' + +test_expect_success 'run' ' +run_dotbot <> ~/tmp_bin/python <