Esempio n. 1
0
    def _purge_document(self, path: YAMLPath, data: Any) -> None:
        """
        Record changes necessary to delete every node in the document.

        Parameters:
        1. path (YAMLPath) YAML Path to the document element under evaluation
        2. data (Any) The DOM element under evaluation

        Returns:  N/A
        """
        if isinstance(data, CommentedMap):
            lhs_iteration = -1
            for key, val in data.items():
                lhs_iteration += 1
                next_path = (path +
                             YAMLPath.escape_path_section(key, path.seperator))
                self._diffs.append(
                    DiffEntry(DiffActions.DELETE,
                              next_path,
                              val,
                              None,
                              lhs_parent=data,
                              lhs_iteration=lhs_iteration))
        elif isinstance(data, CommentedSeq):
            for idx, ele in enumerate(data):
                next_path = path + "[{}]".format(idx)
                self._diffs.append(
                    DiffEntry(DiffActions.DELETE,
                              next_path,
                              ele,
                              None,
                              lhs_parent=data,
                              lhs_iteration=idx))
        elif isinstance(data, CommentedSet):
            for idx, ele in enumerate(data):
                next_path = (path +
                             YAMLPath.escape_path_section(ele, path.seperator))
                self._diffs.append(
                    DiffEntry(DiffActions.DELETE,
                              next_path,
                              ele,
                              None,
                              lhs_parent=data,
                              lhs_iteration=idx))
        else:
            if data is not None:
                self._diffs.append(
                    DiffEntry(DiffActions.DELETE, path, data, None))
Esempio n. 2
0
    def _find_eyaml_paths(
            self, data: Any,
            build_path: YAMLPath) -> Generator[YAMLPath, None, None]:
        """
        Find every encrypted value and report each as a YAML Path.

        Recursively generates a set of stringified YAML Paths, each entry
        leading to an EYAML value within the evaluated YAML data.

        Parameters:
        1. data (Any) The parsed YAML data to process
        2. build_path (YAMLPath) A YAML Path under construction

        Returns:  (Generator[Path, None, None]) each YAML Path entry as they
            are discovered

        Raises:  N/A
        """
        if isinstance(data, CommentedSeq):
            for idx, ele in enumerate(data):
                node_anchor = Anchors.get_node_anchor(ele)
                if node_anchor is not None:
                    escaped_section = YAMLPath.escape_path_section(
                        node_anchor, PathSeperators.DOT)
                    tmp_path_segment = f"[&{escaped_section}]"
                else:
                    tmp_path_segment = f"[{idx}]"

                tmp_path = build_path + tmp_path_segment
                if self.is_eyaml_value(ele):
                    yield tmp_path
                else:
                    for subpath in self._find_eyaml_paths(ele, tmp_path):
                        yield subpath

        elif isinstance(data, CommentedMap):
            for key, val in data.non_merged_items():
                tmp_path = build_path + YAMLPath.escape_path_section(
                    key, PathSeperators.DOT)
                if self.is_eyaml_value(val):
                    yield tmp_path
                else:
                    for subpath in self._find_eyaml_paths(val, tmp_path):
                        yield subpath
Esempio n. 3
0
    def _merge_sets(
        self, lhs: CommentedSet, rhs: CommentedSet, path: YAMLPath,
        node_coord: NodeCoords
    ) -> CommentedSet:
        """
        Merge two YAML sets (CommentedSet-wrapped sets).

        Parameters:
        1. lhs (CommentedSet) The merge target.
        2. rhs (CommentedSet) The merge source.
        3. path (YAMLPath) Location within the DOM where this merge is taking
           place.
        4. node_coord (NodeCoords) The RHS root node, its parent, and reference
           within its parent; used for config lookups.

        Returns:  (CommentedSet) The merged result.

        Raises:
        - `MergeException` when a clean merge is impossible.
        """
        merge_mode = self.config.set_merge_mode(node_coord)
        if merge_mode is SetMergeOpts.LEFT:
            return lhs
        if merge_mode is SetMergeOpts.RIGHT:
            return rhs

        tagless_lhs = Nodes.tagless_elements(list(lhs))
        for ele in rhs:
            path_next = (path +
                YAMLPath.escape_path_section(ele, path.seperator))
            self.logger.debug(
                "Processing set element {} at {}.".format(ele, path_next),
                prefix="Merger::_merge_sets:  ", data=ele)

            cmp_val = ele
            if isinstance(ele, TaggedScalar):
                cmp_val = ele.value

            self.logger.debug(
                "Looking for comparison value, {}, in:".format(cmp_val),
                prefix="Merger::_merge_sets:  ", data=tagless_lhs)

            if cmp_val in tagless_lhs:
                continue
            lhs.add(ele)
        return lhs
Esempio n. 4
0
 def _add_everything(self, path: YAMLPath, data: Any) -> None:
     """Add every node in the document."""
     if isinstance(data, CommentedMap):
         rhs_iteration = -1
         for key, val in data.items():
             rhs_iteration += 1
             next_path = (path +
                 YAMLPath.escape_path_section(key, path.seperator))
             self._diffs.append(
                 DiffEntry(
                     DiffActions.ADD, next_path, None, val,
                     rhs_parent=data, rhs_iteration=rhs_iteration))
     elif isinstance(data, CommentedSeq):
         for idx, ele in enumerate(data):
             next_path = path + "[{}]".format(idx)
             self._diffs.append(
                 DiffEntry(
                     DiffActions.ADD, next_path, None, ele,
                     rhs_parent=data, rhs_iteration=idx))
     else:
         if data is not None:
             self._diffs.append(
                 DiffEntry(DiffActions.ADD, path, None, data)
             )
Esempio n. 5
0
 def _purge_document(self, path: YAMLPath, data: Any):
     """Delete every node in the document."""
     if isinstance(data, CommentedMap):
         lhs_iteration = -1
         for key, val in data.items():
             lhs_iteration += 1
             next_path = (path +
                 YAMLPath.escape_path_section(key, path.seperator))
             self._diffs.append(
                 DiffEntry(
                     DiffActions.DELETE, next_path, val, None,
                     lhs_parent=data, lhs_iteration=lhs_iteration))
     elif isinstance(data, CommentedSeq):
         for idx, ele in enumerate(data):
             next_path = path + "[{}]".format(idx)
             self._diffs.append(
                 DiffEntry(
                     DiffActions.DELETE, next_path, ele, None,
                     lhs_parent=data, lhs_iteration=idx))
     else:
         if data is not None:
             self._diffs.append(
                 DiffEntry(DiffActions.DELETE, path, data, None)
             )
Esempio n. 6
0
def search_for_paths(logger: ConsolePrinter, processor: EYAMLProcessor,
                     data: Any, terms: SearchTerms,
                     pathsep: PathSeperators = PathSeperators.DOT,
                     build_path: str = "",
                     seen_anchors: Optional[List[str]] = None,
                     **kwargs: bool) -> Generator[YAMLPath, None, None]:
    """
    Recursively search a data structure for nodes matching an expression.

    The nodes can be keys, values, and/or elements.  When dealing with anchors
    and their aliases, the caller indicates whether to include only the
    original anchor or the anchor and all of its (duplicate) aliases.
    """
    search_values: bool = kwargs.pop("search_values", True)
    search_keys: bool = kwargs.pop("search_keys", False)
    search_anchors: bool = kwargs.pop("search_anchors", False)
    include_key_aliases: bool = kwargs.pop("include_key_aliases", True)
    include_value_aliases: bool = kwargs.pop("include_value_aliases", False)
    decrypt_eyaml: bool = kwargs.pop("decrypt_eyaml", False)
    expand_children: bool = kwargs.pop("expand_children", False)
    strsep = str(pathsep)
    invert = terms.inverted
    method = terms.method
    term = terms.term

    if seen_anchors is None:
        seen_anchors = []

    if isinstance(data, CommentedSeq):
        # Build the path
        if not build_path and pathsep is PathSeperators.FSLASH:
            build_path = strsep
        build_path += "["

        for idx, ele in enumerate(data):
            # Any element may or may not have an Anchor/Alias
            anchor_matched = Searches.search_anchor(
                ele, terms, seen_anchors, search_anchors=search_anchors,
                include_aliases=include_value_aliases)
            logger.debug(
                ("yaml_paths::search_for_paths<list>:"
                 + "anchor search => {}.")
                .format(anchor_matched)
            )

            # Build the temporary YAML Path using either Anchor or Index
            if anchor_matched is AnchorMatches.NO_ANCHOR:
                # Not an anchor/alias, so ref this node by its index
                tmp_path = build_path + str(idx) + "]"
            else:
                tmp_path = "{}&{}]".format(
                    build_path,
                    YAMLPath.escape_path_section(ele.anchor.value, pathsep)
                )

            if anchor_matched is AnchorMatches.ALIAS_EXCLUDED:
                continue

            if anchor_matched in [AnchorMatches.MATCH,
                                  AnchorMatches.ALIAS_INCLUDED]:
                logger.debug(
                    ("yaml_paths::search_for_paths<list>:"
                     + "yielding an Anchor/Alias match, {}.")
                    .format(tmp_path)
                )
                if expand_children:
                    for path in yield_children(
                            logger, ele, terms, pathsep, tmp_path,
                            seen_anchors, search_anchors=search_anchors,
                            include_key_aliases=include_key_aliases,
                            include_value_aliases=include_value_aliases):
                        yield path
                else:
                    yield YAMLPath(tmp_path)
                continue

            if isinstance(ele, (CommentedSeq, CommentedMap)):
                logger.debug(
                    "Recursing into complex data:", data=ele,
                    prefix="yaml_paths::search_for_paths<list>:  ",
                    footer=">>>> >>>> >>>> >>>> >>>> >>>> >>>>")
                for subpath in search_for_paths(
                        logger, processor, ele, terms, pathsep, tmp_path,
                        seen_anchors, search_values=search_values,
                        search_keys=search_keys, search_anchors=search_anchors,
                        include_key_aliases=include_key_aliases,
                        include_value_aliases=include_value_aliases,
                        decrypt_eyaml=decrypt_eyaml,
                        expand_children=expand_children
                ):
                    logger.debug(
                        "Yielding RECURSED match, {}.".format(subpath),
                        prefix="yaml_paths::search_for_paths<list>:  ",
                        footer="<<<< <<<< <<<< <<<< <<<< <<<< <<<<"
                    )
                    yield subpath
            elif search_values:
                if (anchor_matched is AnchorMatches.UNSEARCHABLE_ALIAS
                        and not include_value_aliases):
                    continue

                check_value = ele
                if decrypt_eyaml and processor.is_eyaml_value(ele):
                    check_value = processor.decrypt_eyaml(ele)

                matches = Searches.search_matches(method, term, check_value)
                if (matches and not invert) or (invert and not matches):
                    logger.debug(
                        ("yaml_paths::search_for_paths<list>:"
                         + "yielding VALUE match, {}:  {}."
                        ).format(check_value, tmp_path)
                    )
                    yield YAMLPath(tmp_path)

    # pylint: disable=too-many-nested-blocks
    elif isinstance(data, CommentedMap):
        if build_path:
            build_path += strsep
        elif pathsep is PathSeperators.FSLASH:
            build_path = strsep

        pool = data.non_merged_items()
        if include_key_aliases or include_value_aliases:
            pool = data.items()

        for key, val in pool:
            tmp_path = build_path + YAMLPath.escape_path_section(key, pathsep)

            # Search the value anchor to have it on record, in case the key
            # anchor match would otherwise block the value anchor from
            # appearing in seen_anchors (which is important).
            val_anchor_matched = Searches.search_anchor(
                val, terms, seen_anchors, search_anchors=search_anchors,
                include_aliases=include_value_aliases)
            logger.debug(
                ("yaml_paths::search_for_paths<dict>:"
                 + "VALUE anchor search => {}.")
                .format(val_anchor_matched)
            )

            # Search the key when the caller wishes it.
            if search_keys:
                # The key itself may be an Anchor or Alias.  Search it when the
                # caller wishes.
                key_anchor_matched = Searches.search_anchor(
                    key, terms, seen_anchors, search_anchors=search_anchors,
                    include_aliases=include_key_aliases)
                logger.debug(
                    ("yaml_paths::search_for_paths<dict>:"
                     + "KEY anchor search, {}:  {}.")
                    .format(key, key_anchor_matched)
                )

                if key_anchor_matched in [AnchorMatches.MATCH,
                                          AnchorMatches.ALIAS_INCLUDED]:
                    logger.debug(
                        ("yaml_paths::search_for_paths<dict>:"
                         + "yielding a KEY-ANCHOR match, {}."
                        ).format(key, tmp_path)
                    )
                    if expand_children:
                        for path in yield_children(
                                logger, val, terms, pathsep, tmp_path,
                                seen_anchors, search_anchors=search_anchors,
                                include_key_aliases=include_key_aliases,
                                include_value_aliases=include_value_aliases):
                            yield path
                    else:
                        yield YAMLPath(tmp_path)
                    continue

                # Search the name of the key, itself
                matches = Searches.search_matches(method, term, key)
                if (matches and not invert) or (invert and not matches):
                    logger.debug(
                        ("yaml_paths::search_for_paths<dict>:"
                         + "yielding KEY name match, {}:  {}."
                        ).format(key, tmp_path)
                    )
                    if expand_children:
                        # Include every non-excluded child node under this
                        # matched parent node.
                        for path in yield_children(
                                logger, val, terms, pathsep, tmp_path,
                                seen_anchors, search_anchors=search_anchors,
                                include_key_aliases=include_key_aliases,
                                include_value_aliases=include_value_aliases):
                            yield path
                    else:
                        # No other matches within this node matter because they
                        # are already in the result.
                        yield YAMLPath(tmp_path)
                    continue

            # The value may itself be anchored; search it if requested
            if val_anchor_matched is AnchorMatches.ALIAS_EXCLUDED:
                continue

            if val_anchor_matched in [AnchorMatches.MATCH,
                                      AnchorMatches.ALIAS_INCLUDED]:
                logger.debug(
                    ("yaml_paths::search_for_paths<dict>:"
                     + "yielding a VALUE-ANCHOR match, {}.")
                    .format(tmp_path)
                )
                if expand_children:
                    for path in yield_children(
                            logger, val, terms, pathsep, tmp_path,
                            seen_anchors, search_anchors=search_anchors,
                            include_key_aliases=include_key_aliases,
                            include_value_aliases=include_value_aliases):
                        yield path
                else:
                    yield YAMLPath(tmp_path)
                continue

            if isinstance(val, (CommentedSeq, CommentedMap)):
                logger.debug(
                    "Recursing into complex data:", data=val,
                    prefix="yaml_paths::search_for_paths<dict>:  ",
                    footer=">>>> >>>> >>>> >>>> >>>> >>>> >>>>"
                )
                for subpath in search_for_paths(
                        logger, processor, val, terms, pathsep, tmp_path,
                        seen_anchors, search_values=search_values,
                        search_keys=search_keys, search_anchors=search_anchors,
                        include_key_aliases=include_key_aliases,
                        include_value_aliases=include_value_aliases,
                        decrypt_eyaml=decrypt_eyaml,
                        expand_children=expand_children
                ):
                    logger.debug(
                        "Yielding RECURSED match, {}.".format(subpath),
                        prefix="yaml_paths::search_for_paths<dict>:  ",
                        footer="<<<< <<<< <<<< <<<< <<<< <<<< <<<<"
                    )
                    yield subpath
            elif search_values:
                if (val_anchor_matched is AnchorMatches.UNSEARCHABLE_ALIAS
                        and not include_value_aliases):
                    continue

                check_value = val
                if decrypt_eyaml and processor.is_eyaml_value(val):
                    check_value = processor.decrypt_eyaml(val)

                matches = Searches.search_matches(method, term, check_value)
                if (matches and not invert) or (invert and not matches):
                    logger.debug(
                        ("yaml_paths::search_for_paths<dict>:"
                         + "yielding VALUE match, {}:  {}."
                        ).format(check_value, tmp_path)
                    )
                    yield YAMLPath(tmp_path)
Esempio n. 7
0
def yield_children(logger: ConsolePrinter, data: Any,
                   terms: SearchTerms, pathsep: PathSeperators,
                   build_path: str, seen_anchors: List[str],
                   **kwargs: bool) -> Generator[YAMLPath, None, None]:
    """
    Dump the YAML Path of every child node beneath a given parent.

    Except for unwanted aliases, the dump is unconditional.
    """
    include_key_aliases: bool = kwargs.pop("include_key_aliases", True)
    include_value_aliases: bool = kwargs.pop("include_value_aliases", False)
    search_anchors: bool = kwargs.pop("search_anchors", False)
    logger.debug(
        "Dumping all children in data of type, {}:"
        .format(type(data)), data=data,
        prefix="yaml_paths::yield_children:  ")

    exclude_alias_matchers = [AnchorMatches.UNSEARCHABLE_ALIAS,
                              AnchorMatches.ALIAS_EXCLUDED]

    if isinstance(data, CommentedSeq):
        if not build_path and pathsep is PathSeperators.FSLASH:
            build_path = str(pathsep)
        build_path += "["

        for idx, ele in enumerate(data):
            anchor_matched = Searches.search_anchor(
                ele, terms, seen_anchors, search_anchors=search_anchors,
                include_aliases=include_value_aliases)
            logger.debug(
                ("yaml_paths::yield_children<list>:  "
                 + "element[{}] has anchor search => {}.")
                .format(idx, anchor_matched))

            # Build the temporary YAML Path using either Anchor or Index
            if anchor_matched is AnchorMatches.NO_ANCHOR:
                # Not an anchor/alias, so ref this node by its index
                tmp_path = build_path + str(idx) + "]"
            else:
                tmp_path = "{}&{}]".format(
                    build_path,
                    YAMLPath.escape_path_section(ele.anchor.value, pathsep)
                )

            if (not include_value_aliases
                    and anchor_matched in exclude_alias_matchers):
                continue

            if isinstance(ele, (CommentedMap, CommentedSeq)):
                for path in yield_children(
                        logger, ele, terms, pathsep, tmp_path, seen_anchors,
                        search_anchors=search_anchors,
                        include_key_aliases=include_key_aliases,
                        include_value_aliases=include_value_aliases):
                    yield path
            else:
                yield YAMLPath(tmp_path)

    elif isinstance(data, CommentedMap):
        if build_path:
            build_path += str(pathsep)
        elif pathsep is PathSeperators.FSLASH:
            build_path = str(pathsep)

        pool = data.non_merged_items()
        if include_key_aliases or include_value_aliases:
            pool = data.items()

        for key, val in pool:
            tmp_path = build_path + YAMLPath.escape_path_section(key, pathsep)

            key_anchor_matched = Searches.search_anchor(
                key, terms, seen_anchors, search_anchors=search_anchors,
                include_aliases=include_key_aliases)
            val_anchor_matched = Searches.search_anchor(
                val, terms, seen_anchors, search_anchors=search_anchors,
                include_aliases=include_value_aliases)
            logger.debug(
                ("yaml_paths::yield_children<dict>:  "
                 + "key[{}]:value have value anchor search => {}:{}.")
                .format(key, key_anchor_matched, val_anchor_matched))

            if (
                    (not include_key_aliases
                     and key_anchor_matched in exclude_alias_matchers)
                    or (not include_value_aliases
                        and val_anchor_matched in exclude_alias_matchers)
            ):
                continue

            if isinstance(val, (CommentedSeq, CommentedMap)):
                for path in yield_children(
                        logger, val, terms, pathsep, tmp_path, seen_anchors,
                        search_anchors=search_anchors,
                        include_key_aliases=include_key_aliases,
                        include_value_aliases=include_value_aliases):
                    yield path
            else:
                yield YAMLPath(tmp_path)

    else:
        if not build_path and pathsep is PathSeperators.FSLASH:
            build_path = str(pathsep)
        yield YAMLPath(build_path)
Esempio n. 8
0
    def _merge_dicts(
        self, lhs: CommentedMap, rhs: CommentedMap, path: YAMLPath
    ) -> CommentedMap:
        """
        Merge two YAML maps (CommentedMap-wrapped dicts).

        Parameters:
        1. lhs (CommentedMap) The merge target.
        2. rhs (CommentedMap) The merge source.
        3. path (YAMLPath) Location within the DOM where this merge is taking
           place.

        Returns:  (CommentedMap) The merged result.

        Raises:
        - `MergeException` when a clean merge is impossible.
        """
        if not isinstance(lhs, CommentedMap):
            raise MergeException(
                "Impossible to add Hash data to non-Hash destination.", path)

        self.logger.debug(
            "Merging INTO dict with keys: {}:".format(", ".join([
                    str(k.value) if isinstance(k, TaggedScalar)
                    else str(k)
                    for k in lhs.keys()])),
            data=lhs, prefix="Merger::_merge_dicts:  ",
            header="--------------------")
        self.logger.debug(
            "Merging FROM dict with keys: {}:".format(", ".join([
                    str(k.value) if isinstance(k, TaggedScalar)
                    else str(k)
                    for k in rhs.keys()])),
            data=rhs, prefix="Merger::_merge_dicts:  ",
            footer="====================")

        # Delete all internal YAML merge reference keys lest any later
        # .insert() operation on LHS inexplicably convert them from reference
        # to concrete keys.  This seems like a bug in ruamel.yaml...
        self._delete_mergeref_keys(lhs)

        # Assume deep merge until a node's merge rule indicates otherwise
        buffer: List[Tuple[Any, Any]] = []
        buffer_pos = 0
        for key, val in rhs.non_merged_items():
            path_next = (path +
                YAMLPath.escape_path_section(key, path.seperator))
            if key in lhs:
                # Write the buffer if populated
                for b_key, b_val in buffer:
                    self.logger.debug(
                        "Merger::_merge_dicts:  Inserting key, {}, from"
                        " buffer to position, {}, at path, {}."
                        .format(b_key, buffer_pos, path_next),
                        header="INSERT " * 15)
                    self.logger.debug(
                        "Before INSERT, the LHS document was:",
                        data=lhs, prefix="Merger::_merge_dicts:  ")
                    self.logger.debug(
                        "... and before INSERT, the incoming value will be:",
                        data=b_val, prefix="Merger::_merge_dicts:  ")
                    lhs.insert(buffer_pos, b_key, b_val)
                    self.logger.debug(
                        "After INSERT, the LHS document became:",
                        data=lhs, prefix="Merger::_merge_dicts:  ")
                    buffer_pos += 1
                buffer = []

                # Short-circuit the deep merge if a different merge rule
                # applies to this node.
                node_coord = NodeCoords(val, rhs, key)
                merge_mode = (
                    self.config.hash_merge_mode(node_coord)
                    if isinstance(val, CommentedMap)
                    else self.config.set_merge_mode(node_coord)
                    if isinstance(val, CommentedSet)
                    else self.config.aoh_merge_mode(node_coord)
                )
                self.logger.debug("Merger::_merge_dicts:  Got merge mode, {}."
                                  .format(merge_mode))
                if merge_mode in (
                    HashMergeOpts.LEFT, AoHMergeOpts.LEFT, SetMergeOpts.LEFT
                ):
                    continue
                if merge_mode in (
                    HashMergeOpts.RIGHT, AoHMergeOpts.RIGHT, SetMergeOpts.RIGHT
                ):
                    self.logger.debug(
                        "Merger::_merge_dicts:  Overwriting key, {}, at path,"
                        " {}.".format(key, path_next),
                        header="OVERWRITE " * 15)
                    lhs[key] = val
                    continue

                if isinstance(val, CommentedMap):
                    lhs[key] = self._merge_dicts(lhs[key], val, path_next)

                    # Synchronize any YAML Tag
                    self.logger.debug(
                        "Merger::_merge_dicts:  Setting LHS tag from {} to {}."
                        .format(lhs[key].tag.value, val.tag.value))
                    lhs[key].yaml_set_tag(val.tag.value)

                    self.logger.debug(
                        "Document BEFORE calling combine_merge_anchors:",
                        data=lhs, prefix="Merger::_merge_dicts:  ",
                        header="+------------------+")
                    Anchors.combine_merge_anchors(lhs[key], val)
                    self.logger.debug(
                        "Document AFTER calling combine_merge_anchors:",
                        data=lhs, prefix="Merger::_merge_dicts:  ",
                        footer="+==================+")
                elif isinstance(val, CommentedSeq):
                    lhs[key] = self._merge_lists(
                        lhs[key], val, path_next, parent=rhs, parentref=key)

                    # Synchronize any YAML Tag
                    self.logger.debug(
                        "Merger::_merge_dicts:  Setting LHS tag from {} to {}."
                        .format(lhs[key].tag.value, val.tag.value))
                    lhs[key].yaml_set_tag(val.tag.value)
                elif isinstance(val, CommentedSet):
                    lhs[key] = self._merge_sets(
                        lhs[key], val, path_next, node_coord)

                    # Synchronize any YAML Tag
                    self.logger.debug(
                        "Merger::_merge_dicts:  Setting LHS tag from {} to {}."
                        .format(lhs[key].tag.value, val.tag.value))
                    lhs[key].yaml_set_tag(val.tag.value)
                else:
                    self.logger.debug(
                        "Merger::_merge_dicts:  Updating key, {}, at path,"
                        " {}.".format(key, path_next), header="UPDATE " * 15)
                    self.logger.debug(
                        "Before UPDATE, the LHS document was:",
                        data=lhs, prefix="Merger::_merge_dicts:  ")
                    self.logger.debug(
                        "... and before UPDATE, the incoming value will be:",
                        data=val, prefix="Merger::_merge_dicts:  ")
                    lhs[key] = val
                    self.logger.debug(
                        "After UPDATE, the LHS document became:",
                        data=lhs, prefix="Merger::_merge_dicts:  ")
            else:
                # LHS lacks the RHS key.  Buffer this key-value pair in order
                # to insert it ahead of whatever key(s) follow this one in RHS
                # to keep anchor definitions before their aliases.
                buffer.append((key, val))

            buffer_pos += 1

        # Write any remaining buffered content to the end of LHS
        for b_key, b_val in buffer:
            self.logger.debug(
                "Merger::_merge_dicts:  Appending key, {}, from buffer at"
                " path, {}.".format(b_key, path), header="APPEND " * 15)
            lhs[b_key] = b_val

        self.logger.debug(
            "Completed merge result for path, {}:".format(path),
            data=lhs, prefix="Merger::_merge_dicts:  ")

        return lhs
Esempio n. 9
0
    def min(data: Any, invert: bool, parameters: List[str],
            yaml_path: YAMLPath,
            **kwargs: Any) -> Generator[NodeCoords, None, None]:
        """
        Find whichever nodes/elements have a minimum value.

        Parameters:
        1. data (Any) The data to evaluate
        2. invert (bool) Invert the evaluation
        3. parameters (List[str]) Parsed parameters
        4. yaml_path (YAMLPath) YAML Path begetting this operation

        Keyword Arguments:
        * parent (ruamel.yaml node) The parent node from which this query
          originates
        * parentref (Any) The Index or Key of data within parent
        * relay_segment (PathSegment) YAML Path segment presently under
          evaluation
        * translated_path (YAMLPath) YAML Path indicating precisely which node
          is being evaluated
        * ancestry (List[AncestryEntry]) Stack of ancestors preceding the
          present node under evaluation

        Returns:  (Generator[NodeCoords, None, None]) each result as it is
            generated
        """
        parent: Any = kwargs.pop("parent", None)
        parentref: Any = kwargs.pop("parentref", None)
        translated_path: YAMLPath = kwargs.pop("translated_path", YAMLPath(""))
        ancestry: List[AncestryEntry] = kwargs.pop("ancestry", [])
        relay_segment: PathSegment = kwargs.pop("relay_segment", None)

        # There may be 0 or 1 parameters
        param_count = len(parameters)
        if param_count > 1:
            raise YAMLPathException(
                ("Invalid parameter count to {}([NAME]); up to {} permitted, "
                 " got {} in YAML Path").format(PathSearchKeywords.MIN, 1,
                                                param_count), str(yaml_path))

        scan_node = parameters[0] if param_count > 0 else None
        match_value: Any = None
        match_nodes: List[NodeCoords] = []
        discard_nodes: List[NodeCoords] = []
        unwrapped_data: Any = NodeCoords.unwrap_node_coords(data)
        if Nodes.node_is_aoh(unwrapped_data, accept_nulls=True):
            # A named child node is mandatory
            if scan_node is None:
                raise YAMLPathException((
                    "The {}([NAME]) Search Keyword requires a key name to scan"
                    " when evaluating an Array-of-Hashes in YAML Path").format(
                        PathSearchKeywords.MIN), str(yaml_path))

            for idx, wrapped_ele in enumerate(data):
                ele = NodeCoords.unwrap_node_coords(wrapped_ele)
                next_path = translated_path + "[{}]".format(idx)
                next_ancestry = ancestry + [(data, idx)]
                if ele is not None and scan_node in ele:
                    eval_val = ele[scan_node]
                    if (match_value is None or Searches.search_matches(
                            PathSearchMethods.LESS_THAN, match_value,
                            eval_val)):
                        match_value = eval_val
                        discard_nodes.extend(match_nodes)
                        match_nodes = [
                            NodeCoords(ele, data, idx, next_path,
                                       next_ancestry, relay_segment)
                        ]
                        continue

                    if (match_value is None or Searches.search_matches(
                            PathSearchMethods.EQUALS, match_value, eval_val)):
                        match_nodes.append(
                            NodeCoords(ele, data, idx, next_path,
                                       next_ancestry, relay_segment))
                        continue

                discard_nodes.append(
                    NodeCoords(ele, data, idx, next_path, next_ancestry,
                               relay_segment))

        elif isinstance(data, dict):
            # A named child node is mandatory
            if scan_node is None:
                raise YAMLPathException((
                    "The {}([NAME]) Search Keyword requires a key name to scan"
                    " when comparing Hash/map/dict children in YAML Path"
                ).format(PathSearchKeywords.MIN), str(yaml_path))

            for key, val in data.items():
                next_ancestry = ancestry + [(data, key)]
                next_path = (translated_path + YAMLPath.escape_path_section(
                    key, translated_path.seperator))
                if isinstance(val, dict):
                    if val is not None and scan_node in val:
                        eval_val = val[scan_node]
                        if (match_value is None or Searches.search_matches(
                                PathSearchMethods.LESS_THAN, match_value,
                                eval_val)):
                            match_value = eval_val
                            discard_nodes.extend(match_nodes)
                            match_nodes = [
                                NodeCoords(val, data, key, next_path,
                                           next_ancestry, relay_segment)
                            ]
                            continue

                        if (match_value is None or Searches.search_matches(
                                PathSearchMethods.EQUALS, match_value,
                                eval_val)):
                            match_nodes.append(
                                NodeCoords(val, data, key, next_path,
                                           next_ancestry, relay_segment))
                            continue

                elif scan_node in data:
                    # The user probably meant to operate against the parent
                    raise YAMLPathException(
                        ("The {}([NAME]) Search Keyword operates against"
                         " collections of data which share a common attribute"
                         " yet there is only a single node to consider.  Did"
                         " you mean to evaluate the parent of the selected"
                         " node?  Please review your YAML Path").format(
                             PathSearchKeywords.MIN), str(yaml_path))

                discard_nodes.append(
                    NodeCoords(val, data, key, next_path, next_ancestry,
                               relay_segment))

        elif isinstance(data, list):
            # A named child node is useless
            if scan_node is not None:
                raise YAMLPathException(
                    ("The {}([NAME]) Search Keyword cannot utilize a key name"
                     " when comparing Array/sequence/list elements to one"
                     " another in YAML Path").format(PathSearchKeywords.MIN),
                    str(yaml_path))

            for idx, ele in enumerate(data):
                next_path = translated_path + "[{}]".format(idx)
                next_ancestry = ancestry + [(data, idx)]
                if (ele is not None
                        and (match_value is None or Searches.search_matches(
                            PathSearchMethods.LESS_THAN, match_value, ele))):
                    match_value = ele
                    discard_nodes.extend(match_nodes)
                    match_nodes = [
                        NodeCoords(ele, data, idx, next_path, next_ancestry,
                                   relay_segment)
                    ]
                    continue

                if (ele is not None and Searches.search_matches(
                        PathSearchMethods.EQUALS, match_value, ele)):
                    match_nodes.append(
                        NodeCoords(ele, data, idx, next_path, next_ancestry,
                                   relay_segment))
                    continue

                discard_nodes.append(
                    NodeCoords(ele, data, idx, next_path, next_ancestry,
                               relay_segment))

        else:
            # Non-complex data is always its own maximum and does not invert
            match_value = data
            match_nodes = [
                NodeCoords(data, parent, parentref, translated_path, ancestry,
                           relay_segment)
            ]

        yield_nodes = discard_nodes if invert else match_nodes
        for node_coord in yield_nodes:
            yield node_coord
Esempio n. 10
0
    def _diff_sets(self, path: YAMLPath, lhs: CommentedSet,
                   rhs: CommentedSet) -> None:
        """
        Diff two sets.

        Parameters:
        1. path (YAMLPath) YAML Path to the document element under evaluation
        2. lhs (Any) The left-hand-side (original) document
        3. rhs (Any) The right-hand-side (altered) document

        Keyword Arguments:
        * rhs_parent (Any) Parent data node of rhs
        * parentref (Any) Reference indicating rhs within rhs_parent

        Returns:  N/A
        """
        self.logger.debug("Comparing LHS:",
                          prefix="Differ::_diff_sets:  ",
                          data=lhs)
        self.logger.debug("Against RHS:",
                          prefix="Differ::_diff_sets:  ",
                          data=rhs)

        # Sets must allways have a tag of !!set lest PyYAML/ruamel.yaml fail to
        # load them as set data.  Keep this code commented until a case is
        # found which proves otherwise.
        # # Check first for a difference in YAML Tag
        # lhs_tag = lhs.tag.value if hasattr(lhs, "tag") else None
        # rhs_tag = rhs.tag.value if hasattr(rhs, "tag") else None
        # if lhs_tag != rhs_tag:
        #     self.logger.debug(
        #         "Sets have different YAML Tags; {} != {}:".format(
        #             lhs_tag, rhs_tag),
        #         prefix="Differ::_diff_sets:  ")
        #     self._diffs.append(
        #         DiffEntry(
        #             DiffActions.DELETE, path, lhs, None, key_tag=lhs_tag))
        #     self._diffs.append(
        #         DiffEntry(
        #             DiffActions.ADD, path, None, rhs, key_tag=rhs_tag))
        #     return

        lhs_keys = set(lhs)
        rhs_keys = set(rhs)
        lhs_key_indicies = Differ._get_key_indicies(lhs)
        rhs_key_indicies = Differ._get_key_indicies(rhs)

        self.logger.debug("Got LHS key indicies:",
                          prefix="Differ::_diff_sets:  ",
                          data=lhs_key_indicies)
        self.logger.debug("Got RHS key indicies:",
                          prefix="Differ::_diff_sets:  ",
                          data=rhs_key_indicies)

        # Look for changes
        for key in [(key) for key in rhs if key in lhs and key in rhs]:
            next_path = (path +
                         YAMLPath.escape_path_section(key, path.seperator))
            lhs_ele = None
            rhs_ele = None
            for ele in lhs:
                if ele == key:
                    lhs_ele = ele
                    break
            for ele in rhs:
                if ele == key:
                    rhs_ele = ele
                    break
            self._diff_between(next_path,
                               lhs_ele,
                               rhs_ele,
                               lhs_parent=lhs,
                               lhs_iteration=lhs_key_indicies[key],
                               rhs_parent=rhs,
                               rhs_iteration=rhs_key_indicies[key],
                               parentref=key)

        # Look for deleted keys
        for key in lhs_keys - rhs_keys:
            next_path = (path +
                         YAMLPath.escape_path_section(key, path.seperator))
            self._diffs.append(
                DiffEntry(
                    DiffActions.DELETE,
                    next_path,
                    key,
                    None,
                    lhs_parent=lhs,
                    lhs_iteration=lhs_key_indicies[key],
                    rhs_parent=rhs,
                    key_tag=key.tag.value if hasattr(key, "tag") else None))

        # Look for new keys
        for key in rhs_keys - lhs_keys:
            next_path = (path +
                         YAMLPath.escape_path_section(key, path.seperator))
            self._diffs.append(
                DiffEntry(
                    DiffActions.ADD,
                    next_path,
                    None,
                    key,
                    lhs_parent=lhs,
                    rhs_parent=rhs,
                    rhs_iteration=rhs_key_indicies[key],
                    key_tag=key.tag.value if hasattr(key, "tag") else None))
Esempio n. 11
0
    def _diff_dicts(self, path: YAMLPath, lhs: CommentedMap,
                    rhs: CommentedMap) -> None:
        """
        Diff two dicts.

        Parameters:
        1. path (YAMLPath) YAML Path to the document element under evaluation
        2. lhs (Any) The left-hand-side (original) document
        3. rhs (Any) The right-hand-side (altered) document
        """
        self.logger.debug("Comparing LHS:",
                          prefix="Differ::_diff_dicts:  ",
                          data=lhs)
        self.logger.debug("Against RHS:",
                          prefix="Differ::_diff_dicts:  ",
                          data=rhs)

        # Check first for a difference in YAML Tag
        lhs_tag = lhs.tag.value if hasattr(lhs, "tag") else None
        rhs_tag = rhs.tag.value if hasattr(rhs, "tag") else None
        if lhs_tag != rhs_tag:
            self.logger.debug(
                "Dictionaries have different YAML Tags; {} != {}:".format(
                    lhs_tag, rhs_tag),
                prefix="Differ::_diff_dicts:  ")
            self._diffs.append(
                DiffEntry(DiffActions.DELETE, path, lhs, None,
                          key_tag=lhs_tag))
            self._diffs.append(
                DiffEntry(DiffActions.ADD, path, None, rhs, key_tag=rhs_tag))
            return

        lhs_keys = set(lhs)
        rhs_keys = set(rhs)
        lhs_key_indicies = Differ._get_key_indicies(lhs)
        rhs_key_indicies = Differ._get_key_indicies(rhs)

        self.logger.debug("Got LHS key indicies:",
                          prefix="Differ::_diff_dicts:  ",
                          data=lhs_key_indicies)
        self.logger.debug("Got RHS key indicies:",
                          prefix="Differ::_diff_dicts:  ",
                          data=rhs_key_indicies)

        # Look for changes
        for key, val in [(key, val) for key, val in rhs.items()
                         if key in lhs and key in rhs]:
            next_path = (path +
                         YAMLPath.escape_path_section(key, path.seperator))
            self._diff_between(next_path,
                               lhs[key],
                               val,
                               lhs_parent=lhs,
                               lhs_iteration=lhs_key_indicies[key],
                               rhs_parent=rhs,
                               rhs_iteration=rhs_key_indicies[key],
                               parentref=key)

        # Look for deleted keys
        for key in lhs_keys - rhs_keys:
            next_path = (path +
                         YAMLPath.escape_path_section(key, path.seperator))
            self._diffs.append(
                DiffEntry(
                    DiffActions.DELETE,
                    next_path,
                    lhs[key],
                    None,
                    lhs_parent=lhs,
                    lhs_iteration=lhs_key_indicies[key],
                    rhs_parent=rhs,
                    key_tag=key.tag.value if hasattr(key, "tag") else None))

        # Look for new keys
        for key in rhs_keys - lhs_keys:
            next_path = (path +
                         YAMLPath.escape_path_section(key, path.seperator))
            self._diffs.append(
                DiffEntry(
                    DiffActions.ADD,
                    next_path,
                    None,
                    rhs[key],
                    lhs_parent=lhs,
                    rhs_parent=rhs,
                    rhs_iteration=rhs_key_indicies[key],
                    key_tag=key.tag.value if hasattr(key, "tag") else None))
Esempio n. 12
0
def escape_path_section(*args):
    """Relay function call to static method."""
    return YAMLPath.escape_path_section(*args)