1
0
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:
deadc0de
2019-01-31 20:25:23 +01:00
committed by GitHub
12 changed files with 370 additions and 36 deletions

4
.gitignore vendored
View File

@@ -7,3 +7,7 @@
dist/ dist/
build/ build/
*.egg-info/ *.egg-info/
tags
env
htmlcov

View File

@@ -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

View File

@@ -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]

View File

@@ -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']

View File

@@ -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__

View File

@@ -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
View File

@@ -0,0 +1,7 @@
from enum import IntEnum
class LinkTypes(IntEnum):
NOLINK = 0
PARENTS = 1
CHILDREN = 2

View File

@@ -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()

View File

@@ -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'] = ''

View File

@@ -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):

View File

@@ -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()

View File

@@ -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):