1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-12 18:10:15 +00:00

Merge pull request #292 from deadc0de6/chmod

Chmod
This commit is contained in:
deadc0de
2020-11-21 13:52:03 +01:00
committed by GitHub
51 changed files with 2967 additions and 919 deletions

View File

@@ -20,15 +20,24 @@ jobs:
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install -r tests-requirements.txt pip install -r tests-requirements.txt
pip install --user --upgrade coverage
pip install -r requirements.txt pip install -r requirements.txt
npm install -g remark-cli remark-validate-links npm install -g remark-cli remark-validate-links
npm install -g markdown-link-check npm install -g markdown-link-check
- name: Run tests - name: Run sequential tests
run: | run: |
./tests.sh ./tests.sh
env: env:
DOTDROP_FORCE_NODEBUG: yes DOTDROP_FORCE_NODEBUG: yes
DOTDROP_NOBANNER: yes DOTDROP_NOBANNER: yes
DOTDROP_WORKERS: 1
- name: Run parallel tests
run: |
./tests.sh
env:
DOTDROP_FORCE_NODEBUG: yes
DOTDROP_NOBANNER: yes
DOTDROP_WORKERS: 4
- name: Coveralls - name: Coveralls
run: | run: |
coveralls coveralls

View File

@@ -46,6 +46,7 @@ Entry | Description
`src` | dotfile path within the `dotpath` (dotfile with empty `src` are ignored and considered installed, can use `variables` and `dynvariables`, make sure to quote) `src` | dotfile path within the `dotpath` (dotfile with empty `src` are ignored and considered installed, can use `variables` and `dynvariables`, make sure to quote)
`link` | define how this dotfile is installed. Possible values: *nolink*, *link*, *link_children* (see [Symlinking dotfiles](config.md#symlink-dotfiles)) (defaults to value of `link_dotfile_default`) `link` | define how this dotfile is installed. Possible values: *nolink*, *link*, *link_children* (see [Symlinking dotfiles](config.md#symlink-dotfiles)) (defaults to value of `link_dotfile_default`)
`actions` | list of action keys that need to be defined in the **actions** entry below (see [actions](config-details.md#entry-actions)) `actions` | list of action keys that need to be defined in the **actions** entry below (see [actions](config-details.md#entry-actions))
`chmod` | defines the file permission in octal notation to apply during installation (see [permissions](config.md#permissions))
`cmpignore` | list of patterns to ignore when comparing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) `cmpignore` | list of patterns to ignore when comparing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns))
`ignoreempty` | if true empty template will not be deployed (defaults to value of `ignoreempty`) `ignoreempty` | if true empty template will not be deployed (defaults to value of `ignoreempty`)
`instignore` | list of patterns to ignore when installing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns)) `instignore` | list of patterns to ignore when installing (enclose in quotes when using wildcards, see [ignore patterns](config.md#ignore-patterns))

View File

@@ -59,6 +59,31 @@ Here are some rules on the use of variables in configs:
* external/imported `(dyn)variables` take precedence over * external/imported `(dyn)variables` take precedence over
`(dyn)variables` defined inside the main config file `(dyn)variables` defined inside the main config file
## Permissions
Dotdrop allows to control the permission applied to a dotfile using the
config dotfile entry [chmod](config-format.md#dotfiles-entry).
A [chmod](config-format.md#dotfiles-entry) entry on a directory is applied to the
directory only, not recursively.
On `import` the following rules are applied:
* if the `-m --preserve-mode` switch is provided the imported file permissions are
stored in a `chmod` entry
* if imported file permissions differ from umask then its permissions are automatically
stored in the `chmod` entry
* otherwise no `chmod` entry is added
On `install` the following rules are applied:
* if `chmod` is specified in the dotfile, it will be applied to the installed dotfile
* otherwise the permissions of the dotfile in the `dotpath` are applied.
On `update`:
* if the permissions of the file in the filesystem differ from the dotfile in the `dotpath`
then the dotfile entry `chmod` is added/updated accordingly
## Symlink dotfiles ## Symlink dotfiles
Dotdrop is able to install dotfiles in three different ways Dotdrop is able to install dotfiles in three different ways

View File

@@ -1,26 +0,0 @@
# Manage system dotfiles
Dotdrop doesn't allow to handle file rights and permissions (at least not directly). Every operations (`mkdir`, `cp`, `mv`, `ln`, file creation) are executed with the rights of the user calling dotdrop. The rights of the stored dotfile are mirrored on the deployed dotfile (`chmod` like). It works well for local/user dotfiles but doesn't allow to manage global/system config files (`/etc` or `/var` for example) directly.
Using dotdrop with `sudo` to handle local **and** global dotfiles in the same *session* is a bad idea as the resulting files will all have messed up owners.
It is therefore recommended to have two different config files (and thus two different *dotpath*) for handling these two uses cases:
* one `config.yaml` for the local/user dotfiles (with its dedicated *dotpath*)
* another config file for the global/system dotfiles (with its dedicated *dotpath*)
The default config file (`config.yaml`) is used when installing the user dotfiles as usual
```bash
# default config file is config.yaml
$ ./dotdrop.sh import <some-dotfile>
$ ./dotdrop.sh install
...
```
A different config file (for example `global-config.yaml` and its associated *dotpath*) is used when installing/managing global dotfiles and is to be used with `sudo` or directly by the root user
```bash
# specifying explicitly the config file with the --cfg switch
$ sudo ./dotdrop.sh import --cfg=global-config.yaml <some-dotfile>
$ sudo ./dotdrop.sh install --cfg=global-config.yaml
...
```

View File

@@ -28,7 +28,7 @@
## Manage system dotfiles ## Manage system dotfiles
[Manage system dotfiles](global-config-files.md) [Manage system dotfiles](system-config-files.md)
## Merge files on install ## Merge files on install

View File

@@ -0,0 +1,29 @@
# Manage system dotfiles
Dotdrop doesn't allow to handle file owernership (at least not directly). Every file operations (create/copy file/directory, create symlinks, etc) are executed with the rights of the user calling dotdrop.
Using dotdrop with `sudo` to unprivileged and privileged files in the same *session* is a bad idea as the resulting files will all have messed up owners.
It is therefore recommended to have two different config files (and thus two different *dotpath*)
for handling these two uses cases:
For example:
* one `config-user.yaml` for the local/user dotfiles (with its dedicated *dotpath*, for example `dotfiles-user`)
* one `config-root.yaml` for the system/root dotfiles (with its dedicated *dotpath*, for example `dotfiles-root`)
`config-user.yaml` is used when managing the user's dotfiles
```bash
## user config file is config-user.yaml
$ ./dotdrop.sh import --cfg config-user.yaml <some-dotfile>
$ ./dotdrop.sh install --cfg config-user.yaml
...
```
`config-root.yaml` is used when managing system's dotfiles and is to be used with `sudo` or directly by the root user
```bash
## root config file is config-root.yaml
$ sudo ./dotdrop.sh import --cfg=config-root.yaml <some-dotfile>
$ sudo ./dotdrop.sh install --cfg=config-root.yaml
...
```

View File

@@ -226,6 +226,28 @@ dotdrop. It will:
For more options, see the usage with `dotdrop --help` For more options, see the usage with `dotdrop --help`
## Concurrency
The command line switch `-w --workers` if set to a value greater than one allows to use
multiple concurrent workers to execute an operation. It can be applied to the following
commands:
* `install`
* `compare`
* `update`
It should be set to a maximum of the number of cores available (usually returned
on linux by the command `nproc`).
It may speed up the operation but cannot be used interractively (it needs `-f --force` to be set
except for `compare`) and cannot be used with `-d --dry`. Also information printed to stdout/stderr
will probably be messed up.
**WARNING** this feature hasn't been extensively tested and is to be used at your own risk.
If you try it out and find any issue, please [report it](https://github.com/deadc0de6/dotdrop/issues).
Also if you find it useful and have been able to successfully speed up your operation when using
`-w --workers`, do please also report it [in an issue](https://github.com/deadc0de6/dotdrop/issues).
## Environment variables ## Environment variables
Following environment variables can be used to specify different CLI options. Following environment variables can be used to specify different CLI options.
@@ -255,3 +277,11 @@ export DOTDROP_FORCE_NODEBUG=
```bash ```bash
export DOTDROP_TMPDIR="/tmp/dotdrop-tmp" export DOTDROP_TMPDIR="/tmp/dotdrop-tmp"
``` ```
* `DOTDROP_WORKDIR`: overwrite the `workdir` defined in the config
```bash
export DOTDROP_WORKDIR="/tmp/dotdrop-workdir"
```
* `DOTDROP_WORKERS`: overwrite the `-w --workers` cli argument
```bash
export DOTDROP_WORKERS="10"
```

View File

@@ -43,103 +43,9 @@ class CfgAggregator:
self.log = Logger() self.log = Logger()
self._load() self._load()
def _load(self): ########################################################
"""load lower level config""" # public methods
self.cfgyaml = CfgYaml(self.path, ########################################################
self.profile_key,
debug=self.debug)
# settings
self.settings = Settings.parse(None, self.cfgyaml.settings)
# dotfiles
self.dotfiles = Dotfile.parse_dict(self.cfgyaml.dotfiles)
if self.debug:
self._debug_list('dotfiles', self.dotfiles)
# profiles
self.profiles = Profile.parse_dict(self.cfgyaml.profiles)
if self.debug:
self._debug_list('profiles', self.profiles)
# actions
self.actions = Action.parse_dict(self.cfgyaml.actions)
if self.debug:
self._debug_list('actions', self.actions)
# trans_r
self.trans_r = Transform.parse_dict(self.cfgyaml.trans_r)
if self.debug:
self._debug_list('trans_r', self.trans_r)
# trans_w
self.trans_w = Transform.parse_dict(self.cfgyaml.trans_w)
if self.debug:
self._debug_list('trans_w', self.trans_w)
# variables
self.variables = self.cfgyaml.variables
if self.debug:
self._debug_dict('variables', self.variables)
# patch dotfiles in profiles
self._patch_keys_to_objs(self.profiles,
"dotfiles", self.get_dotfile)
# patch action in dotfiles actions
self._patch_keys_to_objs(self.dotfiles,
"actions", self._get_action_w_args)
# patch action in profiles actions
self._patch_keys_to_objs(self.profiles,
"actions", self._get_action_w_args)
# patch actions in settings default_actions
self._patch_keys_to_objs([self.settings],
"default_actions", self._get_action_w_args)
if self.debug:
msg = 'default actions: {}'.format(self.settings.default_actions)
self.log.dbg(msg)
# patch trans_w/trans_r in dotfiles
self._patch_keys_to_objs(self.dotfiles,
"trans_r",
self._get_trans_w_args(self._get_trans_r),
islist=False)
self._patch_keys_to_objs(self.dotfiles,
"trans_w",
self._get_trans_w_args(self._get_trans_w),
islist=False)
def _patch_keys_to_objs(self, containers, keys, get_by_key, islist=True):
"""
map for each key in the attribute 'keys' in 'containers'
the returned object from the method 'get_by_key'
"""
if not containers:
return
if self.debug:
self.log.dbg('patching {} ...'.format(keys))
for c in containers:
objects = []
okeys = getattr(c, keys)
if not okeys:
continue
if not islist:
okeys = [okeys]
for k in okeys:
o = get_by_key(k)
if not o:
err = '{} does not contain'.format(c)
err += ' a {} entry named {}'.format(keys, k)
self.log.err(err)
raise Exception(err)
objects.append(o)
if not islist:
objects = objects[0]
# if self.debug:
# er = 'patching {}.{} with {}'
# self.log.dbg(er.format(c, keys, objects))
setattr(c, keys, objects)
def del_dotfile(self, dotfile): def del_dotfile(self, dotfile):
"""remove this dotfile from the config""" """remove this dotfile from the config"""
@@ -149,27 +55,21 @@ class CfgAggregator:
"""remove this dotfile from this profile""" """remove this dotfile from this profile"""
return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key) return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key)
def _create_new_dotfile(self, src, dst, link): def new_dotfile(self, src, dst, link, chmod=None):
"""create a new dotfile"""
# get a new dotfile with a unique key
key = self._get_new_dotfile_key(dst)
if self.debug:
self.log.dbg('new dotfile key: {}'.format(key))
# add the dotfile
self.cfgyaml.add_dotfile(key, src, dst, link)
return Dotfile(key, dst, src)
def new(self, src, dst, link):
""" """
import a new dotfile import a new dotfile
@src: path in dotpath @src: path in dotpath
@dst: path in FS @dst: path in FS
@link: LinkType @link: LinkType
@chmod: file permission
""" """
dst = self.path_to_dotfile_dst(dst) dst = self.path_to_dotfile_dst(dst)
dotfile = self.get_dotfile_by_src_dst(src, dst) dotfile = self.get_dotfile_by_src_dst(src, dst)
if not dotfile: if not dotfile:
dotfile = self._create_new_dotfile(src, dst, link) dotfile = self._create_new_dotfile(src, dst, link, chmod=chmod)
if not dotfile:
return False
key = dotfile.key key = dotfile.key
ret = self.cfgyaml.add_dotfile_to_profile(key, self.profile_key) ret = self.cfgyaml.add_dotfile_to_profile(key, self.profile_key)
@@ -177,82 +77,16 @@ class CfgAggregator:
msg = 'new dotfile {} to profile {}' msg = 'new dotfile {} to profile {}'
self.log.dbg(msg.format(key, self.profile_key)) self.log.dbg(msg.format(key, self.profile_key))
self.save() if ret:
if ret and not self.dry: self._save_and_reload()
# reload
if self.debug:
self.log.dbg('reloading config')
olddebug = self.debug
self.debug = False
self._load()
self.debug = olddebug
return ret return ret
def _get_new_dotfile_key(self, dst): def update_dotfile(self, key, chmod):
"""return a new unique dotfile key""" """update an existing dotfile"""
path = os.path.expanduser(dst) ret = self.cfgyaml.update_dotfile(key, chmod)
existing_keys = self.cfgyaml.get_all_dotfile_keys() if ret:
if self.settings.longkey: self._save_and_reload()
return self._get_long_key(path, existing_keys) return ret
return self._get_short_key(path, existing_keys)
def _norm_key_elem(self, elem):
"""normalize path element for sanity"""
elem = elem.lstrip('.')
elem = elem.replace(' ', '-')
return elem.lower()
def _split_path_for_key(self, path):
"""return a list of path elements, excluded home path"""
p = strip_home(path)
dirs = []
while True:
p, f = os.path.split(p)
dirs.append(f)
if not p or not f:
break
dirs.reverse()
# remove empty entries
dirs = filter(None, dirs)
# normalize entries
return list(map(self._norm_key_elem, dirs))
def _get_long_key(self, path, keys):
"""
return a unique long key representing the
absolute path of path
"""
dirs = self._split_path_for_key(path)
prefix = self.dir_prefix if os.path.isdir(path) else self.file_prefix
key = self.key_sep.join([prefix] + dirs)
return self._uniq_key(key, keys)
def _get_short_key(self, path, keys):
"""
return a unique key where path
is known not to be an already existing dotfile
"""
dirs = self._split_path_for_key(path)
dirs.reverse()
prefix = self.dir_prefix if os.path.isdir(path) else self.file_prefix
entries = []
for d in dirs:
entries.insert(0, d)
key = self.key_sep.join([prefix] + entries)
if key not in keys:
return key
return self._uniq_key(key, keys)
def _uniq_key(self, key, keys):
"""unique dotfile key"""
newkey = key
cnt = 1
while newkey in keys:
# if unable to get a unique path
# get a random one
newkey = self.key_sep.join([key, str(cnt)])
cnt += 1
return newkey
def path_to_dotfile_dst(self, path): def path_to_dotfile_dst(self, path):
"""normalize the path to match dotfile dst""" """normalize the path to match dotfile dst"""
@@ -353,6 +187,216 @@ class CfgAggregator:
except StopIteration: except StopIteration:
return None return None
########################################################
# accessors for public methods
########################################################
def _create_new_dotfile(self, src, dst, link, chmod=None):
"""create a new dotfile"""
# get a new dotfile with a unique key
key = self._get_new_dotfile_key(dst)
if self.debug:
self.log.dbg('new dotfile key: {}'.format(key))
# add the dotfile
if not self.cfgyaml.add_dotfile(key, src, dst, link, chmod=chmod):
return None
return Dotfile(key, dst, src)
########################################################
# parsing
########################################################
def _load(self):
"""load lower level config"""
self.cfgyaml = CfgYaml(self.path,
self.profile_key,
debug=self.debug)
# settings
self.settings = Settings.parse(None, self.cfgyaml.settings)
# dotfiles
self.dotfiles = Dotfile.parse_dict(self.cfgyaml.dotfiles)
if self.debug:
self._debug_list('dotfiles', self.dotfiles)
# profiles
self.profiles = Profile.parse_dict(self.cfgyaml.profiles)
if self.debug:
self._debug_list('profiles', self.profiles)
# actions
self.actions = Action.parse_dict(self.cfgyaml.actions)
if self.debug:
self._debug_list('actions', self.actions)
# trans_r
self.trans_r = Transform.parse_dict(self.cfgyaml.trans_r)
if self.debug:
self._debug_list('trans_r', self.trans_r)
# trans_w
self.trans_w = Transform.parse_dict(self.cfgyaml.trans_w)
if self.debug:
self._debug_list('trans_w', self.trans_w)
# variables
self.variables = self.cfgyaml.variables
if self.debug:
self._debug_dict('variables', self.variables)
# patch dotfiles in profiles
self._patch_keys_to_objs(self.profiles,
"dotfiles", self.get_dotfile)
# patch action in dotfiles actions
self._patch_keys_to_objs(self.dotfiles,
"actions", self._get_action_w_args)
# patch action in profiles actions
self._patch_keys_to_objs(self.profiles,
"actions", self._get_action_w_args)
# patch actions in settings default_actions
self._patch_keys_to_objs([self.settings],
"default_actions", self._get_action_w_args)
if self.debug:
msg = 'default actions: {}'.format(self.settings.default_actions)
self.log.dbg(msg)
# patch trans_w/trans_r in dotfiles
self._patch_keys_to_objs(self.dotfiles,
"trans_r",
self._get_trans_w_args(self._get_trans_r),
islist=False)
self._patch_keys_to_objs(self.dotfiles,
"trans_w",
self._get_trans_w_args(self._get_trans_w),
islist=False)
def _patch_keys_to_objs(self, containers, keys, get_by_key, islist=True):
"""
map for each key in the attribute 'keys' in 'containers'
the returned object from the method 'get_by_key'
"""
if not containers:
return
if self.debug:
self.log.dbg('patching {} ...'.format(keys))
for c in containers:
objects = []
okeys = getattr(c, keys)
if not okeys:
continue
if not islist:
okeys = [okeys]
for k in okeys:
o = get_by_key(k)
if not o:
err = '{} does not contain'.format(c)
err += ' a {} entry named {}'.format(keys, k)
self.log.err(err)
raise Exception(err)
objects.append(o)
if not islist:
objects = objects[0]
# if self.debug:
# er = 'patching {}.{} with {}'
# self.log.dbg(er.format(c, keys, objects))
setattr(c, keys, objects)
########################################################
# dotfile key
########################################################
def _get_new_dotfile_key(self, dst):
"""return a new unique dotfile key"""
path = os.path.expanduser(dst)
existing_keys = self.cfgyaml.get_all_dotfile_keys()
if self.settings.longkey:
return self._get_long_key(path, existing_keys)
return self._get_short_key(path, existing_keys)
def _norm_key_elem(self, elem):
"""normalize path element for sanity"""
elem = elem.lstrip('.')
elem = elem.replace(' ', '-')
return elem.lower()
def _get_long_key(self, path, keys):
"""
return a unique long key representing the
absolute path of path
"""
dirs = self._split_path_for_key(path)
prefix = self.dir_prefix if os.path.isdir(path) else self.file_prefix
key = self.key_sep.join([prefix] + dirs)
return self._uniq_key(key, keys)
def _get_short_key(self, path, keys):
"""
return a unique key where path
is known not to be an already existing dotfile
"""
dirs = self._split_path_for_key(path)
dirs.reverse()
prefix = self.dir_prefix if os.path.isdir(path) else self.file_prefix
entries = []
for d in dirs:
entries.insert(0, d)
key = self.key_sep.join([prefix] + entries)
if key not in keys:
return key
return self._uniq_key(key, keys)
def _uniq_key(self, key, keys):
"""unique dotfile key"""
newkey = key
cnt = 1
while newkey in keys:
# if unable to get a unique path
# get a random one
newkey = self.key_sep.join([key, str(cnt)])
cnt += 1
return newkey
########################################################
# helpers
########################################################
def _save_and_reload(self):
if self.dry:
return
self.save()
if self.debug:
self.log.dbg('reloading config')
olddebug = self.debug
self.debug = False
self._load()
self.debug = olddebug
def _norm_path(self, path):
if not path:
return path
path = os.path.expanduser(path)
path = os.path.expandvars(path)
path = os.path.abspath(path)
return path
def _split_path_for_key(self, path):
"""return a list of path elements, excluded home path"""
p = strip_home(path)
dirs = []
while True:
p, f = os.path.split(p)
dirs.append(f)
if not p or not f:
break
dirs.reverse()
# remove empty entries
dirs = filter(None, dirs)
# normalize entries
return list(map(self._norm_key_elem, dirs))
def _get_action(self, key): def _get_action(self, key):
"""return action by key""" """return action by key"""
try: try:
@@ -404,14 +448,6 @@ class CfgAggregator:
except StopIteration: except StopIteration:
return None return None
def _norm_path(self, path):
if not path:
return path
path = os.path.expanduser(path)
path = os.path.expandvars(path)
path = os.path.abspath(path)
return path
def _debug_list(self, title, elems): def _debug_list(self, title, elems):
"""pretty print list""" """pretty print list"""
if not self.debug: if not self.debug:

View File

@@ -58,6 +58,7 @@ class CfgYaml:
key_dotfile_actions = 'actions' key_dotfile_actions = 'actions'
key_dotfile_noempty = 'ignoreempty' key_dotfile_noempty = 'ignoreempty'
key_dotfile_template = 'template' key_dotfile_template = 'template'
key_dotfile_chmod = 'chmod'
# profile # profile
key_profile_dotfiles = 'dotfiles' key_profile_dotfiles = 'dotfiles'
@@ -316,7 +317,29 @@ class CfgYaml:
"""return all existing dotfile keys""" """return all existing dotfile keys"""
return self.dotfiles.keys() return self.dotfiles.keys()
def add_dotfile(self, key, src, dst, link): def update_dotfile(self, key, chmod):
"""update an existing dotfile"""
if key not in self.dotfiles.keys():
return False
df = self._yaml_dict[self.key_dotfiles][key]
old = None
if self.key_dotfile_chmod in df:
old = df[self.key_dotfile_chmod]
if old == chmod:
return False
if self._debug:
self._dbg('update dotfile: {}'.format(key))
self._dbg('old chmod value: {}'.format(old))
self._dbg('new chmod value: {}'.format(chmod))
df = self._yaml_dict[self.key_dotfiles][key]
if not chmod:
del df[self.key_dotfile_chmod]
else:
df[self.key_dotfile_chmod] = str(format(chmod, 'o'))
self._dirty = True
return True
def add_dotfile(self, key, src, dst, link, chmod=None):
"""add a new dotfile""" """add a new dotfile"""
if key in self.dotfiles.keys(): if key in self.dotfiles.keys():
return False return False
@@ -324,16 +347,25 @@ class CfgYaml:
self._dbg('adding new dotfile: {}'.format(key)) self._dbg('adding new dotfile: {}'.format(key))
self._dbg('new dotfile src: {}'.format(src)) self._dbg('new dotfile src: {}'.format(src))
self._dbg('new dotfile dst: {}'.format(dst)) self._dbg('new dotfile dst: {}'.format(dst))
self._dbg('new dotfile link: {}'.format(link))
self._dbg('new dotfile chmod: {}'.format(chmod))
df_dict = { df_dict = {
self.key_dotfile_src: src, self.key_dotfile_src: src,
self.key_dotfile_dst: dst, self.key_dotfile_dst: dst,
} }
# link
dfl = self.settings[self.key_settings_link_dotfile_default] dfl = self.settings[self.key_settings_link_dotfile_default]
if str(link) != dfl: if str(link) != dfl:
df_dict[self.key_dotfile_link] = str(link) df_dict[self.key_dotfile_link] = str(link)
# chmod
if chmod:
df_dict[self.key_dotfile_chmod] = str(format(chmod, 'o'))
# add to global dict
self._yaml_dict[self.key_dotfiles][key] = df_dict self._yaml_dict[self.key_dotfiles][key] = df_dict
self._dirty = True self._dirty = True
return True
def del_dotfile(self, key): def del_dotfile(self, key):
"""remove this dotfile from config""" """remove this dotfile from config"""
@@ -593,7 +625,7 @@ class CfgYaml:
return new return new
def _norm_dotfiles(self, dotfiles): def _norm_dotfiles(self, dotfiles):
"""normalize dotfiles entries""" """normalize and check dotfiles entries"""
if not dotfiles: if not dotfiles:
return dotfiles return dotfiles
new = {} new = {}
@@ -623,6 +655,27 @@ class CfgYaml:
if self.key_dotfile_template not in v: if self.key_dotfile_template not in v:
val = self.settings.get(self.key_settings_template, True) val = self.settings.get(self.key_settings_template, True)
v[self.key_dotfile_template] = val v[self.key_dotfile_template] = val
# validate value of chmod if defined
if self.key_dotfile_chmod in v:
val = str(v[self.key_dotfile_chmod])
if len(val) < 3:
err = 'bad format for chmod: {}'.format(val)
self._log.err(err)
raise YamlException('config content error: {}'.format(err))
try:
int(val)
except Exception:
err = 'bad format for chmod: {}'.format(val)
self._log.err(err)
raise YamlException('config content error: {}'.format(err))
for x in list(val):
y = int(x)
if y >= 0 or y <= 7:
continue
err = 'bad format for chmod: {}'.format(val)
self._log.err(err)
raise YamlException('config content error: {}'.format(err))
v[self.key_dotfile_chmod] = int(val, 8)
return new return new

View File

@@ -10,7 +10,8 @@ import filecmp
# local imports # local imports
from dotdrop.logger import Logger from dotdrop.logger import Logger
from dotdrop.utils import must_ignore, uniq_list, diff from dotdrop.utils import must_ignore, uniq_list, diff, \
get_file_perm
class Comparator: class Comparator:
@@ -31,6 +32,7 @@ class Comparator:
if self.debug: if self.debug:
self.log.dbg('comparing {} and {}'.format(left, right)) self.log.dbg('comparing {} and {}'.format(left, right))
self.log.dbg('ignore pattern(s): {}'.format(ignore)) self.log.dbg('ignore pattern(s): {}'.format(ignore))
# test type of file # test type of file
if os.path.isdir(left) and not os.path.isdir(right): if os.path.isdir(left) and not os.path.isdir(right):
return '\"{}\" is a dir while \"{}\" is a file\n'.format(left, return '\"{}\" is a dir while \"{}\" is a file\n'.format(left,
@@ -38,14 +40,37 @@ class Comparator:
if not os.path.isdir(left) and os.path.isdir(right): if not os.path.isdir(left) and os.path.isdir(right):
return '\"{}\" is a file while \"{}\" is a dir\n'.format(left, return '\"{}\" is a file while \"{}\" is a dir\n'.format(left,
right) right)
# test content # test content
if not os.path.isdir(left): if not os.path.isdir(left):
if self.debug:
self.log.dbg('{} is a file'.format(left))
if self.debug: if self.debug:
self.log.dbg('is file') self.log.dbg('is file')
return self._comp_file(left, right, ignore) ret = self._comp_file(left, right, ignore)
if not ret:
ret = self._comp_mode(left, right)
return ret
if self.debug: if self.debug:
self.log.dbg('is directory') self.log.dbg('{} is a directory'.format(left))
return self._comp_dir(left, right, ignore)
ret = self._comp_dir(left, right, ignore)
if not ret:
ret = self._comp_mode(left, right)
return ret
def _comp_mode(self, left, right):
"""compare mode"""
left_mode = get_file_perm(left)
right_mode = get_file_perm(right)
if left_mode == right_mode:
return ''
if self.debug:
msg = 'mode differ {} ({:o}) and {} ({:o})'
self.log.dbg(msg.format(left, left_mode, right, right_mode))
ret = 'modes differ for {} ({:o}) vs {:o}\n'
return ret.format(right, right_mode, left_mode)
def _comp_file(self, left, right, ignore): def _comp_file(self, left, right, ignore):
"""compare a file""" """compare a file"""
@@ -123,7 +148,7 @@ class Comparator:
def _diff(self, left, right, header=False): def _diff(self, left, right, header=False):
"""diff two files""" """diff two files"""
out = diff(modified=left, original=right, raw=False, out = diff(modified=left, original=right,
diff_cmd=self.diff_cmd, debug=self.debug) diff_cmd=self.diff_cmd, debug=self.debug)
if header: if header:
lshort = os.path.basename(left) lshort = os.path.basename(left)

View File

@@ -9,7 +9,6 @@ import os
import sys import sys
import time import time
from concurrent import futures from concurrent import futures
import shutil
# local imports # local imports
from dotdrop.options import Options from dotdrop.options import Options
@@ -18,8 +17,10 @@ from dotdrop.templategen import Templategen
from dotdrop.installer import Installer from dotdrop.installer import Installer
from dotdrop.updater import Updater from dotdrop.updater import Updater
from dotdrop.comparator import Comparator from dotdrop.comparator import Comparator
from dotdrop.utils import get_tmpdir, removepath, strip_home, \ from dotdrop.importer import Importer
uniq_list, patch_ignores, dependencies_met from dotdrop.utils import get_tmpdir, removepath, \
uniq_list, patch_ignores, dependencies_met, \
adapt_workers
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
from dotdrop.exceptions import YamlException, UndefinedException from dotdrop.exceptions import YamlException, UndefinedException
@@ -71,6 +72,115 @@ def action_executor(o, actions, defactions, templater, post=False):
return execute return execute
def _dotfile_update(o, path, key=False):
"""
update a dotfile pointed by path
if key is false or by key (in path)
"""
updater = Updater(o.dotpath, o.variables, o.conf,
dry=o.dry, safe=o.safe, debug=o.debug,
ignore=o.update_ignore,
showpatch=o.update_showpatch)
if key:
return updater.update_key(path)
return updater.update_path(path)
def _dotfile_compare(o, dotfile, tmp):
"""
compare a dotfile
returns True if same
"""
t = _get_templater(o)
inst = Installer(create=o.create, backup=o.backup,
dry=o.dry, base=o.dotpath,
workdir=o.workdir, debug=o.debug,
backup_suffix=o.install_backup_suffix,
diff_cmd=o.diff_command)
comp = Comparator(diff_cmd=o.diff_command, debug=o.debug)
# add dotfile variables
newvars = dotfile.get_dotfile_variables()
t.add_tmp_vars(newvars=newvars)
# dotfiles does not exist / not installed
if o.debug:
LOG.dbg('comparing {}'.format(dotfile))
src = dotfile.src
if not os.path.lexists(os.path.expanduser(dotfile.dst)):
line = '=> compare {}: \"{}\" does not exist on destination'
LOG.log(line.format(dotfile.key, dotfile.dst))
return False
# apply transformation
tmpsrc = None
if dotfile.trans_r:
if o.debug:
LOG.dbg('applying transformation before comparing')
tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug)
if not tmpsrc:
# could not apply trans
return False
src = tmpsrc
# is a symlink pointing to itself
asrc = os.path.join(o.dotpath, os.path.expanduser(src))
adst = os.path.expanduser(dotfile.dst)
if os.path.samefile(asrc, adst):
if o.debug:
line = '=> compare {}: diffing with \"{}\"'
LOG.dbg(line.format(dotfile.key, dotfile.dst))
LOG.dbg('points to itself')
return True
insttmp = None
if dotfile.template and Templategen.is_template(src):
# install dotfile to temporary dir for compare
ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst,
is_template=True,
chmod=dotfile.chmod)
if not ret:
# failed to install to tmp
line = '=> compare {} error: {}'
LOG.log(line.format(dotfile.key, err))
LOG.err(err)
return False
src = insttmp
# compare
ignores = list(set(o.compare_ignore + dotfile.cmpignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug)
diff = comp.compare(src, dotfile.dst, ignore=ignores)
# clean tmp transformed dotfile if any
if tmpsrc:
tmpsrc = os.path.join(o.dotpath, tmpsrc)
if os.path.exists(tmpsrc):
removepath(tmpsrc, LOG)
# clean tmp template dotfile if any
if insttmp:
if os.path.exists(insttmp):
removepath(insttmp, LOG)
if diff != '':
# print diff results
line = '=> compare {}: diffing with \"{}\"'
LOG.log(line.format(dotfile.key, dotfile.dst))
if o.compare_fileonly:
LOG.raw('<files are different>')
else:
LOG.emph(diff)
return False
# no difference
if o.debug:
line = '=> compare {}: diffing with \"{}\"'
LOG.dbg(line.format(dotfile.key, dotfile.dst))
LOG.dbg('same file')
return True
def _dotfile_install(o, dotfile, tmpdir=None): def _dotfile_install(o, dotfile, tmpdir=None):
""" """
install a dotfile install a dotfile
@@ -97,17 +207,22 @@ def _dotfile_install(o, dotfile, tmpdir=None):
LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key)) LOG.dbg('installing dotfile: \"{}\"'.format(dotfile.key))
LOG.dbg(dotfile.prt()) LOG.dbg(dotfile.prt())
is_template = dotfile.template and Templategen.is_template(dotfile.src)
if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK: if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.LINK:
# link # link
r, err = inst.link(t, dotfile.src, dotfile.dst, r, err = inst.install(t, dotfile.src, dotfile.dst,
actionexec=pre_actions_exec, dotfile.link,
template=dotfile.template) actionexec=pre_actions_exec,
is_template=is_template,
chmod=dotfile.chmod)
elif hasattr(dotfile, 'link') and \ elif hasattr(dotfile, 'link') and \
dotfile.link == LinkTypes.LINK_CHILDREN: dotfile.link == LinkTypes.LINK_CHILDREN:
# link_children # link_children
r, err = inst.link_children(t, dotfile.src, dotfile.dst, r, err = inst.install(t, dotfile.src, dotfile.dst,
actionexec=pre_actions_exec, dotfile.link,
template=dotfile.template) actionexec=pre_actions_exec,
is_template=is_template,
chmod=dotfile.chmod)
else: else:
# nolink # nolink
src = dotfile.src src = dotfile.src
@@ -120,10 +235,12 @@ def _dotfile_install(o, dotfile, tmpdir=None):
ignores = list(set(o.install_ignore + dotfile.instignore)) ignores = list(set(o.install_ignore + dotfile.instignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug) ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug)
r, err = inst.install(t, src, dotfile.dst, r, err = inst.install(t, src, dotfile.dst,
LinkTypes.NOLINK,
actionexec=pre_actions_exec, actionexec=pre_actions_exec,
noempty=dotfile.noempty, noempty=dotfile.noempty,
ignore=ignores, ignore=ignores,
template=dotfile.template) is_template=is_template,
chmod=dotfile.chmod)
if tmp: if tmp:
tmp = os.path.join(o.dotpath, tmp) tmp = os.path.join(o.dotpath, tmp)
if os.path.exists(tmp): if os.path.exists(tmp):
@@ -161,6 +278,9 @@ def cmd_install(o):
"""install dotfiles for this profile""" """install dotfiles for this profile"""
dotfiles = o.dotfiles dotfiles = o.dotfiles
prof = o.conf.get_profile() prof = o.conf.get_profile()
adapt_workers(o, LOG)
pro_pre_actions = prof.get_pre_actions() if prof else [] pro_pre_actions = prof.get_pre_actions() if prof else []
pro_post_actions = prof.get_post_actions() if prof else [] pro_post_actions = prof.get_post_actions() if prof else []
@@ -189,14 +309,23 @@ def cmd_install(o):
return False return False
# install each dotfile # install each dotfile
if o.install_parallel > 1: if o.workers > 1:
# in parallel # in parallel
ex = futures.ThreadPoolExecutor(max_workers=o.install_parallel) if o.debug:
LOG.dbg('run with {} workers'.format(o.workers))
ex = futures.ThreadPoolExecutor(max_workers=o.workers)
wait_for = [ wait_for = []
ex.submit(_dotfile_install, o, dotfile, tmpdir=tmpdir) for dotfile in dotfiles:
for dotfile in dotfiles if not dotfile.src or not dotfile.dst:
] # fake dotfile are always considered installed
if o.debug:
LOG.dbg('fake dotfile installed')
installed += 1
else:
j = ex.submit(_dotfile_install, o, dotfile, tmpdir=tmpdir)
wait_for.append(j)
# check result
for f in futures.as_completed(wait_for): for f in futures.as_completed(wait_for):
r, key, err = f.result() r, key, err = f.result()
if r: if r:
@@ -207,7 +336,16 @@ def cmd_install(o):
else: else:
# sequentially # sequentially
for dotfile in dotfiles: for dotfile in dotfiles:
r, key, err = _dotfile_install(o, dotfile, tmpdir=tmpdir) if not dotfile.src or not dotfile.dst:
# fake dotfile are always considered installed
if o.debug:
LOG.dbg('fake dotfile installed')
key = dotfile.key
r = True
err = None
else:
r, key, err = _dotfile_install(o, dotfile, tmpdir=tmpdir)
# check result
if r: if r:
installed += 1 installed += 1
elif err: elif err:
@@ -239,153 +377,101 @@ def cmd_compare(o, tmp):
msg = 'no dotfile defined for this profile (\"{}\")' msg = 'no dotfile defined for this profile (\"{}\")'
LOG.warn(msg.format(o.profile)) LOG.warn(msg.format(o.profile))
return True return True
# compare only specific files # compare only specific files
same = True
selected = dotfiles selected = dotfiles
if o.compare_focus: if o.compare_focus:
selected = _select(o.compare_focus, dotfiles) selected = _select(o.compare_focus, dotfiles)
if len(selected) < 1: if len(selected) < 1:
LOG.log('\nno dotfile to compare')
return False return False
t = _get_templater(o) same = True
tvars = t.add_tmp_vars() cnt = 0
inst = Installer(create=o.create, backup=o.backup, if o.workers > 1:
dry=o.dry, base=o.dotpath, # in parallel
workdir=o.workdir, debug=o.debug,
backup_suffix=o.install_backup_suffix,
diff_cmd=o.diff_command)
comp = Comparator(diff_cmd=o.diff_command, debug=o.debug)
for dotfile in selected:
if not dotfile.src and not dotfile.dst:
# ignore fake dotfile
continue
# add dotfile variables
t.restore_vars(tvars)
newvars = dotfile.get_dotfile_variables()
t.add_tmp_vars(newvars=newvars)
# dotfiles does not exist / not installed
if o.debug: if o.debug:
LOG.dbg('comparing {}'.format(dotfile)) LOG.dbg('run with {} workers'.format(o.workers))
src = dotfile.src ex = futures.ThreadPoolExecutor(max_workers=o.workers)
if not os.path.lexists(os.path.expanduser(dotfile.dst)): wait_for = []
line = '=> compare {}: \"{}\" does not exist on destination' for dotfile in selected:
LOG.log(line.format(dotfile.key, dotfile.dst)) j = ex.submit(_dotfile_compare, o, dotfile, tmp)
same = False wait_for.append(j)
continue # check result
for f in futures.as_completed(wait_for):
# apply transformation if not dotfile.src and not dotfile.dst:
tmpsrc = None # ignore fake dotfile
if dotfile.trans_r:
if o.debug:
LOG.dbg('applying transformation before comparing')
tmpsrc = apply_trans(o.dotpath, dotfile, t, debug=o.debug)
if not tmpsrc:
# could not apply trans
same = False
continue continue
src = tmpsrc if not f.result():
same = False
# is a symlink pointing to itself cnt += 1
asrc = os.path.join(o.dotpath, os.path.expanduser(src)) else:
adst = os.path.expanduser(dotfile.dst) # sequentially
if os.path.samefile(asrc, adst): for dotfile in selected:
if o.debug: if not dotfile.src and not dotfile.dst:
line = '=> compare {}: diffing with \"{}\"' # ignore fake dotfile
LOG.dbg(line.format(dotfile.key, dotfile.dst)) continue
LOG.dbg('points to itself') if not _dotfile_compare(o, dotfile, tmp):
continue same = False
cnt += 1
# install dotfile to temporary dir and compare
ret, err, insttmp = inst.install_to_temp(t, tmp, src, dotfile.dst,
template=dotfile.template)
if not ret:
# failed to install to tmp
line = '=> compare {}: error'
LOG.log(line.format(dotfile.key, err))
LOG.err(err)
same = False
continue
ignores = list(set(o.compare_ignore + dotfile.cmpignore))
ignores = patch_ignores(ignores, dotfile.dst, debug=o.debug)
diff = comp.compare(insttmp, dotfile.dst, ignore=ignores)
# clean tmp transformed dotfile if any
if tmpsrc:
tmpsrc = os.path.join(o.dotpath, tmpsrc)
if os.path.exists(tmpsrc):
removepath(tmpsrc, LOG)
if diff == '':
# no difference
if o.debug:
line = '=> compare {}: diffing with \"{}\"'
LOG.dbg(line.format(dotfile.key, dotfile.dst))
LOG.dbg('same file')
else:
# print diff results
line = '=> compare {}: diffing with \"{}\"'
LOG.log(line.format(dotfile.key, dotfile.dst))
if o.compare_fileonly:
LOG.raw('<files are different>')
else:
LOG.emph(diff)
same = False
LOG.log('\n{} dotfile(s) compared.'.format(cnt))
return same return same
def cmd_update(o): def cmd_update(o):
"""update the dotfile(s) from path(s) or key(s)""" """update the dotfile(s) from path(s) or key(s)"""
ret = True cnt = 0
paths = o.update_path paths = o.update_path
iskey = o.update_iskey iskey = o.update_iskey
ignore = o.update_ignore
showpatch = o.update_showpatch adapt_workers(o, LOG)
if not paths: if not paths:
# update the entire profile # update the entire profile
if iskey: if iskey:
if o.debug:
LOG.dbg('update by keys: {}'.format(paths))
paths = [d.key for d in o.dotfiles] paths = [d.key for d in o.dotfiles]
else: else:
if o.debug:
LOG.dbg('update by paths: {}'.format(paths))
paths = [d.dst for d in o.dotfiles] paths = [d.dst for d in o.dotfiles]
msg = 'Update all dotfiles for profile \"{}\"'.format(o.profile) msg = 'Update all dotfiles for profile \"{}\"'.format(o.profile)
if o.safe and not LOG.ask(msg): if o.safe and not LOG.ask(msg):
LOG.log('\n{} file(s) updated.'.format(cnt))
return False return False
if not paths: if not paths:
LOG.log('no dotfile to update') LOG.log('\nno dotfile to update')
return True return True
if o.debug: if o.debug:
LOG.dbg('dotfile to update: {}'.format(paths)) LOG.dbg('dotfile to update: {}'.format(paths))
updater = Updater(o.dotpath, o.variables, # update each dotfile
o.conf.get_dotfile, if o.workers > 1:
o.conf.get_dotfile_by_dst, # in parallel
o.conf.path_to_dotfile_dst,
dry=o.dry, safe=o.safe, debug=o.debug,
ignore=ignore, showpatch=showpatch)
if not iskey:
# update paths
if o.debug: if o.debug:
LOG.dbg('update by paths: {}'.format(paths)) LOG.dbg('run with {} workers'.format(o.workers))
ex = futures.ThreadPoolExecutor(max_workers=o.workers)
wait_for = []
for path in paths: for path in paths:
if not updater.update_path(path): j = ex.submit(_dotfile_update, o, path, key=iskey)
ret = False wait_for.append(j)
# check result
for f in futures.as_completed(wait_for):
if f.result():
cnt += 1
else: else:
# update keys # sequentially
keys = paths for path in paths:
if not keys: if _dotfile_update(o, path, key=iskey):
# if not provided, take all keys cnt += 1
keys = [d.key for d in o.dotfiles]
if o.debug: LOG.log('\n{} file(s) updated.'.format(cnt))
LOG.dbg('update by keys: {}'.format(keys)) return cnt == len(paths)
for key in keys:
if not updater.update_key(key):
ret = False
return ret
def cmd_importer(o): def cmd_importer(o):
@@ -393,119 +479,26 @@ def cmd_importer(o):
ret = True ret = True
cnt = 0 cnt = 0
paths = o.import_path paths = o.import_path
importer = Importer(o.profile, o.conf, o.dotpath, o.diff_command,
dry=o.dry, safe=o.safe, debug=o.debug,
keepdot=o.keepdot)
for path in paths: for path in paths:
if o.debug: r = importer.import_path(path, import_as=o.import_as,
LOG.dbg('trying to import {}'.format(path)) import_link=o.import_link,
if not os.path.exists(path): import_mode=o.import_mode)
LOG.err('\"{}\" does not exist, ignored!'.format(path)) if r < 0:
ret = False ret = False
continue elif r > 0:
dst = path.rstrip(os.sep)
dst = os.path.abspath(dst)
if o.safe:
# ask for symlinks
realdst = os.path.realpath(dst)
if dst != realdst:
msg = '\"{}\" is a symlink, dereference it and continue?'
if not LOG.ask(msg.format(dst)):
continue
src = strip_home(dst)
if o.import_as:
# handle import as
src = os.path.expanduser(o.import_as)
src = src.rstrip(os.sep)
src = os.path.abspath(src)
src = strip_home(src)
if o.debug:
LOG.dbg('import src for {} as {}'.format(dst, src))
strip = '.' + os.sep
if o.keepdot:
strip = os.sep
src = src.lstrip(strip)
# set the link attribute
linktype = o.import_link
if linktype == LinkTypes.LINK_CHILDREN and \
not os.path.isdir(path):
LOG.err('importing \"{}\" failed!'.format(path))
ret = False
continue
if o.debug:
LOG.dbg('import dotfile: src:{} dst:{}'.format(src, dst))
# test no other dotfile exists with same
# dst for this profile but different src
dfs = o.conf.get_dotfile_by_dst(dst)
if dfs:
invalid = False
for df in dfs:
profiles = o.conf.get_profiles_by_dotfile_key(df.key)
profiles = [x.key for x in profiles]
if o.profile in profiles and \
not o.conf.get_dotfile_by_src_dst(src, dst):
# same profile
# different src
LOG.err('duplicate dotfile for this profile')
ret = False
invalid = True
break
if invalid:
continue
# prepare hierarchy for dotfile
srcf = os.path.join(o.dotpath, src)
overwrite = not os.path.exists(srcf)
if os.path.exists(srcf):
overwrite = True
if o.safe:
c = Comparator(debug=o.debug, diff_cmd=o.diff_command)
diff = c.compare(srcf, dst)
if diff != '':
# files are different, dunno what to do
LOG.log('diff \"{}\" VS \"{}\"'.format(dst, srcf))
LOG.emph(diff)
# ask user
msg = 'Dotfile \"{}\" already exists, overwrite?'
overwrite = LOG.ask(msg.format(srcf))
if o.debug:
LOG.dbg('will overwrite: {}'.format(overwrite))
if overwrite:
cmd = 'mkdir -p {}'.format(os.path.dirname(srcf))
if o.dry:
LOG.dry('would run: {}'.format(cmd))
else:
try:
os.makedirs(os.path.dirname(srcf), exist_ok=True)
except Exception:
LOG.err('importing \"{}\" failed!'.format(path))
ret = False
continue
if o.dry:
LOG.dry('would copy {} to {}'.format(dst, srcf))
else:
if os.path.isdir(dst):
if os.path.exists(srcf):
shutil.rmtree(srcf)
shutil.copytree(dst, srcf)
else:
shutil.copy2(dst, srcf)
retconf = o.conf.new(src, dst, linktype)
if retconf:
LOG.sub('\"{}\" imported'.format(path))
cnt += 1 cnt += 1
else:
LOG.warn('\"{}\" ignored'.format(path))
if o.dry: if o.dry:
LOG.dry('new config file would be:') LOG.dry('new config file would be:')
LOG.raw(o.conf.dump()) LOG.raw(o.conf.dump())
else: else:
o.conf.save() o.conf.save()
LOG.log('\n{} file(s) imported.'.format(cnt)) LOG.log('\n{} file(s) imported.'.format(cnt))
return ret return ret
@@ -522,7 +515,7 @@ def cmd_list_profiles(o):
LOG.log('') LOG.log('')
def cmd_list_files(o): def cmd_files(o):
"""list all dotfiles for a specific profile""" """list all dotfiles for a specific profile"""
if o.profile not in [p.key for p in o.profiles]: if o.profile not in [p.key for p in o.profiles]:
LOG.warn('unknown profile \"{}\"'.format(o.profile)) LOG.warn('unknown profile \"{}\"'.format(o.profile))
@@ -540,12 +533,18 @@ def cmd_list_files(o):
fmt = '{},dst:{},src:{},link:{}' fmt = '{},dst:{},src:{},link:{}'
fmt = fmt.format(dotfile.key, dotfile.dst, fmt = fmt.format(dotfile.key, dotfile.dst,
dotfile.src, dotfile.link.name.lower()) dotfile.src, dotfile.link.name.lower())
if dotfile.chmod:
fmt += ',chmod:{:o}'
else:
fmt += ',chmod:None'
LOG.raw(fmt) LOG.raw(fmt)
else: else:
LOG.log('{}'.format(dotfile.key), bold=True) LOG.log('{}'.format(dotfile.key), bold=True)
LOG.sub('dst: {}'.format(dotfile.dst)) LOG.sub('dst: {}'.format(dotfile.dst))
LOG.sub('src: {}'.format(dotfile.src)) LOG.sub('src: {}'.format(dotfile.src))
LOG.sub('link: {}'.format(dotfile.link.name.lower())) LOG.sub('link: {}'.format(dotfile.link.name.lower()))
if dotfile.chmod:
LOG.sub('chmod: {:o}'.format(dotfile.chmod))
LOG.log('') LOG.log('')
@@ -596,7 +595,8 @@ def cmd_remove(o):
k = dotfile.key k = dotfile.key
# ignore if uses any type of link # ignore if uses any type of link
if dotfile.link != LinkTypes.NOLINK: if dotfile.link != LinkTypes.NOLINK:
LOG.warn('dotfile uses link, remove manually') msg = '{} uses link/link_children, remove manually'
LOG.warn(msg.format(k))
continue continue
if o.debug: if o.debug:
@@ -679,12 +679,16 @@ def _get_templater(o):
def _detail(dotpath, dotfile): def _detail(dotpath, dotfile):
"""display details on all files under a dotfile entry""" """display details on all files under a dotfile entry"""
LOG.log('{} (dst: \"{}\", link: {})'.format(dotfile.key, dotfile.dst, entry = '{}'.format(dotfile.key)
dotfile.link.name.lower())) attribs = []
attribs.append('dst: \"{}\"'.format(dotfile.dst))
attribs.append('link: \"{}\"'.format(dotfile.link.name.lower()))
attribs.append('chmod: \"{}\"'.format(dotfile.chmod))
LOG.log('{} ({})'.format(entry, ', '.join(attribs)))
path = os.path.join(dotpath, os.path.expanduser(dotfile.src)) path = os.path.join(dotpath, os.path.expanduser(dotfile.src))
if not os.path.isdir(path): if not os.path.isdir(path):
template = 'no' template = 'no'
if Templategen.is_template(path): if dotfile.template and Templategen.is_template(path):
template = 'yes' template = 'yes'
LOG.sub('{} (template:{})'.format(path, template)) LOG.sub('{} (template:{})'.format(path, template))
else: else:
@@ -692,7 +696,7 @@ def _detail(dotpath, dotfile):
for f in files: for f in files:
p = os.path.join(root, f) p = os.path.join(root, f)
template = 'no' template = 'no'
if Templategen.is_template(p): if dotfile.template and Templategen.is_template(p):
template = 'yes' template = 'yes'
LOG.sub('{} (template:{})'.format(p, template)) LOG.sub('{} (template:{})'.format(p, template))
@@ -778,7 +782,7 @@ def main():
command = 'files' command = 'files'
if o.debug: if o.debug:
LOG.dbg('running cmd: {}'.format(command)) LOG.dbg('running cmd: {}'.format(command))
cmd_list_files(o) cmd_files(o)
elif o.cmd_install: elif o.cmd_install:
# install the dotfiles stored in dotdrop # install the dotfiles stored in dotdrop

View File

@@ -22,7 +22,7 @@ class Dotfile(DictParser):
actions=[], trans_r=None, trans_w=None, actions=[], trans_r=None, trans_w=None,
link=LinkTypes.NOLINK, noempty=False, link=LinkTypes.NOLINK, noempty=False,
cmpignore=[], upignore=[], cmpignore=[], upignore=[],
instignore=[], template=True): instignore=[], template=True, chmod=None):
""" """
constructor constructor
@key: dotfile key @key: dotfile key
@@ -37,6 +37,7 @@ class Dotfile(DictParser):
@cmpignore: patterns to ignore when comparing @cmpignore: patterns to ignore when comparing
@instignore: patterns to ignore when installing @instignore: patterns to ignore when installing
@template: template this dotfile @template: template this dotfile
@chmod: file permission
""" """
self.actions = actions self.actions = actions
self.dst = dst self.dst = dst
@@ -50,6 +51,7 @@ class Dotfile(DictParser):
self.cmpignore = cmpignore self.cmpignore = cmpignore
self.instignore = instignore self.instignore = instignore
self.template = template self.template = template
self.chmod = chmod
if self.link != LinkTypes.NOLINK and \ if self.link != LinkTypes.NOLINK and \
( (
@@ -113,6 +115,8 @@ class Dotfile(DictParser):
msg += ', dst:\"{}\"'.format(self.dst) msg += ', dst:\"{}\"'.format(self.dst)
msg += ', link:\"{}\"'.format(str(self.link)) msg += ', link:\"{}\"'.format(str(self.link))
msg += ', template:{}'.format(self.template) msg += ', template:{}'.format(self.template)
if self.chmod:
msg += ', chmod:{:o}'.format(self.chmod)
return msg return msg
def prt(self): def prt(self):
@@ -123,6 +127,8 @@ class Dotfile(DictParser):
out += '\n{}dst: \"{}\"'.format(indent, self.dst) out += '\n{}dst: \"{}\"'.format(indent, self.dst)
out += '\n{}link: \"{}\"'.format(indent, str(self.link)) out += '\n{}link: \"{}\"'.format(indent, str(self.link))
out += '\n{}template: \"{}\"'.format(indent, str(self.template)) out += '\n{}template: \"{}\"'.format(indent, str(self.template))
if self.chmod:
out += '\n{}chmod: \"{:o}\"'.format(indent, self.chmod)
out += '\n{}pre-action:'.format(indent) out += '\n{}pre-action:'.format(indent)
some = self.get_pre_actions() some = self.get_pre_actions()

203
dotdrop/importer.py Normal file
View File

@@ -0,0 +1,203 @@
"""
author: deadc0de6 (https://github.com/deadc0de6)
Copyright (c) 2020, deadc0de6
handle import of dotfiles
"""
import os
import shutil
# local imports
from dotdrop.logger import Logger
from dotdrop.utils import strip_home, get_default_file_perms, \
get_file_perm, get_umask
from dotdrop.linktypes import LinkTypes
from dotdrop.comparator import Comparator
class Importer:
def __init__(self, profile, conf, dotpath, diff_cmd,
dry=False, safe=True, debug=False,
keepdot=True):
"""constructor
@profile: the selected profile
@conf: configuration manager
@dotpath: dotfiles dotpath
@diff_cmd: diff command to use
@dry: simulate
@safe: ask for overwrite if True
@debug: enable debug
@keepdot: keep dot prefix
"""
self.profile = profile
self.conf = conf
self.dotpath = dotpath
self.diff_cmd = diff_cmd
self.dry = dry
self.safe = safe
self.debug = debug
self.keepdot = keepdot
self.umask = get_umask()
self.log = Logger()
def import_path(self, path, import_as=None,
import_link=LinkTypes.NOLINK, import_mode=False):
"""
import a dotfile pointed by path
returns:
1: 1 dotfile imported
0: ignored
-1: error
"""
if self.debug:
self.log.dbg('import {}'.format(path))
if not os.path.exists(path):
self.log.err('\"{}\" does not exist, ignored!'.format(path))
return -1
return self._import(path, import_as=import_as,
import_link=import_link, import_mode=import_mode)
def _import(self, path, import_as=None,
import_link=LinkTypes.NOLINK, import_mode=False):
"""
import path
returns:
1: 1 dotfile imported
0: ignored
-1: error
"""
# normalize path
dst = path.rstrip(os.sep)
dst = os.path.abspath(dst)
# ask confirmation for symlinks
if self.safe:
realdst = os.path.realpath(dst)
if dst != realdst:
msg = '\"{}\" is a symlink, dereference it and continue?'
if not self.log.ask(msg.format(dst)):
return 0
# create src path
src = strip_home(dst)
if import_as:
# handle import as
src = os.path.expanduser(import_as)
src = src.rstrip(os.sep)
src = os.path.abspath(src)
src = strip_home(src)
if self.debug:
self.log.dbg('import src for {} as {}'.format(dst, src))
# with or without dot prefix
strip = '.' + os.sep
if self.keepdot:
strip = os.sep
src = src.lstrip(strip)
# get the permission
perm = get_file_perm(dst)
# get the link attribute
linktype = import_link
if linktype == LinkTypes.LINK_CHILDREN and \
not os.path.isdir(path):
self.log.err('importing \"{}\" failed!'.format(path))
return -1
if self._already_exists(src, dst):
return -1
if self.debug:
self.log.dbg('import dotfile: src:{} dst:{}'.format(src, dst))
if not self._prepare_hierarchy(src, dst):
return -1
# handle file mode
chmod = None
dflperm = get_default_file_perms(dst, self.umask)
if self.debug:
self.log.dbg('import mode: {}'.format(import_mode))
if import_mode or perm != dflperm:
if self.debug:
msg = 'adopt mode {:o} (umask {:o})'
self.log.dbg(msg.format(perm, dflperm))
chmod = perm
# add file to config file
retconf = self.conf.new_dotfile(src, dst, linktype, chmod=chmod)
if not retconf:
self.log.warn('\"{}\" ignored'.format(path))
return 0
self.log.sub('\"{}\" imported'.format(path))
return 1
def _prepare_hierarchy(self, src, dst):
"""prepare hierarchy for dotfile"""
srcf = os.path.join(self.dotpath, src)
# a dotfile in dotpath already exists at that spot
if os.path.exists(srcf):
if self.safe:
c = Comparator(debug=self.debug,
diff_cmd=self.diff_cmd)
diff = c.compare(srcf, dst)
if diff != '':
# files are different, dunno what to do
self.log.log('diff \"{}\" VS \"{}\"'.format(dst, srcf))
self.log.emph(diff)
# ask user
msg = 'Dotfile \"{}\" already exists, overwrite?'
if not self.log.ask(msg.format(srcf)):
return False
if self.debug:
self.log.dbg('will overwrite existing file')
# create directory hierarchy
cmd = 'mkdir -p {}'.format(os.path.dirname(srcf))
if self.dry:
self.log.dry('would run: {}'.format(cmd))
else:
try:
os.makedirs(os.path.dirname(srcf), exist_ok=True)
except Exception:
self.log.err('importing \"{}\" failed!'.format(dst))
return False
if self.dry:
self.log.dry('would copy {} to {}'.format(dst, srcf))
else:
# copy the file to the dotpath
if os.path.isdir(dst):
if os.path.exists(srcf):
shutil.rmtree(srcf)
shutil.copytree(dst, srcf)
else:
shutil.copy2(dst, srcf)
return True
def _already_exists(self, src, dst):
"""
test no other dotfile exists with same
dst for this profile but different src
"""
dfs = self.conf.get_dotfile_by_dst(dst)
if not dfs:
return False
for df in dfs:
profiles = self.conf.get_profiles_by_dotfile_key(df.key)
profiles = [x.key for x in profiles]
if self.profile in profiles and \
not self.conf.get_dotfile_by_src_dst(src, dst):
# same profile
# different src
self.log.err('duplicate dotfile for this profile')
return True
return False

View File

@@ -11,7 +11,7 @@ import shutil
# local imports # local imports
from dotdrop.logger import Logger from dotdrop.logger import Logger
from dotdrop.templategen import Templategen from dotdrop.linktypes import LinkTypes
import dotdrop.utils as utils import dotdrop.utils as utils
from dotdrop.exceptions import UndefinedException from dotdrop.exceptions import UndefinedException
@@ -22,7 +22,7 @@ class Installer:
dry=False, safe=False, workdir='~/.config/dotdrop', dry=False, safe=False, workdir='~/.config/dotdrop',
debug=False, diff=True, totemp=None, showdiff=False, debug=False, diff=True, totemp=None, showdiff=False,
backup_suffix='.dotdropbak', diff_cmd=''): backup_suffix='.dotdropbak', diff_cmd=''):
"""constructor """
@base: directory path where to search for templates @base: directory path where to search for templates
@create: create directory hierarchy if missing when installing @create: create directory hierarchy if missing when installing
@backup: backup existing dotfile when installing @backup: backup existing dotfile when installing
@@ -40,7 +40,11 @@ class Installer:
self.backup = backup self.backup = backup
self.dry = dry self.dry = dry
self.safe = safe self.safe = safe
self.workdir = os.path.expanduser(workdir) workdir = os.path.expanduser(workdir)
workdir = os.path.normpath(workdir)
self.workdir = workdir
base = os.path.expanduser(base)
base = os.path.normpath(base)
self.base = base self.base = base
self.debug = debug self.debug = debug
self.diff = diff self.diff = diff
@@ -48,34 +52,33 @@ class Installer:
self.showdiff = showdiff self.showdiff = showdiff
self.backup_suffix = backup_suffix self.backup_suffix = backup_suffix
self.diff_cmd = diff_cmd self.diff_cmd = diff_cmd
self.comparing = False
self.action_executed = False self.action_executed = False
# avoids printing file copied logs
# when using install_to_tmp for comparing
self.comparing = False
self.log = Logger() self.log = Logger()
def _log_install(self, boolean, err): ########################################################
if not self.debug: # public methods
return boolean, err ########################################################
if boolean:
self.log.dbg('install: SUCCESS')
else:
if err:
self.log.dbg('install: ERROR: {}'.format(err))
else:
self.log.dbg('install: IGNORED')
return boolean, err
def install(self, templater, src, dst, def install(self, templater, src, dst, linktype,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[], template=True): ignore=[], is_template=True,
chmod=None):
""" """
install src to dst using a template install src to dst
@templater: the templater object @templater: the templater object
@src: dotfile source path in dotpath @src: dotfile source path in dotpath
@dst: dotfile destination path in the FS @dst: dotfile destination path in the FS
@linktype: linktypes.LinkTypes
@actionexec: action executor callback @actionexec: action executor callback
@noempty: render empty template flag @noempty: render empty template flag
@ignore: pattern to ignore when installing @ignore: pattern to ignore when installing
@template: template this dotfile @is_template: this dotfile is a template
@chmod: rights to apply if any
return return
- True, None : success - True, None : success
@@ -83,126 +86,200 @@ class Installer:
- False, None : ignored - False, None : ignored
""" """
if self.debug: if self.debug:
self.log.dbg('installing \"{}\" to \"{}\"'.format(src, dst)) msg = 'installing \"{}\" to \"{}\" (link: {})'
if not dst or not src: self.log.dbg(msg.format(src, dst, str(linktype)))
if self.debug: src, dst, cont, err = self._check_paths(src, dst, chmod)
self.log.dbg('empty dst for {}'.format(src)) if not cont:
return self._log_install(True, None) return self._log_install(cont, err)
self.action_executed = False
src = os.path.join(self.base, os.path.expanduser(src)) # check source file exists
src = os.path.join(self.base, src)
if not os.path.exists(src): if not os.path.exists(src):
err = 'source dotfile does not exist: {}'.format(src) err = 'source dotfile does not exist: {}'.format(src)
return self._log_install(False, err) return self._log_install(False, err)
dst = os.path.expanduser(dst)
self.action_executed = False
# install to temporary dir
# and ignore any actions
if self.totemp: if self.totemp:
dst = self._pivot_path(dst, self.totemp) r, err, _ = self.install_to_temp(templater, self.totemp,
if utils.samefile(src, dst): src, dst, is_template=is_template,
# symlink loop chmod=chmod)
err = 'dotfile points to itself: {}'.format(dst) return self._log_install(r, err)
return self._log_install(False, err)
isdir = os.path.isdir(src) isdir = os.path.isdir(src)
if self.debug: if self.debug:
self.log.dbg('install {} to {}'.format(src, dst)) self.log.dbg('install {} to {}'.format(src, dst))
self.log.dbg('is a directory \"{}\": {}'.format(src, isdir)) self.log.dbg('\"{}\" is a directory: {}'.format(src, isdir))
if isdir:
b, e = self._install_dir(templater, src, dst,
actionexec=actionexec,
noempty=noempty, ignore=ignore,
template=template)
return self._log_install(b, e)
b, e = self._install_file(templater, src, dst,
actionexec=actionexec,
noempty=noempty, ignore=ignore,
template=template)
return self._log_install(b, e)
def link(self, templater, src, dst, actionexec=None, template=True): if linktype == LinkTypes.NOLINK:
# normal file
if isdir:
r, err = self._copy_dir(templater, src, dst,
actionexec=actionexec,
noempty=noempty, ignore=ignore,
is_template=is_template,
chmod=chmod)
else:
r, err = self._copy_file(templater, src, dst,
actionexec=actionexec,
noempty=noempty, ignore=ignore,
is_template=is_template,
chmod=chmod)
elif linktype == LinkTypes.LINK:
# symlink
r, err = self._link(templater, src, dst,
actionexec=actionexec,
is_template=is_template)
elif linktype == LinkTypes.LINK_CHILDREN:
# symlink direct children
if not isdir:
if self.debug:
msg = 'symlink children of {} to {}'
self.log.dbg(msg.format(src, dst))
err = 'source dotfile is not a directory: {}'.format(src)
r = False
else:
r, err = self._link_children(templater, src, dst,
actionexec=actionexec,
is_template=is_template)
if self.debug:
self.log.dbg('before chmod: {} err:{}'.format(r, err))
if self.dry:
return self._log_install(r, err)
# handle chmod
# - on success (r, not err)
# - no change (not r, not err)
# but not when
# - error (not r, err)
# - aborted (not r, err)
if (r or (not r and not err)):
if not chmod:
chmod = utils.get_file_perm(src)
dstperms = utils.get_file_perm(dst)
if dstperms != chmod:
# apply mode
msg = 'chmod {} to {:o}'.format(dst, chmod)
if self.safe and not self.log.ask(msg):
r = False
err = 'aborted'
else:
if not self.comparing:
self.log.sub('chmod {} to {:o}'.format(dst, chmod))
if utils.chmod(dst, chmod, debug=self.debug):
r = True
else:
r = False
err = 'chmod failed'
return self._log_install(r, err)
def install_to_temp(self, templater, tmpdir, src, dst,
is_template=True, chmod=None):
""" """
set src as the link target of dst install a dotfile to a tempdir
@templater: the templater
@templater: the templater object
@tmpdir: where to install
@src: dotfile source path in dotpath @src: dotfile source path in dotpath
@dst: dotfile destination path in the FS @dst: dotfile destination path in the FS
@actionexec: action executor callback @is_template: this dotfile is a template
@template: template this dotfile @chmod: rights to apply if any
return
- success, error-if-any, dotfile-installed-path
"""
if self.debug:
self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst))
src, dst, cont, err = self._check_paths(src, dst, chmod)
if not cont:
return self._log_install(cont, err)
ret = False
tmpdst = ''
# save flags
self.comparing = True
drysaved = self.dry
self.dry = False
diffsaved = self.diff
self.diff = False
createsaved = self.create
self.create = True
totemp = self.totemp
self.totemp = None
# install the dotfile to a temp directory
tmpdst = self._pivot_path(dst, tmpdir)
ret, err = self.install(templater, src, tmpdst,
LinkTypes.NOLINK,
is_template=is_template,
chmod=chmod)
if self.debug:
if ret:
self.log.dbg('tmp installed in {}'.format(tmpdst))
# restore flags
self.dry = drysaved
self.diff = diffsaved
self.create = createsaved
self.comparing = False
self.totemp = totemp
return ret, err, tmpdst
########################################################
# low level accessors for public methods
########################################################
def _link(self, templater, src, dst, actionexec=None, is_template=True):
"""
install link:link
return return
- True, None : success - True, None : success
- False, error_msg : error - False, error_msg : error
- False, None : ignored - False, None : ignored
- False, 'aborted' : user aborted
""" """
if self.debug: if is_template:
self.log.dbg('link \"{}\" to \"{}\"'.format(src, dst))
if not dst or not src:
if self.debug: if self.debug:
self.log.dbg('empty dst for {}'.format(src)) self.log.dbg('is a template')
return self._log_install(True, None) self.log.dbg('install to {}'.format(self.workdir))
self.action_executed = False
src = os.path.normpath(os.path.join(self.base,
os.path.expanduser(src)))
if not os.path.exists(src):
err = 'source dotfile does not exist: {}'.format(src)
return self._log_install(False, err)
dst = os.path.normpath(os.path.expanduser(dst))
if self.totemp:
# ignore actions
b, e = self.install(templater, src, dst, actionexec=None,
template=template)
return self._log_install(b, e)
if template and Templategen.is_template(src):
if self.debug:
self.log.dbg('dotfile is a template')
self.log.dbg('install to {} and symlink'.format(self.workdir))
tmp = self._pivot_path(dst, self.workdir, striphome=True) tmp = self._pivot_path(dst, self.workdir, striphome=True)
i, err = self.install(templater, src, tmp, actionexec=actionexec, r, err = self.install(templater, src, tmp,
template=template) LinkTypes.NOLINK,
if not i and not os.path.exists(tmp): actionexec=actionexec,
return self._log_install(i, err) is_template=is_template)
if not r and not os.path.exists(tmp):
return r, err
src = tmp src = tmp
b, e = self._link(src, dst, actionexec=actionexec) r, err = self._symlink(src, dst, actionexec=actionexec)
return self._log_install(b, e) return r, err
def link_children(self, templater, src, dst, actionexec=None, def _link_children(self, templater, src, dst,
template=True): actionexec=None, is_template=True):
""" """
link all files under a given directory install link:link_children
@templater: the templater
@src: dotfile source path in dotpath
@dst: dotfile destination path in the FS
@actionexec: action executor callback
@template: template this dotfile
return return
- True, None: success - True, None : success
- False, error_msg: error - False, error_msg : error
- False, None, ignored - False, None : ignored
- False, 'aborted' : user aborted
""" """
if self.debug: parent = os.path.join(self.base, src)
self.log.dbg('link_children \"{}\" to \"{}\"'.format(src, dst))
if not dst or not src:
if self.debug:
self.log.dbg('empty dst for {}'.format(src))
return self._log_install(True, None)
self.action_executed = False
parent = os.path.join(self.base, os.path.expanduser(src))
# Fail if source doesn't exist
if not os.path.exists(parent):
err = 'source dotfile does not exist: {}'.format(parent)
return self._log_install(False, err)
# Fail if source not a directory
if not os.path.isdir(parent):
if self.debug:
self.log.dbg('symlink children of {} to {}'.format(src, dst))
err = 'source dotfile is not a directory: {}'.format(parent)
return self._log_install(False, err)
dst = os.path.normpath(os.path.expanduser(dst))
if not os.path.lexists(dst): if not os.path.lexists(dst):
self.log.sub('creating directory "{}"'.format(dst)) if self.dry:
os.makedirs(dst) self.log.dry('would create directory "{}"'.format(dst))
else:
if not self.comparing:
self.log.sub('creating directory "{}"'.format(dst))
self._create_dirs(dst)
if os.path.isfile(dst): if os.path.isfile(dst):
msg = ''.join([ msg = ''.join([
@@ -211,10 +288,9 @@ class Installer:
]).format(dst) ]).format(dst)
if self.safe and not self.log.ask(msg): if self.safe and not self.log.ask(msg):
err = 'ignoring "{}", nothing installed'.format(dst) return False, 'aborted'
return self._log_install(False, err)
os.unlink(dst) os.unlink(dst)
os.mkdir(dst) self._create_dirs(dst)
children = os.listdir(parent) children = os.listdir(parent)
srcs = [os.path.normpath(os.path.join(parent, child)) srcs = [os.path.normpath(os.path.join(parent, child))
@@ -224,25 +300,27 @@ class Installer:
installed = 0 installed = 0
for i in range(len(children)): for i in range(len(children)):
src = srcs[i] subsrc = srcs[i]
dst = dsts[i] subdst = dsts[i]
if self.debug: if self.debug:
self.log.dbg('symlink child {} to {}'.format(src, dst)) self.log.dbg('symlink child {} to {}'.format(subsrc, subdst))
if template and Templategen.is_template(src): if is_template:
if self.debug: if self.debug:
self.log.dbg('dotfile is a template') self.log.dbg('child is a template')
self.log.dbg('install to {} and symlink' self.log.dbg('install to {} and symlink'
.format(self.workdir)) .format(self.workdir))
tmp = self._pivot_path(dst, self.workdir, striphome=True) tmp = self._pivot_path(subdst, self.workdir, striphome=True)
r, e = self.install(templater, src, tmp, actionexec=actionexec, r, e = self.install(templater, subsrc, tmp,
template=template) LinkTypes.NOLINK,
actionexec=actionexec,
is_template=is_template)
if not r and e and not os.path.exists(tmp): if not r and e and not os.path.exists(tmp):
continue continue
src = tmp subsrc = tmp
ret, err = self._link(src, dst, actionexec=actionexec) ret, err = self._symlink(subsrc, subdst, actionexec=actionexec)
if ret: if ret:
installed += 1 installed += 1
# void actionexec if dotfile installed # void actionexec if dotfile installed
@@ -250,18 +328,23 @@ class Installer:
actionexec = None actionexec = None
else: else:
if err: if err:
return self._log_install(ret, err) return ret, err
return self._log_install(installed > 0, None) return installed > 0, None
def _link(self, src, dst, actionexec=None): ########################################################
# file operations
########################################################
def _symlink(self, src, dst, actionexec=None):
""" """
set src as a link target of dst set src as a link target of dst
return return
- True, None: success - True, None : success
- False, error_msg: error - False, error_msg : error
- False, None, ignored - False, None : ignored
- False, 'aborted' : user aborted
""" """
overwrite = not self.safe overwrite = not self.safe
if os.path.lexists(dst): if os.path.lexists(dst):
@@ -274,11 +357,10 @@ class Installer:
self.log.dry('would remove {} and link to {}'.format(dst, src)) self.log.dry('would remove {} and link to {}'.format(dst, src))
return True, None return True, None
if self.showdiff: if self.showdiff:
self._diff_before_write(src, dst, quiet=False) self._show_diff_before_write(src, dst)
msg = 'Remove "{}" for link creation?'.format(dst) msg = 'Remove "{}" for link creation?'.format(dst)
if self.safe and not self.log.ask(msg): if self.safe and not self.log.ask(msg):
err = 'ignoring "{}", link was not created'.format(dst) return False, 'aborted'
return False, err
overwrite = True overwrite = True
try: try:
utils.removepath(dst) utils.removepath(dst)
@@ -299,41 +381,49 @@ class Installer:
if os.path.lexists(dst): if os.path.lexists(dst):
msg = 'Remove "{}" for link creation?'.format(dst) msg = 'Remove "{}" for link creation?'.format(dst)
if self.safe and not overwrite and not self.log.ask(msg): if self.safe and not overwrite and not self.log.ask(msg):
err = 'ignoring "{}", link was not created'.format(dst) return False, 'aborted'
return False, err
try: try:
utils.removepath(dst) utils.removepath(dst)
except OSError as e: except OSError as e:
err = 'something went wrong with {}: {}'.format(src, e) err = 'something went wrong with {}: {}'.format(src, e)
return False, err return False, err
os.symlink(src, dst) os.symlink(src, dst)
self.log.sub('linked {} to {}'.format(dst, src)) if not self.comparing:
self.log.sub('linked {} to {}'.format(dst, src))
return True, None return True, None
def _get_tmp_file_vars(self, src, dst): def _copy_file(self, templater, src, dst,
tmp = {} actionexec=None, noempty=False,
tmp['_dotfile_sub_abs_src'] = src ignore=[], is_template=True,
tmp['_dotfile_sub_abs_dst'] = dst chmod=None):
return tmp """
install src to dst when is a file
def _install_file(self, templater, src, dst, return
actionexec=None, noempty=False, - True, None : success
ignore=[], template=True): - False, error_msg : error
"""install src to dst when is a file""" - False, None : ignored
- False, 'aborted' : user aborted
"""
if self.debug: if self.debug:
self.log.dbg('deploy file: {}'.format(src)) self.log.dbg('deploy file: {}'.format(src))
self.log.dbg('ignore empty: {}'.format(noempty)) self.log.dbg('ignore empty: {}'.format(noempty))
self.log.dbg('ignore pattern: {}'.format(ignore)) self.log.dbg('ignore pattern: {}'.format(ignore))
self.log.dbg('template: {}'.format(template)) self.log.dbg('is_template: {}'.format(is_template))
self.log.dbg('no empty: {}'.format(noempty)) self.log.dbg('no empty: {}'.format(noempty))
# check no loop
if utils.samefile(src, dst):
err = 'dotfile points to itself: {}'.format(dst)
return False, err
if utils.must_ignore([src, dst], ignore, debug=self.debug): if utils.must_ignore([src, dst], ignore, debug=self.debug):
if self.debug: if self.debug:
self.log.dbg('ignoring install of {} to {}'.format(src, dst)) self.log.dbg('ignoring install of {} to {}'.format(src, dst))
return False, None return False, None
if utils.samefile(src, dst): if utils.samefile(src, dst):
# symlink loop # loop
err = 'dotfile points to itself: {}'.format(dst) err = 'dotfile points to itself: {}'.format(dst)
return False, err return False, err
@@ -343,7 +433,7 @@ class Installer:
# handle the file # handle the file
content = None content = None
if template: if is_template:
# template the file # template the file
saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst)) saved = templater.add_tmp_vars(self._get_tmp_file_vars(src, dst))
try: try:
@@ -352,6 +442,7 @@ class Installer:
return False, str(e) return False, str(e)
finally: finally:
templater.restore_vars(saved) templater.restore_vars(saved)
# test is empty
if noempty and utils.content_empty(content): if noempty and utils.content_empty(content):
if self.debug: if self.debug:
self.log.dbg('ignoring empty template: {}'.format(src)) self.log.dbg('ignoring empty template: {}'.format(src))
@@ -359,52 +450,53 @@ class Installer:
if content is None: if content is None:
err = 'empty template {}'.format(src) err = 'empty template {}'.format(src)
return False, err return False, err
# write the file
ret, err = self._write(src, dst, ret, err = self._write(src, dst,
content=content, content=content,
actionexec=actionexec, actionexec=actionexec,
template=template) chmod=chmod)
if ret and not err:
# build return values
if ret < 0:
# error
return False, err
if ret > 0:
# already exists
if self.debug:
self.log.dbg('ignoring {}'.format(dst))
return False, None
if ret == 0:
# success
if not self.dry and not self.comparing: if not self.dry and not self.comparing:
self.log.sub('copied {} to {}'.format(src, dst)) self.log.sub('install {} to {}'.format(src, dst))
return True, None return ret, err
# error
err = 'installing {} to {}'.format(src, dst)
return False, err
def _install_dir(self, templater, src, dst, def _copy_dir(self, templater, src, dst,
actionexec=None, noempty=False, actionexec=None, noempty=False,
ignore=[], template=True): ignore=[], is_template=True, chmod=None):
"""install src to dst when is a directory""" """
install src to dst when is a directory
return
- True, None : success
- False, error_msg : error
- False, None : ignored
- False, 'aborted' : user aborted
"""
if self.debug: if self.debug:
self.log.dbg('install dir {}'.format(src)) self.log.dbg('deploy dir {}'.format(src))
self.log.dbg('ignore empty: {}'.format(noempty))
# default to nothing installed and no error # default to nothing installed and no error
ret = False, None ret = False, None
# create the directory anyway
if not self._create_dirs(dst): if not self._create_dirs(dst):
err = 'creating directory for {}'.format(dst) err = 'creating directory for {}'.format(dst)
return False, err return False, err
# handle all files in dir # handle all files in dir
for entry in os.listdir(src): for entry in os.listdir(src):
f = os.path.join(src, entry) f = os.path.join(src, entry)
if self.debug:
self.log.dbg('deploy sub from {}: {}'.format(dst, entry))
if not os.path.isdir(f): if not os.path.isdir(f):
# is file # is file
res, err = self._install_file(templater, f, res, err = self._copy_file(templater, f,
os.path.join(dst, entry), os.path.join(dst, entry),
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore, ignore=ignore,
template=template) is_template=is_template,
chmod=None)
if not res and err: if not res and err:
# error occured # error occured
ret = res, err ret = res, err
@@ -414,12 +506,13 @@ class Installer:
ret = True, None ret = True, None
else: else:
# is directory # is directory
res, err = self._install_dir(templater, f, res, err = self._copy_dir(templater, f,
os.path.join(dst, entry), os.path.join(dst, entry),
actionexec=actionexec, actionexec=actionexec,
noempty=noempty, noempty=noempty,
ignore=ignore, ignore=ignore,
template=template) is_template=is_template,
chmod=None)
if not res and err: if not res and err:
# error occured # error occured
ret = res, err ret = res, err
@@ -429,82 +522,67 @@ class Installer:
ret = True, None ret = True, None
return ret return ret
def _fake_diff(self, dst, content):
"""
fake diff by comparing file content with content
returns True if same
"""
cur = ''
with open(dst, 'br') as f:
cur = f.read()
return cur == content
def _write(self, src, dst, content=None, def _write(self, src, dst, content=None,
actionexec=None, template=True): actionexec=None, chmod=None):
""" """
copy dotfile / write content to file copy dotfile / write content to file
return 0, None: for success,
1, None: when already exists return
-1, err: when error - True, None : success
content is always empty if template is False - False, error_msg : error
and is to be ignored - False, None : ignored
- False, 'aborted' : user aborted
""" """
overwrite = not self.safe overwrite = not self.safe
if self.dry: if self.dry:
self.log.dry('would install {}'.format(dst)) self.log.dry('would install {}'.format(dst))
return 0, None return True, None
if os.path.lexists(dst): if os.path.lexists(dst):
rights = os.stat(src).st_mode
samerights = False
try: try:
samerights = os.stat(dst).st_mode == rights os.stat(dst)
except OSError as e: except OSError as e:
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
# broken symlink # broken symlink
err = 'broken symlink {}'.format(dst) err = 'broken symlink {}'.format(dst)
return -1, err return False, err
diff = None
src_mode = chmod
if not src_mode:
src_mode = utils.get_file_perm(src)
if self.diff: if self.diff:
diff = self._diff_before_write(src, dst, if not self._is_different(src, dst, content=content):
content=content,
quiet=True)
if not diff and samerights:
if self.debug: if self.debug:
self.log.dbg('{} is the same'.format(dst)) self.log.dbg('{} is the same'.format(dst))
return 1, None return False, None
if self.safe: if self.safe:
if self.debug: if self.debug:
self.log.dbg('change detected for {}'.format(dst)) self.log.dbg('change detected for {}'.format(dst))
if self.showdiff: if self.showdiff:
if diff is None: # get diff
# get diff self._show_diff_before_write(src, dst,
diff = self._diff_before_write(src, dst, content=content)
content=content,
quiet=True)
if diff:
self._print_diff(src, dst, diff)
if not self.log.ask('Overwrite \"{}\"'.format(dst)): if not self.log.ask('Overwrite \"{}\"'.format(dst)):
self.log.warn('ignoring {}'.format(dst)) return False, 'aborted'
return 1, None
overwrite = True overwrite = True
if self.backup and os.path.lexists(dst): if self.backup and os.path.lexists(dst):
self._backup(dst) self._backup(dst)
base = os.path.dirname(dst) base = os.path.dirname(dst)
if not self._create_dirs(base): if not self._create_dirs(base):
err = 'creating directory for {}'.format(dst) err = 'creating directory for {}'.format(dst)
return -1, err return False, err
r, e = self._exec_pre_actions(actionexec) r, e = self._exec_pre_actions(actionexec)
if not r: if not r:
return -1, e return False, e
if self.debug: if self.debug:
self.log.dbg('install dotfile to \"{}\"'.format(dst)) self.log.dbg('install file to \"{}\"'.format(dst))
# re-check in case action created the file # re-check in case action created the file
if self.safe and not overwrite and os.path.lexists(dst): if self.safe and not overwrite and os.path.lexists(dst):
if not self.log.ask('Overwrite \"{}\"'.format(dst)): if not self.log.ask('Overwrite \"{}\"'.format(dst)):
self.log.warn('ignoring {}'.format(dst)) self.log.warn('ignoring {}'.format(dst))
return 1, None return False, 'aborted'
if template: if content:
# write content the file # write content the file
try: try:
with open(dst, 'wb') as f: with open(dst, 'wb') as f:
@@ -512,19 +590,44 @@ class Installer:
shutil.copymode(src, dst) shutil.copymode(src, dst)
except NotADirectoryError as e: except NotADirectoryError as e:
err = 'opening dest file: {}'.format(e) err = 'opening dest file: {}'.format(e)
return -1, err return False, err
except Exception as e: except Exception as e:
return -1, str(e) return False, str(e)
else: else:
# copy file # copy file
try: try:
shutil.copyfile(src, dst) shutil.copyfile(src, dst)
shutil.copymode(src, dst) shutil.copymode(src, dst)
except Exception as e: except Exception as e:
return -1, str(e) return False, str(e)
return 0, None return True, None
def _diff_before_write(self, src, dst, content=None, quiet=False): ########################################################
# helpers
########################################################
def _get_tmp_file_vars(self, src, dst):
tmp = {}
tmp['_dotfile_sub_abs_src'] = src
tmp['_dotfile_sub_abs_dst'] = dst
return tmp
def _is_different(self, src, dst, content=None):
"""
returns True if file is different and
needs to be installed
"""
# check file content
if content:
tmp = utils.write_to_tmpfile(content)
src = tmp
r = utils.fastdiff(src, dst)
if r:
if self.debug:
self.log.dbg('content differ')
return r
def _show_diff_before_write(self, src, dst, content=None):
""" """
diff before writing diff before writing
using a temp file if content is not None using a temp file if content is not None
@@ -534,12 +637,12 @@ class Installer:
if content: if content:
tmp = utils.write_to_tmpfile(content) tmp = utils.write_to_tmpfile(content)
src = tmp src = tmp
diff = utils.diff(modified=src, original=dst, raw=False, diff = utils.diff(modified=src, original=dst,
diff_cmd=self.diff_cmd) diff_cmd=self.diff_cmd)
if tmp: if tmp:
utils.removepath(tmp, logger=self.log) utils.removepath(tmp, logger=self.log)
if not quiet and diff: if diff:
self._print_diff(src, dst, diff) self._print_diff(src, dst, diff)
return diff return diff
@@ -561,7 +664,10 @@ class Installer:
return True return True
if self.debug: if self.debug:
self.log.dbg('mkdir -p {}'.format(directory)) self.log.dbg('mkdir -p {}'.format(directory))
os.makedirs(directory) if not self.comparing:
self.log.sub('create directory {}'.format(directory))
os.makedirs(directory, exist_ok=True)
return os.path.exists(directory) return os.path.exists(directory)
def _backup(self, path): def _backup(self, path):
@@ -595,38 +701,36 @@ class Installer:
self.action_executed = True self.action_executed = True
return ret, err return ret, err
def _install_to_temp(self, templater, src, dst, tmpdir, template=True): def _log_install(self, boolean, err):
"""install a dotfile to a tempdir""" """log installation process"""
tmpdst = self._pivot_path(dst, tmpdir) if not self.debug:
r = self.install(templater, src, tmpdst, template=template) return boolean, err
return r, tmpdst if boolean:
self.log.dbg('install: SUCCESS')
else:
if err:
self.log.dbg('install: ERROR: {}'.format(err))
else:
self.log.dbg('install: IGNORED')
return boolean, err
def _check_paths(self, src, dst, chmod):
"""
check and normalize param
returns <src>, <dst>, <continue>, <error>
"""
# check both path are valid
if not dst or not src:
err = 'empty dst or src for {}'.format(src)
if self.debug:
self.log.dbg(err)
return None, None, False, err
def install_to_temp(self, templater, tmpdir, src, dst, template=True):
"""install a dotfile to a tempdir"""
ret = False
tmpdst = ''
# save some flags while comparing
self.comparing = True
drysaved = self.dry
self.dry = False
diffsaved = self.diff
self.diff = False
createsaved = self.create
self.create = True
# normalize src and dst # normalize src and dst
src = os.path.expanduser(src) src = os.path.expanduser(src)
src = os.path.normpath(src)
dst = os.path.expanduser(dst) dst = os.path.expanduser(dst)
if self.debug: dst = os.path.normpath(dst)
self.log.dbg('tmp install {} (defined dst: {})'.format(src, dst))
# install the dotfile to a temp directory for comparing return src, dst, True, None
r, tmpdst = self._install_to_temp(templater, src, dst, tmpdir,
template=template)
ret, err = r
if self.debug:
self.log.dbg('tmp installed in {}'.format(tmpdst))
# reset flags
self.dry = drysaved
self.diff = diffsaved
self.comparing = False
self.create = createsaved
return ret, err, tmpdst

View File

@@ -25,6 +25,7 @@ ENV_NOBANNER = 'DOTDROP_NOBANNER'
ENV_DEBUG = 'DOTDROP_DEBUG' ENV_DEBUG = 'DOTDROP_DEBUG'
ENV_NODEBUG = 'DOTDROP_FORCE_NODEBUG' ENV_NODEBUG = 'DOTDROP_FORCE_NODEBUG'
ENV_XDG = 'XDG_CONFIG_HOME' ENV_XDG = 'XDG_CONFIG_HOME'
ENV_WORKERS = 'DOTDROP_WORKERS'
BACKUP_SUFFIX = '.dotdropbak' BACKUP_SUFFIX = '.dotdropbak'
PROFILE = socket.gethostname() PROFILE = socket.gethostname()
@@ -54,12 +55,12 @@ USAGE = """
Usage: Usage:
dotdrop install [-VbtfndDa] [-c <path>] [-p <profile>] dotdrop install [-VbtfndDa] [-c <path>] [-p <profile>]
[-w <nb>] [<key>...] [-w <nb>] [<key>...]
dotdrop import [-Vbdf] [-c <path>] [-p <profile>] [-s <path>] dotdrop import [-Vbdfm] [-c <path>] [-p <profile>] [-s <path>]
[-l <link>] <path>... [-l <link>] <path>...
dotdrop compare [-LVb] [-c <path>] [-p <profile>] dotdrop compare [-LVb] [-c <path>] [-p <profile>]
[-C <file>...] [-i <pattern>...] [-w <nb>] [-C <file>...] [-i <pattern>...]
dotdrop update [-VbfdkP] [-c <path>] [-p <profile>] dotdrop update [-VbfdkP] [-c <path>] [-p <profile>]
[-i <pattern>...] [<path>...] [-w <nb>] [-i <pattern>...] [<path>...]
dotdrop remove [-Vbfdk] [-c <path>] [-p <profile>] [<path>...] dotdrop remove [-Vbfdk] [-c <path>] [-p <profile>] [<path>...]
dotdrop files [-VbTG] [-c <path>] [-p <profile>] dotdrop files [-VbTG] [-c <path>] [-p <profile>]
dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...] dotdrop detail [-Vb] [-c <path>] [-p <profile>] [<key>...]
@@ -73,15 +74,16 @@ Options:
-c --cfg=<path> Path to the config. -c --cfg=<path> Path to the config.
-C --file=<path> Path of dotfile to compare. -C --file=<path> Path of dotfile to compare.
-d --dry Dry run. -d --dry Dry run.
-l --link=<link> Link option (nolink|link|link_children).
-L --file-only Do not show diff but only the files that differ.
-p --profile=<profile> Specify the profile to use [default: {}].
-D --showdiff Show a diff before overwriting. -D --showdiff Show a diff before overwriting.
-f --force Do not ask user confirmation for anything. -f --force Do not ask user confirmation for anything.
-G --grepable Grepable output. -G --grepable Grepable output.
-i --ignore=<pattern> Pattern to ignore. -i --ignore=<pattern> Pattern to ignore.
-k --key Treat <path> as a dotfile key. -k --key Treat <path> as a dotfile key.
-l --link=<link> Link option (nolink|link|link_children).
-L --file-only Do not show diff but only the files that differ.
-m --preserve-mode Insert a chmod entry in the dotfile with its mode.
-n --nodiff Do not diff when installing. -n --nodiff Do not diff when installing.
-p --profile=<profile> Specify the profile to use [default: {}].
-P --show-patch Provide a one-liner to manually patch template. -P --show-patch Provide a one-liner to manually patch template.
-s --as=<path> Import as a different path from actual path. -s --as=<path> Import as a different path from actual path.
-t --temp Install to a temporary directory for review. -t --temp Install to a temporary directory for review.
@@ -129,6 +131,9 @@ class Options(AttrMonitor):
if not self.confpath: if not self.confpath:
raise YamlException('no config file found') raise YamlException('no config file found')
if self.debug: if self.debug:
self.log.dbg('#################################################')
self.log.dbg('#################### DOTDROP ####################')
self.log.dbg('#################################################')
self.log.dbg('version: {}'.format(VERSION)) self.log.dbg('version: {}'.format(VERSION))
self.log.dbg('command: {}'.format(' '.join(sys.argv))) self.log.dbg('command: {}'.format(' '.join(sys.argv)))
self.log.dbg('config file: {}'.format(self.confpath)) self.log.dbg('config file: {}'.format(self.confpath))
@@ -212,6 +217,16 @@ class Options(AttrMonitor):
# adapt attributes based on arguments # adapt attributes based on arguments
self.safe = not self.args['--force'] self.safe = not self.args['--force']
try:
if ENV_WORKERS in os.environ:
workers = int(os.environ[ENV_WORKERS])
else:
workers = int(self.args['--workers'])
self.workers = workers
except ValueError:
self.log.err('bad option for --workers')
sys.exit(USAGE)
# import link default value # import link default value
self.import_link = self.link_on_import self.import_link = self.link_on_import
if self.args['--link']: if self.args['--link']:
@@ -241,14 +256,6 @@ class Options(AttrMonitor):
self.install_default_actions_post = [a for a in self.default_actions self.install_default_actions_post = [a for a in self.default_actions
if a.kind == Action.post] if a.kind == Action.post]
self.install_ignore = self.instignore self.install_ignore = self.instignore
try:
self.install_parallel = int(self.args['--workers'])
except ValueError:
self.log.err('bad option for --workers')
sys.exit(USAGE)
if self.safe and self.install_parallel > 1:
self.log.err('\"-w --workers\" must be used with \"-f --force\"')
sys.exit(USAGE)
# "compare" specifics # "compare" specifics
self.compare_focus = self.args['--file'] self.compare_focus = self.args['--file']
@@ -261,6 +268,7 @@ class Options(AttrMonitor):
# "import" specifics # "import" specifics
self.import_path = self.args['<path>'] self.import_path = self.args['<path>']
self.import_as = self.args['--as'] self.import_as = self.args['--as']
self.import_mode = self.args['--preserve-mode']
# "update" specifics # "update" specifics
self.update_path = self.args['<path>'] self.update_path = self.args['<path>']

View File

@@ -5,11 +5,16 @@ Copyright (c) 2019, deadc0de6
settings block settings block
""" """
import os
# local imports # local imports
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
from dotdrop.dictparser import DictParser from dotdrop.dictparser import DictParser
ENV_WORKDIR = 'DOTDROP_WORKDIR'
class Settings(DictParser): class Settings(DictParser):
# key in yaml file # key in yaml file
key_yaml = 'config' key_yaml = 'config'
@@ -68,6 +73,8 @@ class Settings(DictParser):
self.cmpignore = cmpignore self.cmpignore = cmpignore
self.instignore = instignore self.instignore = instignore
self.workdir = workdir self.workdir = workdir
if ENV_WORKDIR in os.environ:
self.workdir = os.environ[ENV_WORKDIR]
self.link_dotfile_default = LinkTypes.get(link_dotfile_default) self.link_dotfile_default = LinkTypes.get(link_dotfile_default)
self.link_on_import = LinkTypes.get(link_on_import) self.link_on_import = LinkTypes.get(link_on_import)
self.minversion = minversion self.minversion = minversion

View File

@@ -6,6 +6,9 @@ jinja2 template generator
""" """
import os import os
import io
import re
import mmap
from jinja2 import Environment, FileSystemLoader, \ from jinja2 import Environment, FileSystemLoader, \
ChoiceLoader, FunctionLoader, TemplateNotFound, \ ChoiceLoader, FunctionLoader, TemplateNotFound, \
StrictUndefined StrictUndefined
@@ -154,7 +157,7 @@ class Templategen:
except ImportError: except ImportError:
# fallback # fallback
_, filetype = utils.run(['file', '-b', '--mime-type', src], _, filetype = utils.run(['file', '-b', '--mime-type', src],
raw=False, debug=self.debug) debug=self.debug)
if self.debug: if self.debug:
self.log.dbg('using \"file\" for filetype identification') self.log.dbg('using \"file\" for filetype identification')
filetype = filetype.strip() filetype = filetype.strip()
@@ -245,16 +248,19 @@ class Templategen:
"""test if file pointed by path is a template""" """test if file pointed by path is a template"""
if not os.path.isfile(path): if not os.path.isfile(path):
return False return False
if os.stat(path).st_size == 0:
return False
markers = [BLOCK_START, VAR_START, COMMENT_START]
patterns = [re.compile(marker.encode()) for marker in markers]
try: try:
with open(path, 'r') as f: with io.open(path, "r", encoding="utf-8") as f:
data = f.read() m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
for pattern in patterns:
if pattern.search(m):
return True
except UnicodeDecodeError: except UnicodeDecodeError:
# is binary so surely no template # is binary so surely no template
return False return False
markers = [BLOCK_START, VAR_START, COMMENT_START]
for marker in markers:
if marker in data:
return True
return False return False
def _debug_dict(self, title, elems): def _debug_dict(self, title, elems):

View File

@@ -13,7 +13,7 @@ import filecmp
from dotdrop.logger import Logger from dotdrop.logger import Logger
from dotdrop.templategen import Templategen from dotdrop.templategen import Templategen
from dotdrop.utils import patch_ignores, removepath, get_unique_tmp_name, \ from dotdrop.utils import patch_ignores, removepath, get_unique_tmp_name, \
write_to_tmpfile, must_ignore, mirror_file_rights write_to_tmpfile, must_ignore, mirror_file_rights, get_file_perm
from dotdrop.exceptions import UndefinedException from dotdrop.exceptions import UndefinedException
@@ -22,17 +22,13 @@ TILD = '~'
class Updater: class Updater:
def __init__(self, dotpath, variables, def __init__(self, dotpath, variables, conf,
dotfile_key_getter, dotfile_dst_getter, dry=False, safe=True, debug=False,
dotfile_path_normalizer, ignore=[], showpatch=False):
dry=False, safe=True,
debug=False, ignore=[], showpatch=False):
"""constructor """constructor
@dotpath: path where dotfiles are stored @dotpath: path where dotfiles are stored
@variables: dictionary of variables for the templates @variables: dictionary of variables for the templates
@dotfile_key_getter: func to get a dotfile by key @conf: configuration manager
@dotfile_dst_getter: func to get a dotfile by dst
@dotfile_path_normalizer: func to normalize dotfile dst
@dry: simulate @dry: simulate
@safe: ask for overwrite if True @safe: ask for overwrite if True
@debug: enable debug @debug: enable debug
@@ -41,9 +37,7 @@ class Updater:
""" """
self.dotpath = dotpath self.dotpath = dotpath
self.variables = variables self.variables = variables
self.dotfile_key_getter = dotfile_key_getter self.conf = conf
self.dotfile_dst_getter = dotfile_dst_getter
self.dotfile_path_normalizer = dotfile_path_normalizer
self.dry = dry self.dry = dry
self.safe = safe self.safe = safe
self.debug = debug self.debug = debug
@@ -62,7 +56,7 @@ class Updater:
if not os.path.lexists(path): if not os.path.lexists(path):
self.log.err('\"{}\" does not exist!'.format(path)) self.log.err('\"{}\" does not exist!'.format(path))
return False return False
dotfiles = self.dotfile_dst_getter(path) dotfiles = self.conf.get_dotfile_by_dst(path)
if not dotfiles: if not dotfiles:
return False return False
for dotfile in dotfiles: for dotfile in dotfiles:
@@ -80,12 +74,12 @@ class Updater:
def update_key(self, key): def update_key(self, key):
"""update the dotfile referenced by key""" """update the dotfile referenced by key"""
dotfile = self.dotfile_key_getter(key) dotfile = self.conf.get_dotfile(key)
if not dotfile: if not dotfile:
return False return False
if self.debug: if self.debug:
self.log.dbg('updating {} from key \"{}\"'.format(dotfile, key)) self.log.dbg('updating {} from key \"{}\"'.format(dotfile, key))
path = self.dotfile_path_normalizer(dotfile.dst) path = self.conf.path_to_dotfile_dst(dotfile.dst)
return self._update(path, dotfile) return self._update(path, dotfile)
def _update(self, path, dotfile): def _update(self, path, dotfile):
@@ -108,10 +102,26 @@ class Updater:
new_path = self._apply_trans_w(path, dotfile) new_path = self._apply_trans_w(path, dotfile)
if not new_path: if not new_path:
return False return False
# save current rights
fsmode = get_file_perm(path)
dfmode = get_file_perm(dtpath)
# handle the pointed file
if os.path.isdir(new_path): if os.path.isdir(new_path):
ret = self._handle_dir(new_path, dtpath) ret = self._handle_dir(new_path, dtpath)
else: else:
ret = self._handle_file(new_path, dtpath) ret = self._handle_file(new_path, dtpath)
if fsmode != dfmode:
# mirror rights
if self.debug:
m = 'adopt mode {:o} for {}'
self.log.dbg(m.format(fsmode, dotfile.key))
r = self.conf.update_dotfile(dotfile.key, fsmode)
if r:
ret = True
# clean temporary files # clean temporary files
if new_path != path and os.path.exists(new_path): if new_path != path and os.path.exists(new_path):
removepath(new_path, logger=self.log) removepath(new_path, logger=self.log)
@@ -162,14 +172,21 @@ class Updater:
def _same_rights(self, left, right): def _same_rights(self, left, right):
"""return True if files have the same modes""" """return True if files have the same modes"""
try: try:
lefts = os.stat(left) lefts = get_file_perm(left)
rights = os.stat(right) rights = get_file_perm(right)
return lefts.st_mode == rights.st_mode return lefts == rights
except OSError as e: except OSError as e:
self.log.err(e) self.log.err(e)
return False return False
def _mirror_rights(self, src, dst): def _mirror_rights(self, src, dst):
srcr = get_file_perm(src)
dstr = get_file_perm(dst)
if srcr == dstr:
return
if self.debug:
msg = 'copy rights from {} ({:o}) to {} ({:o})'
self.log.dbg(msg.format(src, srcr, dst, dstr))
try: try:
mirror_file_rights(src, dst) mirror_file_rights(src, dst)
except OSError as e: except OSError as e:
@@ -228,7 +245,9 @@ class Updater:
# find the differences # find the differences
diff = filecmp.dircmp(path, dtpath, ignore=None) diff = filecmp.dircmp(path, dtpath, ignore=None)
# handle directories diff # handle directories diff
return self._merge_dirs(diff) ret = self._merge_dirs(diff)
self._mirror_rights(path, dtpath)
return ret
def _merge_dirs(self, diff): def _merge_dirs(self, diff):
"""Synchronize directories recursively.""" """Synchronize directories recursively."""

View File

@@ -12,6 +12,7 @@ import uuid
import fnmatch import fnmatch
import inspect import inspect
import importlib import importlib
import filecmp
from shutil import rmtree, which from shutil import rmtree, which
# local import # local import
@@ -32,7 +33,7 @@ DONOTDELETE = [
NOREMOVE = [os.path.normpath(p) for p in DONOTDELETE] NOREMOVE = [os.path.normpath(p) for p in DONOTDELETE]
def run(cmd, raw=True, debug=False, checkerr=False): def run(cmd, debug=False):
"""run a command (expects a list)""" """run a command (expects a list)"""
if debug: if debug:
LOG.dbg('exec: {}'.format(' '.join(cmd))) LOG.dbg('exec: {}'.format(' '.join(cmd)))
@@ -42,13 +43,6 @@ def run(cmd, raw=True, debug=False, checkerr=False):
ret = p.returncode ret = p.returncode
out = out.splitlines(keepends=True) out = out.splitlines(keepends=True)
lines = ''.join([x.decode('utf-8', 'replace') for x in out]) lines = ''.join([x.decode('utf-8', 'replace') for x in out])
if checkerr and ret != 0:
c = ' '.join(cmd)
errl = lines.rstrip()
m = '\"{}\" returned non zero ({}): {}'.format(c, ret, errl)
LOG.err(m)
if raw:
return ret == 0, out
return ret == 0, lines return ret == 0, lines
@@ -73,7 +67,12 @@ def shell(cmd, debug=False):
return ret == 0, out return ret == 0, out
def diff(original, modified, raw=True, def fastdiff(left, right):
"""fast compare files and returns True if different"""
return not filecmp.cmp(left, right, shallow=False)
def diff(original, modified,
diff_cmd='', debug=False): diff_cmd='', debug=False):
"""compare two files, returns '' if same""" """compare two files, returns '' if same"""
if not diff_cmd: if not diff_cmd:
@@ -86,7 +85,7 @@ def diff(original, modified, raw=True,
"{modified}": modified, "{modified}": modified,
} }
cmd = [replacements.get(x, x) for x in diff_cmd.split()] cmd = [replacements.get(x, x) for x in diff_cmd.split()]
_, out = run(cmd, raw=raw, debug=debug) _, out = run(cmd, debug=debug)
return out return out
@@ -310,5 +309,44 @@ def dependencies_met():
def mirror_file_rights(src, dst): def mirror_file_rights(src, dst):
"""mirror file rights of src to dst (can rise exc)""" """mirror file rights of src to dst (can rise exc)"""
rights = os.stat(src).st_mode if not os.path.exists(src) or not os.path.exists(dst):
return
rights = get_file_perm(src)
os.chmod(dst, rights) os.chmod(dst, rights)
def get_umask():
"""return current umask value"""
cur = os.umask(0)
os.umask(cur)
# return 0o777 - cur
return cur
def get_default_file_perms(path, umask):
"""get default rights for a file"""
base = 0o666
if os.path.isdir(path):
base = 0o777
return base - umask
def get_file_perm(path):
"""return file permission"""
return os.stat(path).st_mode & 0o777
def chmod(path, mode, debug=False):
if debug:
LOG.dbg('chmod {} {}'.format(oct(mode), path))
os.chmod(path, mode)
return get_file_perm(path) == mode
def adapt_workers(options, logger):
if options.safe and options.workers > 1:
logger.warn('workers set to 1 when --force is not used')
options.workers = 1
if options.dry and options.workers > 1:
logger.warn('workers set to 1 when --dry is used')
options.workers = 1

View File

@@ -46,6 +46,15 @@ echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
# this is the test # this is the test
################################################################ ################################################################
# $1 pattern
# $2 path
grep_or_fail()
{
set +e
grep "${1}" "${2}" >/dev/null 2>&1 || (echo "pattern not found in ${2}" && exit 1)
set -e
}
# the action temp # the action temp
tmpa=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` tmpa=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
# the dotfile source # the dotfile source
@@ -136,38 +145,36 @@ cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V
# checks # checks
[ ! -e ${tmpa}/pre ] && echo 'pre action not executed' && exit 1 [ ! -e ${tmpa}/pre ] && echo 'pre action not executed' && exit 1
grep pre ${tmpa}/pre >/dev/null grep_or_fail pre ${tmpa}/pre
[ ! -e ${tmpa}/naked ] && echo 'naked action not executed' && exit 1 [ ! -e ${tmpa}/naked ] && echo 'naked action not executed' && exit 1
grep naked ${tmpa}/naked >/dev/null grep_or_fail naked ${tmpa}/naked
[ ! -e ${tmpa}/multiple ] && echo 'pre action multiple not executed' && exit 1 [ ! -e ${tmpa}/multiple ] && echo 'pre action multiple not executed' && exit 1
grep multiple ${tmpa}/multiple >/dev/null grep_or_fail multiple ${tmpa}/multiple
[ "`wc -l ${tmpa}/multiple | awk '{print $1}'`" -gt "1" ] && echo 'pre action multiple executed twice' && exit 1 [ "`wc -l ${tmpa}/multiple | awk '{print $1}'`" -gt "1" ] && echo 'pre action multiple executed twice' && exit 1
[ ! -e ${tmpa}/pre2 ] && echo 'pre action 2 not executed' && exit 1 [ ! -e ${tmpa}/pre2 ] && echo 'pre action 2 not executed' && exit 1
grep pre2 ${tmpa}/pre2 >/dev/null grep_or_fail pre2 ${tmpa}/pre2
[ ! -e ${tmpa}/naked2 ] && echo 'naked action 2 not executed' && exit 1 [ ! -e ${tmpa}/naked2 ] && echo 'naked action 2 not executed' && exit 1
grep naked2 ${tmpa}/naked2 >/dev/null grep_or_fail naked2 ${tmpa}/naked2
[ ! -e ${tmpa}/multiple2 ] && echo 'pre action multiple 2 not executed' && exit 1 [ ! -e ${tmpa}/multiple2 ] && echo 'pre action multiple 2 not executed' && exit 1
grep multiple2 ${tmpa}/multiple2 >/dev/null grep_or_fail multiple2 ${tmpa}/multiple2
[ "`wc -l ${tmpa}/multiple2 | awk '{print $1}'`" -gt "1" ] && echo 'pre action multiple 2 executed twice' && exit 1 [ "`wc -l ${tmpa}/multiple2 | awk '{print $1}'`" -gt "1" ] && echo 'pre action multiple 2 executed twice' && exit 1
[ ! -e ${tmpa}/naked3 ] && echo 'naked action 3 not executed' && exit 1 [ ! -e ${tmpa}/naked3 ] && echo 'naked action 3 not executed' && exit 1
grep naked3 ${tmpa}/naked3 >/dev/null grep_or_fail naked3 ${tmpa}/naked3
# remove the pre action result and re-install
# remove the pre action result and re-run
rm ${tmpa}/pre rm ${tmpa}/pre
cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 [ -e ${tmpa}/pre ] && echo "pre exists" && exit 1
[ -e ${tmpa}/pre ] && exit 1
# ensure failing actions make the installation fail # ensure failing actions make the installation fail
# install # install
set +e set +e
cd ${ddpath} | ${bin} install -f -c ${cfg} -p p2 -V cd ${ddpath} | ${bin} install -f -c ${cfg} -p p2 -V
set -e set -e
[ -e ${tmpd}/fail ] && exit 1 [ -e ${tmpd}/fail ] && echo "fail exists" && exit 1
## CLEANING ## CLEANING
rm -rf ${tmps} ${tmpd} ${tmpa} rm -rf ${tmps} ${tmpd} ${tmpa}

120
tests-ng/chmod-compare.sh Executable file
View File

@@ -0,0 +1,120 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test chmod on compare
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# create the dotfile
dnormal="${tmpd}/dir_normal"
mkdir -p ${dnormal}
echo "dir_normal/f1" > ${dnormal}/file1
echo "dir_normal/f2" > ${dnormal}/file2
chmod 777 ${dnormal}
dlink="${tmpd}/dir_link"
mkdir -p ${dlink}
echo "dir_link/f1" > ${dlink}/file1
echo "dir_link/f2" > ${dlink}/file2
chmod 777 ${dlink}
dlinkchildren="${tmpd}/dir_link_children"
mkdir -p ${dlinkchildren}
echo "dir_linkchildren/f1" > ${dlinkchildren}/file1
echo "dir_linkchildren/f2" > ${dlinkchildren}/file2
chmod 777 ${dlinkchildren}
fnormal="${tmpd}/filenormal"
echo "filenormal" > ${fnormal}
chmod 777 ${fnormal}
flink="${tmpd}/filelink"
echo "filelink" > ${flink}
chmod 777 ${flink}
toimport="${dnormal} ${dlink} ${dlinkchildren} ${fnormal} ${flink}"
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
#cat ${cfg}
# import
for i in ${toimport}; do
cd ${ddpath} | ${bin} import -c ${cfg} -f -p p1 ${i}
done
#cat ${cfg}
# patch rights
chmod 700 ${dnormal}
chmod 700 ${dlink}
chmod 700 ${dlinkchildren}
chmod 700 ${fnormal}
chmod 700 ${flink}
set +e
cnt=`cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 2>&1 | grep 'modes differ' | wc -l`
set -e
[ "${cnt}" != "5" ] && echo "compare modes failed (${cnt})" && exit 1
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

219
tests-ng/chmod-import.sh Executable file
View File

@@ -0,0 +1,219 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test chmod on import
# with files and directories
# with different link
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# $1 file
chmod_to_umask()
{
u=`umask`
u=`echo ${u} | sed 's/^0*//'`
if [ -d ${1} ]; then
v=$((777 - u))
else
v=$((666 - u))
fi
chmod ${v} ${1}
}
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# create the dotfiles
dnormal="${tmpd}/dir_normal"
mkdir -p ${dnormal}
echo "dir_normal/f1" > ${dnormal}/file1
echo "dir_normal/f2" > ${dnormal}/file2
chmod 777 ${dnormal}
dlink="${tmpd}/dir_link"
mkdir -p ${dlink}
echo "dir_link/f1" > ${dlink}/file1
echo "dir_link/f2" > ${dlink}/file2
chmod 777 ${dlink}
dlinkchildren="${tmpd}/dir_link_children"
mkdir -p ${dlinkchildren}
echo "dir_linkchildren/f1" > ${dlinkchildren}/file1
echo "dir_linkchildren/f2" > ${dlinkchildren}/file2
chmod 777 ${dlinkchildren}
fnormal="${tmpd}/filenormal"
echo "filenormal" > ${fnormal}
chmod 777 ${fnormal}
flink="${tmpd}/filelink"
echo "filelink" > ${flink}
chmod 777 ${flink}
toimport="${dnormal} ${dlink} ${dlinkchildren} ${fnormal} ${flink}"
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
#cat ${cfg}
# import without --preserve-mode
for i in ${toimport}; do
cd ${ddpath} | ${bin} import -c ${cfg} -f -p p1 -V ${i}
done
cat ${cfg}
# list files
cd ${ddpath} | ${bin} detail -c ${cfg} -p p1 -V
tot=`echo ${toimport} | wc -w`
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "${tot}" ] && echo "not all chmod inserted (1)" && exit 1
## with link
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
# clean
rm -rf ${tmps}/dotfiles
mkdir -p ${tmps}/dotfiles
# import without --preserve-mode and link
for i in ${toimport}; do
cd ${ddpath} | ${bin} import -c ${cfg} -l link -f -p p1 -V ${i}
done
cat ${cfg}
# list files
cd ${ddpath} | ${bin} detail -c ${cfg} -p p1 -V
tot=`echo ${toimport} | wc -w`
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "${tot}" ] && echo "not all chmod inserted (2)" && exit 1
tot=`echo ${toimport} | wc -w`
cnt=`cat ${cfg} | grep 'link: link' | wc -l`
[ "${cnt}" != "${tot}" ] && echo "not all link inserted" && exit 1
## --preserve-mode
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
# clean
rm -rf ${tmps}/dotfiles
mkdir -p ${tmps}/dotfiles
# import with --preserve-mode
for i in ${toimport}; do
chmod_to_umask ${i}
cd ${ddpath} | ${bin} import -c ${cfg} -m -f -p p1 -V ${i}
done
cat ${cfg}
# list files
cd ${ddpath} | ${bin} detail -c ${cfg} -p p1 -V
tot=`echo ${toimport} | wc -w`
cnt=`cat ${cfg} | grep "chmod: " | wc -l`
[ "${cnt}" != "${tot}" ] && echo "not all chmod inserted (3)" && exit 1
## import normal
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
# clean
rm -rf ${tmps}/dotfiles
mkdir -p ${tmps}/dotfiles
# import without --preserve-mode
for i in ${toimport}; do
chmod_to_umask ${i}
cd ${ddpath} | ${bin} import -c ${cfg} -f -p p1 -V ${i}
done
cat ${cfg}
# list files
cd ${ddpath} | ${bin} detail -c ${cfg} -p p1 -V
cnt=`cat ${cfg} | grep chmod | wc -l`
[ "${cnt}" != "0" ] && echo "chmod inserted but not needed" && exit 1
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

275
tests-ng/chmod-install.sh Executable file
View File

@@ -0,0 +1,275 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test chmod on install
# with files and directories
# with different link
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# $1 path
# $2 rights
has_rights()
{
echo "testing ${1} is ${2}"
[ ! -e "$1" ] && echo "`basename $1` does not exist" && exit 1
local mode=`stat -L -c '%a' "$1"`
[ "${mode}" != "$2" ] && echo "bad mode for `basename $1` (${mode} VS expected ${2})" && exit 1
true
}
get_file_mode()
{
u=`umask`
u=`echo ${u} | sed 's/^0*//'`
v=$((666 - u))
echo "${v}"
}
get_dir_mode()
{
u=`umask`
u=`echo ${u} | sed 's/^0*//'`
v=$((777 - u))
echo "${v}"
}
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# create the config file
cfg="${tmps}/config.yaml"
echo 'f777' > ${tmps}/dotfiles/f777
echo 'link' > ${tmps}/dotfiles/link
mkdir -p ${tmps}/dotfiles/dir
echo "f1" > ${tmps}/dotfiles/dir/f1
echo "exists" > ${tmps}/dotfiles/exists
chmod 644 ${tmps}/dotfiles/exists
echo "exists" > ${tmpd}/exists
chmod 644 ${tmpd}/exists
echo "existslink" > ${tmps}/dotfiles/existslink
chmod 644 ${tmpd}/exists
mkdir -p ${tmps}/dotfiles/direxists
echo "f1" > ${tmps}/dotfiles/direxists/f1
mkdir -p ${tmpd}/direxists
echo "f1" > ${tmpd}/direxists/f1
chmod 644 ${tmpd}/direxists/f1
chmod 744 ${tmpd}/direxists
mkdir -p ${tmps}/dotfiles/linkchildren
echo "f1" > ${tmps}/dotfiles/linkchildren/f1
mkdir -p ${tmps}/dotfiles/linkchildren/d1
echo "f2" > ${tmps}/dotfiles/linkchildren/d1/f2
echo '{{@@ profile @@}}' > ${tmps}/dotfiles/symlinktemplate
mkdir -p ${tmps}/dotfiles/symlinktemplatedir
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/symlinktemplatedir/t
echo 'nomode' > ${tmps}/dotfiles/nomode
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
f_f777:
src: f777
dst: ${tmpd}/f777
chmod: 777
f_link:
src: link
dst: ${tmpd}/link
chmod: 777
link: link
d_dir:
src: dir
dst: ${tmpd}/dir
chmod: 777
f_exists:
src: exists
dst: ${tmpd}/exists
chmod: 777
f_existslink:
src: existslink
dst: ${tmpd}/existslink
chmod: 777
link: link
d_direxists:
src: direxists
dst: ${tmpd}/direxists
chmod: 777
d_linkchildren:
src: linkchildren
dst: ${tmpd}/linkchildren
chmod: 777
link: link_children
f_symlinktemplate:
src: symlinktemplate
dst: ${tmpd}/symlinktemplate
chmod: 777
link: link
d_symlinktemplatedir:
src: symlinktemplatedir
dst: ${tmpd}/symlinktemplatedir
chmod: 777
link: link
f_nomode:
src: nomode
dst: ${tmpd}/nomode
profiles:
p1:
dotfiles:
- f_f777
- f_link
- d_dir
- f_exists
- f_existslink
- d_direxists
- d_linkchildren
- f_symlinktemplate
- d_symlinktemplatedir
- f_nomode
p2:
dotfiles:
- f_exists
- f_existslink
- d_linkchildren
- f_symlinktemplate
- f_nomode
_EOF
#cat ${cfg}
# install
echo "first install round"
cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 -V
has_rights "${tmpd}/f777" "777"
has_rights "${tmpd}/link" "777"
has_rights "${tmpd}/dir" "777"
has_rights "${tmpd}/exists" "777"
has_rights "${tmpd}/existslink" "777"
has_rights "${tmpd}/direxists" "777"
has_rights "${tmpd}/direxists/f1" "644"
has_rights "${tmpd}/linkchildren" "777"
has_rights "${tmpd}/linkchildren/f1" "644"
has_rights "${tmpd}/linkchildren/d1" "755"
has_rights "${tmpd}/linkchildren/d1/f2" "644"
has_rights "${tmpd}/symlinktemplate" "777"
m=`get_file_mode`
has_rights "${tmpd}/nomode" "${m}"
grep 'p1' ${tmpd}/symlinktemplate
grep 'p1' ${tmpd}/symlinktemplatedir/t
## second round
echo "exists" > ${tmps}/dotfiles/exists
chmod 600 ${tmps}/dotfiles/exists
echo "exists" > ${tmpd}/exists
chmod 600 ${tmpd}/exists
chmod 600 ${tmpd}/existslink
chmod 700 ${tmpd}/linkchildren
chmod 600 ${tmpd}/symlinktemplate
echo "second install round"
cd ${ddpath} | ${bin} install -c ${cfg} -p p2 -f -V
has_rights "${tmpd}/exists" "777"
has_rights "${tmpd}/existslink" "777"
has_rights "${tmpd}/linkchildren/f1" "644"
has_rights "${tmpd}/linkchildren/d1" "755"
has_rights "${tmpd}/linkchildren/d1/f2" "644"
has_rights "${tmpd}/symlinktemplate" "777"
m=`get_file_mode`
has_rights "${tmpd}/nomode" "${m}"
## no user confirmation expected
## same mode
echo "same mode"
echo "nomode" > ${tmps}/dotfiles/nomode
chmod 600 ${tmps}/dotfiles/nomode
echo "nomode" > ${tmpd}/nomode
chmod 600 ${tmpd}/nomode
cd ${ddpath} | ${bin} install -c ${cfg} -f -p p2 -V f_nomode
echo "same mode"
has_rights "${tmpd}/nomode" "600"
## no user confirmation with force
## different mode
echo "different mode"
echo "nomode" > ${tmps}/dotfiles/nomode
chmod 600 ${tmps}/dotfiles/nomode
echo "nomode" > ${tmpd}/nomode
chmod 700 ${tmpd}/nomode
cd ${ddpath} | ${bin} install -c ${cfg} -f -p p2 -V f_nomode
echo "different mode (1)"
has_rights "${tmpd}/nomode" "600"
## user confirmation expected
## different mode
echo "different mode"
echo "nomode" > ${tmps}/dotfiles/nomode
chmod 600 ${tmps}/dotfiles/nomode
echo "nomode" > ${tmpd}/nomode
chmod 700 ${tmpd}/nomode
cd ${ddpath} | printf 'y\ny\n' | ${bin} install -f -c ${cfg} -p p2 -V f_nomode
echo "different mode (2)"
has_rights "${tmpd}/nomode" "600"
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

127
tests-ng/chmod-more.sh Executable file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test chmod on import
# with files and directories
# with different link
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# $1 path
# $2 rights
has_rights()
{
echo "testing ${1} is ${2}"
[ ! -e "$1" ] && echo "`basename $1` does not exist" && exit 1
local mode=`stat -L -c '%a' "$1"`
[ "${mode}" != "$2" ] && echo "bad mode for `basename $1` (${mode} instead of ${2})" && exit 1
true
}
# $1 file
chmod_to_umask()
{
u=`umask`
u=`echo ${u} | sed 's/^0*//'`
if [ -d ${1} ]; then
v=$((777 - u))
else
v=$((666 - u))
fi
chmod ${v} ${1}
}
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# create the dotfiles
f1="${tmpd}/f1"
touch ${f1}
chmod 777 ${f1}
stat -c '%a' ${f1}
f2="${tmpd}/f2"
touch ${f2}
chmod 644 ${f2}
stat -c '%a' ${f2}
toimport="${f1} ${f2}"
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
#cat ${cfg}
# import without --preserve-mode
for i in ${toimport}; do
stat -c '%a' ${i}
cd ${ddpath} | ${bin} import -c ${cfg} -f -p p1 -V ${i}
done
cat ${cfg}
has_rights "${tmpd}/f1" "777"
has_rights "${tmps}/dotfiles/${tmpd}/f1" "777"
has_rights "${tmpd}/f2" "644"
has_rights "${tmps}/dotfiles/${tmpd}/f2" "644"
# install
cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 -V | grep '0 dotfile(s) installed' || (echo "should not install" && exit 1)
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

157
tests-ng/chmod-update.sh Executable file
View File

@@ -0,0 +1,157 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test chmod on update
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# create the dotfile
dnormal="${tmpd}/dir_normal"
mkdir -p ${dnormal}
echo "dir_normal/f1" > ${dnormal}/file1
echo "dir_normal/f2" > ${dnormal}/file2
dlink="${tmpd}/dir_link"
mkdir -p ${dlink}
echo "dir_link/f1" > ${dlink}/file1
echo "dir_link/f2" > ${dlink}/file2
dlinkchildren="${tmpd}/dir_link_children"
mkdir -p ${dlinkchildren}
echo "dir_linkchildren/f1" > ${dlinkchildren}/file1
echo "dir_linkchildren/f2" > ${dlinkchildren}/file2
fnormal="${tmpd}/filenormal"
echo "filenormal" > ${fnormal}
flink="${tmpd}/filelink"
echo "filelink" > ${flink}
toimport="${dnormal} ${dlink} ${dlinkchildren} ${fnormal} ${flink}"
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
profiles:
_EOF
# import
for i in ${toimport}; do
cd ${ddpath} | ${bin} import -c ${cfg} -f -p p1 -V ${i}
done
cat ${cfg}
# test no chmod
cnt=`cat ${cfg} | grep chmod | wc -l`
[ "${cnt}" != "0" ] && echo "chmod wrongly inserted" && exit 1
######################
# update dnormal
chmod 777 ${dnormal}
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 -V ${dnormal}
# check rights updated
[ "`stat -c '%a' ${tmps}/dotfiles/${tmpd}/$(basename ${dnormal})`" != "777" ] && echo "rights not updated (1)" && exit 1
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "1" ] && echo "chmod not updated (1)" && exit 1
######################
# update dlink
chmod 777 ${dlink}
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 -V ${dlink}
# check rights updated
[ "`stat -c '%a' ${tmps}/dotfiles/${tmpd}/$(basename ${dlink})`" != "777" ] && echo "rights not updated (2)" && exit 1
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "2" ] && echo "chmod not updated (2)" && exit 1
######################
# update dlinkchildren
chmod 777 ${dlinkchildren}
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 -V ${dlinkchildren}
# check rights updated
[ "`stat -c '%a' ${tmps}/dotfiles/${tmpd}/$(basename ${dlinkchildren})`" != "777" ] && echo "rights not updated (3)" && exit 1
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "3" ] && echo "chmod not updated (3)" && exit 1
######################
# update fnormal
chmod 777 ${fnormal}
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 -V ${fnormal}
# check rights updated
[ "`stat -c '%a' ${tmps}/dotfiles/${tmpd}/$(basename ${fnormal})`" != "777" ] && echo "rights not updated (4)" && exit 1
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "4" ] && echo "chmod not updated (4)" && exit 1
######################
# update flink
chmod 777 ${flink}
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 -V ${flink}
# check rights updated
[ "`stat -c '%a' ${tmps}/dotfiles/${tmpd}/$(basename ${flink})`" != "777" ] && echo "rights not updated (5)" && exit 1
cnt=`cat ${cfg} | grep "chmod: '777'" | wc -l`
[ "${cnt}" != "5" ] && echo "chmod not updated (5)" && exit 1
## CLEANING
rm -rf ${tmps} ${tmpd}
echo "OK"
exit 0

View File

@@ -56,6 +56,8 @@ basedir=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
echo "[+] dotdrop dir: ${basedir}" echo "[+] dotdrop dir: ${basedir}"
echo "[+] dotpath dir: ${basedir}/dotfiles" echo "[+] dotpath dir: ${basedir}/dotfiles"
export DOTDROP_WORKERS=1
# create the config file # create the config file
cfg="${basedir}/config.yaml" cfg="${basedir}/config.yaml"
cat > ${cfg} << _EOF cat > ${cfg} << _EOF
@@ -89,7 +91,7 @@ cd ${ddpath} | ${bin} install -D -c ${cfg} -p p1 --verbose f_x
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
echo "[+] test install not existing src" echo "[+] test install not existing src"
cd ${ddpath} | ${bin} install -c ${cfg} --dry -p p1 --verbose f_y cd ${ddpath} | ${bin} install -c ${cfg} -f --dry -p p1 --verbose f_y
echo "[+] test install to temp" echo "[+] test install to temp"
cd ${ddpath} | ${bin} install -t -c ${cfg} -p p1 --verbose f_x cd ${ddpath} | ${bin} install -t -c ${cfg} -p p1 --verbose f_x

View File

@@ -126,12 +126,12 @@ set -e
# test values have been correctly updated # test values have been correctly updated
echo "========> test for updated entries" echo "========> test for updated entries"
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_link' | head -1 | grep ',link:link$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_link' | head -1 | grep ',link:link,'
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_nolink' | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_nolink' | head -1 | grep ',link:nolink,'
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_nolink1' | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_nolink1' | head -1 | grep ',link:nolink,'
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_children' | head -1 | grep ',link:link_children$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_children' | head -1 | grep ',link:link_children,'
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_children2' | head -1 | grep ',link:link_children$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_children2' | head -1 | grep ',link:link_children,'
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_children3' | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | grep '^f_children3' | head -1 | grep ',link:nolink,'
## CLEANING ## CLEANING
rm -rf ${tmps} ${tmpd} rm -rf ${tmps} ${tmpd}

View File

@@ -81,7 +81,7 @@ echo "modified" > ${tmpd}/singlefile
# default diff (unified) # default diff (unified)
echo "[+] comparing with default diff (unified)" echo "[+] comparing with default diff (unified)"
set +e set +e
cd ${ddpath} | ${bin} compare -c ${cfg} 2>&1 | grep -v '=>' | grep -v '^+++\|^---' > ${tmpd}/normal cd ${ddpath} | ${bin} compare -c ${cfg} 2>&1 | grep -v '=>' | grep -v '\->' | grep -v 'dotfile(s) compared' | sed '$d' | grep -v '^+++\|^---' > ${tmpd}/normal
diff -u -r ${tmpd}/singlefile ${basedir}/dotfiles/${tmpd}/singlefile | grep -v '^+++\|^---' > ${tmpd}/real diff -u -r ${tmpd}/singlefile ${basedir}/dotfiles/${tmpd}/singlefile | grep -v '^+++\|^---' > ${tmpd}/real
set -e set -e
@@ -96,7 +96,7 @@ sed '/dotpath: dotfiles/a \ \ diff_command: "diff -r {0} {1}"' ${cfg} > ${cfg2}
# normal diff # normal diff
echo "[+] comparing with normal diff" echo "[+] comparing with normal diff"
set +e set +e
cd ${ddpath} | ${bin} compare -c ${cfg2} 2>&1 | grep -v '=>' > ${tmpd}/unified cd ${ddpath} | ${bin} compare -c ${cfg2} 2>&1 | grep -v '=>' | grep -v '\->' | grep -v 'dotfile(s) compared' | sed '$d' > ${tmpd}/unified
diff -r ${tmpd}/singlefile ${basedir}/dotfiles/${tmpd}/singlefile > ${tmpd}/real diff -r ${tmpd}/singlefile ${basedir}/dotfiles/${tmpd}/singlefile > ${tmpd}/real
set -e set -e
@@ -113,7 +113,7 @@ sed '/dotpath: dotfiles/a \ \ diff_command: "echo fakediff"' ${cfg} > ${cfg3}
# fake diff # fake diff
echo "[+] comparing with fake diff" echo "[+] comparing with fake diff"
set +e set +e
cd ${ddpath} | ${bin} compare -c ${cfg3} 2>&1 | grep -v '=>' > ${tmpd}/fake cd ${ddpath} | ${bin} compare -c ${cfg3} 2>&1 | grep -v '=>' | grep -v '\->' | grep -v 'dotfile(s) compared' | sed '$d' > ${tmpd}/fake
set -e set -e
# verify # verify

319
tests-ng/dry.sh Executable file
View File

@@ -0,0 +1,319 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test dry
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
# the dotfile source
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${tmps}/dotfiles
# the dotfile destination
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
#echo "dotfile destination: ${tmpd}"
# workdir
tmpw=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
# temp
tmpa=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
# -----------------------------
# test install
# -----------------------------
# cleaning
rm -rf ${tmps}/*
mkdir -p ${tmps}/dotfiles
rm -rf ${tmpw}/*
rm -rf ${tmpd}/*
rm -rf ${tmpa}/*
# create the config file
cfg="${tmps}/config.yaml"
echo '{{@@ profile @@}}' > ${tmps}/dotfiles/file
echo '{{@@ profile @@}}' > ${tmps}/dotfiles/link
mkdir -p ${tmps}/dotfiles/dir
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/dir/f1
mkdir -p ${tmps}/dotfiles/dirchildren
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/dirchildren/f1
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/dirchildren/f2
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
workdir: ${tmpw}
actions:
pre:
preaction: echo 'pre' > ${tmpa}/pre
post:
postaction: echo 'post' > ${tmpa}/post
dotfiles:
f_file:
src: file
dst: ${tmpd}/file
actions:
- preaction
- postaction
f_link:
src: link
dst: ${tmpd}/link
link: link
actions:
- preaction
- postaction
d_dir:
src: dir
dst: ${tmpd}/dir
actions:
- preaction
- postaction
d_dirchildren:
src: dirchildren
dst: ${tmpd}/dirchildren
link: link_children
actions:
- preaction
- postaction
profiles:
p1:
dotfiles:
- f_file
- f_link
- d_dir
- d_dirchildren
_EOF
# install
echo "dry install"
cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 -V --dry
cnt=`ls -1 ${tmpd} | wc -l`
ls -1 ${tmpd}
[ "${cnt}" != "0" ] && echo "dry install failed (1)" && exit 1
cnt=`ls -1 ${tmpw} | wc -l`
ls -1 ${tmpw}
[ "${cnt}" != "0" ] && echo "dry install failed (2)" && exit 1
cnt=`ls -1 ${tmpa} | wc -l`
ls -1 ${tmpa}
[ "${cnt}" != "0" ] && echo "dry install failed (3)" && exit 1
# -----------------------------
# test import
# -----------------------------
# cleaning
rm -rf ${tmps}/*
mkdir -p ${tmps}/dotfiles
rm -rf ${tmpw}/*
rm -rf ${tmpd}/*
rm -rf ${tmpa}/*
# create the config file
cfg="${tmps}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
workdir: ${tmpw}
dotfiles:
profiles:
_EOF
cp ${cfg} ${tmpa}/config.yaml
echo 'content' > ${tmpd}/file
echo 'content' > ${tmpd}/link
mkdir -p ${tmpd}/dir
echo "content" > ${tmpd}/dir/f1
mkdir -p ${tmpd}/dirchildren
echo "content" > ${tmpd}/dirchildren/f1
echo "content" > ${tmpd}/dirchildren/f2
dotfiles="${tmpd}/file ${tmpd}/link ${tmpd}/dir ${tmpd}/dirchildren"
echo "dry import"
cd ${ddpath} | ${bin} import -c ${cfg} -f -p p1 -V --dry ${dotfiles}
cnt=`ls -1 ${tmps}/dotfiles | wc -l`
ls -1 ${tmps}/dotfiles
[ "${cnt}" != "0" ] && echo "dry import failed (1)" && exit 1
diff ${cfg} ${tmpa}/config.yaml || (echo "dry import failed (2)" && exit 1)
# -----------------------------
# test update
# -----------------------------
# cleaning
rm -rf ${tmps}/*
mkdir -p ${tmps}/dotfiles
rm -rf ${tmpw}/*
rm -rf ${tmpd}/*
rm -rf ${tmpa}/*
echo 'original' > ${tmps}/dotfiles/file
echo 'original' > ${tmps}/dotfiles/link
mkdir -p ${tmps}/dotfiles/dir
echo "original" > ${tmps}/dotfiles/dir/f1
mkdir -p ${tmps}/dotfiles/dirchildren
echo "original" > ${tmps}/dotfiles/dirchildren/f1
echo "original" > ${tmps}/dotfiles/dirchildren/f2
echo 'modified' > ${tmpd}/file
echo 'modified' > ${tmpd}/link
mkdir -p ${tmpd}/dir
echo "modified" > ${tmpd}/dir/f1
mkdir -p ${tmpd}/dirchildren
echo "modified" > ${tmpd}/dirchildren/f1
echo "modified" > ${tmpd}/dirchildren/f2
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
workdir: ${tmpw}
dotfiles:
f_file:
src: file
dst: ${tmpd}/file
f_link:
src: link
dst: ${tmpd}/link
link: link
d_dir:
src: dir
dst: ${tmpd}/dir
d_dirchildren:
src: dirchildren
dst: ${tmpd}/dirchildren
link: link_children
profiles:
p1:
dotfiles:
- f_file
- f_link
- d_dir
- d_dirchildren
_EOF
cp ${cfg} ${tmpa}/config.yaml
echo "dry update"
dotfiles="${tmpd}/file ${tmpd}/link ${tmpd}/dir ${tmpd}/dirchildren"
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 -V --dry ${dotfiles}
grep 'modified' ${tmps}/dotfiles/file && echo "dry update failed (1)" && exit 1
grep 'modified' ${tmps}/dotfiles/link && echo "dry update failed (2)" && exit 1
grep "modified" ${tmps}/dotfiles/dir/f1 && echo "dry update failed (3)" && exit 1
grep "modified" ${tmps}/dotfiles/dirchildren/f1 && echo "dry update failed (4)" && exit 1
grep "modified" ${tmps}/dotfiles/dirchildren/f2 && echo "dry update failed (5)" && exit 1
diff ${cfg} ${tmpa}/config.yaml || (echo "dry update failed (6)" && exit 1)
# -----------------------------
# test remove
# -----------------------------
# cleaning
rm -rf ${tmps}/*
mkdir -p ${tmps}/dotfiles
rm -rf ${tmpw}/*
rm -rf ${tmpd}/*
rm -rf ${tmpa}/*
echo '{{@@ profile @@}}' > ${tmps}/dotfiles/file
echo '{{@@ profile @@}}' > ${tmps}/dotfiles/link
mkdir -p ${tmps}/dotfiles/dir
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/dir/f1
mkdir -p ${tmps}/dotfiles/dirchildren
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/dirchildren/f1
echo "{{@@ profile @@}}" > ${tmps}/dotfiles/dirchildren/f2
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
workdir: ${tmpw}
dotfiles:
f_file:
src: file
dst: ${tmpd}/file
f_link:
src: link
dst: ${tmpd}/link
link: link
d_dir:
src: dir
dst: ${tmpd}/dir
d_dirchildren:
src: dirchildren
dst: ${tmpd}/dirchildren
link: link_children
profiles:
p1:
dotfiles:
- f_file
- f_link
- d_dir
- d_dirchildren
_EOF
cp ${cfg} ${tmpa}/config.yaml
echo "dry remove"
dotfiles="${tmpd}/file ${tmpd}/link ${tmpd}/dir ${tmpd}/dirchildren"
cd ${ddpath} | ${bin} remove -c ${cfg} -f -p p1 -V --dry ${dotfiles}
[ ! -e ${tmps}/dotfiles/file ] && echo "dry remove failed (1)" && exit 1
[ ! -e ${tmps}/dotfiles/link ] && echo "dry remove failed (2)" && exit 1
[ ! -d ${tmps}/dotfiles/dir ] && echo "dry remove failed (3)" && exit 1
[ ! -e ${tmps}/dotfiles/dir/f1 ] && echo "dry remove failed (4)" && exit 1
[ ! -d ${tmps}/dotfiles/dirchildren ] && echo "dry remove failed (5)" && exit 1
[ ! -e ${tmps}/dotfiles/dirchildren/f1 ] && echo "dry remove failed (6)" && exit 1
[ ! -e ${tmps}/dotfiles/dirchildren/f2 ] && echo "dry remove failed (7)" && exit 1
diff ${cfg} ${tmpa}/config.yaml || (echo "dry remove failed (8)" && exit 1)
## CLEANING
rm -rf ${tmps} ${tmpd} ${tmpw} ${tmpa}
echo "OK"
exit 0

View File

@@ -99,8 +99,9 @@ cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc
# check files haven't been updated # check files haven't been updated
[ ! -e ${dt}/a/c/acfile ] && echo "acfile not found" && exit 1 [ ! -e ${dt}/a/c/acfile ] && echo "acfile not found" && exit 1
cat ${dt}/a/c/acfile set +e
grep 'b' ${dt}/a/c/acfile >/dev/null grep 'b' ${dt}/a/c/acfile || (echo "acfile not updated" && exit 1)
set -e
[ -e ${dt}/a/newfile ] && echo "newfile found" && exit 1 [ -e ${dt}/a/newfile ] && echo "newfile found" && exit 1
## CLEANING ## CLEANING

View File

@@ -98,7 +98,7 @@ mkdir -p ${tmps}/dotfiles/
echo "abc" > ${tmps}/dotfiles/abc echo "abc" > ${tmps}/dotfiles/abc
# install # install
cd ${ddpath} | ${bin} install -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 -V
# checks # checks
[ ! -e ${tmpd}/abc ] && echo "dotfile not installed" && exit 1 [ ! -e ${tmpd}/abc ] && echo "dotfile not installed" && exit 1

View File

@@ -143,7 +143,7 @@ cd ${ddpath} | ${bin} files -c ${cfg1} -p pup -V | grep f_sub
cd ${ddpath} | ${bin} files -c ${cfg1} -p psubsub -V | grep f_sub cd ${ddpath} | ${bin} files -c ${cfg1} -p psubsub -V | grep f_sub
# test compare too # test compare too
cd ${ddpath} | ${bin} install -c ${cfg1} -p p2 -V cd ${ddpath} | ${bin} install -c ${cfg1} -p p2 -V -f
cd ${ddpath} | ${bin} compare -c ${cfg1} -p p2 -V cd ${ddpath} | ${bin} compare -c ${cfg1} -p p2 -V
# test with non-existing dotpath this time # test with non-existing dotpath this time
@@ -172,7 +172,7 @@ profiles:
dotfiles: dotfiles:
- f_asub - f_asub
_EOF _EOF
cd ${ddpath} | ${bin} install -c ${cfg1} -p p2 -V cd ${ddpath} | ${bin} install -c ${cfg1} -p p2 -V -f
cd ${ddpath} | ${bin} compare -c ${cfg1} -p p2 -V cd ${ddpath} | ${bin} compare -c ${cfg1} -p p2 -V
## CLEANING ## CLEANING

View File

@@ -89,7 +89,7 @@ _EOF
cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V --link=link_children ${dt} cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V --link=link_children ${dt}
# check is set to link_children # check is set to link_children
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "d_`basename ${dt}`" | grep ',link:link_children$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "d_`basename ${dt}`" | grep ',link:link_children,'
# checks file exists in dotpath # checks file exists in dotpath
[ ! -e ${dotpath}/${dt} ] && echo "dotfile not imported" && exit 1 [ ! -e ${dotpath}/${dt} ] && echo "dotfile not imported" && exit 1

View File

@@ -97,9 +97,9 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 --verbose ${dftoimport}
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
echo "[+] install" echo "[+] install"
cd ${ddpath} | ${bin} install -c ${cfg} -p p1 --verbose | grep '^5 dotfile(s) installed.$' cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 --verbose | grep '^5 dotfile(s) installed.$'
rm -f ${dftoimport} rm -f ${dftoimport}
cd ${ddpath} | ${bin} install -c ${cfg} -p p1 --verbose | grep '^6 dotfile(s) installed.$' cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 --verbose | grep '^6 dotfile(s) installed.$'
nb=`cd ${ddpath} | ${bin} files -c ${cfg} -p p1 --verbose | grep '^[a-zA-Z]' | wc -l` nb=`cd ${ddpath} | ${bin} files -c ${cfg} -p p1 --verbose | grep '^[a-zA-Z]' | wc -l`
[ "${nb}" != "6" ] && echo 'error in dotfile list' && exit 1 [ "${nb}" != "6" ] && echo 'error in dotfile list' && exit 1

View File

@@ -55,6 +55,7 @@ tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
# temporary # temporary
tmpa=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` tmpa=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
export DOTDROP_WORKERS=1
# create the config file # create the config file
cfg="${tmps}/config.yaml" cfg="${tmps}/config.yaml"
@@ -66,8 +67,8 @@ config:
actions: actions:
pre: pre:
first: 'echo first > ${tmpa}/cookie' first: 'echo first > ${tmpa}/cookie'
second: 'echo second >> ${tmpa}/cookie' second: 'sleep 1; echo second >> ${tmpa}/cookie'
third: 'echo third >> ${tmpa}/cookie' third: 'sleep 1; echo third >> ${tmpa}/cookie'
dotfiles: dotfiles:
f_first: f_first:
dst: ${tmpd}/first dst: ${tmpd}/first
@@ -115,9 +116,9 @@ for ((i=0;i<${attempts};i++)); do
echo "second timestamp: `stat -c %y ${tmpd}/second`" echo "second timestamp: `stat -c %y ${tmpd}/second`"
echo "third timestamp: `stat -c %y ${tmpd}/third`" echo "third timestamp: `stat -c %y ${tmpd}/third`"
ts_first=`date "+%S%N" -d "$(stat -c %y ${tmpd}/first)"` ts_first=`date "+%s" -d "$(stat -c %y ${tmpd}/first)"`
ts_second=`date "+%S%N" -d "$(stat -c %y ${tmpd}/second)"` ts_second=`date "+%s" -d "$(stat -c %y ${tmpd}/second)"`
ts_third=`date "+%S%N" -d "$(stat -c %y ${tmpd}/third)"` ts_third=`date "+%s" -d "$(stat -c %y ${tmpd}/third)"`
#echo "first ts: ${ts_first}" #echo "first ts: ${ts_first}"
#echo "second ts: ${ts_second}" #echo "second ts: ${ts_second}"

View File

@@ -87,7 +87,7 @@ profiles:
_EOF _EOF
echo "[+] install" echo "[+] install"
cd ${ddpath} | ${bin} install -c ${cfg} -p p1 --verbose | grep '^5 dotfile(s) installed.$' cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 --verbose | grep '^5 dotfile(s) installed.$'
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
## CLEANING ## CLEANING

View File

@@ -82,7 +82,7 @@ echo "new data" > ${basedir}/dotfiles/${tmpd}/readmes/README.md
# install # install
rm -rf ${tmpd} rm -rf ${tmpd}
echo "[+] install normal" echo "[+] install normal"
cd ${ddpath} | ${bin} install --showdiff -c ${cfg} --verbose cd ${ddpath} | ${bin} install --showdiff -c ${cfg} --verbose -f
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
nb=`find ${tmpd} -iname 'README.md' | wc -l` nb=`find ${tmpd} -iname 'README.md' | wc -l`
echo "(1) found ${nb} README.md file(s)" echo "(1) found ${nb} README.md file(s)"
@@ -96,7 +96,7 @@ cat ${cfg2}
# install # install
rm -rf ${tmpd} rm -rf ${tmpd}
echo "[+] install with ignore in dotfile" echo "[+] install with ignore in dotfile"
cd ${ddpath} | ${bin} install -c ${cfg2} --verbose cd ${ddpath} | ${bin} install -c ${cfg2} --verbose -f
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
nb=`find ${tmpd} -iname 'README.md' | wc -l` nb=`find ${tmpd} -iname 'README.md' | wc -l`
echo "(2) found ${nb} README.md file(s)" echo "(2) found ${nb} README.md file(s)"
@@ -110,7 +110,7 @@ cat ${cfg2}
# install # install
rm -rf ${tmpd} rm -rf ${tmpd}
echo "[+] install with ignore in config" echo "[+] install with ignore in config"
cd ${ddpath} | ${bin} install -c ${cfg2} --verbose cd ${ddpath} | ${bin} install -c ${cfg2} --verbose -f
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
nb=`find ${tmpd} -iname 'README.md' | wc -l` nb=`find ${tmpd} -iname 'README.md' | wc -l`
echo "(3) found ${nb} README.md file(s)" echo "(3) found ${nb} README.md file(s)"
@@ -118,7 +118,7 @@ echo "(3) found ${nb} README.md file(s)"
## reinstall to trigger showdiff ## reinstall to trigger showdiff
echo "showdiff" > ${tmpd}/program/a echo "showdiff" > ${tmpd}/program/a
cd ${ddpath} | echo "y" | ${bin} install --showdiff -c ${cfg} --verbose cd ${ddpath} | echo "y" | ${bin} install --showdiff -c ${cfg} --verbose -f
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
## CLEANING ## CLEANING

View File

@@ -84,7 +84,7 @@ echo 'test_y' > ${basedir}/dotfiles/y
echo "00000000 01 02 03 04 05" | xxd -r - ${basedir}/dotfiles/z echo "00000000 01 02 03 04 05" | xxd -r - ${basedir}/dotfiles/z
echo "[+] install" echo "[+] install"
cd ${ddpath} | ${bin} install -c ${cfg} -p p1 --showdiff --verbose --temp | grep '^3 dotfile(s) installed.$' cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 --showdiff --verbose --temp | grep '^3 dotfile(s) installed.$'
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && exit 1
## CLEANING ## CLEANING

127
tests-ng/install.sh Executable file
View File

@@ -0,0 +1,127 @@
#!/usr/bin/env bash
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# test install
# returns 1 in case of error
#
# exit on first error
set -e
# all this crap to get current path
rl="readlink -f"
if ! ${rl} "${0}" >/dev/null 2>&1; then
rl="realpath"
if ! hash ${rl}; then
echo "\"${rl}\" not found !" && exit 1
fi
fi
cur=$(dirname "$(${rl} "${0}")")
#hash dotdrop >/dev/null 2>&1
#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1
#echo "called with ${1}"
# dotdrop path can be pass as argument
ddpath="${cur}/../"
[ "${1}" != "" ] && ddpath="${1}"
[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1
export PYTHONPATH="${ddpath}:${PYTHONPATH}"
bin="python3 -m dotdrop.dotdrop"
hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true
echo "dotdrop path: ${ddpath}"
echo "pythonpath: ${PYTHONPATH}"
# get the helpers
source ${cur}/helpers
echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################
# this is the test
################################################################
get_file_mode()
{
u=`umask`
u=`echo ${u} | sed 's/^0*//'`
v=$((666 - u))
echo "${v}"
}
# $1 path
# $2 rights
has_rights()
{
echo "testing ${1} is ${2}"
[ ! -e "$1" ] && echo "`basename $1` does not exist" && exit 1
local mode=`stat -L -c '%a' "$1"`
[ "${mode}" != "$2" ] && echo "bad mode for `basename $1` (${mode} VS expected ${2})" && exit 1
true
}
# dotdrop directory
basedir=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
mkdir -p ${basedir}/dotfiles
echo "[+] dotdrop dir: ${basedir}"
echo "[+] dotpath dir: ${basedir}/dotfiles"
tmpd=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
echo "content" > ${basedir}/dotfiles/x
# create the config file
cfg="${basedir}/config.yaml"
cat > ${cfg} << _EOF
config:
backup: true
create: true
dotpath: dotfiles
dotfiles:
f_x:
src: x
dst: ${tmpd}/x
profiles:
p1:
dotfiles:
- f_x
_EOF
echo "[+] install"
cd ${ddpath} | ${bin} install -c ${cfg} -f -p p1 --verbose | grep '^1 dotfile(s) installed.$'
[ "$?" != "0" ] && exit 1
[ ! -e ${tmpd}/x ] && echo "f_x not installed" && exit 1
# update chmod
chmod 666 ${tmpd}/x
cd ${ddpath} | ${bin} update -c ${cfg} -f -p p1 --verbose ${tmpd}/x
# chmod updated
cat ${cfg} | grep "chmod: '666'"
chmod 644 ${tmpd}/x
mode=`get_file_mode ${tmpd}/x`
echo "[+] re-install with no"
cd ${ddpath} | printf "N\n" | ${bin} install -c ${cfg} -p p1 --verbose
[ "$?" != "0" ] && exit 1
# if user answers N, chmod should not be done
has_rights "${tmpd}/x" "${mode}"
echo "[+] re-install with yes"
cd ${ddpath} | printf "y\n" | ${bin} install -c ${cfg} -p p1 --verbose
[ "$?" != "0" ] && exit 1
has_rights "${tmpd}/x" "666"
## CLEANING
rm -rf ${basedir} ${tmpd}
echo "OK"
exit 0

View File

@@ -80,7 +80,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -114,7 +114,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -148,7 +148,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V --link=nolink
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -182,7 +182,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V --link=link
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:link$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:link,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -216,7 +216,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:link$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:link,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -250,7 +250,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V --link=nolink
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -284,7 +284,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V --link=nolink
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -318,7 +318,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V --link=nolink
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:nolink,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -350,7 +350,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} --link=link -p p1 ${df} -V
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:link$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "f_`basename ${df}`" | head -1 | grep ',link:link,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -411,7 +411,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} --link=link_children -p p1 ${df} -V
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "d_`basename ${df}`" | head -1 | grep ',link:link_children$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "d_`basename ${df}`" | head -1 | grep ',link:link_children,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert
@@ -451,7 +451,7 @@ cd ${ddpath} | ${bin} import -c ${cfg} -p p1 ${df} -V
# checks # checks
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V
cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "d_`basename ${df}`" | head -1 | grep ',link:link_children$' cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -V -G | grep "d_`basename ${df}`" | head -1 | grep ',link:link_children,'
# try to install # try to install
rm -rf ${tmpd}/qwert rm -rf ${tmpd}/qwert

View File

@@ -88,7 +88,7 @@ cat > ${tmps}/dotfiles/abc << _EOF
_EOF _EOF
# install # install
cd ${ddpath} | ${bin} install -c ${cfg} -p p0 -V cd ${ddpath} | ${bin} install -c ${cfg} -p p0 -V -f
# test file content # test file content
cat ${tmpd}/abc cat ${tmpd}/abc

View File

@@ -94,6 +94,9 @@ profiles:
_EOF _EOF
#cat ${cfg} #cat ${cfg}
# list profiles
cd ${ddpath} | ${bin} profiles -c ${cfg} -V
# create the dotfile # create the dotfile
echo "test" > ${tmps}/dotfiles/abc echo "test" > ${tmps}/dotfiles/abc
echo "test" > ${tmps}/dotfiles/def echo "test" > ${tmps}/dotfiles/def

81
tests-ng/tests-launcher.py Executable file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
# author: deadc0de6 (https://github.com/deadc0de6)
# Copyright (c) 2020, deadc0de6
#
# tests launcher
#
import os
import sys
import subprocess
from concurrent import futures
MAX_JOBS = 10
def run_test(path):
cur = os.path.dirname(sys.argv[0])
name = os.path.basename(path)
path = os.path.join(cur, name)
p = subprocess.Popen(path, shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
out, _ = p.communicate()
out = out.decode()
r = p.returncode == 0
reason = 'returncode'
if 'Traceback' in out:
r = False
reason = 'traceback'
return r, reason, path, out
def get_tests():
tests = []
cur = os.path.dirname(sys.argv[0])
for (_, _, filenames) in os.walk(cur):
for path in filenames:
if not path.endswith('.sh'):
continue
tests.append(path)
break
return tests
def main():
global MAX_JOBS
if len(sys.argv) > 1:
MAX_JOBS = int(sys.argv[1])
tests = get_tests()
with futures.ThreadPoolExecutor(max_workers=MAX_JOBS) as ex:
wait_for = []
for test in tests:
j = ex.submit(run_test, test)
wait_for.append(j)
for f in futures.as_completed(wait_for):
r, reason, p, log = f.result()
if not r:
ex.shutdown(wait=False)
for x in wait_for:
x.cancel()
print()
print(log)
print('test {} failed ({})'.format(p, reason))
return False
else:
sys.stdout.write('.')
sys.stdout.flush()
sys.stdout.write('\n')
return True
if __name__ == '__main__':
if not main():
sys.exit(1)
sys.exit(0)

View File

@@ -89,6 +89,7 @@ dotfiles:
src: ghi src: ghi
trans: uncompress trans: uncompress
trans_write: compress trans_write: compress
chmod: 700
profiles: profiles:
p1: p1:
dotfiles: dotfiles:
@@ -125,40 +126,43 @@ tar -tf ${tmps}/dotfiles/ghi
# test install and compare # test install and compare
########################### ###########################
echo "[+] run install"
# install # install
cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -b -V cd ${ddpath} | ${bin} install -f -c ${cfg} -p p1 -b -V
# check canary dotfile # check canary dotfile
[ ! -e ${tmpd}/def ] && exit 1 [ ! -e ${tmpd}/def ] && echo "def does not exist" && exit 1
# check base64 dotfile # check base64 dotfile
[ ! -e ${tmpd}/abc ] && exit 1 [ ! -e ${tmpd}/abc ] && echo "abc does not exist" && exit 1
content=`cat ${tmpd}/abc` content=`cat ${tmpd}/abc`
[ "${content}" != "${token}" ] && exit 1 [ "${content}" != "${token}" ] && echo "bad content for abc" && exit 1
# check directory dotfile # check directory dotfile
[ ! -e ${tmpd}/ghi/a/dir1/otherfile ] && exit 1 [ ! -e ${tmpd}/ghi/a/dir1/otherfile ] && echo "otherfile does not exist" && exit 1
content=`cat ${tmpd}/ghi/a/somefile` content=`cat ${tmpd}/ghi/a/somefile`
[ "${content}" != "${tokend}" ] && exit 1 [ "${content}" != "${tokend}" ] && echo "bad content for somefile" && exit 1
content=`cat ${tmpd}/ghi/a/dir1/otherfile` content=`cat ${tmpd}/ghi/a/dir1/otherfile`
[ "${content}" != "${tokend}" ] && exit 1 [ "${content}" != "${tokend}" ] && echo "bad content for otherfile" && exit 1
# compare # compare
set +e
cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -b -V cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -b -V
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && echo "compare failed (0)" && exit 1
set -e
# change base64 deployed file # change base64 deployed file
echo ${touched} > ${tmpd}/abc echo ${touched} > ${tmpd}/abc
set +e set +e
cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -b -V cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -b -V
[ "$?" != "1" ] && exit 1 [ "$?" != "1" ] && echo "compare failed (1)" && exit 1
set -e set -e
# change uncompressed deployed dotfile # change uncompressed deployed dotfile
echo ${touched} > ${tmpd}/ghi/a/somefile echo ${touched} > ${tmpd}/ghi/a/somefile
set +e set +e
cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -b -V cd ${ddpath} | ${bin} compare -c ${cfg} -p p1 -b -V
[ "$?" != "1" ] && exit 1 [ "$?" != "1" ] && echo "compare failed (2)" && exit 1
set -e set -e
########################### ###########################
@@ -167,38 +171,44 @@ set -e
# update single file # update single file
echo 'update' > ${tmpd}/def echo 'update' > ${tmpd}/def
set +e
cd ${ddpath} | ${bin} update -f -k -c ${cfg} -p p1 -b -V f_def cd ${ddpath} | ${bin} update -f -k -c ${cfg} -p p1 -b -V f_def
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && echo "update failed (1)" && exit 1
set -e
[ ! -e ${tmpd}/def ] && echo 'dotfile in FS removed' && exit 1 [ ! -e ${tmpd}/def ] && echo 'dotfile in FS removed' && exit 1
[ ! -e ${tmps}/dotfiles/def ] && echo 'dotfile in dotpath removed' && exit 1 [ ! -e ${tmps}/dotfiles/def ] && echo 'dotfile in dotpath removed' && exit 1
# update single file # update single file
set +e
cd ${ddpath} | ${bin} update -f -k -c ${cfg} -p p1 -b -V f_abc cd ${ddpath} | ${bin} update -f -k -c ${cfg} -p p1 -b -V f_abc
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && echo "update failed (2)" && exit 1
set -e
# test updated file # test updated file
[ ! -e ${tmps}/dotfiles/abc ] && exit 1 [ ! -e ${tmps}/dotfiles/abc ] && echo "abc does not exist" && exit 1
content=`cat ${tmps}/dotfiles/abc` content=`cat ${tmps}/dotfiles/abc`
bcontent=`echo ${touched} | base64` bcontent=`echo ${touched} | base64`
[ "${content}" != "${bcontent}" ] && exit 1 [ "${content}" != "${bcontent}" ] && echo "bad content for abc" && exit 1
# update directory # update directory
echo ${touched} > ${tmpd}/ghi/b/newfile echo ${touched} > ${tmpd}/ghi/b/newfile
rm -r ${tmpd}/ghi/c rm -r ${tmpd}/ghi/c
cd ${ddpath} | ${bin} update -f -k -c ${cfg} -p p1 -b -V d_ghi cd ${ddpath} | ${bin} update -f -k -c ${cfg} -p p1 -b -V d_ghi
[ "$?" != "0" ] && exit 1 [ "$?" != "0" ] && echo "update failed" && exit 1
# test updated directory # test updated directory
tar -tf ${tmps}/dotfiles/ghi | grep './b/newfile' set +e
tar -tf ${tmps}/dotfiles/ghi | grep './a/dir1/otherfile' tar -tf ${tmps}/dotfiles/ghi | grep './b/newfile' || (echo "newfile not found in tar" && exit 1)
tar -tf ${tmps}/dotfiles/ghi | grep './a/dir1/otherfile' || (echo "otherfile not found in tar" && exit 1)
set -e
tmpy=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` tmpy=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
tar -xf ${tmps}/dotfiles/ghi -C ${tmpy} tar -xf ${tmps}/dotfiles/ghi -C ${tmpy}
content=`cat ${tmpy}/a/somefile` content=`cat ${tmpy}/a/somefile`
[ "${content}" != "${touched}" ] && exit 1 [ "${content}" != "${touched}" ] && echo "bad content" && exit 1
# check canary dotfile # check canary dotfile
[ ! -e ${tmps}/dotfiles/def ] && exit 1 [ ! -e ${tmps}/dotfiles/def ] && echo "def not found" && exit 1
## CLEANING ## CLEANING
rm -rf ${tmps} ${tmpd} ${tmpx} ${tmpy} rm -rf ${tmps} ${tmpd} ${tmpx} ${tmpy}

View File

@@ -46,6 +46,15 @@ echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
# this is the test # this is the test
################################################################ ################################################################
# $1 pattern
# $2 path
grep_or_fail()
{
set +e
grep "${1}" "${2}" >/dev/null 2>&1 || (echo "pattern not found in ${2}" && exit 1)
set -e
}
# dotdrop directory # dotdrop directory
tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d` tmps=`mktemp -d --suffix='-dotdrop-tests' || mktemp -d`
dt="${tmps}/dotfiles" dt="${tmps}/dotfiles"
@@ -98,7 +107,7 @@ cd ${ddpath} | ${bin} update -f -c ${cfg} --verbose --profile=p1 --key f_abc
#tree ${dt} #tree ${dt}
# check files haven't been updated # check files haven't been updated
grep 'b' ${dt}/a/c/acfile >/dev/null grep_or_fail 'b' "${dt}/a/c/acfile"
[ -e ${dt}/a/newfile ] && echo "should not have been updated" && exit 1 [ -e ${dt}/a/newfile ] && echo "should not have been updated" && exit 1
## CLEANING ## CLEANING

View File

@@ -45,6 +45,7 @@ echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)"
################################################################ ################################################################
# this is the test # this is the test
################################################################ ################################################################
unset DOTDROP_WORKDIR
string="blabla" string="blabla"
# the dotfile source # the dotfile source

View File

@@ -3,7 +3,8 @@
# Copyright (c) 2017, deadc0de6 # Copyright (c) 2017, deadc0de6
# stop on first error # stop on first error
set -ev #set -ev
set -e
# PEP8 tests # PEP8 tests
which pycodestyle >/dev/null 2>&1 which pycodestyle >/dev/null 2>&1
@@ -30,10 +31,23 @@ export DOTDROP_FORCE_NODEBUG=yes
# coverage file location # coverage file location
cur=`dirname $(readlink -f "${0}")` cur=`dirname $(readlink -f "${0}")`
export COVERAGE_FILE="${cur}/.coverage"
workers=${DOTDROP_WORKERS}
if [ ! -z ${workers} ]; then
unset DOTDROP_WORKERS
echo "DISABLE workers"
fi
# execute tests with coverage # execute tests with coverage
PYTHONPATH="dotdrop" ${nosebin} -s --with-coverage --cover-package=dotdrop if [ -z ${GITHUB_WORKFLOW} ]; then
## local
export COVERAGE_FILE=
PYTHONPATH="dotdrop" ${nosebin} -s --processes=-1 --with-coverage --cover-package=dotdrop
else
## CI/CD
export COVERAGE_FILE="${cur}/.coverage"
PYTHONPATH="dotdrop" ${nosebin} --processes=0 --with-coverage --cover-package=dotdrop
fi
#PYTHONPATH="dotdrop" python3 -m pytest tests #PYTHONPATH="dotdrop" python3 -m pytest tests
# enable debug logs # enable debug logs
@@ -41,32 +55,23 @@ export DOTDROP_DEBUG=yes
unset DOTDROP_FORCE_NODEBUG unset DOTDROP_FORCE_NODEBUG
# do not print debugs when running tests (faster) # do not print debugs when running tests (faster)
#export DOTDROP_FORCE_NODEBUG=yes #export DOTDROP_FORCE_NODEBUG=yes
export DOTDROP_WORKDIR=/tmp/dotdrop-tests-workdir
## execute bash script tests if [ ! -z ${workers} ]; then
[ "$1" = '--python-only' ] || { DOTDROP_WORKERS=${workers}
echo "doing extended tests" echo "ENABLE workers: ${workers}"
logdir=`mktemp -d` fi
for scr in tests-ng/*.sh; do
logfile="${logdir}/`basename ${scr}`.log" # run bash tests
echo "-> running test ${scr} (logfile:${logfile})" if [ -z ${GITHUB_WORKFLOW} ]; then
set +e ## local
${scr} > "${logfile}" 2>&1 export COVERAGE_FILE=
if [ "$?" -ne 0 ]; then tests-ng/tests-launcher.py
cat ${logfile} else
echo "test ${scr} finished with error" ## CI/CD
rm -rf ${logdir} export COVERAGE_FILE="${cur}/.coverage"
exit 1 tests-ng/tests-launcher.py 1
elif grep Traceback ${logfile}; then fi
cat ${logfile}
echo "test ${scr} crashed"
rm -rf ${logdir}
exit 1
fi
set -e
echo "test ${scr} ok"
done
rm -rf ${logdir}
}
## test the doc with remark ## test the doc with remark
## https://github.com/remarkjs/remark-validate-links ## https://github.com/remarkjs/remark-validate-links
@@ -81,18 +86,18 @@ else
remark -f -u validate-links *.md remark -f -u validate-links *.md
fi fi
## test the doc with markdown-link-check ### test the doc with markdown-link-check
## https://github.com/tcort/markdown-link-check ### https://github.com/tcort/markdown-link-check
set +e #set +e
which markdown-link-check >/dev/null 2>&1 #which markdown-link-check >/dev/null 2>&1
r="$?" #r="$?"
set -e #set -e
if [ "$r" != "0" ]; then #if [ "$r" != "0" ]; then
echo "[WARNING] install \"markdown-link-check\" to test the doc" # echo "[WARNING] install \"markdown-link-check\" to test the doc"
else #else
for i in `find docs -iname '*.md'`; do markdown-link-check $i; done # for i in `find docs -iname '*.md'`; do markdown-link-check $i; done
markdown-link-check README.md # markdown-link-check README.md
fi #fi
## done ## done
echo "All test finished successfully in ${SECONDS}s" echo "All test finished successfully in ${SECONDS}s"

View File

@@ -65,7 +65,9 @@ def get_string(length):
def get_tempdir(): def get_tempdir():
"""Get a temporary directory""" """Get a temporary directory"""
return tempfile.mkdtemp(suffix=TMPSUFFIX) tmpdir = tempfile.mkdtemp(suffix=TMPSUFFIX)
os.chmod(tmpdir, 0o755)
return tmpdir
def create_random_file(directory, content=None, def create_random_file(directory, content=None,
@@ -132,6 +134,7 @@ def _fake_args():
args['--as'] = None args['--as'] = None
args['--file-only'] = False args['--file-only'] = False
args['--workers'] = 1 args['--workers'] = 1
args['--preserve-mode'] = False
# cmds # cmds
args['profiles'] = False args['profiles'] = False
args['files'] = False args['files'] = False

View File

@@ -10,7 +10,7 @@ import os
from dotdrop.dotdrop import cmd_importer from dotdrop.dotdrop import cmd_importer
from dotdrop.dotdrop import cmd_list_profiles from dotdrop.dotdrop import cmd_list_profiles
from dotdrop.dotdrop import cmd_list_files from dotdrop.dotdrop import cmd_files
from dotdrop.dotdrop import cmd_update from dotdrop.dotdrop import cmd_update
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
@@ -184,7 +184,7 @@ class TestImport(unittest.TestCase):
self.assertTrue(os.path.exists(s4)) self.assertTrue(os.path.exists(s4))
cmd_list_profiles(o) cmd_list_profiles(o)
cmd_list_files(o) cmd_files(o)
# fake test update # fake test update
editcontent = 'edited' editcontent = 'edited'

View File

@@ -6,7 +6,7 @@ basic unittest for the install function
import os import os
import unittest import unittest
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock
import filecmp import filecmp
from dotdrop.cfg_aggregator import CfgAggregator as Cfg from dotdrop.cfg_aggregator import CfgAggregator as Cfg
@@ -349,8 +349,9 @@ exec bspwm
srcs = [create_random_file(src_dir)[0] for _ in range(3)] srcs = [create_random_file(src_dir)[0] for _ in range(3)]
installer = Installer() installer = Installer()
installer.link_children(templater=MagicMock(), src=src_dir, installer.install(templater=MagicMock(), src=src_dir,
dst=dst_dir, actionexec=None) dst=dst_dir, linktype=LinkTypes.LINK_CHILDREN,
actionexec=None)
# Ensure all destination files point to source # Ensure all destination files point to source
for src in srcs: for src in srcs:
@@ -365,8 +366,10 @@ exec bspwm
# logger = MagicMock() # logger = MagicMock()
# installer.log.err = logger # installer.log.err = logger
res, err = installer.link_children(templater=MagicMock(), src=src, res, err = installer.install(templater=MagicMock(), src=src,
dst='/dev/null', actionexec=None) dst='/dev/null',
linktype=LinkTypes.LINK_CHILDREN,
actionexec=None)
self.assertFalse(res) self.assertFalse(res)
e = 'source dotfile does not exist: {}'.format(src) e = 'source dotfile does not exist: {}'.format(src)
@@ -387,8 +390,10 @@ exec bspwm
# installer.log.err = logger # installer.log.err = logger
# pass src file not src dir # pass src file not src dir
res, err = installer.link_children(templater=templater, src=src, res, err = installer.install(templater=templater, src=src,
dst='/dev/null', actionexec=None) dst='/dev/null',
linktype=LinkTypes.LINK_CHILDREN,
actionexec=None)
# ensure nothing performed # ensure nothing performed
self.assertFalse(res) self.assertFalse(res)
@@ -410,8 +415,9 @@ exec bspwm
self.assertFalse(os.path.exists(dst_dir)) self.assertFalse(os.path.exists(dst_dir))
installer = Installer() installer = Installer()
installer.link_children(templater=MagicMock(), src=src_dir, installer.install(templater=MagicMock(), src=src_dir,
dst=dst_dir, actionexec=None) dst=dst_dir, linktype=LinkTypes.LINK_CHILDREN,
actionexec=None)
# ensure dst dir created # ensure dst dir created
self.assertTrue(os.path.exists(dst_dir)) self.assertTrue(os.path.exists(dst_dir))
@@ -442,8 +448,9 @@ exec bspwm
installer.safe = True installer.safe = True
installer.log.ask = ask installer.log.ask = ask
installer.link_children(templater=MagicMock(), src=src_dir, dst=dst, installer.install(templater=MagicMock(), src=src_dir,
actionexec=None) dst=dst, linktype=LinkTypes.LINK_CHILDREN,
actionexec=None)
# ensure destination now a directory # ensure destination now a directory
self.assertTrue(os.path.isdir(dst)) self.assertTrue(os.path.isdir(dst))
@@ -453,8 +460,7 @@ exec bspwm
'Remove regular file {} and replace with empty directory?' 'Remove regular file {} and replace with empty directory?'
.format(dst)) .format(dst))
@patch('dotdrop.installer.Templategen') def test_runs_templater(self):
def test_runs_templater(self, mocked_templategen):
"""test runs templater""" """test runs templater"""
# create source dir # create source dir
src_dir = get_tempdir() src_dir = get_tempdir()
@@ -473,11 +479,9 @@ exec bspwm
installer = Installer() installer = Installer()
templater = MagicMock() templater = MagicMock()
templater.generate.return_value = b'content' templater.generate.return_value = b'content'
# make templategen treat everything as a template
mocked_templategen.is_template.return_value = True
installer.link_children(templater=templater, src=src_dir, dst=dst_dir, installer.install(templater=templater, src=src_dir, dst=dst_dir,
actionexec=None) linktype=LinkTypes.LINK_CHILDREN, actionexec=None)
for src in srcs: for src in srcs:
dst = os.path.join(dst_dir, os.path.basename(src)) dst = os.path.join(dst_dir, os.path.basename(src))

View File

@@ -9,7 +9,7 @@ import unittest
import os import os
from dotdrop.dotdrop import cmd_list_profiles from dotdrop.dotdrop import cmd_list_profiles
from dotdrop.dotdrop import cmd_list_files from dotdrop.dotdrop import cmd_files
from dotdrop.dotdrop import cmd_detail from dotdrop.dotdrop import cmd_detail
from dotdrop.dotdrop import cmd_importer from dotdrop.dotdrop import cmd_importer
@@ -87,9 +87,9 @@ class TestListings(unittest.TestCase):
# list files # list files
o.files_templateonly = False o.files_templateonly = False
cmd_list_files(o) cmd_files(o)
o.files_templateonly = True o.files_templateonly = True
cmd_list_files(o) cmd_files(o)
# details # details
o.detail_keys = None o.detail_keys = None