From 2ec7a24129ce8c1fc12c4e557174297bcd4bf31a Mon Sep 17 00:00:00 2001 From: "E. Keys" Date: Sun, 22 Nov 2020 12:59:30 -0500 Subject: [PATCH 1/9] Add exclude parameter for link globbing - Added `exclude` parameter to _link_. Now, an array of glob patterns can be given that will be used to remove items from a glob match. This parameter will only have an effect when `glob` is `true`. - Updated README to add description for `exclude` and add in examples. Resolves #247 --- README.md | 13 ++++ dotbot/plugins/link.py | 16 +++- test/tests/link-glob-exclude.bash | 123 ++++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 test/tests/link-glob-exclude.bash diff --git a/README.md b/README.md index ae7e570..3b0aae0 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,7 @@ mapped to extended configuration dictionaries. | `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default: false) | | `if` | Execute this in your `$SHELL` and only link if it is successful. | | `ignore-missing` | Do not fail if the source is missing and create the link anyway (default: false) | +| `exclude` | Array of paths to remove from glob matches. Uses same syntax as `path`. Ignored if `glob` is `false`. (default: empty, keep all matches) | #### Example @@ -218,6 +219,12 @@ Explicit sources: glob: true path: config/* relink: true + exclude: [ config/Code ] + ~/.config/Code/User/: + create: true + glob: true + path: config/Code/User/* + relink: true ``` Implicit sources: @@ -234,6 +241,12 @@ Implicit sources: glob: true path: config/* relink: true + exclude: [ config/Code ] + ~/.config/Code/User/: + create: true + glob: true + path: config/Code/User/* + relink: true ``` ### Create diff --git a/dotbot/plugins/link.py b/dotbot/plugins/link.py index 6f2b562..fd2d14d 100644 --- a/dotbot/plugins/link.py +++ b/dotbot/plugins/link.py @@ -34,6 +34,7 @@ class Link(dotbot.Plugin): use_glob = defaults.get('glob', False) test = defaults.get('if', None) ignore_missing = defaults.get('ignore-missing', False) + exclude_paths = defaults.get('exclude', []) if isinstance(source, dict): # extended config test = source.get('if', test) @@ -44,6 +45,7 @@ class Link(dotbot.Plugin): create = source.get('create', create) use_glob = source.get('glob', use_glob) ignore_missing = source.get('ignore-missing', ignore_missing) + exclude_paths = source.get('exclude', exclude_paths) path = self._default_source(destination, source.get('path')) else: path = self._default_source(destination, source) @@ -52,8 +54,7 @@ class Link(dotbot.Plugin): continue path = os.path.expandvars(os.path.expanduser(path)) if use_glob: - self._log.debug("Globbing with path: " + str(path)) - glob_results = glob.glob(path) + glob_results = self._create_glob_results(path, exclude_paths) if len(glob_results) == 0: self._log.warning("Globbing couldn't find anything matching " + str(path)) success = False @@ -120,6 +121,17 @@ class Link(dotbot.Plugin): return basename else: return source + + def _create_glob_results(self, path, exclude_paths): + self._log.debug("Globbing with path: " + str(path)) + base_include = glob.glob(path) + to_exclude = [] + for expath in exclude_paths: + self._log.debug("Excluding globs with path: " + str(expath)) + to_exclude.extend(glob.glob(expath)) + self._log.debug("Excluded globs from '" + path + "': " + str(to_exclude)) + ret = set(base_include) - set(to_exclude) + return list(ret) def _is_link(self, path): ''' diff --git a/test/tests/link-glob-exclude.bash b/test/tests/link-glob-exclude.bash new file mode 100644 index 0000000..bb6fb76 --- /dev/null +++ b/test/tests/link-glob-exclude.bash @@ -0,0 +1,123 @@ +test_description='link glob exclude' +. '../test-lib.bash' + +test_expect_success 'setup 1' ' +mkdir -p ${DOTFILES}/config/{foo,bar,baz} && +echo "apple" > ${DOTFILES}/config/foo/a && +echo "banana" > ${DOTFILES}/config/bar/b && +echo "cherry" > ${DOTFILES}/config/bar/c && +echo "donut" > ${DOTFILES}/config/baz/d +' + +test_expect_success 'run 1' ' +run_dotbot -v < ${DOTFILES}/config/baz/buzz/e +' + +test_expect_success 'run 2' ' +run_dotbot -v < ${DOTFILES}/config/baz/bizz/g +' + +test_expect_success 'run 3' ' +run_dotbot -v < ${DOTFILES}/config/fiz/f +' + +test_expect_success 'run 4' ' +run_dotbot -v < Date: Thu, 25 Feb 2021 06:25:17 -0500 Subject: [PATCH 2/9] Add mode option to create directive See . Thanks to @eengstrom for the feature suggestion. --- README.md | 19 +++++++++++++++++-- dotbot/plugins/create.py | 18 ++++++++++++------ test/tests/create-mode.bash | 26 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 test/tests/create-mode.bash diff --git a/README.md b/README.md index e2e27b1..f7fccdf 100644 --- a/README.md +++ b/README.md @@ -247,15 +247,30 @@ apps, plugins, shell commands, etc. #### Format -Create commands are specified as an array of directories to be created. +Create commands are specified as an array of directories to be created. If you +want to use the optional extended configuration, create commands are specified +as dictionaries. For convenience, it's permissible to leave the options blank +(null) in the dictionary syntax. + +| Parameter | Explanation | +| --- | --- | +| `mode` | The file mode to use for creating the leaf directory (default: 0777) | + +The `mode` parameter is treated in the same way as in Python's +[os.mkdir](https://docs.python.org/3/library/os.html#mkdir-modebits). Its +behavior is platform-dependent. On Unix systems, the current umask value is +first masked out. #### Example ```yaml - create: - - ~/projects - ~/downloads - ~/.vim/undo-history +- create: + ~/.ssh: + mode: 0700 + ~/projects: ``` ### Shell diff --git a/dotbot/plugins/create.py b/dotbot/plugins/create.py index 015645a..d7754fb 100644 --- a/dotbot/plugins/create.py +++ b/dotbot/plugins/create.py @@ -19,9 +19,15 @@ class Create(dotbot.Plugin): def _process_paths(self, paths): success = True - for path in paths: - path = os.path.expandvars(os.path.expanduser(path)) - success &= self._create(path) + defaults = self._context.defaults().get('create', {}) + for key in paths: + path = os.path.expandvars(os.path.expanduser(key)) + mode = defaults.get('mode', 0o777) # same as the default for os.makedirs + if isinstance(paths, dict): + options = paths[key] + if options: + mode = options.get('mode', mode) + success &= self._create(path, mode) if success: self._log.info('All paths have been set up') else: @@ -35,13 +41,13 @@ class Create(dotbot.Plugin): path = os.path.expanduser(path) return os.path.exists(path) - def _create(self, path): + def _create(self, path, mode): success = True if not self._exists(path): - self._log.debug('Trying to create path %s' % path) + self._log.debug('Trying to create path %s with mode %o' % (path, mode)) try: self._log.lowinfo('Creating path %s' % path) - os.makedirs(path) + os.makedirs(path, mode) except OSError: self._log.warning('Failed to create path %s' % path) success = False diff --git a/test/tests/create-mode.bash b/test/tests/create-mode.bash new file mode 100644 index 0000000..e2dd649 --- /dev/null +++ b/test/tests/create-mode.bash @@ -0,0 +1,26 @@ +test_description='create mode' +. '../test-lib.bash' + +test_expect_success 'run' ' +run_dotbot -v < Date: Sat, 21 Nov 2020 10:00:37 -0500 Subject: [PATCH 3/9] Improve globbing behavior with leading '.' --- README.md | 6 ++++ dotbot/plugins/link.py | 2 ++ test/tests/link-glob-multi-star.bash | 2 +- test/tests/link-glob.bash | 46 ++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7fccdf..b8705f4 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,12 @@ mapped to extended configuration dictionaries. | `if` | Execute this in your `$SHELL` and only link if it is successful. | | `ignore-missing` | Do not fail if the source is missing and create the link anyway (default: false) | +Dotbot uses [glob.glob](https://docs.python.org/3/library/glob.html#glob.glob) +to resolve glob paths. However, due to its design, using a glob path such as +`config/*` for example, will not match items that being with `.`. To +specifically capture items that being with `.`, you will need to use a path +like this: `config/.*`. + #### Example ```yaml diff --git a/dotbot/plugins/link.py b/dotbot/plugins/link.py index 6f2b562..374dc76 100644 --- a/dotbot/plugins/link.py +++ b/dotbot/plugins/link.py @@ -76,6 +76,8 @@ class Link(dotbot.Plugin): else: self._log.lowinfo("Globs from '" + path + "': " + str(glob_results)) glob_base = path[:glob_star_loc] + if glob_base.endswith('/.') or glob_base == '.': + glob_base = path[:glob_star_loc - 1] for glob_full_item in glob_results: glob_item = glob_full_item[len(glob_base):] glob_link_destination = os.path.join(destination, glob_item) diff --git a/test/tests/link-glob-multi-star.bash b/test/tests/link-glob-multi-star.bash index 11ae740..3a2ca71 100644 --- a/test/tests/link-glob-multi-star.bash +++ b/test/tests/link-glob-multi-star.bash @@ -1,4 +1,4 @@ -test_description='link glob' +test_description='link glob multi star' . '../test-lib.bash' test_expect_success 'setup' ' diff --git a/test/tests/link-glob.bash b/test/tests/link-glob.bash index f1c813d..866d550 100644 --- a/test/tests/link-glob.bash +++ b/test/tests/link-glob.bash @@ -45,3 +45,49 @@ grep "apple" ~/bin/a && grep "banana" ~/bin/b && grep "cherry" ~/bin/c ' + +test_expect_success 'setup 3' ' +rm -rf ~/bin && +echo "dot_apple" > ${DOTFILES}/bin/.a && +echo "dot_banana" > ${DOTFILES}/bin/.b && +echo "dot_cherry" > ${DOTFILES}/bin/.c +' + +test_expect_success 'run 3' ' +run_dotbot -v < ${DOTFILES}/.a && +echo "dot_banana" > ${DOTFILES}/.b && +echo "dot_cherry" > ${DOTFILES}/.c +' + +test_expect_success 'run 4' ' +run_dotbot -v < Date: Wed, 2 Dec 2020 18:49:36 -0500 Subject: [PATCH 4/9] Make parsed CLI options available to plugins --- dotbot/cli.py | 2 +- dotbot/context.py | 7 ++++++- dotbot/dispatcher.py | 9 +++++---- test/tests/plugin.bash | 42 +++++++++++++++++++++++++++++++++++++++--- 4 files changed, 51 insertions(+), 9 deletions(-) diff --git a/dotbot/cli.py b/dotbot/cli.py index 32db016..1a986f5 100644 --- a/dotbot/cli.py +++ b/dotbot/cli.py @@ -97,7 +97,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, only=options.only, skip=options.skip) + dispatcher = Dispatcher(base_directory, only=options.only, skip=options.skip, options=options) success = dispatcher.dispatch(tasks) if success: log.info('\n==> All tasks executed successfully') diff --git a/dotbot/context.py b/dotbot/context.py index 8c42d47..cb2b5df 100644 --- a/dotbot/context.py +++ b/dotbot/context.py @@ -1,14 +1,16 @@ import copy import os +from argparse import Namespace class Context(object): ''' Contextual data and information for plugins. ''' - def __init__(self, base_directory): + def __init__(self, base_directory, options=Namespace()): self._base_directory = base_directory self._defaults = {} + self._options = options pass def set_base_directory(self, base_directory): @@ -25,3 +27,6 @@ class Context(object): def defaults(self): return copy.deepcopy(self._defaults) + + def options(self): + return copy.deepcopy(self._options) diff --git a/dotbot/dispatcher.py b/dotbot/dispatcher.py index 646f6a0..856befa 100644 --- a/dotbot/dispatcher.py +++ b/dotbot/dispatcher.py @@ -1,22 +1,23 @@ import os +from argparse import Namespace from .plugin import Plugin from .messenger import Messenger from .context import Context class Dispatcher(object): - def __init__(self, base_directory, only=None, skip=None): + def __init__(self, base_directory, only=None, skip=None, options=Namespace()): self._log = Messenger() - self._setup_context(base_directory) + self._setup_context(base_directory, options) self._load_plugins() self._only = only self._skip = skip - def _setup_context(self, base_directory): + def _setup_context(self, base_directory, options): path = os.path.abspath( os.path.expanduser(base_directory)) if not os.path.exists(path): raise DispatchError('Nonexistent base directory') - self._context = Context(path) + self._context = Context(path, options) def dispatch(self, tasks): success = True diff --git a/test/tests/plugin.bash b/test/tests/plugin.bash index bdf0c7f..dc2613a 100644 --- a/test/tests/plugin.bash +++ b/test/tests/plugin.bash @@ -1,7 +1,7 @@ test_description='plugin loading works' . '../test-lib.bash' -test_expect_success 'setup' ' +test_expect_success 'setup 1' ' cat > ${DOTFILES}/test.py < ${DOTFILES}/test.py < Date: Wed, 2 Dec 2020 22:49:17 -0500 Subject: [PATCH 5/9] Add cli option force shell show stderr/stdout Passing `--verbose` flag two times will now force shell commands to show stderr/stdout output regardless of settings in config file. Resolves #104 --- dotbot/cli.py | 12 ++-- dotbot/plugins/shell.py | 15 +++++ test/tests/shell-cli-override-config.bash | 79 +++++++++++++++++++++++ 3 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 test/tests/shell-cli-override-config.bash diff --git a/dotbot/cli.py b/dotbot/cli.py index 1a986f5..f9fba73 100644 --- a/dotbot/cli.py +++ b/dotbot/cli.py @@ -1,7 +1,7 @@ import os, glob import sys -from argparse import ArgumentParser +from argparse import ArgumentParser, RawTextHelpFormatter from .config import ConfigReader, ReadingError from .dispatcher import Dispatcher, DispatchError from .messenger import Messenger @@ -16,8 +16,10 @@ def add_options(parser): help='suppress almost all output') parser.add_argument('-q', '--quiet', action='store_true', help='suppress most output') - parser.add_argument('-v', '--verbose', action='store_true', - help='enable verbose output') + parser.add_argument('-v', '--verbose', action='count', default=0, + help='enable verbose output\n' + '-v: typical verbose\n' + '-vv: also, set shell commands stderr/stdout to true') parser.add_argument('-d', '--base-directory', help='execute commands from within BASEDIR', metavar='BASEDIR') @@ -47,7 +49,7 @@ def read_config(config_file): def main(): log = Messenger() try: - parser = ArgumentParser() + parser = ArgumentParser(formatter_class=RawTextHelpFormatter) add_options(parser) options = parser.parse_args() if options.version: @@ -57,7 +59,7 @@ def main(): log.set_level(Level.WARNING) if options.quiet: log.set_level(Level.INFO) - if options.verbose: + if options.verbose > 0: log.set_level(Level.DEBUG) if options.force_color and options.no_color: diff --git a/dotbot/plugins/shell.py b/dotbot/plugins/shell.py index 3092f20..bae8b99 100644 --- a/dotbot/plugins/shell.py +++ b/dotbot/plugins/shell.py @@ -10,6 +10,7 @@ class Shell(dotbot.Plugin): ''' _directive = 'shell' + _has_shown_override_message = False def can_handle(self, directive): return directive == self._directive @@ -23,6 +24,7 @@ class Shell(dotbot.Plugin): def _process_commands(self, data): success = True defaults = self._context.defaults().get('shell', {}) + options = self._get_option_overrides() for item in data: stdin = defaults.get('stdin', False) stdout = defaults.get('stdout', False) @@ -47,6 +49,8 @@ class Shell(dotbot.Plugin): self._log.lowinfo('%s' % msg) else: self._log.lowinfo('%s [%s]' % (msg, cmd)) + stdout = options.get('stdout', stdout) + stderr = options.get('stderr', stderr) ret = dotbot.util.shell_command( cmd, cwd=self._context.base_directory(), @@ -62,3 +66,14 @@ class Shell(dotbot.Plugin): else: self._log.error('Some commands were not successfully executed') return success + + def _get_option_overrides(self): + ret = {} + options = self._context.options() + if options.verbose > 1: + ret['stderr'] = True + ret['stdout'] = True + if not self._has_shown_override_message: + self._log.debug("Shell: Found cli option to force show stderr and stdout.") + self._has_shown_override_message = True + return ret diff --git a/test/tests/shell-cli-override-config.bash b/test/tests/shell-cli-override-config.bash new file mode 100644 index 0000000..908c10d --- /dev/null +++ b/test/tests/shell-cli-override-config.bash @@ -0,0 +1,79 @@ +test_description='cli options can override config file' +. '../test-lib.bash' + +test_expect_success 'run 1' ' +(run_dotbot -vv | (grep "^apple")) <&2 +EOF +' + +test_expect_success 'run 5' ' +(run_dotbot -vv 2>&1 | (grep "^apple")) <&2 +EOF +' + +test_expect_success 'run 6' ' +(run_dotbot -vv 2>&1 | (grep "^apple")) <&2 + stdout: false +EOF +' + +test_expect_success 'run 7' ' +(run_dotbot -vv 2>&1 | (grep "^apple")) <&2 +EOF +' + +# Make sure that we must use verbose level 2 +# This preserves backwards compatability +test_expect_failure 'run 8' ' +(run_dotbot -v | (grep "^apple")) <&2 +EOF +' From dac7a9bc888f807a8ce579bdc22abb3716958f00 Mon Sep 17 00:00:00 2001 From: Anish Athalye Date: Thu, 25 Feb 2021 08:14:34 -0500 Subject: [PATCH 6/9] Add --debug flag to test driver This is easier than the old method of adding `DEBUG=true` to the top of test files. --- test/README.md | 4 ++-- test/driver-lib.bash | 2 +- test/test | 21 +++++++++++++++++++-- test/test-lib.bash | 1 - 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/test/README.md b/test/README.md index 9ec79f5..a4d8208 100644 --- a/test/README.md +++ b/test/README.md @@ -51,8 +51,8 @@ edits on your host machine). You can run the test suite by `cd /dotbot/test` and then running `./test`. Selected tests can be run by passing paths to the tests as arguments, e.g. `./test tests/create.bash tests/defaults.bash`. -To debug tests, you can prepend the line `DEBUG=true` as the first line to any -individual test (a `.bash` file inside `test/tests`). This will enable printing +To debug tests, you can run the test driver with the `--debug` (or `-d` short +form) flag, e.g. `./test --debug tests/link-if.bash`. This will enable printing stdout/stderr. When finished with testing, it is good to shut down the virtual machine by diff --git a/test/driver-lib.bash b/test/driver-lib.bash index ab59240..b7b314e 100644 --- a/test/driver-lib.bash +++ b/test/driver-lib.bash @@ -60,7 +60,7 @@ run_test() { tests_run=$((tests_run + 1)) printf '[%d/%d] (%s)\n' "${tests_run}" "${tests_total}" "${1}" cleanup - if (cd "${BASEDIR}/test/tests" && HOME=~/fakehome DOTBOT_TEST=true bash "${1}"); then + if (cd "${BASEDIR}/test/tests" && HOME=~/fakehome DEBUG=${2} DOTBOT_TEST=true bash "${1}"); then pass else fail diff --git a/test/test b/test/test index f944512..c52932b 100755 --- a/test/test +++ b/test/test @@ -10,6 +10,23 @@ start="$(date +%s)" check_env +# parse flags; must come before positional arguments +POSITIONAL=() +DEBUG=false +while [[ $# -gt 0 ]]; do + case $1 in + -d|--debug) + DEBUG=true + shift + ;; + *) + POSITIONAL+=("$1") + shift + ;; + esac +done +set -- "${POSITIONAL[@]}" # restore positional arguments + declare -a tests=() if [ $# -eq 0 ]; then @@ -20,10 +37,10 @@ else tests=("$@") fi -initialize "${#tests[@]}" "${VERSION}" +initialize "${#tests[@]}" for file in "${tests[@]}"; do - run_test "$(basename "${file}")" "${VERSION}" + run_test "$(basename "${file}")" "${DEBUG}" done if report; then diff --git a/test/test-lib.bash b/test/test-lib.bash index 5472e46..1fa72cb 100644 --- a/test/test-lib.bash +++ b/test/test-lib.bash @@ -1,4 +1,3 @@ -DEBUG=${DEBUG:-false} DOTBOT_EXEC="${BASEDIR}/bin/dotbot" DOTFILES="${HOME}/dotfiles" INSTALL_CONF='install.conf.yaml' From 66489f795506e42855561d6af971f011272eff5d Mon Sep 17 00:00:00 2001 From: Anish Athalye Date: Thu, 25 Feb 2021 08:16:27 -0500 Subject: [PATCH 7/9] Fix missing endings of heredocs --- test/tests/except.bash | 1 + test/tests/only-defaults.bash | 1 + test/tests/only.bash | 1 + 3 files changed, 3 insertions(+) diff --git a/test/tests/except.bash b/test/tests/except.bash index 356eaef..2973ad9 100644 --- a/test/tests/except.bash +++ b/test/tests/except.bash @@ -24,6 +24,7 @@ run_dotbot --except shell < ~/z - link: ~/x: x +EOF ' test_expect_success 'test' ' diff --git a/test/tests/only-defaults.bash b/test/tests/only-defaults.bash index 4ef133a..de9a10d 100644 --- a/test/tests/only-defaults.bash +++ b/test/tests/only-defaults.bash @@ -14,6 +14,7 @@ run_dotbot --only link < ~/z - link: ~/d/x: x +EOF ' test_expect_success 'test' ' diff --git a/test/tests/only.bash b/test/tests/only.bash index 5222881..1f74441 100644 --- a/test/tests/only.bash +++ b/test/tests/only.bash @@ -24,6 +24,7 @@ run_dotbot --only link < ~/z - link: ~/x: x +EOF ' test_expect_success 'test' ' From f15293b3d51fb938e26f4f13653b7d9c94e0bab7 Mon Sep 17 00:00:00 2001 From: Anish Athalye Date: Thu, 25 Feb 2021 08:26:19 -0500 Subject: [PATCH 8/9] Rename 'canonicalize-path' into 'canonicalize' This parallels 'relative' (it's not 'relative-path'). The old 'canonicalize-path' is still supported for backward compatibility. --- README.md | 2 +- dotbot/plugins/link.py | 7 ++++--- test/tests/link-no-canonicalize.bash | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b4ae518..3a06625 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ mapped to extended configuration dictionaries. | `relink` | Removes the old target if it's a symlink (default: false) | | `force` | Force removes the old target, file or folder, and forces a new link (default: false) | | `relative` | Use a relative path to the source when creating the symlink (default: false, absolute links) | -| `canonicalize-path` | Resolve any symbolic links encountered in the source to symlink to the canonical path (default: true, real paths) | +| `canonicalize` | Resolve any symbolic links encountered in the source to symlink to the canonical path (default: true, real paths) | | `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default: false) | | `if` | Execute this in your `$SHELL` and only link if it is successful. | | `ignore-missing` | Do not fail if the source is missing and create the link anyway (default: false) | diff --git a/dotbot/plugins/link.py b/dotbot/plugins/link.py index ee257f7..0d195fe 100644 --- a/dotbot/plugins/link.py +++ b/dotbot/plugins/link.py @@ -27,7 +27,8 @@ class Link(dotbot.Plugin): for destination, source in links.items(): destination = os.path.expandvars(destination) relative = defaults.get('relative', False) - canonical_path = defaults.get('canonicalize-path', True) + # support old "canonicalize-path" key for compatibility + canonical_path = defaults.get('canonicalize', defaults.get('canonicalize-path', True)) force = defaults.get('force', False) relink = defaults.get('relink', False) create = defaults.get('create', False) @@ -39,7 +40,7 @@ class Link(dotbot.Plugin): # extended config test = source.get('if', test) relative = source.get('relative', relative) - canonical_path = source.get('canonicalize-path', canonical_path) + canonical_path = source.get('canonicalize', source.get('canonicalize-path', canonical_path)) force = source.get('force', force) relink = source.get('relink', relink) create = source.get('create', create) @@ -123,7 +124,7 @@ class Link(dotbot.Plugin): return basename else: return source - + def _create_glob_results(self, path, exclude_paths): self._log.debug("Globbing with path: " + str(path)) base_include = glob.glob(path) diff --git a/test/tests/link-no-canonicalize.bash b/test/tests/link-no-canonicalize.bash index 7f516c6..a65e397 100644 --- a/test/tests/link-no-canonicalize.bash +++ b/test/tests/link-no-canonicalize.bash @@ -3,6 +3,7 @@ test_description='linking path canonicalization can be disabled' test_expect_success 'setup' ' echo "apple" > ${DOTFILES}/f && +echo "grape" > ${DOTFILES}/g && ln -s dotfiles dotfiles-symlink ' @@ -21,3 +22,19 @@ ${DOTBOT_EXEC} -c ./dotfiles-symlink/${INSTALL_CONF} test_expect_success 'test' ' [ "$(readlink ~/.f | cut -d/ -f5-)" = "dotfiles-symlink/f" ] ' + +test_expect_success 'run 2' ' +cat > "${DOTFILES}/${INSTALL_CONF}" < Date: Sun, 22 Nov 2020 09:28:21 +0800 Subject: [PATCH 9/9] Add PowerShell install script --- README.md | 3 +++ tools/git-submodule/install.ps1 | 22 ++++++++++++++++++++++ tools/hg-subrepo/install.ps1 | 21 +++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 tools/git-submodule/install.ps1 create mode 100644 tools/hg-subrepo/install.ps1 diff --git a/README.md b/README.md index 3a06625..bec819e 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ cp dotbot/tools/hg-subrepo/install . touch install.conf.yaml ``` +If you are using PowerShell instead of a POSIX shell, you can use the provided +`install.ps1` script instead of `install`. + To get started, you just need to fill in the `install.conf.yaml` and Dotbot will take care of the rest. To help you get started we have [an example](#full-example) config file as well as [configuration diff --git a/tools/git-submodule/install.ps1 b/tools/git-submodule/install.ps1 new file mode 100644 index 0000000..a5940cf --- /dev/null +++ b/tools/git-submodule/install.ps1 @@ -0,0 +1,22 @@ +$ErrorActionPreference = "Stop" + +$CONFIG = "install.conf.yaml" +$DOTBOT_DIR = "dotbot" + +$DOTBOT_BIN = "bin/dotbot" +$BASEDIR = $PSScriptRoot + +Set-Location $BASEDIR +git -C $DOTBOT_DIR submodule sync --quiet --recursive +git submodule update --init --recursive $DOTBOT_DIR + +foreach ($PYTHON in ('python', 'python3', 'python2')) { + # Python redirects to Microsoft Store in Windows 10 when not installed + if (& { $ErrorActionPreference = "SilentlyContinue" + ![string]::IsNullOrEmpty((&$PYTHON -V)) + $ErrorActionPreference = "Stop" }) { + &$PYTHON $(Join-Path $BASEDIR -ChildPath $DOTBOT_DIR | Join-Path -ChildPath $DOTBOT_BIN) -d $BASEDIR -c $CONFIG $Args + return + } +} +Write-Error "Error: Cannot find Python." diff --git a/tools/hg-subrepo/install.ps1 b/tools/hg-subrepo/install.ps1 new file mode 100644 index 0000000..39078bf --- /dev/null +++ b/tools/hg-subrepo/install.ps1 @@ -0,0 +1,21 @@ +$ErrorActionPreference = "Stop" + +$CONFIG = "install.conf.yaml" +$DOTBOT_DIR = "dotbot" + +$DOTBOT_BIN = "bin/dotbot" +$BASEDIR = $PSScriptRoot + +Set-Location $BASEDIR + +Set-Location $DOTBOT_DIR && git submodule update --init --recursive +foreach ($PYTHON in ('python', 'python3', 'python2')) { + # Python redirects to Microsoft Store in Windows 10 when not installed + if (& { $ErrorActionPreference = "SilentlyContinue" + ![string]::IsNullOrEmpty((&$PYTHON -V)) + $ErrorActionPreference = "Stop" }) { + &$PYTHON $(Join-Path $BASEDIR -ChildPath $DOTBOT_DIR | Join-Path -ChildPath $DOTBOT_BIN) -d $BASEDIR -c $CONFIG $Args + return + } +} +Write-Error "Error: Cannot find Python."