1
0
Fork 0
mirror of synced 2024-06-15 13:51:08 -04:00

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 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
Create commands specify empty directories to be created. This can be useful 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 .create import Create
from .link import Link from .link import Link
from .shell import Shell 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
'