Now that we correctly export the ol.animation.*, ol.easing.* and ol.coordinate.* symbols we can make the generate-exports.py script work for more cases.
324 lines
13 KiB
Python
Executable File
324 lines
13 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
from collections import defaultdict
|
|
from itertools import ifilter
|
|
from operator import attrgetter
|
|
from optparse import OptionParser
|
|
import re
|
|
import sys
|
|
|
|
|
|
def simplerepr(obj):
|
|
keys = sorted(key for key in obj.__dict__.keys() if not key.startswith('_'))
|
|
attrs = ''.join(' %s=%r' % (key, obj.__dict__[key]) for key in keys)
|
|
return '<%s%s>' % (obj.__class__.__name__, attrs)
|
|
|
|
|
|
class Exportable(object):
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
__repr__ = simplerepr
|
|
|
|
def export(self):
|
|
return ''
|
|
|
|
def extern(self):
|
|
return ''
|
|
|
|
def typedef(self):
|
|
return ''
|
|
|
|
|
|
class Class(Exportable):
|
|
|
|
def __init__(self, name, object_literal, objects):
|
|
Exportable.__init__(self, name)
|
|
self.object_literal = object_literal
|
|
self.objects = objects
|
|
self.props = set()
|
|
|
|
__repr__ = simplerepr
|
|
|
|
def nested_options(self):
|
|
def get_class_by_object_literal_name(name):
|
|
for k, o in self.objects.iteritems():
|
|
if isinstance(o, Class) and o.object_literal.name == name:
|
|
return o
|
|
return None
|
|
|
|
for option in sorted(self.object_literal.prop_types.keys()):
|
|
types = self.object_literal.prop_types[option].split('|')
|
|
base, object_literal = None, None
|
|
for t in types:
|
|
if t in self.objects:
|
|
o = self.objects[t]
|
|
if isinstance(o, (Class, Symbol)):
|
|
if base:
|
|
raise RuntimeError('Multiple "class" types found for '
|
|
'option %s.%s: %s, %s.' %
|
|
(self.object_literal.name, option,
|
|
base.name, o.name))
|
|
base = o
|
|
elif isinstance(o, ObjectLiteral):
|
|
if object_literal:
|
|
raise RuntimeError('Multiple "literal" types found for '
|
|
'option %s.%s: %s, %s.' %
|
|
(self.object_literal.name, option,
|
|
object_literal.name, o.name))
|
|
object_literal = o
|
|
if object_literal:
|
|
if not base:
|
|
raise RuntimeError('%s "literal" type found for option %s.%s, '
|
|
'but no "class" type.' %
|
|
(object_literal.name, self.object_literal.name, option))
|
|
klass = get_class_by_object_literal_name(object_literal.name)
|
|
if not klass:
|
|
raise RuntimeError('No constructor taking a %s found.' %
|
|
object_literal.name)
|
|
yield option, object_literal, klass, base
|
|
|
|
def export(self):
|
|
lines = []
|
|
if self.object_literal is None:
|
|
lines.append('\n\ngoog.exportSymbol(\n \'%s\',\n %s);\n' % (self.name, self.name))
|
|
else:
|
|
lines.append('\n\n\n')
|
|
lines.append('/**\n')
|
|
lines.append(' * @constructor\n')
|
|
lines.append(' * @extends {%s}\n' % (self.name,))
|
|
lines.append(' * @param {%s} options Options.\n' % (self.object_literal.extern_name(),))
|
|
lines.append(' */\n')
|
|
lines.append('%s = function(options) {\n' % (self.export_name(),))
|
|
lines.append(' /** @type {%s} */\n' % (self.object_literal.name,))
|
|
lines.append(' var arg;\n');
|
|
lines.append(' if (goog.isDefAndNotNull(options)) {\n')
|
|
# FIXME: we modify the user's options object
|
|
lines.append(''.join(
|
|
' if (!(options.%(o)s instanceof %(base)s)) {\n'
|
|
' options.%(o)s = new %(ctor)s(\n'
|
|
' /** @type {%(extern)s} */ (options.%(o)s));\n'
|
|
' }\n' %
|
|
{'o': o, 'base': b.name, 'ctor': k.export_name(),
|
|
'extern': ol.extern_name()} \
|
|
for o, ol, k, b in self.nested_options()))
|
|
lines.append(' arg = {')
|
|
lines.extend(','.join('\n %s: options.%s' % (key, key) for key in sorted(self.object_literal.prop_types.keys())))
|
|
lines.append('\n };\n')
|
|
lines.append(' } else {\n')
|
|
lines.append(' arg = /** @type {%s} */ (options);\n' % (self.object_literal.name,))
|
|
lines.append(' }\n')
|
|
lines.append(' goog.base(this, arg);\n')
|
|
lines.append('};\n')
|
|
lines.append('goog.inherits(\n')
|
|
lines.append(' %sExport,\n' % (self.name,));
|
|
lines.append(' %s);\n' % (self.name,));
|
|
lines.append('goog.exportSymbol(\n')
|
|
lines.append(' \'%s\',\n' % (self.name,))
|
|
lines.append(' %s);\n' % (self.export_name(),))
|
|
lines.extend('goog.exportProperty(\n %s,\n \'%s\',\n %s.%s);\n' % (self.name, prop, self.name, prop) for prop in sorted(self.props))
|
|
return ''.join(lines)
|
|
|
|
def export_name(self):
|
|
return '%sExport' % self.name
|
|
|
|
|
|
class ObjectLiteral(Exportable):
|
|
|
|
def __init__(self, name):
|
|
Exportable.__init__(self, name)
|
|
self.prop_types = {}
|
|
|
|
__repr__ = simplerepr
|
|
|
|
def extern(self):
|
|
lines = []
|
|
lines.append('\n\n\n')
|
|
lines.append('/**\n')
|
|
lines.append(' * @interface\n')
|
|
lines.append(' */\n')
|
|
lines.append('%s = function() {};\n' % (self.extern_name(),))
|
|
for prop in sorted(self.prop_types.keys()):
|
|
lines.append('\n\n')
|
|
lines.append('/**\n')
|
|
lines.append(' * @type {%s}\n' % (self.prop_types[prop],))
|
|
lines.append(' */\n')
|
|
lines.append('%s.prototype.%s;\n' % (self.extern_name(), prop))
|
|
return ''.join(lines)
|
|
|
|
def extern_name(self):
|
|
return re.sub(r'ol\.(\S+)', r'olx.\1Extern', self.name)
|
|
|
|
def extern_namespace(self):
|
|
return '.'.join(self.extern_name().split('.')[:-1]) or None
|
|
|
|
def provide(self):
|
|
return 'goog.provide(\'%s\');\n' % (self.name,)
|
|
|
|
def typedef(self):
|
|
lines = []
|
|
lines.append('\n\n')
|
|
lines.append('/**\n')
|
|
for i, prop in enumerate(sorted(self.prop_types.keys())):
|
|
prefix = ' * @typedef {{' if i == 0 else ' * '
|
|
suffix = '}}' if i == len(self.prop_types) - 1 else ','
|
|
type = self.prop_types[prop]
|
|
if '|' in type:
|
|
type = '(%s)' % (type,)
|
|
lines.append('%s%s: %s%s\n' % (prefix, prop, type, suffix))
|
|
lines.append(' */\n')
|
|
lines.append('%s;\n' % (self.name,))
|
|
return ''.join(lines)
|
|
|
|
|
|
class Symbol(Exportable):
|
|
|
|
def __init__(self, name, export_symbol, export_as=None):
|
|
Exportable.__init__(self, name)
|
|
self.export_symbol = export_symbol
|
|
self.export_as = export_as or self.name
|
|
self.props = set()
|
|
|
|
__repr__ = simplerepr
|
|
|
|
def export(self):
|
|
lines = []
|
|
if self.export_symbol:
|
|
lines.append('\n\ngoog.exportSymbol(\n \'%s\',\n %s);\n' % (self.name, self.export_as))
|
|
lines.extend('goog.exportProperty(\n %s,\n \'%s\',\n %s.%s);\n' % (self.name, prop, self.name, prop) for prop in sorted(self.props))
|
|
return ''.join(lines)
|
|
|
|
|
|
def main(argv):
|
|
|
|
option_parser = OptionParser()
|
|
option_parser.add_option('--exports', action='store_true')
|
|
option_parser.add_option('--externs', action='store_true')
|
|
option_parser.add_option('--typedef', action='store_true')
|
|
options, args = option_parser.parse_args(argv[1:])
|
|
|
|
objects = {}
|
|
requires = set()
|
|
for arg in args:
|
|
in_comment = False
|
|
object_literal = None
|
|
for line in open(arg):
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
if line == '/**':
|
|
assert not in_comment
|
|
in_comment = True
|
|
continue
|
|
if line == '*/':
|
|
assert in_comment
|
|
in_comment = False
|
|
object_literal = None
|
|
continue
|
|
if in_comment:
|
|
if not line.startswith('*'):
|
|
raise RuntimeError(line) # malformed comment
|
|
m = re.match(r'\*\s*@typedef\s*\{Object\}\s*(?P<name>\S+)', line)
|
|
if m:
|
|
assert object_literal is None
|
|
name = m.group('name')
|
|
if name in objects:
|
|
raise RuntimeError(line) # Name already defined
|
|
object_literal = ObjectLiteral(name)
|
|
objects[name] = object_literal
|
|
continue
|
|
m = re.match(r'\*\s*@property\s*{(?P<type>.*)}\s*(?P<prop>\S+)', line)
|
|
if m:
|
|
assert object_literal is not None
|
|
prop = m.group('prop')
|
|
if prop in object_literal.prop_types:
|
|
raise RuntimeError(line) # Duplicate property
|
|
type = m.group('type')
|
|
object_literal.prop_types[prop] = type
|
|
continue
|
|
continue
|
|
m = re.match(r'@exportClass\s+(?P<name>\S+)(?:\s+(?P<object_literal_name>\S+))?\Z', line)
|
|
if m:
|
|
name = m.group('name')
|
|
if name in objects:
|
|
raise RuntimeError(line) # Name already defined
|
|
object_literal_name = m.group('object_literal_name')
|
|
object_literal = objects[object_literal_name]
|
|
if not isinstance(object_literal, ObjectLiteral):
|
|
raise RuntimeError(line) # Undefined object literal
|
|
klass = Class(name, object_literal, objects)
|
|
objects[name] = klass
|
|
continue
|
|
m = re.match(r'@exportProperty\s+(?P<prop>\S+)\Z', line)
|
|
if m:
|
|
components = m.group('prop').split('.')
|
|
if components[-2] == 'prototype':
|
|
requires.add('.'.join(components[:-2]))
|
|
else:
|
|
requires.add('.'.join(components[:-1]))
|
|
name = '.'.join(components[:-1])
|
|
prop = components[-1]
|
|
if name in objects:
|
|
symbol = objects[name]
|
|
else:
|
|
symbol = Symbol(name, False)
|
|
objects[name] = symbol
|
|
symbol.props.add(prop)
|
|
continue
|
|
m = re.match(r'@exportSymbol\s+(?P<name>\S+)(?:\s+(?P<export_as>\S+))?\Z', line)
|
|
if m:
|
|
name = m.group('name')
|
|
if name in objects:
|
|
raise RuntimeError(line) # Name already defined
|
|
export_as = m.group('export_as')
|
|
symbol = Symbol(name, True, export_as)
|
|
objects[name] = symbol
|
|
if not export_as:
|
|
components = m.group('name').split('.')
|
|
if re.match(r'[A-Z]', components[-1]):
|
|
requires.add(name)
|
|
elif len(components) > 1:
|
|
requires.add('.'.join(components[:-1]))
|
|
continue
|
|
raise RuntimeError(line)
|
|
|
|
objects = sorted(objects.values(), key=attrgetter('name'))
|
|
|
|
if options.exports:
|
|
requires.update(obj.name for obj in objects if isinstance(obj, Class))
|
|
if requires:
|
|
for require in sorted(requires):
|
|
sys.stdout.write('goog.require(\'%s\');\n' % (require,))
|
|
for obj in objects:
|
|
sys.stdout.write(obj.export())
|
|
|
|
if options.externs:
|
|
object_literals = [obj for obj in objects if isinstance(obj, ObjectLiteral)]
|
|
sys.stdout.write('/**\n')
|
|
sys.stdout.write(' * @externs\n')
|
|
sys.stdout.write(' */\n')
|
|
namespaces = sorted(set(filter(None, (object_literal.extern_namespace() for object_literal in object_literals))))
|
|
for namespace in namespaces:
|
|
sys.stdout.write('\n\n')
|
|
sys.stdout.write('/**\n')
|
|
sys.stdout.write(' * @type {Object}\n')
|
|
sys.stdout.write(' */\n')
|
|
if '.' in namespace:
|
|
sys.stdout.write('%s = {};\n' % (namespace,))
|
|
else:
|
|
sys.stdout.write('var %s;\n' % (namespace,))
|
|
for object_literal in object_literals:
|
|
sys.stdout.write(object_literal.extern())
|
|
|
|
if options.typedef:
|
|
object_literals = [obj for obj in objects if isinstance(obj, ObjectLiteral)]
|
|
for object_literal in object_literals:
|
|
sys.stdout.write(object_literal.provide())
|
|
for object_literal in object_literals:
|
|
sys.stdout.write(object_literal.typedef())
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|