mirror of
1
0
Fork 0

Merge branch 'glob'

This commit is contained in:
Anish Athalye 2018-04-13 08:49:09 -04:00
commit 2f4cc0d9cb
6 changed files with 199 additions and 19 deletions

View File

@ -165,17 +165,22 @@ files if necessary. Environment variables in paths are automatically expanded.
Link commands are specified as a dictionary mapping targets to source Link commands are specified as a dictionary mapping targets to source
locations. Source locations are specified relative to the base directory (that locations. Source locations are specified relative to the base directory (that
is specified when running the installer). Directory names should *not* contain is specified when running the installer). If linking directories, *do not* include a trailing slash.
a trailing "/" character.
Link commands support an (optional) extended configuration. In this type of Link commands support an (optional) extended configuration. In this type of
configuration, instead of specifying source locations directly, targets are configuration, instead of specifying source locations directly, targets are
mapped to extended configuration dictionaries. These dictionaries map `path` to mapped to extended configuration dictionaries.
the source path, specify `create` as `true` if the parent directory should be
created if necessary, specify `relink` as `true` if incorrect symbolic links Available extended configuration parameters:
should be automatically overwritten, specify `force` as `true` if the file or
directory should be forcibly linked, and specify `relative` as `true` if the | Link Option | Explanation |
symbolic link should have a relative path. | -- | -- |
| `path` | The target for the symlink, the same as in the shortcut syntax (default:null, automatic (see below)) |
| `create` | When true, create parent directories to the link as needed. (default:false) |
| `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 when creating the symlink (default:false, absolute links) |
| `glob` | Treat a `*` character as a wildcard, and perform link operations on all of those matches (default:false) |
#### Example #### Example
@ -207,6 +212,10 @@ the following three config files equivalent:
~/.zshrc: ~/.zshrc:
force: true force: true
path: zshrc path: zshrc
~/.config/:
glob: true
path: config/*
relink: true
``` ```
```yaml ```yaml
@ -217,6 +226,10 @@ the following three config files equivalent:
relink: true relink: true
~/.zshrc: ~/.zshrc:
force: true force: true
~/.config/:
glob: true
path: config/*
relink: true
``` ```
```json ```json
@ -230,6 +243,11 @@ the following three config files equivalent:
}, },
"~/.zshrc": { "~/.zshrc": {
"force": true "force": true
},
"~/.config/": {
"glob": true,
"path": "config/*",
"relink": true
} }
} }
} }

View File

@ -30,10 +30,11 @@ class Dispatcher(object):
try: try:
success &= plugin.handle(action, task[action]) success &= plugin.handle(action, task[action])
handled = True handled = True
except Exception: except Exception as err:
self._log.error( self._log.error(
'An error was encountered while executing action %s' % 'An error was encountered while executing action %s' %
action) action)
self._log.debug(err)
if not handled: if not handled:
success = False success = False
self._log.error('Action %s not handled' % action) self._log.error('Action %s not handled' % action)

View File

@ -1,4 +1,5 @@
import os import os
import glob
import shutil import shutil
import dotbot import dotbot
@ -27,26 +28,62 @@ class Link(dotbot.Plugin):
force = defaults.get('force', False) force = defaults.get('force', False)
relink = defaults.get('relink', False) relink = defaults.get('relink', False)
create = defaults.get('create', False) create = defaults.get('create', False)
use_glob = defaults.get('glob', False)
if isinstance(source, dict): if isinstance(source, dict):
# extended config # extended config
relative = source.get('relative', relative) relative = source.get('relative', relative)
force = source.get('force', force) force = source.get('force', force)
relink = source.get('relink', relink) relink = source.get('relink', relink)
create = source.get('create', create) create = source.get('create', create)
use_glob = source.get('glob', use_glob)
path = self._default_source(destination, source.get('path')) path = self._default_source(destination, source.get('path'))
else: else:
path = self._default_source(destination, source) path = self._default_source(destination, source)
path = os.path.expandvars(os.path.expanduser(path)) path = os.path.expandvars(os.path.expanduser(path))
if not self._exists(os.path.join(self._context.base_directory(), path)): if use_glob:
success = False self._log.debug("Globbing with path: " + str(path))
self._log.warning('Nonexistent target %s -> %s' % glob_results = glob.glob(path)
(destination, path)) if len(glob_results) is 0:
continue self._log.warning("Globbing couldn't find anything matching " + str(path))
if create: success = False
success &= self._create(destination) continue
if force or relink: glob_star_loc = path.find('*')
success &= self._delete(path, destination, relative, force) if glob_star_loc is -1 and destination[-1] is '/':
success &= self._link(path, destination, relative) self._log.error("Ambiguous action requested.")
self._log.error("No wildcard in glob, directory use undefined: " +
destination + " -> " + str(glob_results))
self._log.warning("Did you want to link the directory or into it?")
success = False
continue
elif glob_star_loc is -1 and len(glob_results) is 1:
# perform a normal link operation
if create:
success &= self._create(destination)
if force or relink:
success &= self._delete(path, destination, relative, force)
success &= self._link(path, destination, relative)
else:
self._log.lowinfo("Globs from '" + path + "': " + str(glob_results))
glob_base = path[:glob_star_loc]
for glob_full_item in glob_results:
glob_item = glob_full_item[len(glob_base):]
glob_link_destination = os.path.join(destination, glob_item)
if create:
success &= self._create(glob_link_destination)
if force or relink:
success &= self._delete(glob_full_item, glob_link_destination, relative, force)
success &= self._link(glob_full_item, glob_link_destination, relative)
else:
if create:
success &= self._create(destination)
if not self._exists(os.path.join(self._context.base_directory(), path)):
success = False
self._log.warning('Nonexistent target %s -> %s' %
(destination, path))
continue
if force or relink:
success &= self._delete(path, destination, relative, force)
success &= self._link(path, destination, relative)
if success: if success:
self._log.info('All links have been set up') self._log.info('All links have been set up')
else: else:
@ -87,6 +124,7 @@ class Link(dotbot.Plugin):
success = True success = True
parent = os.path.abspath(os.path.join(os.path.expanduser(path), os.pardir)) parent = os.path.abspath(os.path.join(os.path.expanduser(path), os.pardir))
if not self._exists(parent): if not self._exists(parent):
self._log.debug("Try to create parent: " + str(parent))
try: try:
os.makedirs(parent) os.makedirs(parent)
except OSError: except OSError:

View File

@ -0,0 +1,45 @@
test_description='link glob ambiguous'
. '../test-lib.bash'
test_expect_success 'setup' '
mkdir ${DOTFILES}/foo
'
test_expect_failure 'run 1' '
run_dotbot <<EOF
- link:
~/foo/:
path: foo
glob: true
EOF
'
test_expect_failure 'test 1' '
test -d ~/foo
'
test_expect_failure 'run 2' '
run_dotbot <<EOF
- link:
~/foo/:
path: foo/
glob: true
EOF
'
test_expect_failure 'test 2' '
test -d ~/foo
'
test_expect_success 'run 3' '
run_dotbot <<EOF
- link:
~/foo:
path: foo
glob: true
EOF
'
test_expect_success 'test 3' '
test -d ~/foo
'

View File

@ -0,0 +1,31 @@
test_description='link glob'
. '../test-lib.bash'
test_expect_success 'setup' '
mkdir ${DOTFILES}/config &&
mkdir ${DOTFILES}/config/foo &&
mkdir ${DOTFILES}/config/bar &&
echo "apple" > ${DOTFILES}/config/foo/a &&
echo "banana" > ${DOTFILES}/config/bar/b &&
echo "cherry" > ${DOTFILES}/config/bar/c
'
test_expect_success 'run' '
run_dotbot -v <<EOF
- defaults:
link:
glob: true
create: true
- link:
~/.config/: config/*/*
EOF
'
test_expect_success 'test' '
! readlink ~/.config/ &&
! readlink ~/.config/foo &&
readlink ~/.config/foo/a &&
grep "apple" ~/.config/foo/a &&
grep "banana" ~/.config/bar/b &&
grep "cherry" ~/.config/bar/c
'

47
test/tests/link-glob.bash Normal file
View File

@ -0,0 +1,47 @@
test_description='link glob'
. '../test-lib.bash'
test_expect_success 'setup 1' '
mkdir ${DOTFILES}/bin &&
echo "apple" > ${DOTFILES}/bin/a &&
echo "banana" > ${DOTFILES}/bin/b &&
echo "cherry" > ${DOTFILES}/bin/c
'
test_expect_success 'run 1' '
run_dotbot -v <<EOF
- defaults:
link:
glob: true
create: true
- link:
~/bin: bin/*
EOF
'
test_expect_success 'test 1' '
grep "apple" ~/bin/a &&
grep "banana" ~/bin/b &&
grep "cherry" ~/bin/c
'
test_expect_success 'setup 2' '
rm -rf ~/bin
'
test_expect_success 'run 2' '
run_dotbot -v <<EOF
- defaults:
link:
glob: true
create: true
- link:
~/bin/: bin/*
EOF
'
test_expect_success 'test 2' '
grep "apple" ~/bin/a &&
grep "banana" ~/bin/b &&
grep "cherry" ~/bin/c
'