1
0
mirror of https://github.com/deadc0de6/dotdrop.git synced 2026-02-16 10:36:11 +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 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,9 +17,9 @@ 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, get_file_perm, \ from dotdrop.utils import get_tmpdir, removepath, \
get_default_file_perms uniq_list, patch_ignores, dependencies_met
from dotdrop.linktypes import LinkTypes from dotdrop.linktypes import LinkTypes
from dotdrop.exceptions import YamlException, UndefinedException from dotdrop.exceptions import YamlException, UndefinedException
@@ -430,135 +429,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)
# 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))
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

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