# Source code for fontTools.ufoLib.validators

    """Various low level data validators."""

    import calendar
    from io import open
    import fs.base
    import fs.osfs

    from collections.abc import Mapping
    from fontTools.ufoLib.utils import numberTypes

    # -------
    # Generic
    # -------

    def isDictEnough(value):
        """
        Some objects will likely come in that aren't
        dicts but are dict-ish enough.
        """
        if isinstance(value, Mapping):
            return True
        for attr in ("keys", "values", "items"):
            if not hasattr(value, attr):
                return False
        return True

    def genericTypeValidator(value, typ):
        """
        Generic. (Added at version 2.)
        """
        return isinstance(value, typ)

    def genericIntListValidator(values, validValues):
        """
        Generic. (Added at version 2.)
        """
        if not isinstance(values, (list, tuple)):
            return False
        valuesSet = set(values)
        validValuesSet = set(validValues)
        if valuesSet - validValuesSet:
            return False
        for value in values:
            if not isinstance(value, int):
                return False
        return True

    def genericNonNegativeIntValidator(value):
        """
        Generic. (Added at version 3.)
        """
        if not isinstance(value, int):
            return False
        if value < 0:
            return False
        return True

    def genericNonNegativeNumberValidator(value):
        """
        Generic. (Added at version 3.)
        """
        if not isinstance(value, numberTypes):
            return False
        if value < 0:
            return False
        return True

    def genericDictValidator(value, prototype):
        """
        Generic. (Added at version 3.)
        """
        # not a dict
        if not isinstance(value, Mapping):
            return False
        # missing required keys
        for key, (typ, required) in prototype.items():
            if not required:
                continue
            if key not in value:
                return False
        # unknown keys
        for key in value.keys():
            if key not in prototype:
                return False
        # incorrect types
        for key, v in value.items():
            prototypeType, required = prototype[key]
            if v is None and not required:
                continue
            if not isinstance(v, prototypeType):
                return False
        return True

    # --------------
    # fontinfo.plist
    # --------------

    # Data Validators

    def fontInfoStyleMapStyleNameValidator(value):
        """
        Version 2+.
        """
        options = ["regular", "italic", "bold", "bold italic"]
        return value in options

    def fontInfoOpenTypeGaspRangeRecordsValidator(value):
        """
        Version 3+.
        """
        if not isinstance(value, list):
            return False
        if len(value) == 0:
            return True
        validBehaviors = [0, 1, 2, 3]
        dictPrototype = dict(rangeMaxPPEM=(int, True), rangeGaspBehavior=(list, True))
        ppemOrder = []
        for rangeRecord in value:
            if not genericDictValidator(rangeRecord, dictPrototype):
                return False
            ppem = rangeRecord["rangeMaxPPEM"]
            behavior = rangeRecord["rangeGaspBehavior"]
            ppemValidity = genericNonNegativeIntValidator(ppem)
            if not ppemValidity:
                return False
            behaviorValidity = genericIntListValidator(behavior, validBehaviors)
            if not behaviorValidity:
                return False
            ppemOrder.append(ppem)
        if ppemOrder != sorted(ppemOrder):
            return False
        return True

    def fontInfoOpenTypeHeadCreatedValidator(value):
        """
        Version 2+.
        """
        # format: 0000/00/00 00:00:00
        if not isinstance(value, str):
            return False
        # basic formatting
        if not len(value) == 19:
            return False
        if value.count(" ") != 1:
            return False
        date, time = value.split(" ")
        if date.count("/") != 2:
            return False
        if time.count(":") != 2:
            return False
        # date
        year, month, day = date.split("/")
        if len(year) != 4:
            return False
        if len(month) != 2:
            return False
        if len(day) != 2:
            return False
        try:
            year = int(year)
            month = int(month)
            day = int(day)
        except ValueError:
            return False
        if month < 1 or month > 12:
            return False
        monthMaxDay = calendar.monthrange(year, month)[1]
        if day < 1 or day > monthMaxDay:
            return False
        # time
        hour, minute, second = time.split(":")
        if len(hour) != 2:
            return False
        if len(minute) != 2:
            return False
        if len(second) != 2:
            return False
        try:
            hour = int(hour)
            minute = int(minute)
            second = int(second)
        except ValueError:
            return False
        if hour < 0 or hour > 23:
            return False
        if minute < 0 or minute > 59:
            return False
        if second < 0 or second > 59:
            return False
        # fallback
        return True

    def fontInfoOpenTypeNameRecordsValidator(value):
        """
        Version 3+.
        """
        if not isinstance(value, list):
            return False
        dictPrototype = dict(
            nameID=(int, True),
            platformID=(int, True),
            encodingID=(int, True),
            languageID=(int, True),
            string=(str, True),
        )
        for nameRecord in value:
            if not genericDictValidator(nameRecord, dictPrototype):
                return False
        return True

    def fontInfoOpenTypeOS2WeightClassValidator(value):
        """
        Version 2+.
        """
        if not isinstance(value, int):
            return False
        if value < 0:
            return False
        return True

    def fontInfoOpenTypeOS2WidthClassValidator(value):
        """
        Version 2+.
        """
        if not isinstance(value, int):
            return False
        if value < 1:
            return False
        if value > 9:
            return False
        return True

    def fontInfoVersion2OpenTypeOS2PanoseValidator(values):
        """
        Version 2.
        """
        if not isinstance(values, (list, tuple)):
            return False
        if len(values) != 10:
            return False
        for value in values:
            if not isinstance(value, int):
                return False
        # XXX further validation?
        return True

    def fontInfoVersion3OpenTypeOS2PanoseValidator(values):
        """
        Version 3+.
        """
        if not isinstance(values, (list, tuple)):
            return False
        if len(values) != 10:
            return False
        for value in values:
            if not isinstance(value, int):
                return False
            if value < 0:
                return False
        # XXX further validation?
        return True

    def fontInfoOpenTypeOS2FamilyClassValidator(values):
        """
        Version 2+.
        """
        if not isinstance(values, (list, tuple)):
            return False
        if len(values) != 2:
            return False
        for value in values:
            if not isinstance(value, int):
                return False
        classID, subclassID = values
        if classID < 0 or classID > 14:
            return False
        if subclassID < 0 or subclassID > 15:
            return False
        return True

    def fontInfoPostscriptBluesValidator(values):
        """
        Version 2+.
        """
        if not isinstance(values, (list, tuple)):
            return False
        if len(values) > 14:
            return False
        if len(values) % 2:
            return False
        for value in values:
            if not isinstance(value, numberTypes):
                return False
        return True

    def fontInfoPostscriptOtherBluesValidator(values):
        """
        Version 2+.
        """
        if not isinstance(values, (list, tuple)):
            return False
        if len(values) > 10:
            return False
        if len(values) % 2:
            return False
        for value in values:
            if not isinstance(value, numberTypes):
                return False
        return True

    def fontInfoPostscriptStemsValidator(values):
        """
        Version 2+.
        """
        if not isinstance(values, (list, tuple)):
            return False
        if len(values) > 12:
            return False
        for value in values:
            if not isinstance(value, numberTypes):
                return False
        return True

    def fontInfoPostscriptWindowsCharacterSetValidator(value):
        """
        Version 2+.
        """
        validValues = list(range(1, 21))
        if value not in validValues:
            return False
        return True

    def fontInfoWOFFMetadataUniqueIDValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(id=(str, True))
        if not genericDictValidator(value, dictPrototype):
            return False
        return True

    def fontInfoWOFFMetadataVendorValidator(value):
        """
        Version 3+.
        """
        dictPrototype = {
            "name": (str, True),
            "url": (str, False),
            "dir": (str, False),
            "class": (str, False),
        }
        if not genericDictValidator(value, dictPrototype):
            return False
        if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
            return False
        return True

    def fontInfoWOFFMetadataCreditsValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(credits=(list, True))
        if not genericDictValidator(value, dictPrototype):
            return False
        if not len(value["credits"]):
            return False
        dictPrototype = {
            "name": (str, True),
            "url": (str, False),
            "role": (str, False),
            "dir": (str, False),
            "class": (str, False),
        }
        for credit in value["credits"]:
            if not genericDictValidator(credit, dictPrototype):
                return False
            if "dir" in credit and credit.get("dir") not in ("ltr", "rtl"):
                return False
        return True

    def fontInfoWOFFMetadataDescriptionValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(url=(str, False), text=(list, True))
        if not genericDictValidator(value, dictPrototype):
            return False
        for text in value["text"]:
            if not fontInfoWOFFMetadataTextValue(text):
                return False
        return True

    def fontInfoWOFFMetadataLicenseValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(url=(str, False), text=(list, False), id=(str, False))
        if not genericDictValidator(value, dictPrototype):
            return False
        if "text" in value:
            for text in value["text"]:
                if not fontInfoWOFFMetadataTextValue(text):
                    return False
        return True

    def fontInfoWOFFMetadataTrademarkValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(text=(list, True))
        if not genericDictValidator(value, dictPrototype):
            return False
        for text in value["text"]:
            if not fontInfoWOFFMetadataTextValue(text):
                return False
        return True

    def fontInfoWOFFMetadataCopyrightValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(text=(list, True))
        if not genericDictValidator(value, dictPrototype):
            return False
        for text in value["text"]:
            if not fontInfoWOFFMetadataTextValue(text):
                return False
        return True

    def fontInfoWOFFMetadataLicenseeValidator(value):
        """
        Version 3+.
        """
        dictPrototype = {"name": (str, True), "dir": (str, False), "class": (str, False)}
        if not genericDictValidator(value, dictPrototype):
            return False
        if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
            return False
        return True

    def fontInfoWOFFMetadataTextValue(value):
        """
        Version 3+.
        """
        dictPrototype = {
            "text": (str, True),
            "language": (str, False),
            "dir": (str, False),
            "class": (str, False),
        }
        if not genericDictValidator(value, dictPrototype):
            return False
        if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
            return False
        return True

    def fontInfoWOFFMetadataExtensionsValidator(value):
        """
        Version 3+.
        """
        if not isinstance(value, list):
            return False
        if not value:
            return False
        for extension in value:
            if not fontInfoWOFFMetadataExtensionValidator(extension):
                return False
        return True

    def fontInfoWOFFMetadataExtensionValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(names=(list, False), items=(list, True), id=(str, False))
        if not genericDictValidator(value, dictPrototype):
            return False
        if "names" in value:
            for name in value["names"]:
                if not fontInfoWOFFMetadataExtensionNameValidator(name):
                    return False
        for item in value["items"]:
            if not fontInfoWOFFMetadataExtensionItemValidator(item):
                return False
        return True

    def fontInfoWOFFMetadataExtensionItemValidator(value):
        """
        Version 3+.
        """
        dictPrototype = dict(id=(str, False), names=(list, True), values=(list, True))
        if not genericDictValidator(value, dictPrototype):
            return False
        for name in value["names"]:
            if not fontInfoWOFFMetadataExtensionNameValidator(name):
                return False
        for val in value["values"]:
            if not fontInfoWOFFMetadataExtensionValueValidator(val):
                return False
        return True

    def fontInfoWOFFMetadataExtensionNameValidator(value):
        """
        Version 3+.
        """
        dictPrototype = {
            "text": (str, True),
            "language": (str, False),
            "dir": (str, False),
            "class": (str, False),
        }
        if not genericDictValidator(value, dictPrototype):
            return False
        if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
            return False
        return True

    def fontInfoWOFFMetadataExtensionValueValidator(value):
        """
        Version 3+.
        """
        dictPrototype = {
            "text": (str, True),
            "language": (str, False),
            "dir": (str, False),
            "class": (str, False),
        }
        if not genericDictValidator(value, dictPrototype):
            return False
        if "dir" in value and value.get("dir") not in ("ltr", "rtl"):
            return False
        return True

    # ----------
    # Guidelines
    # ----------

    def guidelinesValidator(value, identifiers=None):
        """
        Version 3+.
        """
        if not isinstance(value, list):
            return False
        if identifiers is None:
            identifiers = set()
        for guide in value:
            if not guidelineValidator(guide):
                return False
            identifier = guide.get("identifier")
            if identifier is not None:
                if identifier in identifiers:
                    return False
                identifiers.add(identifier)
        return True

    _guidelineDictPrototype = dict(
        x=((int, float), False),
        y=((int, float), False),
        angle=((int, float), False),
        name=(str, False),
        color=(str, False),
        identifier=(str, False),
    )

    def guidelineValidator(value):
        """
        Version 3+.
        """
        if not genericDictValidator(value, _guidelineDictPrototype):
            return False
        x = value.get("x")
        y = value.get("y")
        angle = value.get("angle")
        # x or y must be present
        if x is None and y is None:
            return False
        # if x or y are None, angle must not be present
        if x is None or y is None:
            if angle is not None:
                return False
        # if x and y are defined, angle must be defined
        if x is not None and y is not None and angle is None:
            return False
        # angle must be between 0 and 360
        if angle is not None:
            if angle < 0:
                return False
            if angle > 360:
                return False
        # identifier must be 1 or more characters
        identifier = value.get("identifier")
        if identifier is not None and not identifierValidator(identifier):
            return False
        # color must follow the proper format
        color = value.get("color")
        if color is not None and not colorValidator(color):
            return False
        return True

    # -------
    # Anchors
    # -------

    def anchorsValidator(value, identifiers=None):
        """
        Version 3+.
        """
        if not isinstance(value, list):
            return False
        if identifiers is None:
            identifiers = set()
        for anchor in value:
            if not anchorValidator(anchor):
                return False
            identifier = anchor.get("identifier")
            if identifier is not None:
                if identifier in identifiers:
                    return False
                identifiers.add(identifier)
        return True

    _anchorDictPrototype = dict(
        x=((int, float), False),
        y=((int, float), False),
        name=(str, False),
        color=(str, False),
        identifier=(str, False),
    )

    def anchorValidator(value):
        """
        Version 3+.
        """
        if not genericDictValidator(value, _anchorDictPrototype):
            return False
        x = value.get("x")
        y = value.get("y")
        # x and y must be present
        if x is None or y is None:
            return False
        # identifier must be 1 or more characters
        identifier = value.get("identifier")
        if identifier is not None and not identifierValidator(identifier):
            return False
        # color must follow the proper format
        color = value.get("color")
        if color is not None and not colorValidator(color):
            return False
        return True

    # ----------
    # Identifier
    # ----------

    def identifierValidator(value):
        """
        Version 3+.

        >>> identifierValidator("a")
        True
        >>> identifierValidator("")
        False
        >>> identifierValidator("a" * 101)
        False
        """
        validCharactersMin = 0x20
        validCharactersMax = 0x7E
        if not isinstance(value, str):
            return False
        if not value:
            return False
        if len(value) > 100:
            return False
        for c in value:
            c = ord(c)
            if c < validCharactersMin or c > validCharactersMax:
                return False
        return True

    # -----
    # Color
    # -----

    def colorValidator(value):
        """
        Version 3+.

        >>> colorValidator("0,0,0,0")
        True
        >>> colorValidator(".5,.5,.5,.5")
        True
        >>> colorValidator("0.5,0.5,0.5,0.5")
        True
        >>> colorValidator("1,1,1,1")
        True

        >>> colorValidator("2,0,0,0")
        False
        >>> colorValidator("0,2,0,0")
        False
        >>> colorValidator("0,0,2,0")
        False
        >>> colorValidator("0,0,0,2")
        False

        >>> colorValidator("1r,1,1,1")
        False
        >>> colorValidator("1,1g,1,1")
        False
        >>> colorValidator("1,1,1b,1")
        False
        >>> colorValidator("1,1,1,1a")
        False

        >>> colorValidator("1 1 1 1")
        False
        >>> colorValidator("1 1,1,1")
        False
        >>> colorValidator("1,1 1,1")
        False
        >>> colorValidator("1,1,1 1")
        False

        >>> colorValidator("1, 1, 1, 1")
        True
        """
        if not isinstance(value, str):
            return False
        parts = value.split(",")
        if len(parts) != 4:
            return False
        for part in parts:
            part = part.strip()
            converted = False
            try:
                part = int(part)
                converted = True
            except ValueError:
                pass
            if not converted:
                try:
                    part = float(part)
                    converted = True
                except ValueError:
                    pass
            if not converted:
                return False
            if part < 0:
                return False
            if part > 1:
                return False
        return True

    # -----
    # image
    # -----

    pngSignature = b"\x89PNG\r\n\x1a\n"

    _imageDictPrototype = dict(
        fileName=(str, True),
        xScale=((int, float), False),
        xyScale=((int, float), False),
        yxScale=((int, float), False),
        yScale=((int, float), False),
        xOffset=((int, float), False),
        yOffset=((int, float), False),
        color=(str, False),
    )

    def imageValidator(value):
        """
        Version 3+.
        """
        if not genericDictValidator(value, _imageDictPrototype):
            return False
        # fileName must be one or more characters
        if not value["fileName"]:
            return False
        # color must follow the proper format
        color = value.get("color")
        if color is not None and not colorValidator(color):
            return False
        return True

    def pngValidator(path=None, data=None, fileObj=None):
        """
        Version 3+.

        This checks the signature of the image data.
        """
        assert path is not None or data is not None or fileObj is not None
        if path is not None:
            with open(path, "rb") as f:
                signature = f.read(8)
        elif data is not None:
            signature = data[:8]
        elif fileObj is not None:
            pos = fileObj.tell()
            signature = fileObj.read(8)
            fileObj.seek(pos)
        if signature != pngSignature:
            return False, "Image does not begin with the PNG signature."
        return True, None

    # -------------------
    # layercontents.plist
    # -------------------

    def layerContentsValidator(value, ufoPathOrFileSystem):
        """
        Check the validity of layercontents.plist.
        Version 3+.
        """
        if isinstance(ufoPathOrFileSystem, fs.base.FS):
            fileSystem = ufoPathOrFileSystem
        else:
            fileSystem = fs.osfs.OSFS(ufoPathOrFileSystem)

        bogusFileMessage = "layercontents.plist in not in the correct format."
        # file isn't in the right format
        if not isinstance(value, list):
            return False, bogusFileMessage
        # work through each entry
        usedLayerNames = set()
        usedDirectories = set()
        contents = {}
        for entry in value:
            # layer entry in the incorrect format
            if not isinstance(entry, list):
                return False, bogusFileMessage
            if not len(entry) == 2:
                return False, bogusFileMessage
            for i in entry:
                if not isinstance(i, str):
                    return False, bogusFileMessage
            layerName, directoryName = entry
            # check directory naming
            if directoryName != "glyphs":
                if not directoryName.startswith("glyphs."):
                    return (
                        False,
                        "Invalid directory name (%s) in layercontents.plist."
                        % directoryName,
                    )
            if len(layerName) == 0:
                return False, "Empty layer name in layercontents.plist."
            # directory doesn't exist
            if not fileSystem.exists(directoryName):
                return False, "A glyphset does not exist at %s." % directoryName
            # default layer name
            if layerName == "public.default" and directoryName != "glyphs":
                return (
                    False,
                    "The name public.default is being used by a layer that is not the default.",
                )
            # check usage
            if layerName in usedLayerNames:
                return (
                    False,
                    "The layer name %s is used by more than one layer." % layerName,
                )
            usedLayerNames.add(layerName)
            if directoryName in usedDirectories:
                return (
                    False,
                    "The directory %s is used by more than one layer." % directoryName,
                )
            usedDirectories.add(directoryName)
            # store
            contents[layerName] = directoryName
        # missing default layer
        foundDefault = "glyphs" in contents.values()
        if not foundDefault:
            return False, "The required default glyph set is not in the UFO."
        return True, None

    # ------------
    # groups.plist
    # ------------

    def groupsValidator(value):
        """
        Check the validity of the groups.
        Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).

        >>> groups = {"A" : ["A", "A"], "A2" : ["A"]}
        >>> groupsValidator(groups)
        (True, None)

        >>> groups = {"" : ["A"]}
        >>> valid, msg = groupsValidator(groups)
        >>> valid
        False
        >>> print(msg)
        A group has an empty name.

        >>> groups = {"public.awesome" : ["A"]}
        >>> groupsValidator(groups)
        (True, None)

        >>> groups = {"public.kern1." : ["A"]}
        >>> valid, msg = groupsValidator(groups)
        >>> valid
        False
        >>> print(msg)
        The group data contains a kerning group with an incomplete name.
        >>> groups = {"public.kern2." : ["A"]}
        >>> valid, msg = groupsValidator(groups)
        >>> valid
        False
        >>> print(msg)
        The group data contains a kerning group with an incomplete name.

        >>> groups = {"public.kern1.A" : ["A"], "public.kern2.A" : ["A"]}
        >>> groupsValidator(groups)
        (True, None)

        >>> groups = {"public.kern1.A1" : ["A"], "public.kern1.A2" : ["A"]}
        >>> valid, msg = groupsValidator(groups)
        >>> valid
        False
        >>> print(msg)
        The glyph "A" occurs in too many kerning groups.
        """
        bogusFormatMessage = "The group data is not in the correct format."
        if not isDictEnough(value):
            return False, bogusFormatMessage
        firstSideMapping = {}
        secondSideMapping = {}
        for groupName, glyphList in value.items():
            if not isinstance(groupName, (str)):
                return False, bogusFormatMessage
            if not isinstance(glyphList, (list, tuple)):
                return False, bogusFormatMessage
            if not groupName:
                return False, "A group has an empty name."
            if groupName.startswith("public."):
                if not groupName.startswith("public.kern1.") and not groupName.startswith(
                    "public.kern2."
                ):
                    # unknown public.* name. silently skip.
                    continue
                else:
                    if len("public.kernN.") == len(groupName):
                        return (
                            False,
                            "The group data contains a kerning group with an incomplete name.",
                        )
                if groupName.startswith("public.kern1."):
                    d = firstSideMapping
                else:
                    d = secondSideMapping
                for glyphName in glyphList:
                    if not isinstance(glyphName, str):
                        return (
                            False,
                            "The group data %s contains an invalid member." % groupName,
                        )
                    if glyphName in d:
                        return (
                            False,
                            'The glyph "%s" occurs in too many kerning groups.' % glyphName,
                        )
                    d[glyphName] = groupName
        return True, None

    # -------------
    # kerning.plist
    # -------------

    def kerningValidator(data):
        """
        Check the validity of the kerning data structure.
        Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).

        >>> kerning = {"A" : {"B" : 100}}
        >>> kerningValidator(kerning)
        (True, None)

        >>> kerning = {"A" : ["B"]}
        >>> valid, msg = kerningValidator(kerning)
        >>> valid
        False
        >>> print(msg)
        The kerning data is not in the correct format.

        >>> kerning = {"A" : {"B" : "100"}}
        >>> valid, msg = kerningValidator(kerning)
        >>> valid
        False
        >>> print(msg)
        The kerning data is not in the correct format.
        """
        bogusFormatMessage = "The kerning data is not in the correct format."
        if not isinstance(data, Mapping):
            return False, bogusFormatMessage
        for first, secondDict in data.items():
            if not isinstance(first, str):
                return False, bogusFormatMessage
            elif not isinstance(secondDict, Mapping):
                return False, bogusFormatMessage
            for second, value in secondDict.items():
                if not isinstance(second, str):
                    return False, bogusFormatMessage
                elif not isinstance(value, numberTypes):
                    return False, bogusFormatMessage
        return True, None

    # -------------
    # lib.plist/lib
    # -------------

    _bogusLibFormatMessage = "The lib data is not in the correct format: %s"

    def fontLibValidator(value):
        """
        Check the validity of the lib.
        Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).

        >>> lib = {"foo" : "bar"}
        >>> fontLibValidator(lib)
        (True, None)

        >>> lib = {"public.awesome" : "hello"}
        >>> fontLibValidator(lib)
        (True, None)

        >>> lib = {"public.glyphOrder" : ["A", "C", "B"]}
        >>> fontLibValidator(lib)
        (True, None)

        >>> lib = "hello"
        >>> valid, msg = fontLibValidator(lib)
        >>> valid
        False
        >>> print(msg)  # doctest: +ELLIPSIS
        The lib data is not in the correct format: expected a dictionary, ...

        >>> lib = {1: "hello"}
        >>> valid, msg = fontLibValidator(lib)
        >>> valid
        False
        >>> print(msg)
        The lib key is not properly formatted: expected str, found int: 1

        >>> lib = {"public.glyphOrder" : "hello"}
        >>> valid, msg = fontLibValidator(lib)
        >>> valid
        False
        >>> print(msg)  # doctest: +ELLIPSIS
        public.glyphOrder is not properly formatted: expected list or tuple,...

        >>> lib = {"public.glyphOrder" : ["A", 1, "B"]}
        >>> valid, msg = fontLibValidator(lib)
        >>> valid
        False
        >>> print(msg)  # doctest: +ELLIPSIS
        public.glyphOrder is not properly formatted: expected str,...
        """
        if not isDictEnough(value):
            reason = "expected a dictionary, found %s" % type(value).__name__
            return False, _bogusLibFormatMessage % reason
        for key, value in value.items():
            if not isinstance(key, str):
                return False, (
                    "The lib key is not properly formatted: expected str, found %s: %r"
                    % (type(key).__name__, key)
                )
            # public.glyphOrder
            if key == "public.glyphOrder":
                bogusGlyphOrderMessage = "public.glyphOrder is not properly formatted: %s"
                if not isinstance(value, (list, tuple)):
                    reason = "expected list or tuple, found %s" % type(value).__name__
                    return False, bogusGlyphOrderMessage % reason
                for glyphName in value:
                    if not isinstance(glyphName, str):
                        reason = "expected str, found %s" % type(glyphName).__name__
                        return False, bogusGlyphOrderMessage % reason
        return True, None

    # --------
    # GLIF lib
    # --------

    def glyphLibValidator(value):
        """
        Check the validity of the lib.
        Version 3+ (though it's backwards compatible with UFO 1 and UFO 2).

        >>> lib = {"foo" : "bar"}
        >>> glyphLibValidator(lib)
        (True, None)

        >>> lib = {"public.awesome" : "hello"}
        >>> glyphLibValidator(lib)
        (True, None)

        >>> lib = {"public.markColor" : "1,0,0,0.5"}
        >>> glyphLibValidator(lib)
        (True, None)

        >>> lib = {"public.markColor" : 1}
        >>> valid, msg = glyphLibValidator(lib)
        >>> valid
        False
        >>> print(msg)
        public.markColor is not properly formatted.
        """
        if not isDictEnough(value):
            reason = "expected a dictionary, found %s" % type(value).__name__
            return False, _bogusLibFormatMessage % reason
        for key, value in value.items():
            if not isinstance(key, str):
                reason = "key (%s) should be a string" % key
                return False, _bogusLibFormatMessage % reason
            # public.markColor
            if key == "public.markColor":
                if not colorValidator(value):
                    return False, "public.markColor is not properly formatted."
        return True, None

    if __name__ == "__main__":
        import doctest

        doctest.testmod()