Blob Blame History Raw
#
# Copyright (c) 2020 Red Hat, Inc.
#
# This file is part of nmstate
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2.1 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

from abc import ABCMeta
from abc import abstractmethod
from collections.abc import Sequence
from collections.abc import Mapping
from functools import total_ordering


@total_ordering
class StateEntry(metaclass=ABCMeta):
    @abstractmethod
    def _keys(self):
        """
        Return the tuple representing this entry, will be used for hashing or
        comparing.
        """
        pass

    def __hash__(self):
        return hash(self._keys())

    def __eq__(self, other):
        return self is other or self._keys() == other._keys()

    def __lt__(self, other):
        return self._keys() < other._keys()

    def __repr__(self):
        return str(self.to_dict())

    @property
    @abstractmethod
    def absent(self):
        pass

    def to_dict(self):
        return {
            key.replace("_", "-"): value
            for key, value in vars(self).items()
            if (not key.startswith("_")) and (value is not None)
        }

    def match(self, other):
        """
        Match self against other. Treat self None attributes as wildcards,
        matching against any value in others.
        Return True for a match, False otherwise.
        """
        for self_value, other_value in zip(self._keys(), other._keys()):
            if self_value is not None and self_value != other_value:
                return False
        return True


def state_match(desire, current):
    """
    Return True when all values defined in desire equal to value in current,
    else False:
        * For mapping(e.g. dict), desire could have less value than current.
        * For sequnce(e.g. list), desire should equal to current.
    """
    if isinstance(desire, Mapping):
        return isinstance(current, Mapping) and all(
            state_match(val, current.get(key)) for key, val in desire.items()
        )
    elif isinstance(desire, Sequence) and not isinstance(desire, str):
        return (
            isinstance(current, Sequence)
            and not isinstance(current, str)
            and len(current) == len(desire)
            and all(state_match(d, c) for d, c in zip(desire, current))
        )
    else:
        return desire == current


def merge_dict(dict_to, dict_from):
    """
    Data will copy from `dict_from` if undefined in `dict_to`.
    For list, the whole list is copied instead of merging.
    """
    for key, from_value in dict_from.items():
        if key not in dict_to:
            dict_to[key] = from_value
        elif isinstance(dict_to[key], Mapping):
            merge_dict(dict_to[key], from_value)