"""
NUC selectors.
"""
from enum import Enum
import re
from dataclasses import dataclass
from typing import Any, Dict, List
_SELECTOR_REGEX: re.Pattern = re.compile("[a-zA-Z0-9_-]+")
type SelectorContext = Dict[str, Dict[str, Any]]
"""
The context for a selector: a key value pair where values are arbitrary dicts.
"""
[docs]
class SelectorTarget(Enum):
"""
The target for a selector.
"""
TOKEN = 0
"""
The target is the token itself.
"""
CONTEXT = 1
"""
The target is the provided context.
"""
[docs]
@dataclass
class Selector:
"""
A selector that specifies a path within a JSON object to be matched.
"""
path: List[str]
target: SelectorTarget
[docs]
@staticmethod
def parse(selector: str) -> "Selector":
"""
Parse a selector from a string.
Arguments
---------
selector
The selector to be parsed.
Example
-------
.. code-block:: py3
from nuc.selector import Selector
selector = Selector.parse(".foo.bar")
"""
target = SelectorTarget.TOKEN
if selector.startswith("$"):
target = SelectorTarget.CONTEXT
selector = selector[1:]
if not selector.startswith("."):
raise MalformedSelectorException("selectors must start with '.' or '$.'")
selector = selector[1:]
if not selector:
match target:
case SelectorTarget.TOKEN:
return Selector([], target)
case SelectorTarget.CONTEXT:
raise MalformedSelectorException("context selector needs path")
labels = []
for label in selector.split("."):
if not label:
raise MalformedSelectorException("selector segment can't be empty")
if not _SELECTOR_REGEX.match(label):
raise MalformedSelectorException("invalid characters found in selector")
labels.append(label)
return Selector(labels, target)
[docs]
def apply(self, value: Dict[str, Any], context: SelectorContext) -> Any:
"""
Apply a selector on a value and return the matched value, if any.
Arguments
---------
value
The dict that this selector should be applied to.
"""
match self.target:
case SelectorTarget.TOKEN:
return self._apply_on_value(self.path, value)
case SelectorTarget.CONTEXT:
if not self.path:
return None
first = self.path[0]
context_value = context.get(first)
if context_value is None:
return None
return self._apply_on_value(self.path[1:], context_value)
@staticmethod
def _apply_on_value(path: List[str], value: Dict[str, Any]) -> Any:
output = value
for label in path:
output = output.get(label)
if not output:
return None
return output
[docs]
def __str__(self) -> str:
match self.target:
case SelectorTarget.TOKEN:
prefix = ""
case SelectorTarget.CONTEXT:
prefix = "$"
return f"{prefix}.{'.'.join(self.path)}"