File indexing completed on 2025-01-19 04:00:00

0001 #!/usr/bin/env python3
0002 import sys
0003 import os
0004 import pkgutil
0005 import inspect
0006 import argparse
0007 import importlib
0008 from functools import reduce
0009 sys.path.insert(0, os.path.join(
0010     os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
0011     "lib"
0012 ))
0013 import lottie.objects
0014 from lottie.objects.base import LottieEnum, LottieObject, PseudoList, LottieBase, PseudoBool
0015 from lottie.objects.properties import Value, MultiDimensional
0016 from lottie import NVector
0017 
0018 
0019 class LanguageBind:
0020     extension = None
0021 
0022     def __init__(self, outpath):
0023         self.outpath = outpath
0024         self.out = None
0025 
0026     def _on_global_start(self):
0027         pass
0028 
0029     def _on_global_close(self):
0030         pass
0031 
0032     def _on_module_start(self, name):
0033         pass
0034 
0035     def _on_module_dependencies(self, depdict):
0036         pass
0037 
0038     def _on_module_close(self, name):
0039         pass
0040 
0041     def _on_class_open(self, cls, name, docs, bases):
0042         pass
0043 
0044     def _on_class_attribute(self, prop):
0045         pass
0046 
0047     def _on_class_init(self, name, valuedict, props):
0048         pass
0049 
0050     def _on_class_to_lottie(self, name, props):
0051         pass
0052 
0053     def _on_class_close(self):
0054         pass
0055 
0056     def _on_enum_open(self, name, docs):
0057         pass
0058 
0059     def _on_enum_value(self, name, value):
0060         pass
0061 
0062     def _on_enum_close(self):
0063         pass
0064 
0065     # Values
0066 
0067     def _on_value_null(self):
0068         pass
0069 
0070     def _on_value_number(self, value):
0071         return str(value)
0072 
0073     def _on_value_string(self, value):
0074         return repr(value)
0075 
0076     def _on_value_bool(self, value):
0077         return str(value).lower()
0078 
0079     def _on_value_list(self, value):
0080         return "[%s]" % ", ".join(map(self.convert_value, value))
0081 
0082     def _on_value_object(self, value, ctorargs):
0083         raise NotImplementedError()
0084 
0085     def _on_value_enum(self, value):
0086         return str(value)
0087 
0088     def _on_value_nvector(self, value):
0089         return self._on_value_list(value.components)
0090 
0091     # Lottie
0092 
0093     # Public
0094 
0095     def module_start(self, name):
0096         self.out = open(os.path.join(self.outpath, self.filename(name)), "w")
0097         self._on_module_start(name)
0098 
0099     def module_close(self, name):
0100         self._on_module_close(name)
0101         self.out.close()
0102         self.out = None
0103 
0104     def global_start(self):
0105         self._on_global_start()
0106 
0107     def global_close(self):
0108         self._on_global_close()
0109 
0110     def filename(self, base):
0111         return "%s.%s" % (base, self.extension)
0112 
0113     def module_dependencies(self, classes):
0114         depdict = {}
0115         for cls in classes:
0116             mod = cls.__module__.split(".")[-1]
0117             name = cls.__name__
0118             if mod not in depdict:
0119                 depdict[mod] = [name]
0120             else:
0121                 depdict[mod].append(name)
0122         self._on_module_dependencies(depdict)
0123 
0124     def class_open(self, cls, name, docs, bases):
0125         self._on_class_open(cls, name, docs.rstrip(), bases)
0126 
0127     def class_attribute(self, prop):
0128         self._on_class_attribute(prop)
0129 
0130     def class_init(self, name, valuedict, props):
0131         vd = {
0132             prop.name: valuedict.get(prop.name, None)
0133             for prop in props
0134         }
0135         self._on_class_init(name, vd, props)
0136 
0137     def class_to_lottie(self, name, props):
0138         self._on_class_to_lottie(name, props)
0139 
0140     def class_close(self):
0141         self._on_class_close()
0142 
0143     def enum_open(self, name, docs):
0144         self._on_enum_open(name, docs)
0145 
0146     def enum_value(self, name, value):
0147         self._on_enum_value(name, self.convert_value(value))
0148 
0149     def enum_close(self):
0150         self._on_enum_close()
0151 
0152     def wl(self, text, indent=0):
0153         self.out.write("%s%s\n" % (" " * (indent*4), text))
0154 
0155     def convert_value(self, value):
0156         if value is None:
0157             return self._on_value_null()
0158         if isinstance(value, LottieEnum):
0159             return self._on_value_enum(value)
0160         if isinstance(value, bool):
0161             return self._on_value_bool(value)
0162         if isinstance(value, (float, int)):
0163             return self._on_value_number(value)
0164         if isinstance(value, str):
0165             return self._on_value_string(value)
0166         if isinstance(value, list):
0167             return self._on_value_list(value)
0168         if isinstance(value, (Value, MultiDimensional)):
0169             return self._on_value_object(value, [self.convert_value(value.value)])
0170         if isinstance(value, LottieObject):
0171             return self._on_value_object(value, [])
0172         if isinstance(value, NVector):
0173             return self._on_value_nvector(value)
0174         raise NotImplementedError("%s" % value)
0175 
0176 
0177 class JsBind(LanguageBind):
0178     extension = "js"
0179 
0180     def _on_global_start(self):
0181         self.all = {}
0182 
0183     def _on_global_close(self):
0184         with open(os.path.join(self.outpath, self.filename("all")), "w") as allf:
0185             for k, v in self.all.items():
0186                 allf.write("export { %s } from './%s.js';\n" % (", ".join(v), k))
0187 
0188     def _on_module_start(self, name):
0189         self.all[name] = []
0190         self.all_current = self.all[name]
0191         self.wl("import { value_to_lottie, value_from_lottie, LottieObject } from '../base.js';")
0192 
0193     def _on_module_dependencies(self, depdict):
0194         for module, values in depdict.items():
0195             if module == "base":
0196                 continue
0197             self.wl("import { %s } from './%s.js';" % (", ".join(values), module))
0198         self.wl("")
0199 
0200     def _on_class_open(self, cls, name, docs, bases):
0201         self.current_class = cls
0202         self.all_current.append(name)
0203         self.wl("/*{docs}\n*/\nexport class {name} extends {base}\n{{"
0204             .format(name=name, docs=docs, base=bases[-1].__name__))
0205 
0206     def _on_class_attribute(self, prop):
0207         self.wl(prop.name + ";", 1)
0208 
0209     def _on_class_close(self):
0210         self.wl("}\n")
0211 
0212     def _on_class_init(self, name, valuedict, props):
0213         self.wl("")
0214 
0215         skip = set()
0216 
0217         if name in ["MultiDimensional", "Value"]:
0218             self.wl("constructor(value=null)", 1)
0219             skip.add("value");
0220         else:
0221             self.wl("constructor()", 1)
0222 
0223         self.wl("{", 1)
0224         self.wl("super();", 2)
0225 
0226         if issubclass(self.current_class, lottie.objects.Layer) or issubclass(self.current_class, lottie.objects.ShapeElement):
0227             if self.current_class.type:
0228                 self.wl("this.type = %r;" % self.current_class.type, 2)
0229                 skip.add("type")
0230 
0231         if name in ["MultiDimensional", "Value"]:
0232             self.wl("this.value = value;", 2)
0233 
0234         for propname, value in valuedict.items():
0235             if propname not in skip:
0236                 self.wl("this.%s = %s;" % (propname, self.convert_value(value)), 2)
0237 
0238 
0239         self.wl("}", 1)
0240 
0241     def _on_value_null(self):
0242         return "null"
0243 
0244     def _on_class_to_lottie(self, name, props):
0245         self.wl("")
0246         self.wl("to_lottie()", 1)
0247         self.wl("{", 1)
0248         self.wl("var arr = {};", 2)
0249         #self.wl("var arr = super.to_lottie();", 2)
0250         for prop in props:
0251             self.wl("if ( this.%s !== null )" % prop.name, 2)
0252             self.out.write(" "*4*3)
0253             self.out.write('arr["%s"] = value_to_lottie(' % prop.lottie)
0254             if prop.list is PseudoList:
0255                 self.out.write("[")
0256             if prop.type is PseudoBool:
0257                 self.out.write("Number(")
0258             self.out.write("this.%s" % prop.name)
0259             if prop.type is PseudoBool:
0260                 self.out.write(")")
0261             if prop.list is PseudoList:
0262                 self.out.write("]")
0263             self.out.write(");\n")
0264         self.wl("return arr;", 2)
0265         self.wl("}", 1)
0266 
0267         self.wl("")
0268         self.wl("static from_lottie(arr)", 1)
0269         self.wl("{", 1)
0270         self.wl("var obj = new %s();" % name, 2)
0271         for prop in props:
0272             self.wl('if ( arr["%s"] !== undefined )' % prop.lottie, 2)
0273             self.out.write(" "*4*3)
0274             self.out.write("obj.%s = value_from_lottie(" % prop.name)
0275             if prop.type is PseudoBool:
0276                 self.out.write("Boolean(")
0277             self.out.write('arr["%s"]' % prop.lottie)
0278             if prop.type is PseudoBool:
0279                 self.out.write(")")
0280             if prop.list is PseudoList:
0281                 self.out.write("[0]")
0282             self.out.write(");\n")
0283 
0284         self.wl("return obj;", 2)
0285         self.wl("}", 1)
0286 
0287     def _on_enum_open(self, name, docs):
0288         self.all_current.append(name)
0289         self.wl("/*{docs}\n*/\nexport const {name} = Object.freeze({{".format(name=name, docs=docs))
0290 
0291     def _on_enum_value(self, name, value):
0292         self.wl("%s: %s," % (name, value), 1)
0293 
0294     def _on_enum_close(self):
0295         self.wl("});")
0296 
0297     # Values
0298 
0299     def _on_value_object(self, value, ctorargs):
0300         return "new %s(%s)" % (
0301             value.__class__.__name__,
0302             ", ".join(ctorargs)
0303         )
0304 
0305     def _on_value_enum(self, value):
0306         return str(value)
0307 
0308 
0309 class PhpBind(LanguageBind):
0310     extension = "php"
0311 
0312     def _on_module_start(self, name):
0313         self.wl("<?php\n")
0314 
0315     def _on_module_dependencies(self, depdict):
0316         for module in depdict.keys():
0317             self.wl("require_once(\"%s.php\");" % module)
0318         self.wl("")
0319 
0320     def _on_class_open(self, cls, name, docs, bases):
0321         self.wl("/*{docs}\n*/\nclass {name} extends {base}\n{{".format(name=name, docs=docs, base=bases[0].__name__))
0322 
0323     def _on_class_attribute(self, prop):
0324         self.wl("public ${prop.name};".format(prop=prop), 1)
0325 
0326     def _on_class_close(self):
0327         self.wl("}\n")
0328 
0329     def _on_class_init(self, name, valuedict, props):
0330         self.wl("")
0331         self.wl("function __construct()", 1)
0332         self.wl("{", 1)
0333         for name, value in valuedict.items():
0334             self.wl("$this->%s = %s;" % (name, self.convert_value(value)), 2)
0335         self.wl("}", 1)
0336 
0337     def _on_value_null(self):
0338         return "null"
0339 
0340     def _on_class_to_lottie(self, name, props):
0341         self.wl("")
0342         self.wl("function to_lottie()", 1)
0343         self.wl("{", 1)
0344         self.wl("$arr = [];", 2)
0345         for prop in props:
0346             self.wl("if ( $this->%s !== null )" % prop.name, 2)
0347             self.out.write(" "*4*3)
0348             self.out.write('$arr["%s"] = self::value_to_lottie(' % prop.lottie)
0349             if prop.list is PseudoList:
0350                 self.out.write("[")
0351             if prop.type is PseudoBool:
0352                 self.out.write("(int)")
0353             self.out.write("$this->%s" % prop.name)
0354             if prop.list is PseudoList:
0355                 self.out.write("]")
0356             self.out.write(");\n")
0357         self.wl("return $arr;", 2)
0358         self.wl("}", 1)
0359 
0360         self.wl("")
0361         self.wl("static function from_lottie($arr)", 1)
0362         self.wl("{", 1)
0363         self.wl("$obj = new %s();" % name, 2)
0364         for prop in props:
0365             self.wl('if ( isset($arr["%s"]) )' % prop.lottie, 2)
0366             self.out.write(" "*4*3)
0367             self.out.write("$obj->%s = self::value_from_lottie(" % prop.name)
0368             if prop.type is PseudoBool:
0369                 self.out.write("(bool)")
0370             self.out.write('$arr["%s"]' % prop.lottie)
0371             if prop.list is PseudoList:
0372                 self.out.write("[0]")
0373             self.out.write(");\n")
0374         self.wl("return $obj;", 2)
0375         self.wl("}", 1)
0376 
0377     def _on_enum_open(self, name, docs):
0378         self._on_class_open(name, docs, [LottieEnum])
0379 
0380     def _on_enum_value(self, name, value):
0381         self.wl("const %s = %s;" % (name, value), 1)
0382 
0383     def _on_enum_close(self):
0384         return self._on_class_close()
0385 
0386     # Values
0387 
0388     def _on_value_object(self, value, ctorargs):
0389         return "new %s(%s)" % (
0390             value.__class__.__name__,
0391             ", ".join(ctorargs)
0392         )
0393 
0394     def _on_value_enum(self, value):
0395         return str(value).replace(".", "::")
0396 
0397 
0398 class ClassWrapper:
0399     def __init__(self, cls):
0400         self.cls = cls
0401         self.deps = set()
0402         self.external_deps = set()
0403         self._load_deps()
0404 
0405     def bind(self, binder):
0406         raise NotImplementedError()
0407 
0408     def _load_deps(self):
0409         for base in self.cls.__bases__:
0410             self._apply_dep(base)
0411 
0412     def _apply_dep(self, dep):
0413         if not isinstance(dep, type) or not issubclass(dep, LottieBase):
0414             return
0415         if dep.__module__ != self.cls.__module__:
0416             self.external_deps.add(dep)
0417         else:
0418             self.deps.add(dep)
0419 
0420     def deps_satisfied(self, deps):
0421         return self.deps.issubset(deps)
0422 
0423 
0424 class ObjectWrapper(ClassWrapper):
0425     def __init__(self, cls):
0426         self.objprops = cls().__dict__
0427         super().__init__(cls)
0428 
0429     def bind(self, binder):
0430         binder.class_open(self.cls, self.cls.__name__, self.cls.__doc__ or "", self.cls.__bases__)
0431         for p in self.cls._props:
0432             binder.class_attribute(p)
0433         binder.class_init(self.cls.__name__, self.objprops, self.cls._props)
0434         binder.class_to_lottie(self.cls.__name__, self.cls._props)
0435         binder.class_close()
0436 
0437     def _load_deps(self):
0438         super()._load_deps()
0439 
0440         for p in self.cls._props:
0441             self._apply_dep(p.type)
0442 
0443         for v in self.objprops.values():
0444             self._apply_dep(type(v))
0445 
0446 
0447 class EnumWrapper(ClassWrapper):
0448     def bind(self, binder):
0449         binder.enum_open(self.cls.__name__, self.cls.__doc__ or "")
0450         for p in self.cls.__members__.values():
0451             binder.enum_value(p.name, p.value)
0452         binder.enum_close()
0453 
0454 
0455 binds = {
0456     "php": PhpBind,
0457     "js": JsBind,
0458 }
0459 
0460 parser = argparse.ArgumentParser()
0461 parser.add_argument(
0462     "--output",
0463     "-o",
0464     default="/tmp/out/",
0465 )
0466 parser.add_argument(
0467     "language",
0468     choices=list(binds.keys()),
0469 )
0470 
0471 
0472 ns = parser.parse_args()
0473 
0474 os.makedirs(ns.output, exist_ok=True)
0475 
0476 
0477 bind = binds[ns.language](ns.output)
0478 
0479 bind.global_start()
0480 
0481 for _, modname, _ in pkgutil.iter_modules(lottie.objects.__path__):
0482     if modname == "base":
0483         continue
0484 
0485     full_modname = "lottie.objects." + modname
0486     module = importlib.import_module(full_modname)
0487 
0488     bind.module_start(modname)
0489     classes = []
0490 
0491     for clsname, cls in inspect.getmembers(module):
0492         if inspect.isclass(cls) and issubclass(cls, LottieBase) and cls.__module__ == full_modname:
0493             if issubclass(cls, LottieEnum):
0494                 classes.append(EnumWrapper(cls))
0495             else:
0496                 classes.append(ObjectWrapper(cls))
0497 
0498     external_deps = reduce(lambda a, b: a | b, (x.external_deps for x in classes))
0499     bind.module_dependencies(external_deps)
0500 
0501     sorted_classes = []
0502     deps = set()
0503     nclasses = len(classes)
0504     while classes:
0505         nc = []
0506         for cls in classes:
0507             if cls.deps_satisfied(deps):
0508                 sorted_classes.append(cls)
0509                 deps.add(cls.cls)
0510             else:
0511                 nc.append(cls)
0512         if len(nc) == nclasses:
0513             sorted_classes += nc
0514             sys.stderr.write(
0515                 "Couldn't satisfy dependencies for: %s\n" % (
0516                     ", ".join(cls.cls.__name__ for cls in nc)
0517                 )
0518             )
0519             break
0520         else:
0521             classes = nc
0522 
0523     for cls in sorted_classes:
0524         cls.bind(bind)
0525 
0526     bind.module_close(modname)
0527 
0528 bind.global_close()