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 @property
def name(self): def name(self):
return self.__conf.content 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 __future__ import annotations
from collections import OrderedDict
from typing import Any, List, Optional, Union from typing import Any, List, Optional, Union
import re, fnmatch
from collections import OrderedDict
from enum import Enum, auto
from jwutils.log import * from jwutils.log import *
def quote(s): def quote(s):
@ -227,3 +231,70 @@ class StringTree: # export
slog(prio, ",------------" + msg + "----------- >", caller=caller) slog(prio, ",------------" + msg + "----------- >", caller=caller)
self.__dump(prio, indent=0, caller=caller) self.__dump(prio, indent=0, caller=caller)
slog(prio, "`------------" + msg + "----------- <", 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)]