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..73c18459 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,34 @@ 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 + 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) + 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