Example #1
0
    def __init__(
        self, logger: ConsolePrinter, lhs: Any, config: MergerConfig
    ) -> None:
        """
        Instantiate this class into an object.

        Parameters:
        1. logger (ConsolePrinter) Instance of ConsoleWriter or subclass
        2. lhs (Any) The prime left-hand-side parsed YAML data
        3. config (MergerConfig) User-defined document merging rules

        Returns:  N/A

        Raises:  N/A
        """
        self.logger: ConsolePrinter = logger
        self.config: MergerConfig = config
        self.data: Any = lhs

        # ryamel.yaml unfortunately tracks comments AFTER each YAML node.  As
        # such, it is impossible to copy comments from RHS to LHS in any
        # sensible way.  Trying leads to absurd merge results that are data-
        # accurate but comment-insane.  This ruamel.yaml design decision forces
        # me to simply delete all comments from all merge documents to produce
        # a sensible result.  That said, enable users to attempt to preserve
        # LHS comments.
        if not self.config.is_preserving_lhs_comments():
            Parsers.delete_all_comments(self.data)
Example #2
0
 def delete_all_comments(cls, *args):
     """Relay function call to static method."""
     if not cls.depwarn_printed:
         cls.depwarn_printed = True
         print(Merger.DEPRECATION_WARNING, file=sys.stderr)
     Parsers.delete_all_comments(*args)
Example #3
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, CommentedSet, set)):
                # Only Scalar values need further processing
                return

        # Resolve any anchor conflicts
        self._resolve_anchor_conflicts(rhs)

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

        # Merge into each insertion point
        merge_performed = False
        lhs_proc = Processor(self.logger, self.data)
        for node_coord in self._get_merge_target_nodes(
            insert_at, lhs_proc, rhs
        ):
            target_node = node_coord.node
            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 dict
                merge_performed = self._insert_dict(
                    insert_at, target_node, rhs)
            elif isinstance(rhs, CommentedSeq):
                # The RHS document root is a list
                merge_performed = self._insert_list(
                    insert_at, target_node, rhs)
            elif isinstance(rhs, CommentedSet):
                # The RHS document is a set
                merge_performed = self._insert_set(
                    insert_at, target_node, rhs)
            else:
                # The RHS document root is a Scalar value
                merge_performed = self._insert_scalar(
                    insert_at, target_node, lhs_proc, rhs)

        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)
Example #4
0
 def data(self, value: Any) -> None:
     """Document data being merged into (mutator)."""
     if not self.config.is_preserving_lhs_comments():
         Parsers.delete_all_comments(value)
     self._data = value
Example #5
0
 def data(self, value: Any) -> None:
     """Document data being merged into (mutator)."""
     Parsers.delete_all_comments(value)
     self._data = value
Example #6
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)