1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-05 12:03:49 +00:00

dedicated importer class

This commit is contained in:
deadc0de6
2020-11-16 20:52:33 +01:00
parent 3dc6531870
commit d40d387b96
3 changed files with 218 additions and 126 deletions

View File

@@ -9,7 +9,6 @@ import os
import sys
import time
from concurrent import futures
import shutil
# local imports
from dotdrop.options import Options
@@ -18,9 +17,9 @@ from dotdrop.templategen import Templategen
from dotdrop.installer import Installer
from dotdrop.updater import Updater
from dotdrop.comparator import Comparator
from dotdrop.utils import get_tmpdir, removepath, strip_home, \
uniq_list, patch_ignores, dependencies_met, get_file_perm, \
get_default_file_perms
from dotdrop.importer import Importer
from dotdrop.utils import get_tmpdir, removepath, \
uniq_list, patch_ignores, dependencies_met
from dotdrop.linktypes import LinkTypes
from dotdrop.exceptions import YamlException, UndefinedException
@@ -430,135 +429,26 @@ def cmd_importer(o):
ret = True
cnt = 0
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:
if o.debug:
LOG.dbg('trying to import {}'.format(path))
if not os.path.exists(path):
LOG.err('\"{}\" does not exist, ignored!'.format(path))
r = importer.import_path(path, import_as=o.import_as,
import_link=o.import_link,
import_mode=o.import_mode)
if r < 0:
ret = False
continue
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)
# get the permission
perm = get_file_perm(dst)
# 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:
# 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)
chmod = None
dflperm = get_default_file_perms(dst, o.umask)
if o.debug:
LOG.dbg('import mode: {}'.format(o.import_mode))
if o.import_mode or perm != dflperm:
if o.debug:
LOG.dbg('adopt mode {:o} (umask {:o})'.format(perm, dflperm))
# insert chmod
chmod = perm
retconf = o.conf.new_dotfile(src, dst, linktype, chmod=chmod)
if retconf:
LOG.sub('\"{}\" imported'.format(path))
elif r > 0:
cnt += 1
else:
LOG.warn('\"{}\" ignored'.format(path))
if o.dry:
LOG.dry('new config file would be:')
LOG.raw(o.conf.dump())
else:
o.conf.save()
LOG.log('\n{} file(s) imported.'.format(cnt))
return ret

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

@@ -16,7 +16,7 @@ from dotdrop.linktypes import LinkTypes
from dotdrop.logger import Logger
from dotdrop.cfg_aggregator import CfgAggregator as Cfg
from dotdrop.action import Action
from dotdrop.utils import uniq_list, get_umask
from dotdrop.utils import uniq_list
from dotdrop.exceptions import YamlException
ENV_PROFILE = 'DOTDROP_PROFILE'
@@ -123,7 +123,6 @@ class Options(AttrMonitor):
self.log = Logger()
self.debug = self.args['--verbose'] or ENV_DEBUG in os.environ
self.dry = self.args['--dry']
self.umask = get_umask()
if ENV_NODEBUG in os.environ:
# force disabling debugs
self.debug = False