From 6722dcb7185c5d11366baf9ec84a558a8ec6b25d Mon Sep 17 00:00:00 2001 From: deadc0de6 Date: Sun, 21 Nov 2021 22:53:27 +0100 Subject: [PATCH] adding key_prefix and key_separator for #335 --- docs/config-format.md | 2 + docs/usage.md | 11 ++-- dotdrop/cfg_aggregator.py | 21 +++++-- dotdrop/options.py | 7 ++- dotdrop/settings.py | 9 ++- dotdrop/utils.py | 14 ++--- tests-ng/key-prefix-sep.sh | 119 +++++++++++++++++++++++++++++++++++++ 7 files changed, 162 insertions(+), 21 deletions(-) create mode 100755 tests-ng/key-prefix-sep.sh diff --git a/docs/config-format.md b/docs/config-format.md index e681853..e6f1a3d 100644 --- a/docs/config-format.md +++ b/docs/config-format.md @@ -33,6 +33,8 @@ Entry | Description | Default `import_variables` | List of paths to load variables from (absolute paths or relative to the config file location; see [Import variables from file](config-details.md#import_variables-entry)) | - `instignore` | List of patterns to ignore when installing, applied to all dotfiles (enclose in quotes when using wildcards; see [ignore patterns](config.md#ignore-patterns)) | - `keepdot` | Preserve leading dot when importing hidden file in the `dotpath` | false +`key_prefix` | Prefix dotfile key on `import` with `f` for file and `d` for directory | true +`key_separator` | Separator to use on dotfile key generation on `import` | `_` `link_dotfile_default` | Set a dotfile's `link` attribute to this value when undefined. Possible values: *nolink*, *link* (See [Symlinking dotfiles](config.md#symlinking-dotfiles)) | `nolink` `link_on_import` | Set a dotfile's `link` attribute to this value when importing. Possible values: *nolink*, *link* [Symlinking dotfiles](config.md#symlinking-dotfiles)) | `nolink` `longkey` | Use long keys for dotfiles when importing (See [Import dotfiles](usage.md#import-dotfiles)) | false diff --git a/docs/usage.md b/docs/usage.md index fef175b..2923be5 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -42,12 +42,13 @@ $ dotdrop import ~/.xinitrc ``` You can control how the dotfile key is generated in the config file -with the config entry `longkey` (defaults to *false*). +with the following config entries: -Two formats are available: - -* *short format* (default): take the shortest unique path -* *long format*: take the full path +* `longkey` + * *short format* (default): take the shortest unique path + * *long format*: take the full path +* `key_prefix`: defines if the key is prefixed with `f` for file and `d` for directory +* `key_separator`: defines the separator to use (defaults to `_`) For example, `~/.config/awesome/rc.lua` gives: diff --git a/dotdrop/cfg_aggregator.py b/dotdrop/cfg_aggregator.py index 32d59f4..68b076e 100644 --- a/dotdrop/cfg_aggregator.py +++ b/dotdrop/cfg_aggregator.py @@ -28,7 +28,6 @@ class CfgAggregator: file_prefix = 'f' dir_prefix = 'd' - key_sep = '_' def __init__(self, path, profile_key, debug=False, dry=False): """ @@ -228,6 +227,8 @@ class CfgAggregator: # settings self.settings = Settings.parse(None, self.cfgyaml.settings) + self.key_prefix = self.settings.key_prefix + self.key_separator = self.settings.key_separator # dotfiles self.dotfiles = Dotfile.parse_dict(self.cfgyaml.dotfiles) @@ -333,8 +334,12 @@ class CfgAggregator: absolute path of path """ dirs = self._split_path_for_key(path) - prefix = self.dir_prefix if os.path.isdir(path) else self.file_prefix - key = self.key_sep.join([prefix] + dirs) + prefix = [] + if self.key_prefix: + prefix = [self.file_prefix] + if os.path.isdir(path): + prefix = [self.dir_prefix] + key = self.key_separator.join(prefix + dirs) return self._uniq_key(key, keys) def _get_short_key(self, path, keys): @@ -344,11 +349,15 @@ class CfgAggregator: """ dirs = self._split_path_for_key(path) dirs.reverse() - prefix = self.dir_prefix if os.path.isdir(path) else self.file_prefix + prefix = [] + if self.key_prefix: + prefix = [self.file_prefix] + if os.path.isdir(path): + prefix = [self.dir_prefix] entries = [] for dri in dirs: entries.insert(0, dri) - key = self.key_sep.join([prefix] + entries) + key = self.key_separator.join(prefix + entries) if key not in keys: return key return self._uniq_key(key, keys) @@ -360,7 +369,7 @@ class CfgAggregator: while newkey in keys: # if unable to get a unique path # get a random one - newkey = self.key_sep.join([key, str(cnt)]) + newkey = self.key_separator.join([key, str(cnt)]) cnt += 1 return newkey diff --git a/dotdrop/options.py b/dotdrop/options.py index 96fd893..b98ce58 100644 --- a/dotdrop/options.py +++ b/dotdrop/options.py @@ -134,6 +134,8 @@ class Options(AttrMonitor): self.chmod_on_import = None self.check_version = None self.clear_workdir = None + self.key_prefix = None + self.key_separator = None # args parsing self.args = {} @@ -223,8 +225,9 @@ class Options(AttrMonitor): self.conf = Cfg(self.confpath, self.profile, debug=self.debug, dry=self.dry) # transform the config settings to self attribute - debug_dict('effective settings', self.conf.get_settings(), self.debug) - for k, val in self.conf.get_settings().items(): + settings = self.conf.get_settings() + debug_dict('effective settings', settings, self.debug) + for k, val in settings.items(): setattr(self, k, val) def _apply_args_files(self): diff --git a/dotdrop/settings.py b/dotdrop/settings.py index eb36cbd..24831be 100644 --- a/dotdrop/settings.py +++ b/dotdrop/settings.py @@ -48,6 +48,8 @@ class Settings(DictParser): key_check_version = 'check_version' key_clear_workdir = 'clear_workdir' key_compare_workdir = 'compare_workdir' + key_key_prefix = 'key_prefix' + key_key_separator = 'key_separator' # import keys key_import_actions = 'import_actions' @@ -69,7 +71,8 @@ class Settings(DictParser): ignore_missing_in_dotdrop=False, force_chmod=False, chmod_on_import=False, check_version=False, clear_workdir=False, - compare_workdir=False): + compare_workdir=False, key_prefix=True, + key_separator='_'): self.backup = backup self.banner = banner self.create = create @@ -102,6 +105,8 @@ class Settings(DictParser): self.check_version = check_version self.clear_workdir = clear_workdir self.compare_workdir = compare_workdir + self.key_prefix = key_prefix + self.key_separator = key_separator def _serialize_seq(self, name, dic): """serialize attribute 'name' into 'dic'""" @@ -131,6 +136,8 @@ class Settings(DictParser): self.key_check_version: self.check_version, self.key_clear_workdir: self.clear_workdir, self.key_compare_workdir: self.compare_workdir, + self.key_key_prefix: self.key_prefix, + self.key_key_separator: self.key_separator, } self._serialize_seq(self.key_default_actions, dic) self._serialize_seq(self.key_import_actions, dic) diff --git a/dotdrop/utils.py b/dotdrop/utils.py index 2f1419d..9cd95cf 100644 --- a/dotdrop/utils.py +++ b/dotdrop/utils.py @@ -246,7 +246,7 @@ def must_ignore(paths, ignores, debug=False): nign = nign[1:] if debug: msg = 'trying to match :\"{}\" with non-ignore-pattern:\"{}\"' - LOG.dbg(msg.format(path, nign)) + LOG.dbg(msg.format(path, nign), force=True) if fnmatch.fnmatch(path, nign): if debug: msg = 'negative ignore \"{}\" match: {}'.format(nign, path) @@ -444,23 +444,23 @@ def debug_list(title, elems, debug): """pretty print list""" if not debug: return - LOG.dbg('{}:'.format(title)) + LOG.dbg('{}:'.format(title), force=debug) for elem in elems: - LOG.dbg('\t- {}'.format(elem)) + LOG.dbg('\t- {}'.format(elem), force=debug) def debug_dict(title, elems, debug): """pretty print dict""" if not debug: return - LOG.dbg('{}:'.format(title)) + LOG.dbg('{}:'.format(title), force=debug) for k, val in elems.items(): if isinstance(val, list): - LOG.dbg('\t- \"{}\":'.format(k)) + LOG.dbg('\t- \"{}\":'.format(k), force=debug) for i in val: - LOG.dbg('\t\t- {}'.format(i)) + LOG.dbg('\t\t- {}'.format(i), force=debug) else: - LOG.dbg('\t- \"{}\": {}'.format(k, val)) + LOG.dbg('\t- \"{}\": {}'.format(k, val), force=debug) def check_version(): diff --git a/tests-ng/key-prefix-sep.sh b/tests-ng/key-prefix-sep.sh new file mode 100755 index 0000000..77221d5 --- /dev/null +++ b/tests-ng/key-prefix-sep.sh @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +# author: deadc0de6 (https://github.com/deadc0de6) +# Copyright (c) 2021, deadc0de6 +# +# test key_prefix and key_separator +# + +# exit on first error +set -e + +# all this crap to get current path +rl="readlink -f" +if ! ${rl} "${0}" >/dev/null 2>&1; then + rl="realpath" + + if ! hash ${rl}; then + echo "\"${rl}\" not found !" && exit 1 + fi +fi +cur=$(dirname "$(${rl} "${0}")") + +#hash dotdrop >/dev/null 2>&1 +#[ "$?" != "0" ] && echo "install dotdrop to run tests" && exit 1 + +#echo "called with ${1}" + +# dotdrop path can be pass as argument +ddpath="${cur}/../" +[ "${1}" != "" ] && ddpath="${1}" +[ ! -d ${ddpath} ] && echo "ddpath \"${ddpath}\" is not a directory" && exit 1 + +export PYTHONPATH="${ddpath}:${PYTHONPATH}" +bin="python3 -m dotdrop.dotdrop" +hash coverage 2>/dev/null && bin="coverage run -a --source=dotdrop -m dotdrop.dotdrop" || true + +echo "dotdrop path: ${ddpath}" +echo "pythonpath: ${PYTHONPATH}" + +# get the helpers +source ${cur}/helpers + +echo -e "$(tput setaf 6)==> RUNNING $(basename $BASH_SOURCE) <==$(tput sgr0)" + +################################################################ +# 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 +mkdir -p ${tmpd}/top +touch ${tmpd}/top/.colors +mkdir -p ${tmpd}/.mutt/sub +touch ${tmpd}/.mutt/sub/colors + +# create the config file +cfg="${tmps}/config.yaml" + +# normal behavior +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + longkey: true + key_prefix: true + key_separator: '_' +dotfiles: +profiles: +_EOF + +# import +cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V ${tmpd}/top/.colors +cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V ${tmpd}/.mutt/sub + +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep -q '_top_colors' +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep -q '_mutt_sub' + +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep '_top_colors' | grep -q 'f_' +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep '_mutt_sub' | grep -q 'd_' + +# pimping +rm -rf ${tmps}/* + +cat > ${cfg} << _EOF +config: + backup: true + create: true + dotpath: dotfiles + longkey: true + key_prefix: false + key_separator: '+' +dotfiles: +profiles: +_EOF + +# import +cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V ${tmpd}/top/.colors +cd ${ddpath} | ${bin} import -c ${cfg} -p p1 -V ${tmpd}/.mutt/sub + +cat ${cfg} +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G + +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep -q '+top+colors' +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep -q '+mutt+sub' + +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep '+top+colors' | grep -qv 'f_' +cd ${ddpath} | ${bin} files -c ${cfg} -p p1 -G | cut -f1 -d',' | grep '+mutt+sub' | grep -qv 'd_' + +echo "OK" +exit 0