File indexing completed on 2025-01-05 04:00:27

0001 import json
0002 import os
0003 import sys
0004 import argparse
0005 
0006 
0007 def ucfirst(x):
0008     return x[0].upper() + x[1:]
0009 
0010 
0011 def schema2py(out, filename, data, limit):
0012     if limit and class_name(filename) not in limit:
0013         return
0014     if data["type"] == "number":
0015         return schema2enum(out, filename, data)
0016     if data["type"] == "object":
0017         return schema2class(out, filename, data)
0018 
0019 
0020 def schema2enum(out, filename, data):
0021     if filename.endswith("/boolean.json"):
0022         return
0023 
0024     out.write("\n\n## \ingroup Lottie\nclass ")
0025     out.write(class_name(filename))
0026     out.write("(TgsEnum):\n")
0027     vals = {}
0028     for c in data["oneOf"]:
0029         out.write(" "*4)
0030         name = ucfirst(c["standsFor"]).replace(" ", "")
0031         value = c["const"]
0032         out.write("%s = %s\n" % (name, value))
0033         vals[value] = name
0034 
0035     if "default" in data:
0036         out.write("\n")
0037         out.write(" "*4)
0038         out.write("@classmethod\n")
0039         out.write(" "*4)
0040         out.write("def default(cls):\n")
0041         out.write(" "*8)
0042         out.write("return cls.%s\n" % vals[data["default"]])
0043 
0044 
0045 class ClassProp:
0046     def __init__(self, out, data):
0047         self.out = out
0048         self.name = self._prop_name(data)
0049         self.value = None
0050         self.raw = data
0051         self.value_comment = None
0052         self.list = False
0053         self.type = "float"
0054         if "const" in data:
0055             self.value = repr(data["const"])
0056         else:
0057             self._get_value(data)
0058             if self.value_comment == "MultiDimensional, MultiDimensionalKeyframed":
0059                 self.value_comment = None
0060             elif self.value_comment == "Value, ValueKeyframed":
0061                 self.value_comment = None
0062 
0063     def ensure_unique_name(self, properties):
0064         if self._check_unique_name(properties):
0065             return
0066         if "description" in self.raw:
0067             self.name = self._format_name(self.raw["description"].replace(".", ""))
0068             if self._check_unique_name(properties):
0069                 return
0070         self.name = self.out
0071         if not self._check_unique_name(properties):
0072             raise Exception("Cannot find unique property name")
0073 
0074     def _check_unique_name(self, properties):
0075         for p in properties:
0076             if p.name == self.name:
0077                 return False
0078         return True
0079 
0080     def _prop_name(self, data):
0081         if "extended_name" in data:
0082             title = data["extended_name"]
0083         elif "title" in data:
0084             title = data["title"]
0085         else:
0086             raise Exception("No name")
0087         if title[0] == "3":
0088             return "threedimensional"
0089         title = self._format_name(title)
0090         if title == "class":
0091             return "css_class"
0092         return title
0093 
0094     def _format_name(self, n):
0095         return n.lower().replace(" ", "_").replace("-", "_")
0096 
0097     def _get_value(self, data):
0098         if data["type"] == "number":
0099             if "oneOf" in data:
0100                 if data["oneOf"][0]["$ref"] == "#/helpers/boolean":
0101                     self.type = bool.__name__
0102                     self.value = "False"
0103                 else:
0104                     self._get_value_object(data)
0105             elif "enum" in data and data["enum"] == [0, 1]:
0106                 self.type = bool.__name__
0107                 self.value = "False"
0108             else:
0109                 self.value = "0"
0110         elif data["type"] == "object":
0111             if "properties" in data and "oneOf" not in data:
0112                 self.value = 'None'
0113             self._get_value_object(data)
0114         elif data["type"] == "array":
0115             self.list = True
0116             self.value = "[]"
0117             if "items" in data:
0118                 intype = data["items"].get("type", "object")
0119                 if intype == "object":
0120                     self.type = "todo_func"
0121                     if len(data["items"]["oneOf"]) == 1:
0122                         self.type = class_name(data["items"]["oneOf"][0]["$ref"])
0123                     self.value_comment = ", ". join(
0124                         class_name(oo["$ref"])
0125                         for oo in data["items"]["oneOf"]
0126                     )
0127                 else:
0128                     self.value_comment = intype
0129         elif data["type"] == "string":
0130             self.type = "str"
0131             self.value = '""'
0132         else:
0133             self.value = 'None'
0134 
0135     def _get_value_object(self, data):
0136         if "oneOf" not in data:
0137             self.value = 'None'
0138         else:
0139             desc = data["oneOf"][0]
0140             if "value" in desc:
0141                 self.value = desc["value"]
0142                 self.value_comment = ", ". join("%(value)s: %(standsFor)s" % oo for oo in data["oneOf"])
0143             else:
0144                 clsname = class_name(desc["$ref"])
0145                 if data["type"] == "number":
0146                     self.value = "%s.default()" % clsname
0147                 else:
0148                     self.type = clsname
0149                     self.value = "%s()" % clsname
0150 
0151                 if len(data["oneOf"]) > 1:
0152                     self.value_comment = ", ".join(
0153                         class_name(oo["$ref"])
0154                         for oo in data["oneOf"]
0155                     )
0156                 if self.value_comment == "MultiDimensional, MultiDimensionalKeyframed":
0157                     self.type = "MultiDimensional"
0158                 elif self.value_comment == "Value, ValueKeyframed":
0159                     self.type = "Value"
0160                 else:
0161                     self.type = "todo_func"
0162 
0163     def write_init(self, out, indent):
0164         out.write(indent)
0165         out.write("## %s\n" % self.raw["description"])
0166         out.write(indent)
0167         out.write("self.%s = %s" % (self.name, self.value))
0168         if self.value_comment:
0169             out.write(" # %s" % self.value_comment)
0170         out.write("\n")
0171 
0172 
0173 def schema2class(out, filename, data, ei=""):
0174     if "properties" not in data:
0175         return
0176 
0177     out.write("\n\n## \ingroup Lottie\nclass ")
0178     out.write(class_name(filename))
0179     out.write("(TgsObject): # TODO check\n")
0180     properties = []
0181     for n, p in data["properties"].items():
0182         prop = ClassProp(n, p)
0183         prop.ensure_unique_name(properties)
0184         properties.append(prop)
0185 
0186     out.write(" "*4+ei)
0187     out.write("_props = [\n")
0188     for p in properties:
0189         out.write(" "*8+ei)
0190         out.write("TgsProp(\"%s\", \"%s\", %s, %s),\n" % (p.name, p.out, p.type, p.list))
0191     out.write(" "*4+ei)
0192     out.write("]\n\n")
0193 
0194     out.write(" "*4+ei)
0195     out.write("def __init__(self):\n")
0196     for p in properties:
0197         p.write_init(out, " "*8+ei)
0198 
0199 
0200 def class_name(filename):
0201     bits = os.path.splitext(filename)[0].rsplit("/", 2)
0202     name = ucfirst(bits.pop())
0203     if bits[-1] == "layers":
0204         name += "Layer"
0205     elif bits[-1] == "effects":
0206         name += "Effect"
0207     elif name == "Transform" and bits[-1] == "shapes":
0208         name += "Shape"
0209     elif name == "Shape" and bits[-1] == "properties":
0210         name += "Property"
0211     elif name == "ShapeKeyframed":
0212         name = "ShapePropertyKeyframed"
0213     return name
0214 
0215 
0216 p = argparse.ArgumentParser(
0217     description="Generates Python classes from the lottie-web JSON schema"
0218 )
0219 p.add_argument("limit", nargs="*")
0220 schema_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "docs", "json")
0221 p.add_argument("--load", "-l", default=schema_path)
0222 
0223 
0224 if __name__ == "__main__":
0225     ns = p.parse_args()
0226 
0227     outfile = sys.stdout
0228     limit = ns.limit
0229 
0230     for root, _, files in os.walk(ns.load):
0231         for file in files:
0232             filepath = os.path.join(root, file)
0233             with open(filepath, "r") as fp:
0234                 data = json.load(fp)
0235             if not data:
0236                 continue
0237             schema2py(outfile, filepath, data, limit)