mirror of
1
0
Fork 0

Merge remote-tracking branch 'upstream/master' into HEAD

This commit is contained in:
Andreas Schmidt 2020-06-28 16:31:12 +02:00
commit 07c17833f5
No known key found for this signature in database
GPG Key ID: FEE0A611BEA6DEA0
13 changed files with 213 additions and 59 deletions

View File

@ -8,6 +8,7 @@ Dotbot makes installing your dotfiles as easy as `git clone $url && cd dotfiles
- [Configuration](#configuration)
- [Directives](#directives) ([Link](#link), [Create](#create), [Shell](#shell), [Clean](#clean), [Defaults](#defaults))
- [Plugins](#plugins)
- [Command-line Arguments](#command-line-arguments)
- [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,
user-contributed plugins, and more.

View File

@ -28,6 +28,10 @@ def add_options(parser):
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('--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',
help='disable color output')
parser.add_argument('--version', action='store_true',
@ -78,7 +82,7 @@ def main():
# default to directory of config file
base_directory = os.path.dirname(os.path.abspath(options.config_file))
os.chdir(base_directory)
dispatcher = Dispatcher(base_directory)
dispatcher = Dispatcher(base_directory, only=options.only, skip=options.skip)
success = dispatcher.dispatch(tasks)
if success:
log.info('\n==> All tasks executed successfully')

View File

@ -4,10 +4,12 @@ from .messenger import Messenger
from .context import Context
class Dispatcher(object):
def __init__(self, base_directory):
def __init__(self, base_directory, only=None, skip=None):
self._log = Messenger()
self._setup_context(base_directory)
self._load_plugins()
self._only = only
self._skip = skip
def _setup_context(self, base_directory):
path = os.path.abspath(
@ -20,6 +22,10 @@ class Dispatcher(object):
success = True
for task in tasks:
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
if action == 'defaults':
self._context.set_defaults(task[action]) # replace, not update

View File

@ -1,4 +1,6 @@
import os, dotbot
import os
import dotbot
class Clean(dotbot.Plugin):
'''

View File

@ -1,8 +1,5 @@
import os
import glob
import shutil
import dotbot
import subprocess
class Create(dotbot.Plugin):

View File

@ -2,6 +2,7 @@ import os
import glob
import shutil
import dotbot
import dotbot.util
import subprocess
@ -105,14 +106,7 @@ class Link(dotbot.Plugin):
return success
def _test_success(self, command):
with open(os.devnull, 'w') as devnull:
ret = subprocess.call(
command,
shell=True,
stdout=devnull,
stderr=devnull,
executable=os.environ.get('SHELL'),
)
ret = dotbot.util.shell_command(command, cwd=self._context.base_directory())
if ret != 0:
self._log.debug('Test \'%s\' returned false' % command)
return ret == 0

View File

@ -1,4 +1,8 @@
import os, subprocess, dotbot
import os
import subprocess
import dotbot
import dotbot.util
class Shell(dotbot.Plugin):
'''
@ -19,48 +23,40 @@ class Shell(dotbot.Plugin):
def _process_commands(self, data):
success = True
defaults = self._context.defaults().get('shell', {})
with open(os.devnull, 'w') as devnull:
for item in data:
stdin = stdout = stderr = devnull
quiet = False
if defaults.get('stdin', False) == True:
stdin = None
if defaults.get('stdout', False) == True:
stdout = None
if defaults.get('stderr', False) == True:
stderr = None
if defaults.get('quiet', False) == True:
quiet = True
if isinstance(item, dict):
cmd = item['command']
msg = item.get('description', None)
if 'stdin' in item:
stdin = None if item['stdin'] == True else devnull
if 'stdout' in item:
stdout = None if item['stdout'] == True else devnull
if 'stderr' in item:
stderr = None if item['stderr'] == True else devnull
if 'quiet' in item:
quiet = True if item['quiet'] == True else False
elif isinstance(item, list):
cmd = item[0]
msg = item[1] if len(item) > 1 else None
else:
cmd = item
msg = None
if msg is None:
self._log.lowinfo(cmd)
elif quiet:
self._log.lowinfo('%s' % msg)
else:
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)
for item in data:
stdin = defaults.get('stdin', False)
stdout = defaults.get('stdout', False)
stderr = defaults.get('stderr', False)
quiet = defaults.get('quiet', False)
if isinstance(item, dict):
cmd = item['command']
msg = item.get('description', None)
stdin = item.get('stdin', stdin)
stdout = item.get('stdout', stdout)
stderr = item.get('stderr', stderr)
quiet = item.get('quiet', quiet)
elif isinstance(item, list):
cmd = item[0]
msg = item[1] if len(item) > 1 else None
else:
cmd = item
msg = None
if msg is None:
self._log.lowinfo(cmd)
elif quiet:
self._log.lowinfo('%s' % msg)
else:
self._log.lowinfo('%s [%s]' % (msg, cmd))
ret = dotbot.util.shell_command(
cmd,
cwd=self._context.base_directory(),
enable_stdin=stdin,
enable_stdout=stdout,
enable_stderr=stderr
)
if ret != 0:
success = False
self._log.warning('Command [%s] failed' % cmd)
if success:
self._log.info('All commands have been executed')
else:

View File

@ -0,0 +1 @@
from .common import shell_command

34
dotbot/util/common.py Normal file
View 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
)

View 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
View 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
'

View 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
View 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
'