Merge remote-tracking branch 'upstream/master' into HEAD
This commit is contained in:
commit
07c17833f5
21
README.md
21
README.md
|
@ -8,6 +8,7 @@ Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Directives](#directives) ([Link](#link), [Create](#create), [Shell](#shell), [Clean](#clean), [Defaults](#defaults))
|
- [Directives](#directives) ([Link](#link), [Create](#create), [Shell](#shell), [Clean](#clean), [Defaults](#defaults))
|
||||||
- [Plugins](#plugins)
|
- [Plugins](#plugins)
|
||||||
|
- [Command-line Arguments](#command-line-arguments)
|
||||||
- [Wiki][wiki]
|
- [Wiki][wiki]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -380,8 +381,24 @@ plugins:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Wiki
|
## Command-line Arguments
|
||||||
----
|
|
||||||
|
Dotbot takes a number of command-line arguments; you can run Dotbot with
|
||||||
|
`--help`, e.g. by running `./install --help`, to see the full list of options.
|
||||||
|
Here, we highlight a couple that are particularly interesting.
|
||||||
|
|
||||||
|
### `--only`
|
||||||
|
|
||||||
|
You can call `./install --only [list of directives]`, such as `./install --only
|
||||||
|
link`, and Dotbot will only run those sections of the config file.
|
||||||
|
|
||||||
|
### `--except`
|
||||||
|
|
||||||
|
You can call `./install --except [list of directives]`, such as `./install
|
||||||
|
--except shell`, and Dotbot will run all the sections of the config file except
|
||||||
|
the ones listed.
|
||||||
|
|
||||||
|
## Wiki
|
||||||
|
|
||||||
Check out the [Dotbot wiki][wiki] for more information, tips and tricks,
|
Check out the [Dotbot wiki][wiki] for more information, tips and tricks,
|
||||||
user-contributed plugins, and more.
|
user-contributed plugins, and more.
|
||||||
|
|
|
@ -28,6 +28,10 @@ def add_options(parser):
|
||||||
action='store_true', help='disable built-in plugins')
|
action='store_true', help='disable built-in plugins')
|
||||||
parser.add_argument('--plugin-dir', action='append', dest='plugin_dirs', default=[],
|
parser.add_argument('--plugin-dir', action='append', dest='plugin_dirs', default=[],
|
||||||
metavar='PLUGIN_DIR', help='load all plugins in PLUGIN_DIR')
|
metavar='PLUGIN_DIR', help='load all plugins in PLUGIN_DIR')
|
||||||
|
parser.add_argument('--only', nargs='+',
|
||||||
|
help='only run specified directives', metavar='DIRECTIVE')
|
||||||
|
parser.add_argument('--except', nargs='+', dest='skip',
|
||||||
|
help='skip specified directives', metavar='DIRECTIVE')
|
||||||
parser.add_argument('--no-color', dest='no_color', action='store_true',
|
parser.add_argument('--no-color', dest='no_color', action='store_true',
|
||||||
help='disable color output')
|
help='disable color output')
|
||||||
parser.add_argument('--version', action='store_true',
|
parser.add_argument('--version', action='store_true',
|
||||||
|
@ -78,7 +82,7 @@ def main():
|
||||||
# default to directory of config file
|
# default to directory of config file
|
||||||
base_directory = os.path.dirname(os.path.abspath(options.config_file))
|
base_directory = os.path.dirname(os.path.abspath(options.config_file))
|
||||||
os.chdir(base_directory)
|
os.chdir(base_directory)
|
||||||
dispatcher = Dispatcher(base_directory)
|
dispatcher = Dispatcher(base_directory, only=options.only, skip=options.skip)
|
||||||
success = dispatcher.dispatch(tasks)
|
success = dispatcher.dispatch(tasks)
|
||||||
if success:
|
if success:
|
||||||
log.info('\n==> All tasks executed successfully')
|
log.info('\n==> All tasks executed successfully')
|
||||||
|
|
|
@ -4,10 +4,12 @@ from .messenger import Messenger
|
||||||
from .context import Context
|
from .context import Context
|
||||||
|
|
||||||
class Dispatcher(object):
|
class Dispatcher(object):
|
||||||
def __init__(self, base_directory):
|
def __init__(self, base_directory, only=None, skip=None):
|
||||||
self._log = Messenger()
|
self._log = Messenger()
|
||||||
self._setup_context(base_directory)
|
self._setup_context(base_directory)
|
||||||
self._load_plugins()
|
self._load_plugins()
|
||||||
|
self._only = only
|
||||||
|
self._skip = skip
|
||||||
|
|
||||||
def _setup_context(self, base_directory):
|
def _setup_context(self, base_directory):
|
||||||
path = os.path.abspath(
|
path = os.path.abspath(
|
||||||
|
@ -20,6 +22,10 @@ class Dispatcher(object):
|
||||||
success = True
|
success = True
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
for action in task:
|
for action in task:
|
||||||
|
if self._only is not None and action not in self._only \
|
||||||
|
or self._skip is not None and action in self._skip:
|
||||||
|
self._log.info('Skipping action %s' % action)
|
||||||
|
continue
|
||||||
handled = False
|
handled = False
|
||||||
if action == 'defaults':
|
if action == 'defaults':
|
||||||
self._context.set_defaults(task[action]) # replace, not update
|
self._context.set_defaults(task[action]) # replace, not update
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import os, dotbot
|
import os
|
||||||
|
import dotbot
|
||||||
|
|
||||||
|
|
||||||
class Clean(dotbot.Plugin):
|
class Clean(dotbot.Plugin):
|
||||||
'''
|
'''
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
import os
|
import os
|
||||||
import glob
|
|
||||||
import shutil
|
|
||||||
import dotbot
|
import dotbot
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
class Create(dotbot.Plugin):
|
class Create(dotbot.Plugin):
|
||||||
|
|
|
@ -2,6 +2,7 @@ import os
|
||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
import dotbot
|
import dotbot
|
||||||
|
import dotbot.util
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,14 +106,7 @@ class Link(dotbot.Plugin):
|
||||||
return success
|
return success
|
||||||
|
|
||||||
def _test_success(self, command):
|
def _test_success(self, command):
|
||||||
with open(os.devnull, 'w') as devnull:
|
ret = dotbot.util.shell_command(command, cwd=self._context.base_directory())
|
||||||
ret = subprocess.call(
|
|
||||||
command,
|
|
||||||
shell=True,
|
|
||||||
stdout=devnull,
|
|
||||||
stderr=devnull,
|
|
||||||
executable=os.environ.get('SHELL'),
|
|
||||||
)
|
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
self._log.debug('Test \'%s\' returned false' % command)
|
self._log.debug('Test \'%s\' returned false' % command)
|
||||||
return ret == 0
|
return ret == 0
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import os, subprocess, dotbot
|
import os
|
||||||
|
import subprocess
|
||||||
|
import dotbot
|
||||||
|
import dotbot.util
|
||||||
|
|
||||||
|
|
||||||
class Shell(dotbot.Plugin):
|
class Shell(dotbot.Plugin):
|
||||||
'''
|
'''
|
||||||
|
@ -19,48 +23,40 @@ class Shell(dotbot.Plugin):
|
||||||
def _process_commands(self, data):
|
def _process_commands(self, data):
|
||||||
success = True
|
success = True
|
||||||
defaults = self._context.defaults().get('shell', {})
|
defaults = self._context.defaults().get('shell', {})
|
||||||
with open(os.devnull, 'w') as devnull:
|
for item in data:
|
||||||
for item in data:
|
stdin = defaults.get('stdin', False)
|
||||||
stdin = stdout = stderr = devnull
|
stdout = defaults.get('stdout', False)
|
||||||
quiet = False
|
stderr = defaults.get('stderr', False)
|
||||||
if defaults.get('stdin', False) == True:
|
quiet = defaults.get('quiet', False)
|
||||||
stdin = None
|
if isinstance(item, dict):
|
||||||
if defaults.get('stdout', False) == True:
|
cmd = item['command']
|
||||||
stdout = None
|
msg = item.get('description', None)
|
||||||
if defaults.get('stderr', False) == True:
|
stdin = item.get('stdin', stdin)
|
||||||
stderr = None
|
stdout = item.get('stdout', stdout)
|
||||||
if defaults.get('quiet', False) == True:
|
stderr = item.get('stderr', stderr)
|
||||||
quiet = True
|
quiet = item.get('quiet', quiet)
|
||||||
if isinstance(item, dict):
|
elif isinstance(item, list):
|
||||||
cmd = item['command']
|
cmd = item[0]
|
||||||
msg = item.get('description', None)
|
msg = item[1] if len(item) > 1 else None
|
||||||
if 'stdin' in item:
|
else:
|
||||||
stdin = None if item['stdin'] == True else devnull
|
cmd = item
|
||||||
if 'stdout' in item:
|
msg = None
|
||||||
stdout = None if item['stdout'] == True else devnull
|
if msg is None:
|
||||||
if 'stderr' in item:
|
self._log.lowinfo(cmd)
|
||||||
stderr = None if item['stderr'] == True else devnull
|
elif quiet:
|
||||||
if 'quiet' in item:
|
self._log.lowinfo('%s' % msg)
|
||||||
quiet = True if item['quiet'] == True else False
|
else:
|
||||||
elif isinstance(item, list):
|
self._log.lowinfo('%s [%s]' % (msg, cmd))
|
||||||
cmd = item[0]
|
ret = dotbot.util.shell_command(
|
||||||
msg = item[1] if len(item) > 1 else None
|
cmd,
|
||||||
else:
|
cwd=self._context.base_directory(),
|
||||||
cmd = item
|
enable_stdin=stdin,
|
||||||
msg = None
|
enable_stdout=stdout,
|
||||||
if msg is None:
|
enable_stderr=stderr
|
||||||
self._log.lowinfo(cmd)
|
)
|
||||||
elif quiet:
|
if ret != 0:
|
||||||
self._log.lowinfo('%s' % msg)
|
success = False
|
||||||
else:
|
self._log.warning('Command [%s] failed' % cmd)
|
||||||
self._log.lowinfo('%s [%s]' % (msg, cmd))
|
|
||||||
executable = os.environ.get('SHELL')
|
|
||||||
ret = subprocess.call(cmd, shell=True, stdin=stdin, stdout=stdout,
|
|
||||||
stderr=stderr, cwd=self._context.base_directory(),
|
|
||||||
executable=executable)
|
|
||||||
if ret != 0:
|
|
||||||
success = False
|
|
||||||
self._log.warning('Command [%s] failed' % cmd)
|
|
||||||
if success:
|
if success:
|
||||||
self._log.info('All commands have been executed')
|
self._log.info('All commands have been executed')
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
from .common import shell_command
|
34
dotbot/util/common.py
Normal file
34
dotbot/util/common.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
|
||||||
|
|
||||||
|
def shell_command(command, cwd=None, enable_stdin=False, enable_stdout=False, enable_stderr=False):
|
||||||
|
with open(os.devnull, 'w') as devnull_w, open(os.devnull, 'r') as devnull_r:
|
||||||
|
stdin = None if enable_stdin else devnull_r
|
||||||
|
stdout = None if enable_stdout else devnull_w
|
||||||
|
stderr = None if enable_stderr else devnull_w
|
||||||
|
executable = os.environ.get('SHELL')
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
# We avoid setting the executable kwarg on Windows because it does
|
||||||
|
# not have the desired effect when combined with shell=True. It
|
||||||
|
# will result in the correct program being run (e.g. bash), but it
|
||||||
|
# will be invoked with a '/c' argument instead of a '-c' argument,
|
||||||
|
# which it won't understand.
|
||||||
|
#
|
||||||
|
# See https://github.com/anishathalye/dotbot/issues/219 and
|
||||||
|
# https://bugs.python.org/issue40467.
|
||||||
|
#
|
||||||
|
# This means that complex commands that require Bash's parsing
|
||||||
|
# won't work; a workaround for this is to write the command as
|
||||||
|
# `bash -c "..."`.
|
||||||
|
executable = None
|
||||||
|
return subprocess.call(
|
||||||
|
command,
|
||||||
|
shell=True,
|
||||||
|
executable=executable,
|
||||||
|
stdin=stdin,
|
||||||
|
stdout=stdout,
|
||||||
|
stderr=stderr,
|
||||||
|
cwd=cwd
|
||||||
|
)
|
21
test/tests/except-multi.bash
Normal file
21
test/tests/except-multi.bash
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
test_description='--except with multiple arguments'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
ln -s ${DOTFILES}/nonexistent ~/bad && touch ${DOTFILES}/y
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
run_dotbot --except clean shell <<EOF
|
||||||
|
- clean: ["~"]
|
||||||
|
- shell:
|
||||||
|
- echo "x" > ~/x
|
||||||
|
- link:
|
||||||
|
~/y: y
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
[ "$(readlink ~/bad | cut -d/ -f4-)" = "dotfiles/nonexistent" ] &&
|
||||||
|
! test -f ~/x && test -f ~/y
|
||||||
|
'
|
31
test/tests/except.bash
Normal file
31
test/tests/except.bash
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
test_description='--except'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
echo "apple" > ${DOTFILES}/x
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
run_dotbot --except link <<EOF
|
||||||
|
- shell:
|
||||||
|
- echo "pear" > ~/y
|
||||||
|
- link:
|
||||||
|
~/x: x
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
grep "pear" ~/y && ! test -f ~/x
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run 2' '
|
||||||
|
run_dotbot --except shell <<EOF
|
||||||
|
- shell:
|
||||||
|
- echo "pear" > ~/z
|
||||||
|
- link:
|
||||||
|
~/x: x
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
grep "apple" ~/x && ! test -f ~/z
|
||||||
|
'
|
20
test/tests/only-multi.bash
Normal file
20
test/tests/only-multi.bash
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
test_description='--only with multiple arguments'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
ln -s ${DOTFILES}/nonexistent ~/bad && touch ${DOTFILES}/y
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
run_dotbot --only clean shell <<EOF
|
||||||
|
- clean: ["~"]
|
||||||
|
- shell:
|
||||||
|
- echo "x" > ~/x
|
||||||
|
- link:
|
||||||
|
~/y: y
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
! test -f ~/bad && grep "x" ~/x && ! test -f ~/y
|
||||||
|
'
|
31
test/tests/only.bash
Normal file
31
test/tests/only.bash
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
test_description='--only'
|
||||||
|
. '../test-lib.bash'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
echo "apple" > ${DOTFILES}/x
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run' '
|
||||||
|
run_dotbot --only shell <<EOF
|
||||||
|
- shell:
|
||||||
|
- echo "pear" > ~/y
|
||||||
|
- link:
|
||||||
|
~/x: x
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
grep "pear" ~/y && ! test -f ~/x
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'run 2' '
|
||||||
|
run_dotbot --only link <<EOF
|
||||||
|
- shell:
|
||||||
|
- echo "pear" > ~/z
|
||||||
|
- link:
|
||||||
|
~/x: x
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'test' '
|
||||||
|
grep "apple" ~/x && ! test -f ~/z
|
||||||
|
'
|
Loading…
Reference in a new issue