add template generation, generalize search heuristics
This commit is contained in:
parent
2f78fa0527
commit
3120638ef3
@ -1,5 +1,7 @@
|
|||||||
from symconf.config import ConfigManager
|
from symconf.config import ConfigManager
|
||||||
|
from symconf.reader import DictReader
|
||||||
|
|
||||||
from symconf import config
|
from symconf import config
|
||||||
|
from symconf import reader
|
||||||
from symconf import theme
|
from symconf import theme
|
||||||
from symconf import util
|
from symconf import util
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import json
|
import json
|
||||||
import inspect
|
import inspect
|
||||||
import tomllib
|
import tomllib
|
||||||
@ -9,6 +10,7 @@ from pathlib import Path
|
|||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
|
|
||||||
from symconf import util
|
from symconf import util
|
||||||
|
from symconf.reader import DictReader
|
||||||
|
|
||||||
|
|
||||||
class ConfigManager:
|
class ConfigManager:
|
||||||
@ -81,34 +83,12 @@ class ConfigManager:
|
|||||||
|
|
||||||
self.app_registry = app_registry.get('app', {})
|
self.app_registry = app_registry.get('app', {})
|
||||||
|
|
||||||
def _resolve_scheme(self, scheme):
|
def _resolve_group(self, group, value='auto'):
|
||||||
# if scheme == 'auto':
|
if value == 'auto':
|
||||||
# os_cmd_groups = {
|
# look group up in app cache and set to current value
|
||||||
# 'Linux': (
|
|
||||||
# "gsettings get org.gnome.desktop.interface color-scheme",
|
|
||||||
# lambda r: r.split('-')[1][:-1],
|
|
||||||
# ),
|
|
||||||
# 'Darwin': (),
|
|
||||||
# }
|
|
||||||
|
|
||||||
# osname = os.uname().sysname
|
|
||||||
# os_group = os_cmd_groups.get(osname, [])
|
|
||||||
|
|
||||||
# for cmd in cmd_list:
|
|
||||||
# subprocess.check_call(cmd.format(scheme=scheme).split())
|
|
||||||
|
|
||||||
# return scheme
|
|
||||||
|
|
||||||
if scheme == 'auto':
|
|
||||||
return 'any'
|
return 'any'
|
||||||
|
|
||||||
return scheme
|
return value
|
||||||
|
|
||||||
def _resolve_palette(self, palette):
|
|
||||||
if palette == 'auto':
|
|
||||||
return 'any'
|
|
||||||
|
|
||||||
return palette
|
|
||||||
|
|
||||||
def app_config_map(self, app_name) -> dict[str, Path]:
|
def app_config_map(self, app_name) -> dict[str, Path]:
|
||||||
'''
|
'''
|
||||||
@ -220,6 +200,9 @@ class ConfigManager:
|
|||||||
prefix_order=None,
|
prefix_order=None,
|
||||||
strict=False,
|
strict=False,
|
||||||
):
|
):
|
||||||
|
'''
|
||||||
|
Find and return matches along the "match trajectory."
|
||||||
|
'''
|
||||||
file_parts = self._get_file_parts(pathnames)
|
file_parts = self._get_file_parts(pathnames)
|
||||||
|
|
||||||
if prefix_order is None:
|
if prefix_order is None:
|
||||||
@ -230,7 +213,7 @@ class ConfigManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
ordered_matches = []
|
ordered_matches = []
|
||||||
for palette_prefix, scheme_prefix in prefix_order:
|
for i, (palette_prefix, scheme_prefix) in enumerate(prefix_order):
|
||||||
for theme_part, conf_part, pathname in file_parts:
|
for theme_part, conf_part, pathname in file_parts:
|
||||||
theme_split = theme_part.split('-')
|
theme_split = theme_part.split('-')
|
||||||
palette_part, scheme_part = '-'.join(theme_split[:-1]), theme_split[-1]
|
palette_part, scheme_part = '-'.join(theme_split[:-1]), theme_split[-1]
|
||||||
@ -238,10 +221,154 @@ class ConfigManager:
|
|||||||
palette_match = palette_prefix == palette_part or palette_prefix == 'any'
|
palette_match = palette_prefix == palette_part or palette_prefix == 'any'
|
||||||
scheme_match = scheme_prefix == scheme_part or scheme_prefix == 'any'
|
scheme_match = scheme_prefix == scheme_part or scheme_prefix == 'any'
|
||||||
if palette_match and scheme_match:
|
if palette_match and scheme_match:
|
||||||
ordered_matches.append((conf_part, theme_part, pathname))
|
ordered_matches.append((conf_part, theme_part, pathname, i+1))
|
||||||
|
|
||||||
return ordered_matches
|
return ordered_matches
|
||||||
|
|
||||||
|
def _get_relaxed_set(
|
||||||
|
self,
|
||||||
|
match_list
|
||||||
|
):
|
||||||
|
'''
|
||||||
|
Mostly to filter "any" matches, latching onto a particular result and getting
|
||||||
|
only its relaxed variants.
|
||||||
|
'''
|
||||||
|
if not match_list:
|
||||||
|
return []
|
||||||
|
|
||||||
|
match = match_list[-1]
|
||||||
|
theme_split = match[1].split('-')
|
||||||
|
palette_tgt, scheme_tgt = '-'.join(theme_split[:-1]), theme_split[-1]
|
||||||
|
|
||||||
|
relaxed_map = {}
|
||||||
|
for conf_part, theme_part, pathname, idx in match_list:
|
||||||
|
theme_split = theme_part.split('-')
|
||||||
|
palette_part, scheme_part = '-'.join(theme_split[:-1]), theme_split[-1]
|
||||||
|
|
||||||
|
palette_match = palette_part == palette_tgt or palette_part == 'none'
|
||||||
|
scheme_match = scheme_part == scheme_tgt or scheme_part == 'none'
|
||||||
|
|
||||||
|
if palette_match and scheme_match:
|
||||||
|
relaxed_map[pathname] = (conf_part, theme_part, pathname, idx)
|
||||||
|
|
||||||
|
return list(relaxed_map.values())
|
||||||
|
|
||||||
|
def _stack_toml(
|
||||||
|
self,
|
||||||
|
path_list
|
||||||
|
):
|
||||||
|
stacked_dict = {}
|
||||||
|
for toml_path in path_list:
|
||||||
|
updated_map = tomllib.load(toml_path.open('rb'))
|
||||||
|
stacked_dict = util.deep_update(stacked_dict, updated_map)
|
||||||
|
|
||||||
|
return stacked_dict
|
||||||
|
|
||||||
|
def template_fill(
|
||||||
|
self,
|
||||||
|
template_str : str,
|
||||||
|
template_dict : dict,
|
||||||
|
pattern : str = r'f{{(\S+)}}',
|
||||||
|
):
|
||||||
|
dr = DictReader.from_dict(template_dict)
|
||||||
|
return re.sub(
|
||||||
|
pattern,
|
||||||
|
lambda m:str(dr.get(m.group(1))),
|
||||||
|
template_str
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_matching_group_dict(
|
||||||
|
self,
|
||||||
|
scheme='auto',
|
||||||
|
palette='auto',
|
||||||
|
**kw_groups,
|
||||||
|
) -> dict:
|
||||||
|
'''
|
||||||
|
Note that "strictness" doesn't really apply in this setting. In the config
|
||||||
|
matching setting, setting strict means there's no relaxation to "none," but here,
|
||||||
|
any "none" group files just help fill any gaps (but are otherwise totally
|
||||||
|
overwritten, even if matched, by more precise matches). You can match ``nones``
|
||||||
|
directly if you want as well. ``get_matching_scripts()`` is similar in this sense.
|
||||||
|
'''
|
||||||
|
scheme = self._resolve_group('scheme', scheme)
|
||||||
|
palette = self._resolve_group('palette', palette)
|
||||||
|
groups = {
|
||||||
|
k : self._resolve_group(k, v)
|
||||||
|
for k, v in kw_groups.items()
|
||||||
|
}
|
||||||
|
# palette lookup will behave like other groups
|
||||||
|
groups['palette'] = palette
|
||||||
|
|
||||||
|
group_dir = Path(self.config_dir, 'groups')
|
||||||
|
if not group_dir.exists():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# handle non-palette-scheme groups
|
||||||
|
group_matches = {}
|
||||||
|
for fkey, fval in groups.items():
|
||||||
|
key_group_dir = Path(group_dir, fkey)
|
||||||
|
|
||||||
|
if not key_group_dir.exists():
|
||||||
|
print(f'Group directory {fkey} doesn\'t exist, skipping')
|
||||||
|
continue
|
||||||
|
|
||||||
|
# mirror matching scheme: 1) prefix order, 2) full enumeration, 3) select
|
||||||
|
# best, 4) make unique, 5) ordered relaxation
|
||||||
|
stem_map = {path.stem : path for path in key_group_dir.iterdir()}
|
||||||
|
|
||||||
|
if fval == 'any':
|
||||||
|
prefix_order = [fval, 'none']
|
||||||
|
else:
|
||||||
|
prefix_order = ['none', fval]
|
||||||
|
|
||||||
|
matches = []
|
||||||
|
for prefix in prefix_order:
|
||||||
|
for stem in stem_map:
|
||||||
|
if prefix == stem or prefix == 'any':
|
||||||
|
matches.append(stem)
|
||||||
|
|
||||||
|
if not matches:
|
||||||
|
# no matches for group, skip
|
||||||
|
continue
|
||||||
|
|
||||||
|
match_dict = {}
|
||||||
|
tgt = matches[-1] # select best based on order, make new target
|
||||||
|
for stem in matches:
|
||||||
|
if stem == tgt or stem == 'none':
|
||||||
|
match_dict[stem] = stem_map[stem]
|
||||||
|
|
||||||
|
group_matches[fkey] = list(match_dict.values())
|
||||||
|
|
||||||
|
# first handle scheme maps; matching palette files should already be found in the
|
||||||
|
# regular group matching process
|
||||||
|
palette_dict = self._stack_toml(group_matches.get('palette', []))
|
||||||
|
|
||||||
|
# then palette-scheme groups (require 2-combo logic)
|
||||||
|
scheme_group_dir = Path(group_dir, 'scheme')
|
||||||
|
scheme_pathnames = [path.name for path in scheme_group_dir.iterdir()]
|
||||||
|
ordered_matches = self.match_pathnames(
|
||||||
|
scheme_pathnames,
|
||||||
|
scheme,
|
||||||
|
palette,
|
||||||
|
)
|
||||||
|
relaxed_matches = self._get_relaxed_set(ordered_matches)
|
||||||
|
|
||||||
|
scheme_dict = {}
|
||||||
|
for conf_part, theme_part, toml_path, _ in relaxed_matches:
|
||||||
|
toml_str = Path(scheme_group_dir, toml_path).open('r').read()
|
||||||
|
filled_toml = self.template_fill(toml_str, palette_dict)
|
||||||
|
|
||||||
|
toml_dict = tomllib.loads(filled_toml)
|
||||||
|
scheme_dict = util.deep_update(scheme_dict, toml_dict)
|
||||||
|
|
||||||
|
template_dict = {
|
||||||
|
group : self._stack_toml(ordered_matches)
|
||||||
|
for group, ordered_matches in group_matches.items()
|
||||||
|
}
|
||||||
|
template_dict['scheme'] = scheme_dict
|
||||||
|
|
||||||
|
return template_dict, relaxed_matches
|
||||||
|
|
||||||
def get_matching_configs(
|
def get_matching_configs(
|
||||||
self,
|
self,
|
||||||
app_name,
|
app_name,
|
||||||
@ -310,8 +437,8 @@ class ConfigManager:
|
|||||||
'''
|
'''
|
||||||
app_dir = Path(self.apps_dir, app_name)
|
app_dir = Path(self.apps_dir, app_name)
|
||||||
|
|
||||||
scheme = self._resolve_scheme(scheme)
|
scheme = self._resolve_group('scheme', scheme)
|
||||||
palette = self._resolve_palette(palette)
|
palette = self._resolve_group('palette', palette)
|
||||||
|
|
||||||
app_config_map = self.app_config_map(app_name)
|
app_config_map = self.app_config_map(app_name)
|
||||||
|
|
||||||
@ -323,11 +450,35 @@ class ConfigManager:
|
|||||||
)
|
)
|
||||||
|
|
||||||
matching_file_map = {}
|
matching_file_map = {}
|
||||||
for conf_part, theme_part, pathname in ordered_matches:
|
for conf_part, theme_part, pathname, idx in ordered_matches:
|
||||||
matching_file_map[conf_part] = app_config_map[pathname]
|
matching_file_map[conf_part] = (app_config_map[pathname], idx)
|
||||||
|
|
||||||
return matching_file_map
|
return matching_file_map
|
||||||
|
|
||||||
|
def get_matching_templates(
|
||||||
|
self,
|
||||||
|
app_name,
|
||||||
|
scheme='auto',
|
||||||
|
palette='auto',
|
||||||
|
**kw_groups,
|
||||||
|
) -> dict:
|
||||||
|
template_dict, relaxed_matches = self.get_matching_group_dict(
|
||||||
|
scheme=scheme,
|
||||||
|
palette=palette,
|
||||||
|
**kw_groups,
|
||||||
|
)
|
||||||
|
max_idx = 0
|
||||||
|
if relaxed_matches:
|
||||||
|
max_idx = max([m[3] for m in relaxed_matches])
|
||||||
|
|
||||||
|
template_map = {}
|
||||||
|
template_dir = Path(self.apps_dir, app_name, 'templates')
|
||||||
|
if template_dir.exists():
|
||||||
|
for template_file in template_dir.iterdir():
|
||||||
|
template_map[template_file.name] = template_file
|
||||||
|
|
||||||
|
return template_map, template_dict, max_idx
|
||||||
|
|
||||||
def get_matching_scripts(
|
def get_matching_scripts(
|
||||||
self,
|
self,
|
||||||
app_name,
|
app_name,
|
||||||
@ -372,16 +523,19 @@ class ConfigManager:
|
|||||||
palette,
|
palette,
|
||||||
prefix_order=prefix_order
|
prefix_order=prefix_order
|
||||||
)
|
)
|
||||||
|
relaxed_matches = self._get_relaxed_set(ordered_matches)
|
||||||
|
|
||||||
# flip list to execute by decreasing specificity
|
# flip list to execute by decreasing specificity
|
||||||
return list(dict.fromkeys(map(lambda x:Path(call_dir, x[2]), ordered_matches)))[::-1]
|
return list(map(lambda x:Path(call_dir, x[2]), relaxed_matches))[::-1]
|
||||||
|
|
||||||
def update_app_config(
|
def update_app_config(
|
||||||
self,
|
self,
|
||||||
app_name,
|
app_name,
|
||||||
app_settings = None,
|
app_settings = None,
|
||||||
|
strict = False,
|
||||||
scheme = 'any',
|
scheme = 'any',
|
||||||
palette = 'any',
|
palette = 'any',
|
||||||
|
**kw_groups,
|
||||||
):
|
):
|
||||||
'''
|
'''
|
||||||
Perform full app config update process, applying symlinks and running scripts.
|
Perform full app config update process, applying symlinks and running scripts.
|
||||||
@ -403,12 +557,42 @@ class ConfigManager:
|
|||||||
print(f'App "{app_name}" incorrectly configured, skipping')
|
print(f'App "{app_name}" incorrectly configured, skipping')
|
||||||
return
|
return
|
||||||
|
|
||||||
to_symlink: list[tuple[Path, Path]] = []
|
# merge templates and user-provided configs
|
||||||
file_map = self.get_matching_configs(
|
template_map, template_dict, tidx = self.get_matching_templates(
|
||||||
app_name,
|
app_name,
|
||||||
scheme=scheme,
|
scheme=scheme,
|
||||||
palette=palette,
|
palette=palette,
|
||||||
|
**kw_groups
|
||||||
)
|
)
|
||||||
|
# set file map to user configs if yields a strictly better match
|
||||||
|
config_map = self.get_matching_configs(
|
||||||
|
app_name,
|
||||||
|
scheme=scheme,
|
||||||
|
palette=palette,
|
||||||
|
strict=strict,
|
||||||
|
)
|
||||||
|
|
||||||
|
# tuples of 1) full paths and 2) whether to fill template
|
||||||
|
generated_path = Path(self.apps_dir, app_name, 'generated')
|
||||||
|
generated_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
file_map = {}
|
||||||
|
for tail, full_path in template_map.items():
|
||||||
|
if tail in config_map and config_map[tail][2] >= tidx:
|
||||||
|
file_map[tail] = config_map[tail]
|
||||||
|
else:
|
||||||
|
template_str = full_path.open('r').read()
|
||||||
|
filled_template = self.template_fill(template_str, template_dict)
|
||||||
|
|
||||||
|
config_path = Path(generated_path, tail)
|
||||||
|
config_path.write_text(filled_template)
|
||||||
|
file_map[tail] = config_path
|
||||||
|
|
||||||
|
for tail, (full_path, idx) in config_map.items():
|
||||||
|
if tail not in file_map:
|
||||||
|
file_map[tail] = full_path
|
||||||
|
|
||||||
|
to_symlink: list[tuple[Path, Path]] = []
|
||||||
if 'config_dir' in app_settings:
|
if 'config_dir' in app_settings:
|
||||||
for config_tail, full_path in file_map.items():
|
for config_tail, full_path in file_map.items():
|
||||||
to_symlink.append((
|
to_symlink.append((
|
||||||
@ -424,8 +608,6 @@ class ConfigManager:
|
|||||||
full_path, # to internal config location
|
full_path, # to internal config location
|
||||||
))
|
))
|
||||||
|
|
||||||
print('├─ ' + Fore.YELLOW + f'{app_name} :: matched {len(to_symlink)} config files')
|
|
||||||
|
|
||||||
links_succ = []
|
links_succ = []
|
||||||
links_fail = []
|
links_fail = []
|
||||||
for from_path, to_path in to_symlink:
|
for from_path, to_path in to_symlink:
|
||||||
@ -457,6 +639,17 @@ class ConfigManager:
|
|||||||
from_path.symlink_to(to_path)
|
from_path.symlink_to(to_path)
|
||||||
links_succ.append((from_path, to_path))
|
links_succ.append((from_path, to_path))
|
||||||
|
|
||||||
|
# run matching scripts for app-specific reload
|
||||||
|
script_list = self.get_matching_scripts(
|
||||||
|
app_name,
|
||||||
|
scheme=scheme,
|
||||||
|
palette=palette,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
'├─ ' + Fore.YELLOW + f'{app_name} :: matched {len(to_symlink)} config files and {len(script_list)} scripts'
|
||||||
|
)
|
||||||
|
|
||||||
# link report
|
# link report
|
||||||
for from_p, to_p in links_succ:
|
for from_p, to_p in links_succ:
|
||||||
from_p = from_p
|
from_p = from_p
|
||||||
@ -468,12 +661,6 @@ class ConfigManager:
|
|||||||
to_p = to_p.relative_to(self.config_dir)
|
to_p = to_p.relative_to(self.config_dir)
|
||||||
print(Fore.RED + f'│ > failed to link {from_p} -> {to_p}')
|
print(Fore.RED + f'│ > failed to link {from_p} -> {to_p}')
|
||||||
|
|
||||||
# run matching scripts for app-specific reload
|
|
||||||
script_list = self.get_matching_scripts(
|
|
||||||
app_name,
|
|
||||||
scheme=scheme,
|
|
||||||
palette=palette,
|
|
||||||
)
|
|
||||||
|
|
||||||
for script in script_list:
|
for script in script_list:
|
||||||
print(Fore.BLUE + f'│ > running script "{script.relative_to(self.config_dir)}"')
|
print(Fore.BLUE + f'│ > running script "{script.relative_to(self.config_dir)}"')
|
||||||
@ -491,6 +678,8 @@ class ConfigManager:
|
|||||||
apps: str | list[str] = '*',
|
apps: str | list[str] = '*',
|
||||||
scheme = 'any',
|
scheme = 'any',
|
||||||
palette = 'any',
|
palette = 'any',
|
||||||
|
strict=False,
|
||||||
|
**kw_groups,
|
||||||
):
|
):
|
||||||
if apps == '*':
|
if apps == '*':
|
||||||
# get all registered apps
|
# get all registered apps
|
||||||
@ -514,4 +703,6 @@ class ConfigManager:
|
|||||||
app_settings=self.app_registry[app_name],
|
app_settings=self.app_registry[app_name],
|
||||||
scheme=scheme,
|
scheme=scheme,
|
||||||
palette=palette,
|
palette=palette,
|
||||||
|
strict=False,
|
||||||
|
**kw_groups,
|
||||||
)
|
)
|
||||||
|
94
symconf/reader.py
Normal file
94
symconf/reader.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import copy
|
||||||
|
import pprint
|
||||||
|
import tomllib
|
||||||
|
import hashlib
|
||||||
|
from typing import Any
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from symconf.util import deep_update
|
||||||
|
|
||||||
|
|
||||||
|
class DictReader:
|
||||||
|
def __init__(self, toml_path=None):
|
||||||
|
self._config = {}
|
||||||
|
self.toml_path = toml_path
|
||||||
|
|
||||||
|
if toml_path is not None:
|
||||||
|
self._config = self._load_toml(toml_path)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return pprint.pformat(self._config, indent=4)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _load_toml(toml_path) -> dict[str, Any]:
|
||||||
|
return tomllib.loads(Path(toml_path).read_text())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, config_dict):
|
||||||
|
new_instance = cls()
|
||||||
|
new_instance._config = copy.deepcopy(config_dict)
|
||||||
|
return new_instance
|
||||||
|
|
||||||
|
def update(self, config, in_place=False):
|
||||||
|
new_config = deep_update(self._config, config._config)
|
||||||
|
|
||||||
|
if in_place:
|
||||||
|
self._config = new_config
|
||||||
|
return self
|
||||||
|
|
||||||
|
return self.from_dict(new_config)
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
return self.from_dict(copy.deepcopy(self._config))
|
||||||
|
|
||||||
|
def get_subconfig(self, key): pass
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
keys = key.split('.')
|
||||||
|
|
||||||
|
subconfig = self._config
|
||||||
|
for subkey in keys[:-1]:
|
||||||
|
subconfig = subconfig.get(subkey)
|
||||||
|
|
||||||
|
if type(subconfig) is not dict:
|
||||||
|
return default
|
||||||
|
|
||||||
|
return subconfig.get(keys[-1], default)
|
||||||
|
|
||||||
|
def set(self, key, value):
|
||||||
|
keys = key.split('.')
|
||||||
|
|
||||||
|
subconfig = self._config
|
||||||
|
for subkey in keys[:-1]:
|
||||||
|
if subkey in subconfig:
|
||||||
|
subconfig = subconfig[subkey]
|
||||||
|
|
||||||
|
if type(subconfig) is not dict:
|
||||||
|
logger.debug(
|
||||||
|
'Attempting to set nested key with an existing non-dict parent'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
subdict = {}
|
||||||
|
subconfig[subkey] = subdict
|
||||||
|
subconfig = subdict
|
||||||
|
|
||||||
|
subconfig.update({ keys[-1]: value })
|
||||||
|
return True
|
||||||
|
|
||||||
|
def generate_hash(self, exclude_keys=None):
|
||||||
|
inst_copy = self.copy()
|
||||||
|
|
||||||
|
if exclude_keys is not None:
|
||||||
|
for key in exclude_keys:
|
||||||
|
inst_copy.set(key, None)
|
||||||
|
|
||||||
|
items = inst_copy._config.items()
|
||||||
|
|
||||||
|
# create hash from config options
|
||||||
|
config_str = str(sorted(items))
|
||||||
|
return hashlib.md5(config_str.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
@ -7,3 +7,15 @@ def absolute_path(path: str | Path) -> Path:
|
|||||||
|
|
||||||
def xdg_config_path():
|
def xdg_config_path():
|
||||||
return Path(BaseDirectory.save_config_path('symconf'))
|
return Path(BaseDirectory.save_config_path('symconf'))
|
||||||
|
|
||||||
|
def deep_update(mapping: dict, *updating_mappings: dict) -> dict:
|
||||||
|
'''Code adapted from pydantic'''
|
||||||
|
updated_mapping = mapping.copy()
|
||||||
|
for updating_mapping in updating_mappings:
|
||||||
|
for k, v in updating_mapping.items():
|
||||||
|
if k in updated_mapping and isinstance(updated_mapping[k], dict) and isinstance(v, dict):
|
||||||
|
updated_mapping[k] = deep_update(updated_mapping[k], v)
|
||||||
|
else:
|
||||||
|
updated_mapping[k] = v
|
||||||
|
return updated_mapping
|
||||||
|
|
||||||
|
@ -101,7 +101,8 @@ def test_matching_scripts():
|
|||||||
3. (palette, none) :: test-none.sh
|
3. (palette, none) :: test-none.sh
|
||||||
4. (palette, scheme) :: (nothing)
|
4. (palette, scheme) :: (nothing)
|
||||||
|
|
||||||
Yielding (ordered by dec specificity) "test-none.sh", "none-light.sh", "none-none.sh".
|
Yielding (ordered by dec specificity) "test-none.sh" as primary match, then relaxation
|
||||||
|
match "none-none.sh".
|
||||||
'''
|
'''
|
||||||
test_any = cm.get_matching_scripts(
|
test_any = cm.get_matching_scripts(
|
||||||
'test',
|
'test',
|
||||||
@ -109,8 +110,8 @@ def test_matching_scripts():
|
|||||||
scheme='any',
|
scheme='any',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(test_any) == 3
|
assert len(test_any) == 2
|
||||||
assert test_any == ['test-none.sh', 'none-light.sh', 'none-none.sh']
|
assert list(map(lambda p:p.name, test_any)) == ['test-none.sh', 'none-none.sh']
|
||||||
|
|
||||||
any_light = cm.get_matching_scripts(
|
any_light = cm.get_matching_scripts(
|
||||||
'test',
|
'test',
|
||||||
@ -118,8 +119,8 @@ def test_matching_scripts():
|
|||||||
scheme='light',
|
scheme='light',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert len(any_light) == 3
|
assert len(any_light) == 2
|
||||||
assert any_light == ['test-none.sh', 'none-light.sh', 'none-none.sh']
|
assert list(map(lambda p:p.name, any_light)) == ['none-light.sh', 'none-none.sh']
|
||||||
|
|
||||||
any_dark = cm.get_matching_scripts(
|
any_dark = cm.get_matching_scripts(
|
||||||
'test',
|
'test',
|
||||||
@ -128,4 +129,4 @@ def test_matching_scripts():
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(any_dark) == 2
|
assert len(any_dark) == 2
|
||||||
assert any_dark == ['test-none.sh', 'none-none.sh']
|
assert list(map(lambda p:p.name, any_dark)) == ['test-none.sh', 'none-none.sh']
|
||||||
|
Loading…
Reference in New Issue
Block a user