mirror of
https://github.com/deadc0de6/dotdrop.git
synced 2026-02-09 22:14:17 +00:00
Merge pull request #77 from Iambecomeroot/feature_link_children
Feature link_children
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -7,3 +7,7 @@
|
|||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
|
tags
|
||||||
|
env
|
||||||
|
htmlcov
|
||||||
|
|
||||||
|
|||||||
69
README.md
69
README.md
@@ -535,14 +535,77 @@ and the second using transformations (see [Transformations](#use-transformations
|
|||||||
|
|
||||||
## Symlink dotfiles
|
## Symlink dotfiles
|
||||||
|
|
||||||
Dotdrop allows to symlink dotfiles. Simply set the `link: true` under the
|
Dotdrop offers two ways to symlink dotfiles. The first simply links `dst` to
|
||||||
dotfile entry in the config file.
|
`src`. To enable it, simply set `link: true` under the dotfile entry in the
|
||||||
|
config file.
|
||||||
|
|
||||||
|
The second symlink method is a little more complicated. It creates a symlink in
|
||||||
|
`dst` for every file/directory in `src`.
|
||||||
|
|
||||||
|
### Why would I use `link_children`?
|
||||||
|
This feature can be very useful dotfiles such as vim where you may not want
|
||||||
|
plugins cluttering your dotfiles repository. First, the simpler `link: true` is
|
||||||
|
shown for comparison. With the `config.yaml` entry shown below, `~/.vim` gets
|
||||||
|
symlinked to `~/.dotfiles/vim/`. This means that using vim will now pollute the
|
||||||
|
dotfiles repository. `plugged` (if using
|
||||||
|
[vim-plug](https://github.com/junegunn/vim-plug)), `spell`, and `swap`
|
||||||
|
directories will appear `~/.dotfiles/vim/`.
|
||||||
|
|
||||||
|
```yml
|
||||||
|
vim:
|
||||||
|
dst: ~/.vim/
|
||||||
|
src: ./vim/
|
||||||
|
actions:
|
||||||
|
- vim-plug-install
|
||||||
|
- vim-plug
|
||||||
|
link: true
|
||||||
|
```
|
||||||
|
```
|
||||||
|
$ readlink ~/.vim
|
||||||
|
~/.dotfiles/vim/
|
||||||
|
$ ls ~/.dotfiles/vim/
|
||||||
|
after autoload plugged plugin snippets spell swap vimrc
|
||||||
|
```
|
||||||
|
Let's say we just want to store `after`, `plugin`, `snippets`, and `vimrc` in
|
||||||
|
our `~/.dotfiles` repository. This is where `link_children` comes in. Using the
|
||||||
|
configuration below, `~/.vim/` is a normal directory and only the children of
|
||||||
|
`~/.dotfiles/vim` are symlinked into it.
|
||||||
|
```yml
|
||||||
|
vim:
|
||||||
|
dst: ~/.vim/
|
||||||
|
src: ./vim/
|
||||||
|
actions:
|
||||||
|
- vim-plug-install
|
||||||
|
- vim-plug
|
||||||
|
link_children: true
|
||||||
|
```
|
||||||
|
|
||||||
|
As can be seen below, `~/.vim/` is a normal directory, not a symlink. Also, the
|
||||||
|
files/directories `after`, `plugin`, `snippets`, and `vimrc` are symlinked to
|
||||||
|
`~/.dotfiles/vim/`.
|
||||||
|
```
|
||||||
|
$ readlink -f ~/.vim
|
||||||
|
~/.vim
|
||||||
|
$ tree -L 1 ~/.vim
|
||||||
|
~/.vim
|
||||||
|
├── after -> /.dotfiles/./vim/after
|
||||||
|
├── autoload
|
||||||
|
├── plugged
|
||||||
|
├── plugin -> /.dotfiles/./vim/plugin
|
||||||
|
├── snippets -> /.dotfiles/./vim/snippets
|
||||||
|
├── spell
|
||||||
|
├── swap
|
||||||
|
└── vimrc -> /.dotfiles/./vim/vimrc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Templating symlinked dotfiles
|
||||||
|
|
||||||
For dotfiles not using any templating directives, those are directly linked
|
For dotfiles not using any templating directives, those are directly linked
|
||||||
to dotdrop's `dotpath` directory (see [Config](#config)).
|
to dotdrop's `dotpath` directory (see [Config](#config)).
|
||||||
When using templating directives, the dotfiles are first installed into
|
When using templating directives, the dotfiles are first installed into
|
||||||
`workdir` (defaults to *~/.config/dotdrop*, see [Config](#config))
|
`workdir` (defaults to *~/.config/dotdrop*, see [Config](#config))
|
||||||
and then symlinked there.
|
and then symlinked there. This applies to both dotfiles with `link: true` and
|
||||||
|
`link_children: true`.
|
||||||
|
|
||||||
For example
|
For example
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from dotdrop.templategen import Templategen
|
|||||||
from dotdrop.logger import Logger
|
from dotdrop.logger import Logger
|
||||||
from dotdrop.action import Action, Transform
|
from dotdrop.action import Action, Transform
|
||||||
from dotdrop.utils import *
|
from dotdrop.utils import *
|
||||||
|
from dotdrop.linktypes import LinkTypes
|
||||||
|
|
||||||
|
|
||||||
class Cfg:
|
class Cfg:
|
||||||
@@ -52,6 +53,7 @@ class Cfg:
|
|||||||
key_dotfiles_src = 'src'
|
key_dotfiles_src = 'src'
|
||||||
key_dotfiles_dst = 'dst'
|
key_dotfiles_dst = 'dst'
|
||||||
key_dotfiles_link = 'link'
|
key_dotfiles_link = 'link'
|
||||||
|
key_dotfiles_link_children = 'link_children'
|
||||||
key_dotfiles_noempty = 'ignoreempty'
|
key_dotfiles_noempty = 'ignoreempty'
|
||||||
key_dotfiles_cmpignore = 'cmpignore'
|
key_dotfiles_cmpignore = 'cmpignore'
|
||||||
key_dotfiles_actions = 'actions'
|
key_dotfiles_actions = 'actions'
|
||||||
@@ -68,7 +70,7 @@ class Cfg:
|
|||||||
default_backup = True
|
default_backup = True
|
||||||
default_create = True
|
default_create = True
|
||||||
default_banner = True
|
default_banner = True
|
||||||
default_link = False
|
default_link = LinkTypes.NOLINK
|
||||||
default_longkey = False
|
default_longkey = False
|
||||||
default_keepdot = False
|
default_keepdot = False
|
||||||
default_showdiff = False
|
default_showdiff = False
|
||||||
@@ -214,10 +216,24 @@ class Cfg:
|
|||||||
# ensures the dotfiles entry is a dict
|
# ensures the dotfiles entry is a dict
|
||||||
self.content[self.key_dotfiles] = {}
|
self.content[self.key_dotfiles] = {}
|
||||||
for k, v in self.content[self.key_dotfiles].items():
|
for k, v in self.content[self.key_dotfiles].items():
|
||||||
src = v[self.key_dotfiles_src]
|
src = os.path.normpath(v[self.key_dotfiles_src])
|
||||||
dst = v[self.key_dotfiles_dst]
|
dst = os.path.normpath(v[self.key_dotfiles_dst])
|
||||||
link = v[self.key_dotfiles_link] if self.key_dotfiles_link \
|
|
||||||
in v else self.default_link
|
# Fail if both `link` and `link_children` present
|
||||||
|
if self.key_dotfiles_link in v \
|
||||||
|
and self.key_dotfiles_link_children in v:
|
||||||
|
msg = 'only one of `link` or `link_children` allowed per'
|
||||||
|
msg += ' dotfile, error on dotfile "{}".'
|
||||||
|
self.log.err(msg.format(k))
|
||||||
|
|
||||||
|
# Otherwise, get link type
|
||||||
|
link = LinkTypes.NOLINK
|
||||||
|
if self.key_dotfiles_link in v and v[self.key_dotfiles_link]:
|
||||||
|
link = LinkTypes.PARENTS
|
||||||
|
if self.key_dotfiles_link_children in v \
|
||||||
|
and v[self.key_dotfiles_link_children]:
|
||||||
|
link = LinkTypes.CHILDREN
|
||||||
|
|
||||||
noempty = v[self.key_dotfiles_noempty] if \
|
noempty = v[self.key_dotfiles_noempty] if \
|
||||||
self.key_dotfiles_noempty \
|
self.key_dotfiles_noempty \
|
||||||
in v else self.lnk_settings[self.key_ignoreempty]
|
in v else self.lnk_settings[self.key_ignoreempty]
|
||||||
@@ -266,7 +282,7 @@ class Cfg:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# disable transformation when link is true
|
# disable transformation when link is true
|
||||||
if link and (trans_r or trans_w):
|
if link != LinkTypes.NOLINK and (trans_r or trans_w):
|
||||||
msg = 'transformations disabled for \"{}\"'.format(dst)
|
msg = 'transformations disabled for \"{}\"'.format(dst)
|
||||||
msg += ' because link is True'
|
msg += ' because link is True'
|
||||||
self.log.warn(msg)
|
self.log.warn(msg)
|
||||||
@@ -527,7 +543,7 @@ class Cfg:
|
|||||||
return False, self._get_long_key(path)
|
return False, self._get_long_key(path)
|
||||||
return False, self._get_short_key(path, self.dotfiles.keys())
|
return False, self._get_short_key(path, self.dotfiles.keys())
|
||||||
|
|
||||||
def new(self, dotfile, profile, link=False, debug=False):
|
def new(self, dotfile, profile, link=LinkTypes.NOLINK, debug=False):
|
||||||
"""import new dotfile
|
"""import new dotfile
|
||||||
dotfile key will change and can be empty"""
|
dotfile key will change and can be empty"""
|
||||||
# keep it short
|
# keep it short
|
||||||
@@ -576,9 +592,9 @@ class Cfg:
|
|||||||
self.key_dotfiles_dst: dotfile.dst,
|
self.key_dotfiles_dst: dotfile.dst,
|
||||||
self.key_dotfiles_src: dotfile.src,
|
self.key_dotfiles_src: dotfile.src,
|
||||||
}
|
}
|
||||||
if link:
|
if link != LinkTypes.NOLINK:
|
||||||
# set the link flag
|
# set the link flag
|
||||||
dots[dotfile.key][self.key_dotfiles_link] = True
|
dots[dotfile.key][self.key_dotfiles_link] = link
|
||||||
|
|
||||||
# link it to this profile in the yaml file
|
# link it to this profile in the yaml file
|
||||||
pro = self.content[self.key_profiles][profile]
|
pro = self.content[self.key_profiles][profile]
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ entry point
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import subprocess
|
|
||||||
import socket
|
import socket
|
||||||
from docopt import docopt
|
from docopt import docopt
|
||||||
|
|
||||||
@@ -20,7 +19,8 @@ from dotdrop.updater import Updater
|
|||||||
from dotdrop.comparator import Comparator
|
from dotdrop.comparator import Comparator
|
||||||
from dotdrop.dotfile import Dotfile
|
from dotdrop.dotfile import Dotfile
|
||||||
from dotdrop.config import Cfg
|
from dotdrop.config import Cfg
|
||||||
from dotdrop.utils import *
|
from dotdrop.utils import get_tmpdir, remove, strip_home, run
|
||||||
|
from dotdrop.linktypes import LinkTypes
|
||||||
|
|
||||||
LOG = Logger()
|
LOG = Logger()
|
||||||
ENV_PROFILE = 'DOTDROP_PROFILE'
|
ENV_PROFILE = 'DOTDROP_PROFILE'
|
||||||
@@ -108,8 +108,10 @@ def cmd_install(opts, conf, temporary=False, keys=[]):
|
|||||||
preactions.append(action)
|
preactions.append(action)
|
||||||
if opts['debug']:
|
if opts['debug']:
|
||||||
LOG.dbg('installing {}'.format(dotfile))
|
LOG.dbg('installing {}'.format(dotfile))
|
||||||
if hasattr(dotfile, 'link') and dotfile.link:
|
if hasattr(dotfile, 'link') and dotfile.link == LinkTypes.PARENTS:
|
||||||
r = inst.link(t, dotfile.src, dotfile.dst, actions=preactions)
|
r = inst.link(t, dotfile.src, dotfile.dst, actions=preactions)
|
||||||
|
elif hasattr(dotfile, 'link') and dotfile.link == LinkTypes.CHILDREN:
|
||||||
|
r = inst.linkall(t, dotfile.src, dotfile.dst, actions=preactions)
|
||||||
else:
|
else:
|
||||||
src = dotfile.src
|
src = dotfile.src
|
||||||
tmp = None
|
tmp = None
|
||||||
@@ -259,6 +261,9 @@ def cmd_importer(opts, conf, paths):
|
|||||||
|
|
||||||
# create a new dotfile
|
# create a new dotfile
|
||||||
dotfile = Dotfile('', dst, src)
|
dotfile = Dotfile('', dst, src)
|
||||||
|
|
||||||
|
linktype = LinkTypes(opts['link'])
|
||||||
|
|
||||||
if opts['debug']:
|
if opts['debug']:
|
||||||
LOG.dbg('new dotfile: {}'.format(dotfile))
|
LOG.dbg('new dotfile: {}'.format(dotfile))
|
||||||
|
|
||||||
@@ -277,7 +282,7 @@ def cmd_importer(opts, conf, paths):
|
|||||||
cmd = ['cp', '-R', '-L', dst, srcf]
|
cmd = ['cp', '-R', '-L', dst, srcf]
|
||||||
if opts['dry']:
|
if opts['dry']:
|
||||||
LOG.dry('would run: {}'.format(' '.join(cmd)))
|
LOG.dry('would run: {}'.format(' '.join(cmd)))
|
||||||
if opts['link']:
|
if linktype == LinkTypes.PARENTS:
|
||||||
LOG.dry('would symlink {} to {}'.format(srcf, dst))
|
LOG.dry('would symlink {} to {}'.format(srcf, dst))
|
||||||
else:
|
else:
|
||||||
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True)
|
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True)
|
||||||
@@ -285,11 +290,11 @@ def cmd_importer(opts, conf, paths):
|
|||||||
LOG.err('importing \"{}\" failed!'.format(path))
|
LOG.err('importing \"{}\" failed!'.format(path))
|
||||||
ret = False
|
ret = False
|
||||||
continue
|
continue
|
||||||
if opts['link']:
|
if linktype == LinkTypes.PARENTS:
|
||||||
remove(dst)
|
remove(dst)
|
||||||
os.symlink(srcf, dst)
|
os.symlink(srcf, dst)
|
||||||
retconf, dotfile = conf.new(dotfile, opts['profile'],
|
retconf, dotfile = conf.new(dotfile, opts['profile'],
|
||||||
link=opts['link'], debug=opts['debug'])
|
link=linktype, debug=opts['debug'])
|
||||||
if retconf:
|
if retconf:
|
||||||
LOG.sub('\"{}\" imported'.format(path))
|
LOG.sub('\"{}\" imported'.format(path))
|
||||||
cnt += 1
|
cnt += 1
|
||||||
@@ -433,9 +438,16 @@ def main():
|
|||||||
opts['profile'] = args['--profile']
|
opts['profile'] = args['--profile']
|
||||||
opts['safe'] = not args['--force']
|
opts['safe'] = not args['--force']
|
||||||
opts['installdiff'] = not args['--nodiff']
|
opts['installdiff'] = not args['--nodiff']
|
||||||
opts['link'] = opts['link_by_default']
|
opts['link'] = LinkTypes.NOLINK
|
||||||
if args['--inv-link']:
|
if opts['link_by_default']:
|
||||||
opts['link'] = not opts['link']
|
opts['link'] = LinkTypes.PARENTS
|
||||||
|
|
||||||
|
# Only invert link type from NOLINK to PARENTS and vice-versa
|
||||||
|
if args['--inv-link'] and opts['link'] == LinkTypes.NOLINK:
|
||||||
|
opts['link'] = LinkTypes.PARENTS
|
||||||
|
if args['--inv-link'] and opts['link'] == LinkTypes.PARENTS:
|
||||||
|
opts['link'] = LinkTypes.NOLINK
|
||||||
|
|
||||||
opts['debug'] = args['--verbose']
|
opts['debug'] = args['--verbose']
|
||||||
opts['variables'] = conf.get_variables(opts['profile'])
|
opts['variables'] = conf.get_variables(opts['profile'])
|
||||||
opts['showdiff'] = opts['showdiff'] or args['--showdiff']
|
opts['showdiff'] = opts['showdiff'] or args['--showdiff']
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ Copyright (c) 2017, deadc0de6
|
|||||||
represents a dotfile in dotdrop
|
represents a dotfile in dotdrop
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from dotdrop.linktypes import LinkTypes
|
||||||
|
|
||||||
|
|
||||||
class Dotfile:
|
class Dotfile:
|
||||||
|
|
||||||
def __init__(self, key, dst, src,
|
def __init__(self, key, dst, src,
|
||||||
actions={}, trans_r=None, trans_w=None,
|
actions={}, trans_r=None, trans_w=None,
|
||||||
link=False, cmpignore=[], noempty=False,
|
link=LinkTypes.NOLINK, cmpignore=[], noempty=False,
|
||||||
upignore=[]):
|
upignore=[]):
|
||||||
# key of dotfile in the config
|
# key of dotfile in the config
|
||||||
self.key = key
|
self.key = key
|
||||||
@@ -35,7 +37,7 @@ class Dotfile:
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"'
|
msg = 'key:\"{}\", src:\"{}\", dst:\"{}\", link:\"{}\"'
|
||||||
return msg.format(self.key, self.src, self.dst, self.link)
|
return msg.format(self.key, self.src, self.dst, self.link.name)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.__dict__ == other.__dict__
|
return self.__dict__ == other.__dict__
|
||||||
|
|||||||
@@ -80,6 +80,74 @@ class Installer:
|
|||||||
src = tmp
|
src = tmp
|
||||||
return self._link(src, dst, actions=actions)
|
return self._link(src, dst, actions=actions)
|
||||||
|
|
||||||
|
def linkall(self, templater, src, dst, actions=[]):
|
||||||
|
"""link all dotfiles in a given directory"""
|
||||||
|
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):
|
||||||
|
self.log.err('source dotfile does not exist: {}'.format(parent))
|
||||||
|
return []
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
|
||||||
|
self.log.err('source dotfile is not a directory: {}'
|
||||||
|
.format(parent))
|
||||||
|
return []
|
||||||
|
|
||||||
|
dst = os.path.normpath(os.path.expanduser(dst))
|
||||||
|
if not os.path.lexists(dst):
|
||||||
|
self.log.sub('creating directory "{}"'.format(dst))
|
||||||
|
os.makedirs(dst)
|
||||||
|
|
||||||
|
if os.path.isfile(dst):
|
||||||
|
msg = ''.join([
|
||||||
|
'Remove regular file {} and ',
|
||||||
|
'replace with empty directory?',
|
||||||
|
]).format(dst)
|
||||||
|
|
||||||
|
if self.safe and not self.log.ask(msg):
|
||||||
|
msg = 'ignoring "{}", nothing installed'
|
||||||
|
self.log.warn(msg.format(dst))
|
||||||
|
return []
|
||||||
|
os.unlink(dst)
|
||||||
|
os.mkdir(dst)
|
||||||
|
|
||||||
|
children = os.listdir(parent)
|
||||||
|
srcs = [os.path.join(parent, child) for child in children]
|
||||||
|
dsts = [os.path.join(dst, child) for child in children]
|
||||||
|
|
||||||
|
for i in range(len(children)):
|
||||||
|
src = srcs[i]
|
||||||
|
dst = dsts[i]
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
self.log.dbg('symlink child {} to {}'.format(src, dst))
|
||||||
|
|
||||||
|
if 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)
|
||||||
|
i = self.install(templater, src, tmp, actions=actions)
|
||||||
|
if not i and not os.path.exists(tmp):
|
||||||
|
continue
|
||||||
|
src = tmp
|
||||||
|
|
||||||
|
result = self._link(src, dst, actions)
|
||||||
|
|
||||||
|
# Empty actions if dotfile installed
|
||||||
|
# This prevents from running actions multiple times
|
||||||
|
if len(result):
|
||||||
|
actions = []
|
||||||
|
|
||||||
|
return (src, dst)
|
||||||
|
|
||||||
def _link(self, src, dst, actions=[]):
|
def _link(self, src, dst, actions=[]):
|
||||||
"""set src as a link target of dst"""
|
"""set src as a link target of dst"""
|
||||||
if os.path.lexists(dst):
|
if os.path.lexists(dst):
|
||||||
|
|||||||
7
dotdrop/linktypes.py
Normal file
7
dotdrop/linktypes.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
|
||||||
|
class LinkTypes(IntEnum):
|
||||||
|
NOLINK = 0
|
||||||
|
PARENTS = 1
|
||||||
|
CHILDREN = 2
|
||||||
@@ -10,12 +10,13 @@ import tempfile
|
|||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import shlex
|
import shlex
|
||||||
|
import functools
|
||||||
|
import operator
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
|
|
||||||
# local import
|
# local import
|
||||||
from dotdrop.logger import Logger
|
from dotdrop.logger import Logger
|
||||||
from dotdrop.version import __version__ as VERSION
|
|
||||||
|
|
||||||
LOG = Logger()
|
LOG = Logger()
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import tempfile
|
|||||||
|
|
||||||
from dotdrop.config import Cfg
|
from dotdrop.config import Cfg
|
||||||
from dotdrop.utils import *
|
from dotdrop.utils import *
|
||||||
|
from dotdrop.linktypes import LinkTypes
|
||||||
|
|
||||||
TMPSUFFIX = '.dotdrop'
|
TMPSUFFIX = '.dotdrop'
|
||||||
|
|
||||||
@@ -87,7 +88,7 @@ def load_config(confpath, profile):
|
|||||||
opts['profile'] = profile
|
opts['profile'] = profile
|
||||||
opts['safe'] = True
|
opts['safe'] = True
|
||||||
opts['installdiff'] = True
|
opts['installdiff'] = True
|
||||||
opts['link'] = False
|
opts['link'] = LinkTypes.NOLINK.value
|
||||||
opts['showdiff'] = True
|
opts['showdiff'] = True
|
||||||
opts['debug'] = True
|
opts['debug'] = True
|
||||||
opts['dopts'] = ''
|
opts['dopts'] = ''
|
||||||
|
|||||||
@@ -7,17 +7,16 @@ basic unittest for the compare function
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
import yaml
|
|
||||||
|
|
||||||
from dotdrop.config import Cfg
|
|
||||||
from dotdrop.dotdrop import cmd_importer
|
from dotdrop.dotdrop import cmd_importer
|
||||||
from dotdrop.dotdrop import cmd_compare
|
from dotdrop.dotdrop import cmd_compare
|
||||||
from dotdrop.dotfile import Dotfile
|
|
||||||
from dotdrop.installer import Installer
|
from dotdrop.installer import Installer
|
||||||
from dotdrop.comparator import Comparator
|
from dotdrop.comparator import Comparator
|
||||||
from dotdrop.templategen import Templategen
|
from dotdrop.templategen import Templategen
|
||||||
|
|
||||||
from tests.helpers import *
|
# from tests.helpers import *
|
||||||
|
from tests.helpers import create_dir, get_string, get_tempdir, clean, \
|
||||||
|
create_random_file, create_fake_config, load_config, edit_content
|
||||||
|
|
||||||
|
|
||||||
class TestCompare(unittest.TestCase):
|
class TestCompare(unittest.TestCase):
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ Copyright (c) 2017, deadc0de6
|
|||||||
basic unittest for the install function
|
basic unittest for the install function
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
from unittest.mock import MagicMock, patch
|
||||||
import filecmp
|
import filecmp
|
||||||
|
|
||||||
from tests.helpers import *
|
from dotdrop.config import Cfg
|
||||||
|
from tests.helpers import create_dir, get_string, get_tempdir, clean, \
|
||||||
|
create_random_file, load_config
|
||||||
from dotdrop.dotfile import Dotfile
|
from dotdrop.dotfile import Dotfile
|
||||||
from dotdrop.installer import Installer
|
from dotdrop.installer import Installer
|
||||||
from dotdrop.action import Action
|
from dotdrop.action import Action
|
||||||
from dotdrop.dotdrop import cmd_install
|
from dotdrop.dotdrop import cmd_install
|
||||||
|
from dotdrop.linktypes import LinkTypes
|
||||||
|
|
||||||
|
|
||||||
class TestInstall(unittest.TestCase):
|
class TestInstall(unittest.TestCase):
|
||||||
@@ -50,7 +55,10 @@ exec bspwm
|
|||||||
f.write(' {}:\n'.format(d.key))
|
f.write(' {}:\n'.format(d.key))
|
||||||
f.write(' dst: {}\n'.format(d.dst))
|
f.write(' dst: {}\n'.format(d.dst))
|
||||||
f.write(' src: {}\n'.format(d.src))
|
f.write(' src: {}\n'.format(d.src))
|
||||||
f.write(' link: {}\n'.format(str(d.link).lower()))
|
f.write(' link: {}\n'
|
||||||
|
.format(str(d.link == LinkTypes.PARENTS).lower()))
|
||||||
|
f.write(' link_children: {}\n'
|
||||||
|
.format(str(d.link == LinkTypes.CHILDREN).lower()))
|
||||||
if len(d.actions) > 0:
|
if len(d.actions) > 0:
|
||||||
f.write(' actions:\n')
|
f.write(' actions:\n')
|
||||||
for action in d.actions:
|
for action in d.actions:
|
||||||
@@ -101,7 +109,7 @@ exec bspwm
|
|||||||
# to test backup
|
# to test backup
|
||||||
f4, c4 = create_random_file(tmp)
|
f4, c4 = create_random_file(tmp)
|
||||||
dst4 = os.path.join(dst, get_string(6))
|
dst4 = os.path.join(dst, get_string(6))
|
||||||
d4 = Dotfile(get_string(6), dst4, os.path.basename(f4))
|
d4 = Dotfile(key=get_string(6), dst=dst4, src=os.path.basename(f4))
|
||||||
with open(dst4, 'w') as f:
|
with open(dst4, 'w') as f:
|
||||||
f.write(get_string(16))
|
f.write(get_string(16))
|
||||||
|
|
||||||
@@ -220,6 +228,161 @@ exec bspwm
|
|||||||
tempcontent = open(dst10, 'r').read().rstrip()
|
tempcontent = open(dst10, 'r').read().rstrip()
|
||||||
self.assertTrue(tempcontent == profile)
|
self.assertTrue(tempcontent == profile)
|
||||||
|
|
||||||
|
def test_link_children(self):
|
||||||
|
|
||||||
|
# create source dir
|
||||||
|
src_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(src_dir))
|
||||||
|
self.addCleanup(clean, src_dir)
|
||||||
|
|
||||||
|
# where dotfiles will be installed
|
||||||
|
dst_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(dst_dir))
|
||||||
|
self.addCleanup(clean, dst_dir)
|
||||||
|
|
||||||
|
# create 3 random files in source
|
||||||
|
srcs = [create_random_file(src_dir)[0] for _ in range(3)]
|
||||||
|
|
||||||
|
installer = Installer()
|
||||||
|
installer.linkall(templater=MagicMock(), src=src_dir, dst=dst_dir,
|
||||||
|
actions=[])
|
||||||
|
|
||||||
|
# Ensure all destination files point to source
|
||||||
|
for src in srcs:
|
||||||
|
dst = os.path.join(dst_dir, src)
|
||||||
|
self.assertEqual(os.path.realpath(dst), src)
|
||||||
|
|
||||||
|
def test_fails_without_src(self):
|
||||||
|
src = '/some/non/existant/file'
|
||||||
|
|
||||||
|
installer = Installer()
|
||||||
|
logger = MagicMock()
|
||||||
|
installer.log.err = logger
|
||||||
|
|
||||||
|
res = installer.linkall(templater=MagicMock(),
|
||||||
|
src=src,
|
||||||
|
dst='/dev/null', actions=[])
|
||||||
|
|
||||||
|
self.assertEqual(res, [])
|
||||||
|
logger.assert_called_with('source dotfile does not exist: {}'
|
||||||
|
.format(src))
|
||||||
|
|
||||||
|
def test_fails_when_src_file(self):
|
||||||
|
|
||||||
|
# create source dir
|
||||||
|
src_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(src_dir))
|
||||||
|
self.addCleanup(clean, src_dir)
|
||||||
|
|
||||||
|
src = create_random_file(src_dir)[0]
|
||||||
|
|
||||||
|
logger = MagicMock()
|
||||||
|
templater = MagicMock()
|
||||||
|
installer = Installer()
|
||||||
|
installer.log.err = logger
|
||||||
|
|
||||||
|
# pass src file not src dir
|
||||||
|
res = installer.linkall(templater=templater, src=src, dst='/dev/null',
|
||||||
|
actions=[])
|
||||||
|
|
||||||
|
# ensure nothing performed
|
||||||
|
self.assertEqual(res, [])
|
||||||
|
# ensure logger logged error
|
||||||
|
logger.assert_called_with('source dotfile is not a directory: {}'
|
||||||
|
.format(src))
|
||||||
|
|
||||||
|
def test_creates_dst(self):
|
||||||
|
src_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(src_dir))
|
||||||
|
self.addCleanup(clean, src_dir)
|
||||||
|
|
||||||
|
# where dotfiles will be installed
|
||||||
|
dst_dir = get_tempdir()
|
||||||
|
self.addCleanup(clean, dst_dir)
|
||||||
|
|
||||||
|
# move dst dir to new (uncreated) dir in dst
|
||||||
|
dst_dir = os.path.join(dst_dir, get_string(6))
|
||||||
|
self.assertFalse(os.path.exists(dst_dir))
|
||||||
|
|
||||||
|
installer = Installer()
|
||||||
|
installer.linkall(templater=MagicMock(), src=src_dir, dst=dst_dir,
|
||||||
|
actions=[])
|
||||||
|
|
||||||
|
# ensure dst dir created
|
||||||
|
self.assertTrue(os.path.exists(dst_dir))
|
||||||
|
|
||||||
|
def test_prompts_to_replace_dst(self):
|
||||||
|
|
||||||
|
# create source dir
|
||||||
|
src_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(src_dir))
|
||||||
|
self.addCleanup(clean, src_dir)
|
||||||
|
|
||||||
|
# where dotfiles will be installed
|
||||||
|
dst_dir = get_tempdir()
|
||||||
|
self.addCleanup(clean, dst_dir)
|
||||||
|
|
||||||
|
# Create destination file to be replaced
|
||||||
|
dst = os.path.join(dst_dir, get_string(6))
|
||||||
|
with open(dst, 'w'):
|
||||||
|
pass
|
||||||
|
self.assertTrue(os.path.isfile(dst))
|
||||||
|
|
||||||
|
# setup mocks
|
||||||
|
ask = MagicMock()
|
||||||
|
ask.return_value = True
|
||||||
|
|
||||||
|
# setup installer
|
||||||
|
installer = Installer()
|
||||||
|
installer.safe = True
|
||||||
|
installer.log.ask = ask
|
||||||
|
|
||||||
|
installer.linkall(templater=MagicMock(), src=src_dir, dst=dst,
|
||||||
|
actions=[])
|
||||||
|
|
||||||
|
# ensure destination now a directory
|
||||||
|
self.assertTrue(os.path.isdir(dst))
|
||||||
|
|
||||||
|
# ensure prompted
|
||||||
|
ask.assert_called_with(
|
||||||
|
'Remove regular file {} and replace with empty directory?'
|
||||||
|
.format(dst))
|
||||||
|
|
||||||
|
@patch('dotdrop.installer.Templategen')
|
||||||
|
def test_runs_templater(self, mocked_templategen):
|
||||||
|
|
||||||
|
# create source dir
|
||||||
|
src_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(src_dir))
|
||||||
|
self.addCleanup(clean, src_dir)
|
||||||
|
|
||||||
|
# where dotfiles will be installed
|
||||||
|
dst_dir = get_tempdir()
|
||||||
|
self.assertTrue(os.path.exists(dst_dir))
|
||||||
|
self.addCleanup(clean, dst_dir)
|
||||||
|
|
||||||
|
# create 3 random files in source
|
||||||
|
srcs = [create_random_file(src_dir)[0] for _ in range(3)]
|
||||||
|
|
||||||
|
# setup installer and mocks
|
||||||
|
installer = Installer()
|
||||||
|
templater = MagicMock()
|
||||||
|
templater.generate.return_value = b'content'
|
||||||
|
# make templategen treat everything as a template
|
||||||
|
mocked_templategen.is_template.return_value = True
|
||||||
|
|
||||||
|
installer.linkall(templater=templater, src=src_dir, dst=dst_dir,
|
||||||
|
actions=[])
|
||||||
|
|
||||||
|
for src in srcs:
|
||||||
|
dst = os.path.join(dst_dir, os.path.basename(src))
|
||||||
|
|
||||||
|
# ensure dst is link
|
||||||
|
self.assertTrue(os.path.islink(dst))
|
||||||
|
# ensure dst not directly linked to src
|
||||||
|
# TODO: maybe check that its actually linked to template folder
|
||||||
|
self.assertNotEqual(os.path.realpath(dst), src)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -7,14 +7,12 @@ basic unittest for the update function
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
import os
|
import os
|
||||||
import yaml
|
|
||||||
|
|
||||||
from dotdrop.config import Cfg
|
|
||||||
from dotdrop.dotdrop import cmd_update
|
from dotdrop.dotdrop import cmd_update
|
||||||
from dotdrop.dotdrop import cmd_importer
|
from dotdrop.dotdrop import cmd_importer
|
||||||
from dotdrop.dotfile import Dotfile
|
|
||||||
|
|
||||||
from tests.helpers import *
|
from tests.helpers import create_dir, get_string, get_tempdir, clean, \
|
||||||
|
create_random_file, create_fake_config, load_config, edit_content
|
||||||
|
|
||||||
|
|
||||||
class TestUpdate(unittest.TestCase):
|
class TestUpdate(unittest.TestCase):
|
||||||
|
|||||||
Reference in New Issue
Block a user