From fc9c12c1b03447070a8a02ebf8936f0d817e12cc Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Mon, 9 Nov 2020 13:36:48 +0100 Subject: [PATCH] add chmod to import command --- dotdrop/cfg_aggregator.py | 9 +++++---- dotdrop/cfg_yaml.py | 31 ++++++++++++++++++++++++++++++- dotdrop/dotdrop.py | 14 ++++++++++++-- dotdrop/dotfile.py | 6 +++++- dotdrop/options.py | 13 ++++++++----- dotdrop/utils.py | 12 ++++++++++++ 6 files changed, 72 insertions(+), 13 deletions(-) diff --git a/dotdrop/cfg_aggregator.py b/dotdrop/cfg_aggregator.py index 230e264..c83afb6 100644 --- a/dotdrop/cfg_aggregator.py +++ b/dotdrop/cfg_aggregator.py @@ -149,27 +149,28 @@ class CfgAggregator: """remove this dotfile from this profile""" return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key) - def _create_new_dotfile(self, src, dst, link): + def _create_new_dotfile(self, src, dst, link, chmod=None): """create a new dotfile""" # get a new dotfile with a unique key key = self._get_new_dotfile_key(dst) if self.debug: self.log.dbg('new dotfile key: {}'.format(key)) # add the dotfile - self.cfgyaml.add_dotfile(key, src, dst, link) + self.cfgyaml.add_dotfile(key, src, dst, link, chmod=chmod) return Dotfile(key, dst, src) - def new(self, src, dst, link): + def new(self, src, dst, link, chmod=None): """ import a new dotfile @src: path in dotpath @dst: path in FS @link: LinkType + @chmod: file permission """ dst = self.path_to_dotfile_dst(dst) dotfile = self.get_dotfile_by_src_dst(src, dst) if not dotfile: - dotfile = self._create_new_dotfile(src, dst, link) + dotfile = self._create_new_dotfile(src, dst, link, chmod=chmod) key = dotfile.key ret = self.cfgyaml.add_dotfile_to_profile(key, self.profile_key) diff --git a/dotdrop/cfg_yaml.py b/dotdrop/cfg_yaml.py index c5ccfab..c3c5e7a 100644 --- a/dotdrop/cfg_yaml.py +++ b/dotdrop/cfg_yaml.py @@ -58,6 +58,7 @@ class CfgYaml: key_dotfile_actions = 'actions' key_dotfile_noempty = 'ignoreempty' key_dotfile_template = 'template' + key_dotfile_chmod = 'chmod' # profile key_profile_dotfiles = 'dotfiles' @@ -316,7 +317,7 @@ class CfgYaml: """return all existing dotfile keys""" return self.dotfiles.keys() - def add_dotfile(self, key, src, dst, link): + def add_dotfile(self, key, src, dst, link, chmod=None): """add a new dotfile""" if key in self.dotfiles.keys(): return False @@ -324,14 +325,23 @@ class CfgYaml: self._dbg('adding new dotfile: {}'.format(key)) self._dbg('new dotfile src: {}'.format(src)) self._dbg('new dotfile dst: {}'.format(dst)) + self._dbg('new dotfile link: {}'.format(link)) + if chmod: + self._dbg('new dotfile chmod: {}'.format(chmod)) df_dict = { self.key_dotfile_src: src, self.key_dotfile_dst: dst, } + # link dfl = self.settings[self.key_settings_link_dotfile_default] if str(link) != dfl: df_dict[self.key_dotfile_link] = str(link) + # chmod + if chmod: + df_dict[self.key_dotfile_chmod] = format(chmod, 'o') + + # add to global dict self._yaml_dict[self.key_dotfiles][key] = df_dict self._dirty = True @@ -623,6 +633,25 @@ class CfgYaml: if self.key_dotfile_template not in v: val = self.settings.get(self.key_settings_template, True) v[self.key_dotfile_template] = val + # validate value of chmod if defined + if self.key_dotfile_chmod in v: + val = v[self.key_dotfile_chmod] + if len(val) < 3: + err = 'bad format for chmod: {}'.format(val) + self._log.err(err) + raise YamlException('dotfile chmod error: {}'.format(err)) + try: + int(val) + except Exception: + err = 'bad format for chmod: {}'.format(val) + self._log.err(err) + raise YamlException('dotfile chmod error: {}'.format(err)) + for x in val: + y = int(x) + if y < 0 or y > 7: + err = 'bad format for chmod: {}'.format(val) + self._log.err(err) + raise YamlException('dotfile chmod error: {}'.format(err)) return new diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index 6c06eee..6b87ff6 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -19,7 +19,7 @@ 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 + uniq_list, patch_ignores, dependencies_met, get_file_perm from dotdrop.linktypes import LinkTypes from dotdrop.exceptions import YamlException, UndefinedException @@ -426,6 +426,9 @@ def cmd_importer(o): 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 \ @@ -485,16 +488,23 @@ def cmd_importer(o): 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) - retconf = o.conf.new(src, dst, linktype) + + chmod = None + if o.import_mode or perm&o.umask: + # insert chmod + chmod = perm + retconf = o.conf.new(src, dst, linktype, chmod=chmod) if retconf: LOG.sub('\"{}\" imported'.format(path)) cnt += 1 diff --git a/dotdrop/dotfile.py b/dotdrop/dotfile.py index da7890d..fee2727 100644 --- a/dotdrop/dotfile.py +++ b/dotdrop/dotfile.py @@ -22,7 +22,7 @@ class Dotfile(DictParser): actions=[], trans_r=None, trans_w=None, link=LinkTypes.NOLINK, noempty=False, cmpignore=[], upignore=[], - instignore=[], template=True): + instignore=[], template=True, chmod=None): """ constructor @key: dotfile key @@ -37,6 +37,7 @@ class Dotfile(DictParser): @cmpignore: patterns to ignore when comparing @instignore: patterns to ignore when installing @template: template this dotfile + @chmod: file permission """ self.actions = actions self.dst = dst @@ -50,6 +51,7 @@ class Dotfile(DictParser): self.cmpignore = cmpignore self.instignore = instignore self.template = template + self.chmod = chmod if self.link != LinkTypes.NOLINK and \ ( @@ -113,6 +115,7 @@ class Dotfile(DictParser): msg += ', dst:\"{}\"'.format(self.dst) msg += ', link:\"{}\"'.format(str(self.link)) msg += ', template:{}'.format(self.template) + msg += ', chmod:{}'.format(self.chmod) return msg def prt(self): @@ -123,6 +126,7 @@ class Dotfile(DictParser): out += '\n{}dst: \"{}\"'.format(indent, self.dst) out += '\n{}link: \"{}\"'.format(indent, str(self.link)) out += '\n{}template: \"{}\"'.format(indent, str(self.template)) + out += '\n{}chmod: \"{}\"'.format(indent, str(self.chmod)) out += '\n{}pre-action:'.format(indent) some = self.get_pre_actions() diff --git a/dotdrop/options.py b/dotdrop/options.py index 8c549a1..3531b54 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -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 +from dotdrop.utils import uniq_list, get_umask from dotdrop.exceptions import YamlException ENV_PROFILE = 'DOTDROP_PROFILE' @@ -54,7 +54,7 @@ USAGE = """ Usage: dotdrop install [-VbtfndDa] [-c ] [-p ] [-w ] [...] - dotdrop import [-Vbdf] [-c ] [-p ] [-s ] + dotdrop import [-Vbdfm] [-c ] [-p ] [-s ] [-l ] ... dotdrop compare [-LVb] [-c ] [-p ] [-C ...] [-i ...] @@ -73,15 +73,16 @@ Options: -c --cfg= Path to the config. -C --file= Path of dotfile to compare. -d --dry Dry run. - -l --link= Link option (nolink|link|link_children). - -L --file-only Do not show diff but only the files that differ. - -p --profile= Specify the profile to use [default: {}]. -D --showdiff Show a diff before overwriting. -f --force Do not ask user confirmation for anything. -G --grepable Grepable output. -i --ignore= Pattern to ignore. -k --key Treat as a dotfile key. + -l --link= Link option (nolink|link|link_children). + -L --file-only Do not show diff but only the files that differ. + -m --preserve-mode Insert a chmod entry in the dotfile with its permissions. -n --nodiff Do not diff when installing. + -p --profile= Specify the profile to use [default: {}]. -P --show-patch Provide a one-liner to manually patch template. -s --as= Import as a different path from actual path. -t --temp Install to a temporary directory for review. @@ -121,6 +122,7 @@ 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 @@ -261,6 +263,7 @@ class Options(AttrMonitor): # "import" specifics self.import_path = self.args[''] self.import_as = self.args['--as'] + self.import_mode = self.args['--preserve-mode'] # "update" specifics self.update_path = self.args[''] diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 7a67ac8..47acc9f 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -302,3 +302,15 @@ def mirror_file_rights(src, dst): """mirror file rights of src to dst (can rise exc)""" rights = os.stat(src).st_mode os.chmod(dst, rights) + + +def get_umask(): + """return current umask value""" + cur = os.umask(0) + os.umask(cur) + return 0o777-cur + + +def get_file_perm(path): + """return file permission""" + return os.stat(path).st_mode & 0o777