From 2ad7bb30097dc22a093b4920685e56dfe55ca94e Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Fri, 8 Nov 2024 14:43:55 +0100 Subject: [PATCH] add -K --dkey --- docs/usage.md | 9 ++- dotdrop/cfg_aggregator.py | 37 ++++++++--- dotdrop/dotdrop.py | 3 +- dotdrop/importer.py | 10 ++- dotdrop/options.py | 5 +- tests-ng/import-with-key.sh | 118 ++++++++++++++++++++++++++++++++++++ 6 files changed, 168 insertions(+), 14 deletions(-) create mode 100755 tests-ng/import-with-key.sh diff --git a/docs/usage.md b/docs/usage.md index a3b4e50..704e1cc 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -40,8 +40,13 @@ $ dotdrop import ~/.xinitrc 1 file(s) imported. ``` -You can control how the dotfile key is generated in the config file -with the following [config entries](config/config-config.md): +You can explicitely provide the key dotdrop should use for the dotfile entry +in the config file with the `-K --dkey` cli switch. Note that the provided +string will be sanitized for yaml. Also if the key already exists, +it will be appended with `_` to avoid duplicates. + +If the key is not provided, it will be automatically created based on the +following [config entries](config/config-config.md): * `longkey` * `false` (default): take the shortest unique path diff --git a/dotdrop/cfg_aggregator.py b/dotdrop/cfg_aggregator.py index 1e9d819..a51c3fa 100644 --- a/dotdrop/cfg_aggregator.py +++ b/dotdrop/cfg_aggregator.py @@ -9,6 +9,7 @@ import os import shlex import platform import distro +import re # local imports @@ -24,6 +25,8 @@ from dotdrop.exceptions import UndefinedException, YamlException, \ TILD = '~' +YAML_OK = '[^0-9a-zA-Z.\-_]+' +YAML_REPL = '_' class CfgAggregator: @@ -69,15 +72,18 @@ class CfgAggregator: return self.cfgyaml.del_dotfile_from_profile(dotfile.key, profile.key) def new_dotfile(self, src, dst, link, chmod=None, - trans_install=None, trans_update=None): + trans_install=None, trans_update=None, + forcekey=None): """ import a new dotfile + @src: path in dotpath @dst: path in FS @link: LinkType @chmod: file permission @trans_install: read transformation @trans_update: write transformation + @forcekey: dotfile key """ dst = self.path_to_dotfile_dst(dst) dotfile = self.get_dotfile_by_src_dst(src, dst) @@ -85,7 +91,8 @@ class CfgAggregator: # add the dotfile dotfile = self._create_new_dotfile(src, dst, link, chmod=chmod, trans_install=trans_install, - trans_update=trans_update) + trans_update=trans_update, + forcekey=forcekey) if not dotfile: return False @@ -237,10 +244,15 @@ class CfgAggregator: ######################################################## def _create_new_dotfile(self, src, dst, link, chmod=None, - trans_install=None, trans_update=None): - """create a new dotfile""" + trans_install=None, trans_update=None, + forcekey=None): + """ + create a new dotfile + """ # get a new dotfile with a unique key - key = self._get_new_dotfile_key(dst) + key = self._get_new_dotfile_key(dst, forcekey=forcekey) + if not key: + return None self.log.dbg(f'new dotfile key: {key}') # add the dotfile trans_install_key = trans_update_key = None @@ -282,6 +294,9 @@ class CfgAggregator: self.key_prefix = self.settings.key_prefix self.key_separator = self.settings.key_separator + # clean key separator + self.key_separator = re.sub(YAML_OK, YAML_REPL, self.key_separator) + # dotfiles self.log.dbg('parsing dotfiles') self.dotfiles = Dotfile.parse_dict(self.cfgyaml.dotfiles) @@ -427,10 +442,17 @@ class CfgAggregator: # dotfile key ######################################################## - def _get_new_dotfile_key(self, dst): + def _get_new_dotfile_key(self, dst, forcekey=None): """return a new unique dotfile key""" - path = os.path.expanduser(dst) existing_keys = self.cfgyaml.get_all_dotfile_keys() + + # use provided key + if forcekey: + key = self._norm_key_elem(forcekey) + return self._uniq_key(key, existing_keys) + + # key creation + path = os.path.expanduser(dst) if self.settings.longkey: return self._get_long_key(path, existing_keys) return self._get_short_key(path, existing_keys) @@ -440,6 +462,7 @@ class CfgAggregator: """normalize path element for sanity""" elem = elem.lstrip('.') elem = elem.replace(' ', '-') + elem = re.sub(YAML_OK, YAML_REPL, elem) return elem.lower() def _get_long_key(self, path, keys): diff --git a/dotdrop/dotdrop.py b/dotdrop/dotdrop.py index ade54f2..98c3d00 100644 --- a/dotdrop/dotdrop.py +++ b/dotdrop/dotdrop.py @@ -537,7 +537,8 @@ def cmd_importer(opts): dry=opts.dry, safe=opts.safe, debug=opts.debug, keepdot=opts.keepdot, - ignore=opts.import_ignore) + ignore=opts.import_ignore, + forcekey=opts.import_force_key) for path in paths: tmpret = importer.import_path(path, diff --git a/dotdrop/importer.py b/dotdrop/importer.py index 5d91484..a5d0714 100644 --- a/dotdrop/importer.py +++ b/dotdrop/importer.py @@ -25,10 +25,10 @@ class Importer: def __init__(self, profile, conf, dotpath, diff_cmd, variables, dry=False, safe=True, debug=False, - keepdot=True, ignore=None): + keepdot=True, ignore=None, forcekey=None): """constructor @profile: the selected profile - @conf: configuration manager + @conf: configuration manager (CfgAggregator) @dotpath: dotfiles dotpath @diff_cmd: diff command to use @variables: dictionary of variables for the templates @@ -37,6 +37,8 @@ class Importer: @debug: enable debug @keepdot: keep dot prefix @ignore: patterns to ignore when importing + @forcekey: force the use of a specific dotfile key + This may raise UndefinedException """ self.profile = profile @@ -51,6 +53,7 @@ class Importer: self.debug = debug self.keepdot = keepdot self.ignore = [] + self.forcekey = forcekey self.log = Logger(debug=self.debug) # patch ignore patterns @@ -197,7 +200,8 @@ class Importer: # add file to config file retconf = self.conf.new_dotfile(src, dst, linktype, chmod=chmod, trans_install=trans_install, - trans_update=trans_update) + trans_update=trans_update, + forcekey=self.forcekey) if not retconf: self.log.warn(f'\"{path}\" ignored during import') return 0 diff --git a/dotdrop/options.py b/dotdrop/options.py index 3302fba..62252ca 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -61,7 +61,8 @@ USAGE = f""" Usage: dotdrop install [-VbtfndDaWR] [-c ] [-p ] [-w ] [...] - dotdrop import [-Vbdfm] [-c ] [-p ] [-i ...] + dotdrop import [-Vbdfm] [-c ] [-p ] + [-i ...] [--dkey=] [--transr=] [--transw=] [-l ] [-s ] ... dotdrop compare [-LVbz] [-c ] [-p ] @@ -88,6 +89,7 @@ Options: -G --grepable Grepable output. -i --ignore= Pattern to ignore. -k --key Treat as a dotfile key. + -K --dkey= Set the dotfile key. -l --link= Link option (nolink|absolute|relative|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 mode. @@ -337,6 +339,7 @@ class Options(AttrMonitor): self.import_ignore = uniq_list(self.import_ignore) self.import_trans_install = self.args['--transr'] self.import_trans_update = self.args['--transw'] + self.import_force_key = self.args['--dkey'] def _apply_args_update(self): """update specifics""" diff --git a/tests-ng/import-with-key.sh b/tests-ng/import-with-key.sh new file mode 100755 index 0000000..c93e834 --- /dev/null +++ b/tests-ng/import-with-key.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2024, deadc0de6 +# +# test with user provided key +# --dkey +# + +## start-cookie +set -eu -o errtrace -o pipefail +cur=$(cd "$(dirname "${0}")" && pwd) +ddpath="${cur}/../" +PPATH="{PYTHONPATH:-}" +export PYTHONPATH="${ddpath}:${PPATH}" +altbin="python3 -m dotdrop.dotdrop" +if hash coverage 2>/dev/null; then + mkdir -p coverages/ + altbin="coverage run -p --data-file coverages/coverage --source=dotdrop -m dotdrop.dotdrop" +fi +bin="${DT_BIN:-${altbin}}" +# shellcheck source=tests-ng/helpers +source "${cur}"/helpers +echo -e "$(tput setaf 6)==> RUNNING $(basename "${BASH_SOURCE[0]}") <==$(tput sgr0)" +## end-cookie + +################################################################ +# this is the test +################################################################ + +# the dotfile source +tmps=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) +mkdir -p "${tmps}"/dotfiles +# the dotfile destination +tmpd=$(mktemp -d --suffix='-dotdrop-tests' || mktemp -d) +#echo "dotfile destination: ${tmpd}" + +clear_on_exit "${tmps}" +clear_on_exit "${tmpd}" + +# create the dotfile +echo "file1" > "${tmpd}"/file1 + +# create the config file +cfg="${tmps}/config.yaml" + +cat > "${cfg}" << _EOF +config: + backup: true + create: true + dotpath: dotfiles +dotfiles: +profiles: +_EOF +#cat ${cfg} + +# import +dkey="myfile1" +cd "${ddpath}" | ${bin} import -f -c "${cfg}" -p p1 -V --dkey "${dkey}" "${tmpd}"/file1 +cat "${cfg}" + +# test +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/file1 ] && echo "not imported in dotpath" && exit 1 +cat "${cfg}" | grep "${dkey}:" &>/dev/null || ( echo "bad key 1" && exit 1 ) + +# import 2 files +echo "firstfile" > "${tmpd}"/firstfile +echo "secondfile" > "${tmpd}"/secondfile + +dkey="nfile" +cd "${ddpath}" | ${bin} import -f -c "${cfg}" -p p1 -V --dkey "${dkey}" "${tmpd}"/firstfile "${tmpd}"/secondfile +cat "${cfg}" + +# test +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/firstfile ] && echo "not imported in dotpath" && exit 1 +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/secondfile ] && echo "not imported in dotpath" && exit 1 +cat "${cfg}" | grep "${dkey}:" &>/dev/null || ( echo "bad key 2a" && exit 1 ) +cat "${cfg}" | grep "${dkey}_1:" &>/dev/null || ( echo "bad key 2b" && exit 1 ) + +# import 2 files with bad chars +echo "file-1.1" > "${tmpd}"/file-1.1 +echo "file-2.2" > "${tmpd}"/file-2.2 + +dkey=".bad-key.1 2" +dkey_clean="bad-key.1-2" +cd "${ddpath}" | ${bin} import -f -c "${cfg}" -p p1 -V --dkey "${dkey}" "${tmpd}"/file-1.1 "${tmpd}"/file-2.2 +cat "${cfg}" + +# test +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/file-1.1 ] && echo "not imported in dotpath" && exit 1 +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/file-2.2 ] && echo "not imported in dotpath" && exit 1 +cat "${cfg}" | grep "${dkey_clean}:" &>/dev/null || ( echo "bad key 3a" && exit 1 ) +cat "${cfg}" | grep "${dkey_clean}_1:" &>/dev/null || ( echo "bad key 3b" && exit 1 ) + +# re-import +echo "lastfile" > "${tmpd}"/lastfile + +dkey="nfile" +cd "${ddpath}" | ${bin} import -f -c "${cfg}" -p p1 -V --dkey "${dkey}" "${tmpd}"/lastfile +cat "${cfg}" + +# test +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/lastfile ] && echo "not imported in dotpath" && exit 1 +cat "${cfg}" | grep "${dkey}_2:" &>/dev/null || ( echo "bad key 4" && exit 1 ) + +# bad char +echo "firstfile" > "${tmpd}"/badchar + +dkey=".key@#\$ˆ&*()abc0032" +dkey_clean="key_abc0032" +cd "${ddpath}" | ${bin} import -f -c "${cfg}" -p p1 -V --dkey "${dkey}" "${tmpd}"/badchar +cat "${cfg}" + +# test +[ ! -e "${tmps}"/dotfiles/"${tmpd}"/badchar ] && echo "not imported in dotpath" && exit 1 +cat "${cfg}" | grep "${dkey_clean}:" &>/dev/null || ( echo "bad key 5" && exit 1 ) + +echo "OK" +exit 0