def create_js_component_class(cls, cls_name, base_class='Component.prototype'): """ Create the JS equivalent of a subclass of the Component class. Given a Python class with actions, properties, emitters and reactions, this creates the code for the JS version of the class. It also supports class constants that are int/float/str, or a tuple/list thereof. The given class does not have to be a subclass of Component. This more or less does what ComponentMeta does, but for JS. """ assert cls_name != 'Component' # we need this special class above instead # Collect meta information of all code pieces that we collect mc = MetaCollector(cls) mc.meta['std_functions'].add( 'op_instantiate') # b/c we use get_class_definition total_code = [] funcs_code = [] # functions and emitters go below class constants const_code = [] err = ('Objects on JS Component classes can only be int, float, str, ' 'or a list/tuple thereof. Not %s -> %r.') total_code.append('\n'.join(get_class_definition(cls_name, base_class)).rstrip()) prefix = '' if cls_name.count('.') else 'var ' total_code[0] = prefix + total_code[0] prototype_prefix = '$' + cls_name.split('.')[-1] + '.' total_code.append('var %s = %s.prototype;' % (prototype_prefix[:-1], cls_name)) # Process class items in original order or sorted by name if we cant class_items = cls.__dict__.items() if sys.version_info < (3, 6): # pragma: no cover class_items = sorted(class_items) for name, val in class_items: if isinstance(val, ActionDescriptor): # Set underlying function as class attribute. This is overwritten # by the instance, but this way super() works. funcname = name # Add function def code = mc.py2js(val._func, prototype_prefix + funcname) code = code.replace('super()', base_class) # fix super # Tweak if this was an autogenerated action # we use flx_ prefixes to indicate autogenerated functions if val._func.__name__.startswith('flx_'): subname = name if name.startswith('set_') or name.startswith('_set_'): subname = name[4:] code = code.replace("flx_name", "'%s'" % subname) funcs_code.append(code.rstrip()) # Mark to not bind the func funcs_code.append(prototype_prefix + funcname + '.nobind = true;') funcs_code.append('') elif isinstance(val, ReactionDescriptor): funcname = name # funcname is simply name, so that super() works # Add function def code = mc.py2js(val._func, prototype_prefix + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func funcs_code.append(prototype_prefix + funcname + '.nobind = true;') # Add connection strings, but not for implicit reactions if val._connection_strings: funcs_code.append(prototype_prefix + funcname + '._connection_strings = ' + reprs(val._connection_strings)) funcs_code.append('') elif isinstance(val, EmitterDescriptor): funcname = name # Add function def code = mc.py2js(val._func, prototype_prefix + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func funcs_code.append(prototype_prefix + funcname + '.nobind = true;') funcs_code.append('') elif isinstance(val, Attribute): pass elif isinstance(val, Property): # Mutator and validator functions are picked up as normal functions. # Set default value on class. default_val = json.dumps(val._default) t = '%s_%s_value = %s;' const_code.append(t % (prototype_prefix, name, default_val)) elif isinstance(val, classmethod): pass # ignore, like magics elif (name.startswith('__') and name not in OK_MAGICS and not name.endswith('_validate')): # These are only magics, since class attributes with double-underscores # have already been mangled. Note that we need to exclude validator # funcs of private properties though. pass elif (name.endswith('_validate') and hasattr(val, '__self__') and isinstance(val.__self__, Property)): # Proxy the validator functions (not inline). prop_class = val.__self__.__class__ mod_name_parts = prop_class.__module__.split('.') module_ns = sys.modules[cls.__module__].__dict__ # Get canonical class name, included part of the module name, as # needed, depending on what names exist in the component module. prop_class_name = prop_class.__name__ if prop_class_name not in module_ns: for ip in reversed(range(0, len(mod_name_parts))): if mod_name_parts[ip] in module_ns: prop_class_name = mod_name_parts[ ip] + '.' + prop_class_name break # Create function that calls the _validate function t = ' = function (value) { return %s(value, %s, %s); }\n' code = prototype_prefix + name + t % ( prop_class_name + '.prototype._validate', json.dumps( name[1:-9]), json.dumps(val.__self__._data)) funcs_code.append(code) mc.meta['vars_unknown'].add(prop_class_name) elif callable(val): # Functions, including methods attached by the meta class code = mc.py2js(val, prototype_prefix + name) code = code.replace('super()', base_class) # fix super if val.__name__.startswith('flx_'): subname = name[8:] if name.startswith('_mutate_') else name code = code.replace("flx_name", "'%s'" % subname) funcs_code.append(code.rstrip()) funcs_code.append('') else: # Static simple (json serializable) attributes, e.g. __actions__ etc. try: serialized = json.dumps(val) except Exception as err: # pragma: no cover raise ValueError('Attributes on JS Component class must be ' 'JSON compatible.\n%s' % str(err)) const_code.append(prototype_prefix + name + ' = ' + serialized) if const_code: total_code.append('') total_code.extend(const_code) if funcs_code: total_code.append('') total_code.extend(funcs_code) total_code.append('') # Return string with meta info (similar to what py2js returns) mc.meta['vars_unknown'].discard('flx_name') return mc.attach_meta('\n'.join(total_code))
def create_js_hasevents_class(cls, cls_name, base_class='HasEvents.prototype'): """ Create the JS equivalent of a subclass of the HasEvents class. Given a Python class with handlers, properties and emitters, this creates the code for the JS version of this class. It also supports class constants that are int/float/str, or a tuple/list thereof. The given class does not have to be a subclass of HasEvents. This more or less does what HasEventsMeta does, but for JS. """ assert cls_name != 'HasEvents' # we need this special class above instead handlers = [] emitters = [] properties = [] total_code = [] funcs_code = [] # functions and emitters go below class constants const_code = [] err = ('Objects on JS HasEvents classes can only be int, float, str, ' 'or a list/tuple thereof. Not %s -> %r.') total_code.append('\n'.join(get_class_definition(cls_name, base_class)).rstrip()) prefix = '' if cls_name.count('.') else 'var ' total_code[0] = prefix + total_code[0] # Functions to ignore special_funcs = ['_%s_func' % name for name in (cls.__handlers__ + cls.__emitters__ + cls.__properties__)] OK_MAGICS = ('__properties__', '__emitters__', '__handlers__', '__proxy_properties__', '__emitter_flags__') for name, val in sorted(cls.__dict__.items()): name = name.replace('_JS__', '_%s__' % cls_name.split('.')[-1]) # fix mangling funcname = '_' + name + '_func' if name in special_funcs: pass elif isinstance(val, BaseEmitter): if isinstance(val, Property): properties.append(name) else: emitters.append(name) # Add function def code = py2js(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Has default val? if isinstance(val, Property) and val._defaults: default_val = json.dumps(val._defaults[0]) t = '%s.prototype.%s.default = %s;' funcs_code.append(t % (cls_name, funcname, default_val)) # Add type of emitter t = '%s.prototype.%s.emitter_type = %s;' emitter_type = val.__class__.__name__ funcs_code.append(t % (cls_name, funcname, reprs(emitter_type))) funcs_code.append('') elif isinstance(val, HandlerDescriptor): handlers.append(name) # Add function def code = py2js(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Add connection strings to the function object t = '%s.prototype.%s._connection_strings = %s;' funcs_code.append(t % (cls_name, funcname, reprs(val._connection_strings))) funcs_code.append('') elif callable(val): code = py2js(val, cls_name + '.prototype.' + name) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) funcs_code.append('') elif name in OK_MAGICS: t = '%s.prototype.%s = %s;' const_code.append(t % (cls_name, name, reprs(val))) elif name.startswith('__'): pass # we create our own __emitters__, etc. else: try: serialized = json.dumps(val) except Exception as err: # pragma: no cover raise ValueError('Attributes on JS HasEvents class must be ' 'JSON compatible.\n%s' % str(err)) #const_code.append('%s.prototype.%s = JSON.parse(%s)' % # (cls_name, name, reprs(serialized))) const_code.append('%s.prototype.%s = %s;' % (cls_name, name, serialized)) if const_code: total_code.append('') total_code.extend(const_code) if funcs_code: total_code.append('') total_code.extend(funcs_code) total_code.append('') return '\n'.join(total_code)
def create_js_hasevents_class(cls, cls_name, base_class='HasEvents.prototype'): """ Create the JS equivalent of a subclass of the HasEvents class. Given a Python class with handlers, properties and emitters, this creates the code for the JS version of this class. It also supports class constants that are int/float/str, or a tuple/list thereof. The given class does not have to be a subclass of HasEvents. This more or less does what HasEventsMeta does, but for JS. """ assert cls_name != 'HasEvents' # we need this special class above instead handlers = [] emitters = [] properties = [] total_code = [] funcs_code = [] # functions and emitters go below class constants const_code = [] err = ('Objects on JS HasEvents classes can only be int, float, str, ' 'or a list/tuple thereof. Not %s -> %r.') total_code.append('\n'.join(get_class_definition(cls_name, base_class)).rstrip()) prefix = '' if cls_name.count('.') else 'var ' total_code[0] = prefix + total_code[0] # Functions to ignore special_funcs = [ '_%s_func' % name for name in (cls.__handlers__ + cls.__emitters__ + cls.__properties__) ] OK_MAGICS = ('__properties__', '__emitters__', '__handlers__', '__proxy_properties__', '__emitter_flags__') for name, val in sorted(cls.__dict__.items()): name = name.replace('_JS__', '_%s__' % cls_name.split('.')[-1]) # fix mangling funcname = '_' + name + '_func' if name in special_funcs: pass elif isinstance(val, BaseEmitter): if isinstance(val, Property): properties.append(name) else: emitters.append(name) # Add function def code = py2js(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Has default val? if isinstance(val, Property) and val._defaults: default_val = json.dumps(val._defaults[0]) t = '%s.prototype.%s.default = %s;' funcs_code.append(t % (cls_name, funcname, default_val)) # Add type of emitter t = '%s.prototype.%s.emitter_type = %s;' emitter_type = val.__class__.__name__ funcs_code.append(t % (cls_name, funcname, reprs(emitter_type))) funcs_code.append('') elif isinstance(val, HandlerDescriptor): handlers.append(name) # Add function def code = py2js(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Add connection strings to the function object t = '%s.prototype.%s._connection_strings = %s;' funcs_code.append( t % (cls_name, funcname, reprs(val._connection_strings))) funcs_code.append('') elif callable(val): code = py2js(val, cls_name + '.prototype.' + name) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) funcs_code.append('') elif name in OK_MAGICS: t = '%s.prototype.%s = %s;' const_code.append(t % (cls_name, name, reprs(val))) elif name.startswith('__'): pass # we create our own __emitters__, etc. else: try: serialized = json.dumps(val) except Exception as err: # pragma: no cover raise ValueError('Attributes on JS HasEvents class must be ' 'JSON compatible.\n%s' % str(err)) #const_code.append('%s.prototype.%s = JSON.parse(%s)' % # (cls_name, name, reprs(serialized))) const_code.append('%s.prototype.%s = %s;' % (cls_name, name, serialized)) if const_code: total_code.append('') total_code.extend(const_code) if funcs_code: total_code.append('') total_code.extend(funcs_code) total_code.append('') return '\n'.join(total_code)
def create_js_hasevents_class(cls, cls_name, base_class='HasEvents.Ƥ'): """ Create the JS equivalent of a subclass of the HasEvents class. Given a Python class with handlers, properties and emitters, this creates the code for the JS version of this class. It also supports class constants that are int/float/str, or a tuple/list thereof. The given class does not have to be a subclass of HasEvents. This more or less does what HasEventsMeta does, but for JS. """ assert cls_name != 'HasEvents' # we need this special class above instead # Collect meta information of all code pieces that we collect meta = { 'vars_unknown': set(), 'vars_global': set(), 'std_functions': set(), 'std_methods': set(), 'linenr': 1e9 } def py2js_local(*args, **kwargs): code = py2js(*args, **kwargs) for key in meta: if key == 'linenr': meta[key] = min(meta[key], code.meta[key]) else: meta[key].update(code.meta[key]) return code handlers = [] emitters = [] properties = [] total_code = [] funcs_code = [] # functions and emitters go below class constants const_code = [] err = ('Objects on JS HasEvents classes can only be int, float, str, ' 'or a list/tuple thereof. Not %s -> %r.') total_code.append('\n'.join(get_class_definition(cls_name, base_class)).rstrip()) prefix = '' if cls_name.count('.') else 'var ' total_code[0] = prefix + total_code[0] # Functions to ignore OK_MAGICS = ('__properties__', '__emitters__', '__handlers__', '__local_properties__') for name, val in sorted(cls.__dict__.items()): name = name.replace('_JS__', '_%s__' % cls_name.split('.')[-1]) # fix mangling if isinstance(val, BaseEmitter): funcname = name if isinstance(val, Property): properties.append(name) else: emitters.append(name) # Add function def code = py2js_local(val._func, cls_name + '.Ƥ.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.Ƥ.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Has default val? if isinstance(val, Property) and val._defaults: default_val = json.dumps(val._defaults[0]) t = '%s.Ƥ.%s.default = %s;' funcs_code.append(t % (cls_name, funcname, default_val)) # Add type of emitter t = '%s.Ƥ.%s.emitter_type = %s;' emitter_type = val.__class__.__name__ funcs_code.append(t % (cls_name, funcname, reprs(emitter_type))) funcs_code.append('') elif isinstance(val, HandlerDescriptor): funcname = name # funcname is simply name, so that super() works handlers.append(name) # Add function def code = py2js_local(val._func, cls_name + '.Ƥ.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.Ƥ.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Add connection strings to the function object t = '%s.Ƥ.%s._connection_strings = %s;' funcs_code.append( t % (cls_name, funcname, reprs(val._connection_strings))) funcs_code.append('') elif callable(val): code = py2js_local(val, cls_name + '.Ƥ.' + name) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) funcs_code.append('') elif name in OK_MAGICS: t = '%s.Ƥ.%s = %s;' const_code.append(t % (cls_name, name, reprs(val))) elif name.startswith('__'): pass # we create our own __emitters__, etc. else: try: serialized = json.dumps(val) except Exception as err: # pragma: no cover raise ValueError('Attributes on JS HasEvents class must be ' 'JSON compatible.\n%s' % str(err)) #const_code.append('%s.Ƥ.%s = JSON.parse(%s)' % # (cls_name, name, reprs(serialized))) const_code.append('%s.Ƥ.%s = %s;' % (cls_name, name, serialized)) if const_code: total_code.append('') total_code.extend(const_code) if funcs_code: total_code.append('') total_code.extend(funcs_code) total_code.append('') # Return string with meta info (similar to what py2js returns) js = JSString('\n'.join(total_code)) js.meta = meta return js
def create_js_hasevents_class(cls, cls_name, base_class='HasEvents.prototype'): """ Create the JS equivalent of a subclass of the HasEvents class. Given a Python class with handlers, properties and emitters, this creates the code for the JS version of this class. It also supports class constants that are int/float/str, or a tuple/list thereof. The given class does not have to be a subclass of HasEvents. This more or less does what HasEventsMeta does, but for JS. """ assert cls_name != 'HasEvents' # we need this special class above instead # Collect meta information of all code pieces that we collect meta = {'vars_unknown': set(), 'vars_global': set(), 'std_functions': set(), 'std_methods': set(), 'linenr': 1e9} def py2js_local(*args, **kwargs): code = py2js(*args, **kwargs) for key in meta: if key == 'linenr': meta[key] = min(meta[key], code.meta[key]) else: meta[key].update(code.meta[key]) return code handlers = [] emitters = [] properties = [] total_code = [] funcs_code = [] # functions and emitters go below class constants const_code = [] err = ('Objects on JS HasEvents classes can only be int, float, str, ' 'or a list/tuple thereof. Not %s -> %r.') total_code.append('\n'.join(get_class_definition(cls_name, base_class)).rstrip()) prefix = '' if cls_name.count('.') else 'var ' total_code[0] = prefix + total_code[0] # Functions to ignore OK_MAGICS = ('__properties__', '__emitters__', '__handlers__', '__local_properties__') # Process class items in original order or sorted by name if we cant class_items = cls.__dict__.items() if sys.version_info < (3, 6): class_items = sorted(class_items) for name, val in class_items: name = name.replace('_JS__', '_%s__' % cls_name.split('.')[-1]) # fix mangling if isinstance(val, BaseEmitter): funcname = name if isinstance(val, Property): properties.append(name) else: emitters.append(name) # Add function def code = py2js_local(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Has default val? if isinstance(val, Property) and val._defaults: default_val = json.dumps(val._defaults[0]) t = '%s.prototype.%s.default = %s;' funcs_code.append(t % (cls_name, funcname, default_val)) # Add type of emitter t = '%s.prototype.%s.emitter_type = %s;' emitter_type = val.__class__.__name__ funcs_code.append(t % (cls_name, funcname, reprs(emitter_type))) funcs_code.append('') elif isinstance(val, HandlerDescriptor): funcname = name # funcname is simply name, so that super() works handlers.append(name) # Add function def code = py2js_local(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Add connection strings to the function object t = '%s.prototype.%s._connection_strings = %s;' funcs_code.append(t % (cls_name, funcname, reprs(val._connection_strings))) funcs_code.append('') elif callable(val): code = py2js_local(val, cls_name + '.prototype.' + name) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) funcs_code.append('') elif name in OK_MAGICS: t = '%s.prototype.%s = %s;' const_code.append(t % (cls_name, name, reprs(val))) elif name.startswith('__'): pass # we create our own __emitters__, etc. else: try: serialized = json.dumps(val) except Exception as err: # pragma: no cover raise ValueError('Attributes on JS HasEvents class must be ' 'JSON compatible.\n%s' % str(err)) #const_code.append('%s.prototype.%s = JSON.parse(%s)' % # (cls_name, name, reprs(serialized))) const_code.append('%s.prototype.%s = %s;' % (cls_name, name, serialized)) if const_code: total_code.append('') total_code.extend(const_code) if funcs_code: total_code.append('') total_code.extend(funcs_code) total_code.append('') # Return string with meta info (similar to what py2js returns) js = JSString('\n'.join(total_code)) js.meta = meta return js
def create_js_hasevents_class(cls, cls_name, base_class='HasEvents.prototype'): """ Create the JS equivalent of a subclass of the HasEvents class. Given a Python class with handlers, properties and emitters, this creates the code for the JS version of this class. It also supports class constants that are int/float/str, or a tuple/list thereof. """ assert cls_name != 'HasEvents' # we need this special class above instead handlers = [] emitters = [] properties = [] total_code = [] funcs_code = [] # functions and emitters go below class constants const_code = [] err = ('Objects on JS HasEvents classes can only be int, float, str, ' 'or a list/tuple thereof. Not %s -> %r.') total_code.extend(get_class_definition(cls_name, base_class)) prefix = '' if cls_name.count('.') else 'var ' total_code[0] = prefix + total_code[0] for name, val in sorted(cls.__dict__.items()): name = name.replace('_JS__', '_%s__' % cls_name.split('.')[-1]) # fix mangling funcname = '_' + name + '_func' if isinstance(val, BaseEmitter): if isinstance(val, Property): properties.append(name) else: emitters.append(name) # Add function def code = py2js(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Add type of emitter t = '%s.prototype.%s._emitter_type = %s;' emitter_type = val.__class__.__name__ funcs_code.append(t % (cls_name, funcname, reprs(emitter_type))) funcs_code.append('') elif isinstance(val, HandlerDescriptor): handlers.append(name) # Add function def code = py2js(val._func, cls_name + '.prototype.' + funcname) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) # Mark to not bind the func t = '%s.prototype.%s.nobind = true;' funcs_code.append(t % (cls_name, funcname)) # Add connection strings to the function object t = '%s.prototype.%s._connection_strings = %s;' funcs_code.append( t % (cls_name, funcname, reprs(val._connection_strings))) funcs_code.append('') elif callable(val): code = py2js(val, cls_name + '.prototype.' + name) code = code.replace('super()', base_class) # fix super funcs_code.append(code.rstrip()) funcs_code.append('') elif name.startswith('__'): pass # we create our own __emitters__ list else: try: serialized = json.dumps(val) except Exception as err: # pragma: no cover raise ValueError('Attributes on JS HasEvents class must be ' 'JSON compatible.\n%s' % str(err)) const_code.append('%s.prototype.%s = JSON.parse(%s)' % (cls_name, name, reprs(serialized))) # Store handlers, properties and emitters that we found if base_class in ('Object', 'HasEvents.prototype'): t = '%s.prototype.__emitters__ = %s;' total_code.append(t % (cls_name, reprs(list(sorted(emitters))))) t = '%s.prototype.__properties__ = %s;' total_code.append(t % (cls_name, reprs(list(sorted(properties))))) t = '%s.prototype.__handlers__ = %s;' total_code.append(t % (cls_name, reprs(list(sorted(handlers))))) else: t = '%s.prototype.__emitters__ = %s.__emitters__.concat(%s).sort();' total_code.append(t % (cls_name, base_class, reprs(emitters))) t = '%s.prototype.__properties__ = %s.__properties__.concat(%s).sort();' total_code.append(t % (cls_name, base_class, reprs(properties))) t = '%s.prototype.__handlers__ = %s.__handlers__.concat(%s).sort();' total_code.append(t % (cls_name, base_class, reprs(handlers))) total_code.append('') total_code.extend(const_code) total_code.append('') total_code.extend(funcs_code) return '\n'.join(total_code)