def test_yield_raw_children_direct(self, tmp_path_factory, quiet_logger):
        from yamlpath.enums import PathSeperators, PathSearchMethods
        from yamlpath.path import SearchTerms
        from yamlpath.func import get_yaml_data, get_yaml_editor
        from yamlpath.commands.yaml_paths import yield_children
        from itertools import zip_longest

        content = """some raw text value
        """
        processor = get_yaml_editor()
        yaml_file = create_temp_yaml_file(tmp_path_factory, content)
        yaml_data = get_yaml_data(processor, quiet_logger, yaml_file)
        seen_anchors = []
        assertions = ["/"]
        results = []
        for assertion, path in zip_longest(
                assertions,
                yield_children(quiet_logger,
                               yaml_data,
                               SearchTerms(False,
                                           PathSearchMethods.STARTS_WITH, "*",
                                           "some"),
                               PathSeperators.FSLASH,
                               "",
                               seen_anchors,
                               search_anchors=False,
                               include_key_aliases=False,
                               include_value_aliases=False)):
            assert assertion == str(path)
    def test_yield_seq_children_direct(self, tmp_path_factory, quiet_logger):
        from yamlpath.enums import PathSeperators, PathSearchMethods
        from yamlpath.path import SearchTerms
        from yamlpath.func import get_yaml_data, get_yaml_editor
        from yamlpath.commands.yaml_paths import yield_children
        from itertools import zip_longest

        content = """---
        - &value Test value
        - value
        - *value
        """
        processor = get_yaml_editor()
        yaml_file = create_temp_yaml_file(tmp_path_factory, content)
        (yaml_data, doc_loaded) = get_yaml_data(processor, quiet_logger,
                                                yaml_file)
        seen_anchors = []
        assertions = ["/&value", "/[1]"]
        results = []
        for assertion, path in zip_longest(
                assertions,
                yield_children(quiet_logger,
                               yaml_data,
                               SearchTerms(False, PathSearchMethods.EQUALS,
                                           "*", "value"),
                               PathSeperators.FSLASH,
                               "",
                               seen_anchors,
                               search_anchors=True,
                               include_aliases=False)):
            assert assertion == str(path)
    def test_yield_map_children_direct(self, tmp_path_factory, quiet_logger,
                                       include_aliases, assertions):
        from yamlpath.enums import PathSeperators, PathSearchMethods
        from yamlpath.path import SearchTerms
        from yamlpath.func import get_yaml_data, get_yaml_editor
        from yamlpath.commands.yaml_paths import yield_children
        from itertools import zip_longest

        content = """---
        aliases:
          - &aValue val2

        hash:
          key1: val1
          key2: *aValue
          key3: val3
        """
        processor = get_yaml_editor()
        yaml_file = create_temp_yaml_file(tmp_path_factory, content)
        (yaml_data, doc_loaded) = get_yaml_data(processor, quiet_logger,
                                                yaml_file)
        seen_anchors = []
        results = []
        for assertion, path in zip_longest(
                assertions,
                yield_children(quiet_logger,
                               yaml_data,
                               SearchTerms(False, PathSearchMethods.EQUALS,
                                           "*", "anchor"),
                               PathSeperators.FSLASH,
                               "",
                               seen_anchors,
                               search_anchors=True,
                               include_value_aliases=include_aliases)):
            assert assertion == str(path)
Example #4
0
 def create_searchterms_from_pathattributes(
         rhs: PathAttributes) -> SearchTerms:
     """Convert a PathAttributes instance to a SearchTerms instance."""
     if isinstance(rhs, SearchTerms):
         newinst: SearchTerms = SearchTerms(rhs.inverted, rhs.method,
                                            rhs.attribute, rhs.term)
         return newinst
     raise AttributeError
Example #5
0
def create_searchterms_from_pathattributes(rhs: PathAttributes) -> SearchTerms:
    """
    Generates a new SearchTerms instance by copying SearchTerms
    attributes from a YAML Path segment's attributes.
    """
    if isinstance(rhs, SearchTerms):
        newinst: SearchTerms = SearchTerms(rhs.inverted, rhs.method,
                                           rhs.attribute, rhs.term)
        return newinst
    raise AttributeError
Example #6
0
 def test_search_anchor(self):
     anchor_value = "anchor_name"
     node = PlainScalarString("anchored value", anchor=anchor_value)
     terms = SearchTerms(False, PathSearchMethods.CONTAINS, ".", "name")
     seen_anchors = []
     search_anchors = True
     include_aliases = True
     assert search_anchor(
         node,
         terms,
         seen_anchors,
         search_anchors=search_anchors,
         include_aliases=include_aliases) == AnchorMatches.MATCH
Example #7
0
    def create_searchterms_from_pathattributes(
            rhs: PathAttributes) -> SearchTerms:
        """
        Convert a PathAttributes instance to a SearchTerms instance.

        Parameters:
        1. rhs (PathAttributes) PathAttributes instance to convert

        Returns:  (SearchTerms) SearchTerms extracted from `rhs`
        """
        if isinstance(rhs, SearchTerms):
            newinst: SearchTerms = SearchTerms(rhs.inverted, rhs.method,
                                               rhs.attribute, rhs.term)
            return newinst
        raise AttributeError
Example #8
0
    def test_nonexistant_path_search_method_error(self, quiet_logger):
        from enum import Enum
        from yamlpath.enums import PathSearchMethods
        names = [m.name for m in PathSearchMethods] + ['DNF']
        PathSearchMethods = Enum('PathSearchMethods', names)

        yamldata = """---
        top_scalar: value
        """
        yaml = YAML()
        data = yaml.load(yamldata)
        processor = Processor(quiet_logger, data)

        with pytest.raises(NotImplementedError):
            nodes = list(processor._get_nodes_by_search(
                data,
                SearchTerms(True, PathSearchMethods.DNF, ".", "top_scalar")
            ))
Example #9
0
    def _expand_splats(
        yaml_path: str, segment_id: str,
        segment_type: Optional[PathSegmentTypes] = None
    ) -> tuple:
        """
        Replace segment IDs with search operators when * is present.

        Parameters:
        1. yaml_path (str) The full YAML Path being processed.
        2. segment_id (str) The segment identifier to parse.
        3. segment_type (Optional[PathSegmentTypes]) Pending predetermined type
           of the segment under evaluation.

        Returns:  (tuple) Coallesced YAML Path segment.
        """
        coal_type = segment_type
        coal_value: Union[str, SearchTerms, None] = segment_id

        if '*' in segment_id:
            splat_count = segment_id.count("*")
            splat_pos = segment_id.index("*")
            segment_len = len(segment_id)
            if splat_count == 1:
                if segment_len == 1:
                    # /*/ -> [.=~/.*/]
                    coal_type = PathSegmentTypes.SEARCH
                    coal_value = SearchTerms(
                        False, PathSearchMethods.REGEX, ".", ".*")
                elif splat_pos == 0:
                    # /*text/ -> [.$text]
                    coal_type = PathSegmentTypes.SEARCH
                    coal_value = SearchTerms(
                        False, PathSearchMethods.ENDS_WITH, ".",
                        segment_id[1:])
                elif splat_pos == segment_len - 1:
                    # /text*/ -> [.^text]
                    coal_type = PathSegmentTypes.SEARCH
                    coal_value = SearchTerms(
                        False, PathSearchMethods.STARTS_WITH, ".",
                        segment_id[0:splat_pos])
                else:
                    # /te*xt/ -> [.=~/^te.*xt$/]
                    coal_type = PathSegmentTypes.SEARCH
                    coal_value = SearchTerms(
                        False, PathSearchMethods.REGEX, ".",
                        "^{}.*{}$".format(
                            segment_id[0:splat_pos],
                            segment_id[splat_pos + 1:]))
            elif splat_count == 2 and segment_len == 2:
                # Traversal operator
                coal_type = PathSegmentTypes.TRAVERSE
                coal_value = None
            elif splat_count > 1:
                # Multi-wildcard search
                search_term = "^"
                was_splat = False
                for char in segment_id:
                    if char == "*":
                        if was_splat:
                            raise YAMLPathException(
                                "The ** traversal operator has no meaning when"
                                " combined with other characters", yaml_path,
                                segment_id)
                        was_splat = True
                        search_term += ".*"
                    else:
                        was_splat = False
                        search_term += char
                search_term += "$"

                coal_type = PathSegmentTypes.SEARCH
                coal_value = SearchTerms(
                    False, PathSearchMethods.REGEX, ".", search_term)

        return (coal_type, coal_value)
Example #10
0
    def _parse_path(self,
                    strip_escapes: bool = True
                   ) -> Deque[PathSegment]:
        r"""
        Parse the YAML Path into its component segments.

        Breaks apart a stringified YAML Path into component segments, each
        identified by its type.  See README.md for sample YAML Paths.

        Parameters:
        1. strip_escapes (bool) True = Remove leading \ symbols, leaving
           only the "escaped" symbol.  False = Leave all leading \ symbols
           intact.

        Returns:  (deque) an empty queue or a queue of tuples, each identifying
          (PathSegmentTypes, segment_attributes).

        Raises:
            - `YAMLPathException` when the YAML Path is invalid
        """
        yaml_path: str = self.original
        path_segments: deque = deque()
        segment_id: str = ""
        segment_type: Optional[PathSegmentTypes] = None
        demarc_stack: List[str] = []
        escape_next: bool = False
        search_inverted: bool = False
        search_method: Optional[PathSearchMethods] = None
        search_attr: str = ""
        seeking_regex_delim: bool = False
        capturing_regex: bool = False
        pathsep: str = str(self.seperator)
        collector_level: int = 0
        collector_operator: CollectorOperators = CollectorOperators.NONE
        seeking_collector_operator: bool = False

        # Empty paths yield empty queues
        if not yaml_path:
            return path_segments

        # Infer the first possible position for a top-level Anchor mark
        first_anchor_pos = 0
        if self.seperator is PathSeperators.FSLASH and len(yaml_path) > 1:
            first_anchor_pos = 1
        seeking_anchor_mark = yaml_path[first_anchor_pos] == "&"

        # Parse the YAML Path
        # pylint: disable=locally-disabled,too-many-nested-blocks
        for char in yaml_path:
            demarc_count = len(demarc_stack)

            if escape_next:
                # Pass-through; capture this escaped character
                escape_next = False

            elif capturing_regex:
                if char == demarc_stack[-1]:
                    # Stop the RegEx capture
                    capturing_regex = False
                    demarc_stack.pop()
                    continue

                # Pass-through; capture everything that isn't the present
                # RegEx delimiter.  This deliberately means users cannot
                # escape the RegEx delimiter itself should it occur within
                # the RegEx; thus, users must select a delimiter that won't
                # appear within the RegEx (which is exactly why the user
                # gets to choose the delimiter).
                # pylint: disable=unnecessary-pass
                pass  # pragma: no cover

            # The escape test MUST come AFTER the RegEx capture test so users
            # won't be forced into "The Backslash Plague".
            # (https://docs.python.org/3/howto/regex.html#the-backslash-plague)
            elif char == "\\":
                # Escape the next character
                escape_next = True
                if strip_escapes:
                    continue

            elif (
                    char == " "
                    and (demarc_count < 1
                         or demarc_stack[-1] not in ["'", '"'])
            ):
                # Ignore unescaped, non-demarcated whitespace
                continue

            elif seeking_regex_delim:
                # This first non-space symbol is now the RegEx delimiter
                seeking_regex_delim = False
                capturing_regex = True
                demarc_stack.append(char)
                demarc_count += 1
                continue

            elif seeking_anchor_mark and char == "&":
                # Found an expected (permissible) ANCHOR mark
                seeking_anchor_mark = False
                segment_type = PathSegmentTypes.ANCHOR
                continue

            elif seeking_collector_operator and char in ['+', '-']:
                seeking_collector_operator = False
                if char == '+':
                    collector_operator = CollectorOperators.ADDITION
                elif char == '-':
                    collector_operator = CollectorOperators.SUBTRACTION
                continue

            elif char in ['"', "'"]:
                # Found a string demarcation mark
                if demarc_count > 0:
                    # Already appending to an ongoing demarcated value
                    if char == demarc_stack[-1]:
                        # Close a matching pair
                        demarc_stack.pop()
                        demarc_count -= 1

                        # Record the element_id when all pairs have closed
                        # unless there is no element_id.
                        if demarc_count < 1:
                            if segment_id:
                                # Unless the element has already been
                                # identified as a special type, assume it is a
                                # KEY.
                                if segment_type is None:
                                    segment_type = PathSegmentTypes.KEY
                                path_segments.append(
                                    (segment_type, segment_id))

                            segment_id = ""
                            segment_type = None
                            continue
                    else:
                        # Embed a nested, demarcated component
                        demarc_stack.append(char)
                        demarc_count += 1
                else:
                    # Fresh demarcated value
                    demarc_stack.append(char)
                    demarc_count += 1
                    continue

            elif char == "(":
                seeking_collector_operator = False
                collector_level += 1
                demarc_stack.append(char)
                demarc_count += 1
                segment_type = PathSegmentTypes.COLLECTOR

                # Preserve nested collectors
                if collector_level == 1:
                    continue

            elif collector_level > 0:
                if (
                        demarc_count > 0
                        and char == ")"
                        and demarc_stack[-1] == "("
                ):
                    collector_level -= 1
                    demarc_count -= 1
                    demarc_stack.pop()

                    if collector_level < 1:
                        path_segments.append(
                            (segment_type,
                             CollectorTerms(segment_id, collector_operator)))
                        segment_id = ""
                        collector_operator = CollectorOperators.NONE
                        seeking_collector_operator = True
                        continue

            elif demarc_count == 0 and char == "[":
                # Array INDEX/SLICE or SEARCH
                if segment_id:
                    # Record its predecessor element; unless it has already
                    # been identified as a special type, assume it is a KEY.
                    if segment_type is None:
                        segment_type = PathSegmentTypes.KEY
                    path_segments.append(self._expand_splats(
                        yaml_path, segment_id, segment_type))
                    segment_id = ""

                demarc_stack.append(char)
                demarc_count += 1
                segment_type = PathSegmentTypes.INDEX
                seeking_anchor_mark = True
                search_inverted = False
                search_method = None
                search_attr = ""
                continue

            elif (
                    demarc_count > 0
                    and demarc_stack[-1] == "["
                    and char in ["=", "^", "$", "%", "!", ">", "<", "~"]
            ):
                # Hash attribute search
                # pylint: disable=no-else-continue
                if char == "!":
                    if search_inverted:
                        raise YAMLPathException(
                            "Double search inversion is meaningless at {}"
                            .format(char)
                            , yaml_path
                        )

                    # Invert the search
                    search_inverted = True
                    continue

                elif char == "=":
                    # Exact value match OR >=|<=
                    segment_type = PathSegmentTypes.SEARCH

                    if search_method is PathSearchMethods.LESS_THAN:
                        search_method = PathSearchMethods.LESS_THAN_OR_EQUAL
                    elif search_method is PathSearchMethods.GREATER_THAN:
                        search_method = PathSearchMethods.GREATER_THAN_OR_EQUAL
                    elif search_method is PathSearchMethods.EQUALS:
                        # Allow ==
                        continue
                    elif search_method is None:
                        search_method = PathSearchMethods.EQUALS

                        if segment_id:
                            search_attr = segment_id
                            segment_id = ""
                        else:
                            raise YAMLPathException(
                                "Missing search operand before operator, {}"
                                .format(char)
                                , yaml_path
                            )
                    else:
                        raise YAMLPathException(
                            "Unsupported search operator combination at {}"
                            .format(char)
                            , yaml_path
                        )

                    continue  # pragma: no cover

                elif char == "~":
                    if search_method == PathSearchMethods.EQUALS:
                        search_method = PathSearchMethods.REGEX
                        seeking_regex_delim = True
                    else:
                        raise YAMLPathException(
                            ("Unexpected use of {} operator.  Please try =~ if"
                             + " you mean to search with a Regular"
                             + " Expression."
                            ).format(char)
                            , yaml_path
                        )

                    continue  # pragma: no cover

                elif not segment_id:
                    # All tests beyond this point require an operand
                    raise YAMLPathException(
                        "Missing search operand before operator, {}"
                        .format(char)
                        , yaml_path
                    )

                elif char == "^":
                    # Value starts with
                    segment_type = PathSegmentTypes.SEARCH
                    search_method = PathSearchMethods.STARTS_WITH
                    if segment_id:
                        search_attr = segment_id
                        segment_id = ""
                    continue

                elif char == "$":
                    # Value ends with
                    segment_type = PathSegmentTypes.SEARCH
                    search_method = PathSearchMethods.ENDS_WITH
                    if segment_id:
                        search_attr = segment_id
                        segment_id = ""
                    continue

                elif char == "%":
                    # Value contains
                    segment_type = PathSegmentTypes.SEARCH
                    search_method = PathSearchMethods.CONTAINS
                    if segment_id:
                        search_attr = segment_id
                        segment_id = ""
                    continue

                elif char == ">":
                    # Value greater than
                    segment_type = PathSegmentTypes.SEARCH
                    search_method = PathSearchMethods.GREATER_THAN
                    if segment_id:
                        search_attr = segment_id
                        segment_id = ""
                    continue

                elif char == "<":
                    # Value less than
                    segment_type = PathSegmentTypes.SEARCH
                    search_method = PathSearchMethods.LESS_THAN
                    if segment_id:
                        search_attr = segment_id
                        segment_id = ""
                    continue

            elif (
                    demarc_count > 0
                    and char == "]"
                    and demarc_stack[-1] == "["
            ):
                # Store the INDEX, SLICE, or SEARCH parameters
                if (
                        segment_type is PathSegmentTypes.INDEX
                        and ':' not in segment_id
                ):
                    try:
                        idx = int(segment_id)
                    except ValueError as wrap_ex:
                        raise YAMLPathException(
                            "Not an integer index:  {}".format(segment_id)
                            , yaml_path
                            , segment_id
                        ) from wrap_ex
                    path_segments.append((segment_type, idx))
                elif (
                        segment_type is PathSegmentTypes.SEARCH
                        and search_method is not None
                ):
                    # Undemarcate the search term, if it is so
                    if segment_id and segment_id[0] in ["'", '"']:
                        leading_mark = segment_id[0]
                        if segment_id[-1] == leading_mark:
                            segment_id = segment_id[1:-1]

                    path_segments.append((
                        segment_type,
                        SearchTerms(search_inverted, search_method,
                                    search_attr, segment_id)
                    ))
                else:
                    path_segments.append((segment_type, segment_id))

                segment_id = ""
                segment_type = None
                demarc_stack.pop()
                demarc_count -= 1
                search_method = None
                continue

            elif demarc_count < 1 and char == pathsep:
                # Do not store empty elements
                if segment_id:
                    # Unless its type has already been identified as a special
                    # type, assume it is a KEY.
                    if segment_type is None:
                        segment_type = PathSegmentTypes.KEY
                    path_segments.append(self._expand_splats(
                        yaml_path, segment_id, segment_type))
                    segment_id = ""

                segment_type = None
                continue

            segment_id += char
            seeking_anchor_mark = False
            seeking_collector_operator = False

        # Check for unmatched subpath demarcations
        if collector_level > 0:
            raise YAMLPathException(
                "YAML Path contains an unmatched () collector pair",
                yaml_path
            )

        # Check for unterminated RegExes
        if capturing_regex:
            raise YAMLPathException(
                "YAML Path contains an unterminated Regular Expression",
                yaml_path
            )

        # Check for mismatched demarcations
        if demarc_count > 0:
            raise YAMLPathException(
                "YAML Path contains at least one unmatched demarcation mark",
                yaml_path
            )

        # Store the final element_id, which must have been a KEY
        if segment_id:
            # Unless its type has already been identified as a special
            # type, assume it is a KEY.
            if segment_type is None:
                segment_type = PathSegmentTypes.KEY
            path_segments.append(self._expand_splats(
                yaml_path, segment_id, segment_type))

        return path_segments
Example #11
0
    def test_create_searchterms_from_pathattributes(self):
        st = SearchTerms(False, PathSearchMethods.EQUALS, ".", "key")
        assert str(st) == str(create_searchterms_from_pathattributes(st))

        with pytest.raises(AttributeError):
            _ = create_searchterms_from_pathattributes("nothing-to-see-here")
Example #12
0
 def test_str(self, invert, method, attr, term, output):
     assert output == str(SearchTerms(invert, method, attr, term))