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

Add option to symlink only child files

This commit is contained in:
Marcel Robitaille
2018-12-29 15:24:02 -04:00
parent 0dd9dea1ff
commit fffe55ecd9
6 changed files with 87 additions and 26 deletions

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:
@@ -67,7 +68,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
@@ -212,10 +213,13 @@ 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 \ link = LinkTypes.PARENTS \
in v else self.default_link if self.key_dotfiles_link in v and v[self.key_dotfiles_link] \
else LinkTypes.CHILDREN \
if 'link_children' in v and v['link_children'] \
else LinkTypes.NOLINK
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]
@@ -264,7 +268,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)
@@ -520,7 +524,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
@@ -569,9 +573,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 * import dotdrop.utils as dd
from dotdrop.linktypes import LinkTypes
LOG = Logger() LOG = Logger()
ENV_PROFILE = 'DOTDROP_PROFILE' ENV_PROFILE = 'DOTDROP_PROFILE'
@@ -92,7 +92,7 @@ def cmd_install(opts, conf, temporary=False, keys=[]):
variables=opts['variables'], debug=opts['debug']) variables=opts['variables'], debug=opts['debug'])
tmpdir = None tmpdir = None
if temporary: if temporary:
tmpdir = get_tmpdir() tmpdir = dd.get_tmpdir()
inst = Installer(create=opts['create'], backup=opts['backup'], inst = Installer(create=opts['create'], backup=opts['backup'],
dry=opts['dry'], safe=opts['safe'], dry=opts['dry'], safe=opts['safe'],
base=opts['dotpath'], workdir=opts['workdir'], base=opts['dotpath'], workdir=opts['workdir'],
@@ -106,8 +106,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
@@ -121,7 +123,7 @@ def cmd_install(opts, conf, temporary=False, keys=[]):
if tmp: if tmp:
tmp = os.path.join(opts['dotpath'], tmp) tmp = os.path.join(opts['dotpath'], tmp)
if os.path.exists(tmp): if os.path.exists(tmp):
remove(tmp) dd.remove(tmp)
if len(r) > 0: if len(r) > 0:
if Cfg.key_actions_post in dotfile.actions: if Cfg.key_actions_post in dotfile.actions:
actions = dotfile.actions[Cfg.key_actions_post] actions = dotfile.actions[Cfg.key_actions_post]
@@ -189,7 +191,7 @@ def cmd_compare(opts, conf, tmp, focus=[], ignore=[]):
# clean tmp transformed dotfile if any # clean tmp transformed dotfile if any
tmpsrc = os.path.join(opts['dotpath'], tmpsrc) tmpsrc = os.path.join(opts['dotpath'], tmpsrc)
if os.path.exists(tmpsrc): if os.path.exists(tmpsrc):
remove(tmpsrc) dd.remove(tmpsrc)
if diff == '': if diff == '':
if opts['debug']: if opts['debug']:
LOG.dbg('diffing \"{}\" VS \"{}\"'.format(dotfile.key, LOG.dbg('diffing \"{}\" VS \"{}\"'.format(dotfile.key,
@@ -243,7 +245,7 @@ def cmd_importer(opts, conf, paths):
continue continue
dst = path.rstrip(os.sep) dst = path.rstrip(os.sep)
dst = os.path.abspath(dst) dst = os.path.abspath(dst)
src = strip_home(dst) src = dd.strip_home(dst)
strip = '.' + os.sep strip = '.' + os.sep
if opts['keepdot']: if opts['keepdot']:
strip = os.sep strip = os.sep
@@ -262,7 +264,7 @@ def cmd_importer(opts, conf, paths):
if opts['dry']: if opts['dry']:
LOG.dry('would run: {}'.format(' '.join(cmd))) LOG.dry('would run: {}'.format(' '.join(cmd)))
else: else:
r, _ = run(cmd, raw=False, debug=opts['debug'], checkerr=True) r, _ = dd.run(cmd, raw=False, debug=opts['debug'], checkerr=True)
if not r: if not r:
LOG.err('importing \"{}\" failed!'.format(path)) LOG.err('importing \"{}\" failed!'.format(path))
ret = False ret = False
@@ -273,13 +275,13 @@ def cmd_importer(opts, conf, paths):
if linkit: if linkit:
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, _ = dd.run(cmd, raw=False, debug=opts['debug'], checkerr=True)
if not r: if not r:
LOG.err('importing \"{}\" failed!'.format(path)) LOG.err('importing \"{}\" failed!'.format(path))
ret = False ret = False
continue continue
if linkit: if linkit:
remove(dst) dd.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=linkit, debug=opts['debug']) link=linkit, debug=opts['debug'])
@@ -400,7 +402,7 @@ def apply_trans(opts, dotfile):
msg = 'transformation \"{}\" failed for {}' msg = 'transformation \"{}\" failed for {}'
LOG.err(msg.format(trans.key, dotfile.key)) LOG.err(msg.format(trans.key, dotfile.key))
if new_src and os.path.exists(new_src): if new_src and os.path.exists(new_src):
remove(new_src) dd.remove(new_src)
return None return None
return new_src return new_src
@@ -469,12 +471,12 @@ def main():
# compare local dotfiles with dotfiles stored in dotdrop # compare local dotfiles with dotfiles stored in dotdrop
if opts['debug']: if opts['debug']:
LOG.dbg('running cmd: compare') LOG.dbg('running cmd: compare')
tmp = get_tmpdir() tmp = dd.get_tmpdir()
opts['dopts'] = args['--dopts'] opts['dopts'] = args['--dopts']
ret = cmd_compare(opts, conf, tmp, focus=args['--file'], ret = cmd_compare(opts, conf, tmp, focus=args['--file'],
ignore=args['--ignore']) ignore=args['--ignore'])
# clean tmp directory # clean tmp directory
remove(tmp) dd.remove(tmp)
elif args['import']: elif args['import']:
# import dotfile(s) # import dotfile(s)

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):
# key of dotfile in the config # key of dotfile in the config
self.key = key self.key = key
# path where to install this dotfile # path where to install this dotfile
@@ -32,10 +34,11 @@ 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__
def __hash__(self): def __hash__(self):
return hash(self.dst) ^ hash(self.src) ^ hash(self.key) return hash(self.dst) ^ hash(self.src) ^ hash(self.key)

View File

@@ -64,7 +64,7 @@ class Installer:
if not os.path.exists(src): if not os.path.exists(src):
self.log.err('source dotfile does not exist: {}'.format(src)) self.log.err('source dotfile does not exist: {}'.format(src))
return [] return []
dst = os.path.expanduser(dst) dst = os.path.normpath(os.path.expanduser(dst))
if self.totemp: if self.totemp:
# ignore actions # ignore actions
return self.install(templater, src, dst, actions=[]) return self.install(templater, src, dst, actions=[])
@@ -80,6 +80,42 @@ 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))
if not os.path.exists(parent):
self.log.err('source dotfile does not exist: {}'.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 = '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]
results = []
for i in range(len(children)):
result = self._link(srcs[i], dsts[i], actions)
if len(result):
actions = []
results.append(result)
return utils.flatten(results)
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):
@@ -308,3 +344,4 @@ class Installer:
self.comparing = False self.comparing = False
self.create = createsaved self.create = createsaved
return ret, tmpdst return ret, tmpdst

8
dotdrop/linktypes.py Normal file
View File

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

View File

@@ -10,11 +10,12 @@ import tempfile
import os import os
import uuid import uuid
import shlex import shlex
import functools
import operator
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()
@@ -109,3 +110,9 @@ def strip_home(path):
if path.startswith(home): if path.startswith(home):
path = path[len(home):] path = path[len(home):]
return path return path
def flatten(a):
"""flatten list"""
return functools.reduce(operator.iconcat, a, [])