Traktor/myenv/Lib/site-packages/fontTools/ufoLib/validators.py

1187 lines
30 KiB
Python
Raw Normal View History

2024-05-23 01:57:24 +02:00
"""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()