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()