From e3482637905d5b2c56c259ad3e17eb9b27c033aa Mon Sep 17 00:00:00 2001 From: Matteo Voges <98756476+MatteoVoges@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:29:03 +0100 Subject: [PATCH 1/3] feat(parser): Add support for relative paths in references This allows us to specify relative paths and use some predefined keyword-functions --- reclass/datatypes/parameters.py | 1 + reclass/utils/dictpath.py | 26 +++++++++++++++++++++++++- reclass/values/refitem.py | 10 +++++----- reclass/values/value.py | 6 +++++- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/reclass/datatypes/parameters.py b/reclass/datatypes/parameters.py index ea803251..d6965c11 100644 --- a/reclass/datatypes/parameters.py +++ b/reclass/datatypes/parameters.py @@ -325,6 +325,7 @@ def _interpolate_inner(self, path, inventory): def _interpolate_render_value(self, path, value, inventory): try: + value.path = path # pass the path to the resolver to support relative paths new = value.render(self._base, inventory) except ResolveError as e: e.context = path diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py index 70c7bb51..2165a586 100644 --- a/reclass/utils/dictpath.py +++ b/reclass/utils/dictpath.py @@ -59,8 +59,9 @@ class DictPath(object): level down the nested dictionary. ''' - def __init__(self, delim, contents=None): + def __init__(self, delim, contents=None, path=''): self._delim = delim + self._refpath = path # path where the reference lays if contents is None: self._parts = [] @@ -122,6 +123,29 @@ def new_subpath(self, key): return DictPath(self._delim, self._parts + [key]) def get_value(self, base): + """ + get the value from a given key (in self._parts) and a dictionary (base) + relative references are allowed, e.g. ${:start:from:here} + """ + # only apply relative paths if keyword is specified + if self._parts and self._parts[0] in ("", "~", ".self_name"): + parts = str(self._refpath).split(":") + for part in self._parts: + if not part: + # e.g. ${::key} --> go one level back + parts = parts[:-1] + elif part == "~": + # e.g. ${~:key} --> go to root (parameters) + parts = [] + elif (part == ".self_name"): + # e.g. ${:.self_name} returns parent key name + return parts + else: + # you can mix normal key stepping with new features + parts.append(part) + self._parts = parts + + # get the value from the dictionary return self._get_innermost_container(base)[self._get_key()] def set_value(self, base, value): diff --git a/reclass/values/refitem.py b/reclass/values/refitem.py index 64bf4503..8b5646d8 100644 --- a/reclass/values/refitem.py +++ b/reclass/values/refitem.py @@ -24,18 +24,18 @@ def _flatten_contents(self, context, inventory=None): result = [str(i.render(context, inventory)) for i in self.contents] return "".join(result) - def _resolve(self, ref, context): - path = DictPath(self._settings.delimiter, ref) + def _resolve(self, ref, context, path=''): + refpath = DictPath(self._settings.delimiter, ref, path=path) try: - return path.get_value(context) + return refpath.get_value(context) except (KeyError, TypeError) as e: raise ResolveError(ref) - def render(self, context, inventory): + def render(self, context, inventory, path=''): #strings = [str(i.render(context, inventory)) for i in self.contents] #return self._resolve("".join(strings), context) return self._resolve(self._flatten_contents(context, inventory), - context) + context, path=path) def __str__(self): strings = [str(i) for i in self.contents] diff --git a/reclass/values/value.py b/reclass/values/value.py index 451617ec..6cee3950 100644 --- a/reclass/values/value.py +++ b/reclass/values/value.py @@ -12,6 +12,7 @@ from .dictitem import DictItem from .listitem import ListItem from .scaitem import ScaItem +from .refitem import RefItem from reclass.errors import InterpolationError from six import string_types @@ -20,9 +21,10 @@ class Value(object): _parser = Parser() - def __init__(self, value, settings, uri, parse_string=True): + def __init__(self, value, settings, uri, parse_string=True, path=''): self._settings = settings self.uri = uri + self.path = path self.overwrite = False self.constant = False if isinstance(value, string_types): @@ -87,6 +89,8 @@ def assembleRefs(self, context): def render(self, context, inventory): try: + if isinstance(self._item, RefItem): + return self._item.render(context, inventory, path=self.path) return self._item.render(context, inventory) except InterpolationError as e: e.uri = self.uri From 7c8d8225265cb8654ba75ef0a5ab14d54b793306 Mon Sep 17 00:00:00 2001 From: Matteo Voges <98756476+MatteoVoges@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:32:26 +0100 Subject: [PATCH 2/3] fix(parser): Choose correct item of the list The keyword '.self_name' was missing its access operator --- reclass/utils/dictpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py index 2165a586..97d92fdb 100644 --- a/reclass/utils/dictpath.py +++ b/reclass/utils/dictpath.py @@ -139,7 +139,7 @@ def get_value(self, base): parts = [] elif (part == ".self_name"): # e.g. ${:.self_name} returns parent key name - return parts + return parts[-1] else: # you can mix normal key stepping with new features parts.append(part) From 2dbff6081d74bd8bc4c9322b1dab9a43d006a61c Mon Sep 17 00:00:00 2001 From: Matteo Voges <98756476+MatteoVoges@users.noreply.github.com> Date: Tue, 21 Mar 2023 18:48:05 +0100 Subject: [PATCH 3/3] fix(parser): Add error handling Handle Indexerror when calling '.self_name' out of range --- reclass/utils/dictpath.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/reclass/utils/dictpath.py b/reclass/utils/dictpath.py index 97d92fdb..73c18459 100644 --- a/reclass/utils/dictpath.py +++ b/reclass/utils/dictpath.py @@ -139,7 +139,12 @@ def get_value(self, base): parts = [] elif (part == ".self_name"): # e.g. ${:.self_name} returns parent key name - return parts[-1] + try: + return parts[-1] + except IndexError: + # You can't access values outside of '.parameters'. Using 'parameters'. + # We could throw a more precise error ... + return "parameters" else: # you can mix normal key stepping with new features parts.append(part)