mirror of
1
0
Fork 0

Implement simple copy plugin

reuse `link` plugin to write `copy`.
This commit is contained in:
Julian_Chu 2020-04-21 02:55:17 +09:00
parent 7ffaa65482
commit ea28276eec
6 changed files with 350 additions and 0 deletions

View File

@ -235,6 +235,34 @@ the following config files equivalent:
relink: true
```
### Copy
Copy command copies files or directories to destination.
#### Format
| Copy Option | Explanation |
| -- | -- |
| `path` | The source file to copy, the same as in the shortcut syntax (default:null, automatic (see below)) |
| `create` | When true, create parent directories to the destination as needed. (default:false) |
| `force` | Force removes the old target, file or folder, and forces a new copy (default:false) |
| `skippable` | If old target exists, skip this copy (default:true) |
#### Example
```yaml
- copy:
~/.config/terminator:
create: true
path: config/terminator
~/.bash_history:
path: bash_history
skippable: true
~/.vimrc:
path: vimrc
force: true
```
### Create
Create commands specify empty directories to be created. This can be useful

View File

@ -2,3 +2,4 @@ from .clean import Clean
from .create import Create
from .link import Link
from .shell import Shell
from .copy import Copy

139
dotbot/plugins/copy.py Normal file
View File

@ -0,0 +1,139 @@
import os
import shutil
import dotbot
def exists(path):
'''
Returns true if the path exists.
'''
path = os.path.expanduser(path)
return os.path.exists(path)
def default_source(destination, source):
if source is None:
basename = os.path.basename(destination)
if basename.startswith('.'):
return basename[1:]
else:
return basename
else:
return source
class Copy(dotbot.Plugin):
'''
copy dotfiles.
'''
_directive = 'copy'
def can_handle(self, directive):
return directive == self._directive
def handle(self, directive, data):
if directive != self._directive:
raise ValueError('Copy cannot handle directive %s' % directive)
return self._iterate_copy(data)
def _make_opts(self, conf_opt):
'''
combine config options and default options
'''
opts = {
'force': False,
'skippable': True,
'create': False
}
opts.update(self._context.defaults().get('copy', {}))
if isinstance(conf_opt, dict):
opts.update(conf_opt)
return opts
def _iterate_copy(self, copy_segment):
success = True
for key, conf_opt in copy_segment.items():
destination = os.path.expandvars(key)
# for this case, create a config with only 'path'
# -copy:
# ~/foo: bar
if not isinstance(conf_opt, dict):
conf_opt = {
'path': default_source(destination, conf_opt)
}
opts = self._make_opts(conf_opt)
src = self._get_source(destination, opts)
success &= self._process_copy(src, destination, opts)
if success:
self._log.info('All files have been copied')
else:
self._log.error('Some files not copied successfully')
return success
def _process_copy(self, src, dst, opts):
if (not exists(src)):
self._log.warning('Nonexistent source %s ' % (src))
return False
if not self._ensure_parent_dir(opts, dst):
self._log.warning('cannot create parent dir for destination %s ' % (dst))
return False
if exists(dst):
# if destination files exists. but we won't skip it either force rewrite it
if not opts.get('force'):
self._log.warning('Destination exists, skip: %s ' % (dst))
# if destination files exists, and it is skippable, return True. otherwise, it failed.
return opts.get('skippable')
return self._copy(src, dst, opts)
def _get_source(self, destination, opts):
source = opts.get('path')
source = default_source(destination, source)
source = os.path.expandvars(os.path.expanduser(source))
return source
def _ensure_parent_dir(self, opts, dst):
parent = os.path.abspath(os.path.join(os.path.expanduser(dst), os.pardir))
if not exists(parent):
return opts.get('create') and self._create(dst)
else:
return True
def _create(self, path):
success = True
parent = os.path.abspath(os.path.join(os.path.expanduser(path), os.pardir))
if not exists(parent):
self._log.debug("Try to create parent: " + str(parent))
try:
os.makedirs(parent)
except OSError:
self._log.warning('Failed to create directory %s' % parent)
success = False
else:
self._log.lowinfo('Creating directory %s' % parent)
return success
def _copy(self, source, dest_name, opts):
'''
copy from source to path.
Returns true if successfully copied files.
'''
success = False
destination = os.path.expanduser(dest_name)
base_directory = self._context.base_directory()
source = os.path.join(base_directory, source)
try:
if os.path.isdir(source):
self._log.warning("copytree %s -> %s" % (source, destination))
shutil.copytree(source, destination, dirs_exist_ok=opts.get('force'))
else:
shutil.copy2(source, destination)
except OSError:
self._log.warning('Copy failed %s -> %s' % (source, destination))
else:
self._log.lowinfo('Copying file %s -> %s' % (source, destination))
success = True
return success

View File

@ -0,0 +1,45 @@
test_description='Basic copy test'
. '../test-lib.bash'
test_expect_success 'setup' '
mkdir -p ${DOTFILES}/box
echo "apple" > ${DOTFILES}/apple
echo "banana" > ${DOTFILES}/box/banana
'
test_expect_failure 'copy file should fail without create option' '
run_dotbot <<EOF
- copy:
~/more/fruits/apple:
path: apple
EOF
'
test_expect_failure 'copy directory should fail without create option' '
run_dotbot <<EOF
- copy:
~/more/fruits/in:
path: box
EOF
'
test_expect_success 'run with create option' '
run_dotbot <<EOF
- copy:
~/more/fruits/apple:
path: apple
create: true
~/more/fruits/unbox/:
path: box
create: true
EOF
'
test_expect_success 'content test' '
grep "apple" ~/more/fruits/apple &&
grep "banana" ~/more/fruits/unbox/banana
'
test_expect_success 'tear down' '
rm -rf ~/more
rm ${DOTFILES}/apple
rm -rf ${DOTFILES}/box
'

View File

@ -0,0 +1,39 @@
test_description='Basic copy test'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/apple
echo "watermelon" > ${DOTFILES}/water
echo "grape" > ${DOTFILES}/grape
mkdir -p ${DOTFILES}/more/fruits
echo "guava" > ${DOTFILES}/more/fruits/guava
'
test_expect_success 'run by conf' '
run_dotbot <<EOF
- copy:
~/apple:
~/melon: water
~/grape:
~/.fruits:
path: more/fruits
EOF
'
test_expect_success 'content test' '
grep "apple" ~/apple &&
grep "watermelon" ~/melon &&
grep "grape" ~/grape &&
grep "guava" ~/.fruits/guava
'
test_expect_success 'tear down' '
rm ~/apple
rm ~/melon
rm ~/grape
rm -rf ~/.fruits
rm ${DOTFILES}/apple
rm ${DOTFILES}/water
rm ${DOTFILES}/grape
rm -rf ${DOTFILES}/fruits
'

View File

@ -0,0 +1,98 @@
test_description='Test copy with force option'
. '../test-lib.bash'
test_expect_success 'setup' '
echo "apple" > ${DOTFILES}/apple
echo "banana" > ~/apple
mkdir -p ${DOTFILES}/fruits/box/
mkdir -p ~/fruits/box/
echo "orange" > ~/fruits/box/lemon
echo "guava" > ~/fruits/box/guava
echo "cherry" > ${DOTFILES}/fruits/box/cherry
echo "lemon" > ${DOTFILES}/fruits/box/lemon
'
# test single file
## destination file already exists, do not overwrite it
test_expect_success 'do not overwrite existing file' '
run_dotbot <<EOF
- copy:
~/apple:
EOF
'
test_expect_success 'content test(not overwrite file)' '
grep "banana" ~/apple
'
## destination file already exists, but it is not skippable
test_expect_failure 'should fail because not skippable' '
run_dotbot <<EOF
- copy:
~/apple:
skippable: false
EOF
'
test_expect_success 'content test(file not skippable)' '
grep "banana" ~/apple
'
## destination file already exists, use option 'force' to overwrite it
test_expect_success 'copy file with force option' '
run_dotbot <<eof
- copy:
~/apple:
force: true
eof
'
test_expect_success 'content test(force copy file)' '
grep "apple" ~/apple
'
# test single directory
## destination directory already exists, do not overwrite it
test_expect_success 'do not overwrite existing directory' '
run_dotbot <<EOF
- copy:
~/fruits:
EOF
'
test_expect_success 'content test(not overwrite directory)' '
grep "orange" ~/fruits/box/lemon &&
grep "guava" ~/fruits/box/guava
'
## destination directory already exists, but it is not skippable
test_expect_failure 'do not overwrite existing directory' '
run_dotbot <<EOF
- copy:
~/fruits:
skippable: false
EOF
'
test_expect_success 'content test(directory not skippable)' '
grep "orange" ~/fruits/box/lemon &&
grep "guava" ~/fruits/box/guava
'
## destination directory already exists, use option 'force' to overwrite it
test_expect_success 'copy directory with force option' '
run_dotbot <<eof
- copy:
~/fruits:
force: true
eof
'
test_expect_success 'content test(force copy directory)' '
grep "lemon" ~/fruits/box/lemon &&
grep "cherry" ~/fruits/box/cherry &&
grep "guava" ~/fruits/box/guava
'
test_expect_success 'tear down' '
rm ~/apple
rm ${DOTFILES}/apple
rm -rf ~/fruits
rm -rf ${DOTFILES}/fruits
'