예제 #1
0
 def _insert_scalar(
     self, insert_at: YAMLPath, lhs: Any, lhs_proc: Processor, rhs: Any
 ) -> bool:
     """Insert an RHS scalar into the LHS document."""
     merge_performed = False
     if isinstance(lhs, CommentedSeq):
         self.logger.debug(
             "Merger::_insert_scalar:  Merging a scalar into a list.")
         Nodes.append_list_element(lhs, rhs)
         merge_performed = True
     elif isinstance(lhs, CommentedSet):
         self.logger.debug(
             "Merger::_insert_scalar:  Merging a scalar into a set.")
         self._merge_sets(
             lhs, CommentedSet([rhs]), insert_at,
             NodeCoords(rhs, None, None))
         merge_performed = True
     elif isinstance(lhs, CommentedMap):
         ex_message = (
             "Impossible to add Scalar value, {}, to a Hash without"
             " a key.  Change the value to a 'key: value' pair, a"
             " '{{key: value}}' Hash, or change the merge target to"
             " an Array or other Scalar value."
             ).format(rhs)
         if len(str(rhs)) < 1 and not sys.stdin.isatty():
             ex_message += (
                 "  You may be seeing this because your workflow"
                 " inadvertently opened a STDIN handle to {}.  If"
                 " this may be the case, try adding --nostdin or -S"
                 " so as to block unintentional STDIN reading."
             ).format(basename(sys.argv[0]))
         raise MergeException(ex_message, insert_at)
     else:
         lhs_proc.set_value(insert_at, rhs)
         merge_performed = True
     return merge_performed
예제 #2
0
    def merge_with(self, rhs: Any) -> None:
        """
        Merge this document with another.

        Parameters:
        1. rhs (Any) The document to merge into this one.

        Returns:  N/A

        Raises:
        - `MergeException` when a clean merge is impossible.
        """
        # Do nothing when RHS is None (empty document)
        if rhs is None:
            return

        # Remove all comments (no sensible way to merge them)
        Parsers.delete_all_comments(rhs)

        # When LHS is None (empty document), just dump all of RHS into it,
        # honoring any --mergeat|-m location as best as possible.
        insert_at = self.config.get_insertion_point()
        if self.data is None:
            self.logger.debug("Replacing None data with:",
                              prefix="Merger::merge_with:  ",
                              data=rhs,
                              data_header="     *****")
            self.data = Nodes.build_next_node(insert_at, 0, rhs)
            self.logger.debug("Merged document is now:",
                              prefix="Merger::merge_with:  ",
                              data=self.data,
                              footer="     ***** ***** *****")
            if isinstance(rhs, (dict, list)):
                # Only Scalar values need further processing
                return

        # Resolve any anchor conflicts
        self._resolve_anchor_conflicts(rhs)

        # Prepare the merge rules
        self.config.prepare(rhs)

        # Identify a reasonable default should a DOM need to be built up to
        # receive the RHS data.
        default_val = rhs
        if isinstance(rhs, CommentedMap):
            default_val = {}
        elif isinstance(rhs, CommentedSeq):
            default_val = []

        # Loop through all insertion points and the elements in RHS
        merge_performed = False
        nodes: List[NodeCoords] = []
        lhs_proc = Processor(self.logger, self.data)
        for node_coord in lhs_proc.get_nodes(insert_at,
                                             default_value=default_val):
            nodes.append(node_coord)

        for node_coord in nodes:
            target_node = (node_coord.node if isinstance(
                node_coord.node,
                (CommentedMap, CommentedSeq)) else node_coord.parent)

            Parsers.set_flow_style(rhs,
                                   (target_node.fa.flow_style() if hasattr(
                                       target_node, "fa") else None))

            if isinstance(rhs, CommentedMap):
                # The RHS document root is a map
                if isinstance(target_node, CommentedSeq):
                    # But the destination is a list
                    self._merge_lists(target_node, CommentedSeq([rhs]),
                                      insert_at)
                else:
                    self._merge_dicts(target_node, rhs, insert_at)

                    # Synchronize YAML Tags
                    self.logger.debug(
                        "Merger::merge_with:  Setting LHS tag from {} to {}.".
                        format(target_node.tag.value, rhs.tag.value))
                    target_node.yaml_set_tag(rhs.tag.value)
                merge_performed = True
            elif isinstance(rhs, CommentedSeq):
                # The RHS document root is a list
                self._merge_lists(target_node, rhs, insert_at)
                merge_performed = True

                # Synchronize any YAML Tag
                self.logger.debug(
                    "Merger::merge_with:  Setting LHS tag from {} to {}.".
                    format(target_node.tag.value, rhs.tag.value))
                target_node.yaml_set_tag(rhs.tag.value)
            else:
                # The RHS document root is a Scalar value
                target_node = node_coord.node
                if isinstance(target_node, CommentedSeq):
                    Nodes.append_list_element(target_node, rhs)
                    merge_performed = True
                elif isinstance(target_node, CommentedMap):
                    raise MergeException(
                        "Impossible to add Scalar value, {}, to a Hash without"
                        " a key.  Change the value to a 'key: value' pair, a"
                        " '{{key: value}}' Hash, or change the merge target to"
                        " an Array or other Scalar value.".format(rhs),
                        insert_at)
                else:
                    lhs_proc.set_value(insert_at, rhs)
                    merge_performed = True

        self.logger.debug("Completed merge operation, resulting in document:",
                          prefix="Merger::merge_with:  ",
                          data=self.data)

        if not merge_performed:
            raise MergeException(
                "A merge was not performed.  Ensure your target path matches"
                " at least one node in the left document(s).", insert_at)
예제 #3
0
    def _merge_arrays_of_hashes(
        self, lhs: CommentedSeq, rhs: CommentedSeq, path: YAMLPath,
        node_coord: NodeCoords
    ) -> CommentedSeq:
        """
        Merge two Arrays-of-Hashes.

        This is a deep merge operation.  Each dict is treated as a record with
        an identity key.  RHS records are merged with LHS records for which the
        identity key matches.  As such, an identity key is required in both LHS
        and RHS records.  This key is configurable.  When there is no LHS match
        for an RHS key, the RHS record is appended to the LHS list.

        Parameters:
        1. lhs (CommentedSeq) The merge target.
        2. rhs (CommentedSeq) 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:  (CommentedSeq) The merged result.

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

        self.logger.debug(
            "Merging {} Hash(es) at {}.".format(len(rhs), path),
            prefix="Merger::_merge_arrays_of_hashes:  ", data=rhs)

        id_key: str = ""
        if len(rhs) > 0 and isinstance(rhs[0], CommentedMap):
            id_key = self.config.aoh_merge_key(
                NodeCoords(rhs[0], rhs, 0), rhs[0])
            self.logger.debug(
                "Merger::_merge_arrays_of_hashes:  RHS AoH yielded id_key:"
                "  {}.".format(id_key))

        merge_mode = self.config.aoh_merge_mode(node_coord)
        for idx, ele in enumerate(rhs):
            path_next = path + "[{}]".format(idx)
            self.logger.debug(
                "Processing element #{} at {}.".format(idx, path_next),
                prefix="Merger::_merge_arrays_of_hashes:  ", data=ele)

            if merge_mode is AoHMergeOpts.DEEP:
                if id_key in ele:
                    id_val = Nodes.tagless_value(ele[id_key])
                else:
                    raise MergeException(
                        "Mandatory identity key, {}, not present in Hash with"
                        " keys:  {}."
                        .format(id_key, ", ".join(ele.keys()))
                        , path_next
                    )

                merged_hash = False
                for lhs_hash in (
                    lhs_hash for lhs_hash in lhs
                    if isinstance(lhs_hash, CommentedMap)
                    and id_key in lhs_hash
                    and Nodes.tagless_value(lhs_hash[id_key]) == id_val
                ):
                    self._merge_dicts(lhs_hash, ele, path_next)
                    merged_hash = True

                    # Synchronize YAML Tags
                    lhs_hash.yaml_set_tag(ele.tag.value)
                    break
                if not merged_hash:
                    Nodes.append_list_element(lhs, ele,
                        ele.anchor.value if hasattr(ele, "anchor") else None)
            elif merge_mode is AoHMergeOpts.UNIQUE:
                if ele not in lhs:
                    Nodes.append_list_element(
                        lhs, ele,
                        ele.anchor.value if hasattr(ele, "anchor") else None)
            else:
                Nodes.append_list_element(lhs, ele,
                    ele.anchor.value if hasattr(ele, "anchor") else None)
        return lhs
예제 #4
0
파일: func.py 프로젝트: wwkimball/yamlpath
def append_list_element(*args):
    """Relay function call to static method."""
    return Nodes.append_list_element(*args)