stree.StringTree.find(): Add method

StringTree.find(key, val) (and Config.find(), for that matter)
returns a list of paths with sections containing children matching
key / val pairs. One of them can be None, which acts as a wildcard.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2025-05-29 12:05:10 +02:00
commit b464f20e6c
2 changed files with 78 additions and 1 deletions

View file

@ -158,3 +158,9 @@ class Config(): # export
@property
def name(self):
return self.__conf.content
def find(self, key: str|None, val: str|None, match:StringTree.Match=StringTree.Match.Equal) -> list[str]:
return self.__conf.find(key, val, match=match)
#def __getattr__(self, name: str):
# return getattr(self.__conf, name)

View file

@ -1,7 +1,11 @@
from __future__ import annotations
from collections import OrderedDict
from typing import Any, List, Optional, Union
import re, fnmatch
from collections import OrderedDict
from enum import Enum, auto
from jwutils.log import *
def quote(s):
@ -227,3 +231,70 @@ class StringTree: # export
slog(prio, ",------------" + msg + "----------- >", caller=caller)
self.__dump(prio, indent=0, caller=caller)
slog(prio, "`------------" + msg + "----------- <", caller=caller)
class Match(Enum):
Equal = auto()
RegExArg = auto()
RegExConf = auto()
GlobArg = auto()
GlobConf = auto()
def __find(self, key: str|None, val: str|None, match: Match, depth_first: bool):
def __children():
for name, child in self.children.items():
ret.extend(child.__find(key, val, match, depth_first))
def __self():
_val = self.value()
_content = self.content
try:
if (
(key == _content and matcher(val, _val))
or (key is None and matcher(val, _val))
or (key == _content and val is None)
):
ret.append(self)
except Exception as e:
if isinstance(e, re.PatternError):
pass
else:
raise
def __debug_matcher(matcher, log_level=DEBUG):
def __matcher(x, y):
slog(log_level, f'Comparing "{x}" ~ "{y}"')
return matcher(x, y)
return __matcher
if not self.children:
return []
matcher = lambda x, y: x == y
match match:
case self.Match.Equal:
pass
case self.Match.RegExArg:
matcher = lambda x, y: re.search(x, y) is not None
case self.Match.RegExConf:
matcher = lambda x, y: re.search(y, x) is not None
case self.Match.GlobArg:
matcher = lambda x, y: fnmatch.fnmatch(y, x)
case self.Match.GlobConf:
matcher = lambda x, y: fnmatch.fnmatch(x, y)
case _:
raise NotImplementedError(f'Matcher {match} is not yet implemented')
ret = []
if depth_first:
__children()
__self()
else:
__self()
__children()
return ret
def find(self, key: str|None=None, val: str|None=None, match: Match=Match.Equal, depth_first: bool=False):
return [ node.parent.path for node in self.__find(key, val, match=match, depth_first=depth_first)]