Chromium Code Reviews| Index: tools/json_schema_compiler/dart_generator.py |
| diff --git a/tools/json_schema_compiler/dart_generator.py b/tools/json_schema_compiler/dart_generator.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4165c711430b28ba03c8ec9a3c9a609684adcd2b |
| --- /dev/null |
| +++ b/tools/json_schema_compiler/dart_generator.py |
| @@ -0,0 +1,628 @@ |
| +# Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| +""" |
| +Generator language component for compiler.py that adds Dart language support. |
| + |
| +Pass 'dart' with the -l flag to compiler.py to activate the use of this library. |
| +""" |
| + |
| +from collections import defaultdict |
| + |
| +from code import Code |
| +from model import * |
| + |
| +import copy |
|
not at google - send to devlin
2013/01/25 18:14:33
unused?
sashab
2013/01/29 08:27:13
Fixed; nice spotting
|
| + |
| +LICENSE = """ |
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
|
not at google - send to devlin
2013/01/25 18:14:33
use datetime.now().year rather than 2013
sashab
2013/01/29 08:27:13
Good idea! :)
|
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file.""" |
| + |
| +class DartGenerator(object): |
| + """A .dart generator for a namespace. |
| + """ |
| + |
| + def __init__(self, namespace, custom_hooks_file): |
| + self._namespace = namespace |
| + self._types = namespace.types |
| + self._hooks = self._ParseCustomHooksFile(custom_hooks_file) |
| + |
| + def _ParseCustomHooksFile(self, filename): |
| + """Parses the custom hooks file at filename into a dictionary: |
| + (type_name, method_name) -> [code_string, list of lines] |
| + If the method is a getter or setter, it will be space-separated and appended |
| + to the end of the method_name. |
| + |
| + e.g. |
| + ('app.window.AppWindow', 'contentWindow'): ['void contentWindow ()'] |
| + ('app.window.AppWindow', 'contentWindow get'): ['int get contentWindow()'] |
| + """ |
| + if filename == None: |
| + return {} |
| + |
| + hooks = {} |
| + with open(filename) as f: |
| + current_key = None |
| + for line in f: |
| + if line.startswith('// START'): |
| + # TODO(sashab): Account for any whitespace, not just spaces |
| + current_key = tuple(line[len('// START'):].strip().split(' ', 1)) |
| + hooks[current_key] = [] |
| + elif line.startswith('// END'): |
| + current_key = None |
| + elif current_key: |
| + hooks[current_key].append(line) |
| + return hooks |
| + |
| + def Generate(self): |
| + """Generates a Code object with the .dart for the entire namespace. |
| + """ |
| + c = Code() |
| + (c.Append(LICENSE) |
| + .Append() |
| + .Append('part of chrome;')) |
| + |
| + # Add all types. |
| + if self._types: |
| + (c.Append() |
| + .Append('/**') |
| + .Append(' * Types') |
| + .Append(' */') |
| + ) |
| + for type_name in self._types: |
| + c.Concat(self._GenerateType(self._types[type_name])) |
| + |
| + # Add all events. |
| + if self._namespace.events: |
| + (c.Append() |
| + .Append('/**') |
| + .Append(' * Events') |
| + .Append(' */') |
| + ) |
| + for event_name in self._namespace.events: |
| + c.Concat(self._GenerateEvent(self._namespace.events[event_name])) |
| + |
| + # Add main class for this file. |
| + (c.Append() |
| + .Append('/**') |
| + .Append(' * Functions') |
| + .Append(' */') |
| + ) |
| + c.Concat(self._GenerateMainClass()) |
| + |
| + return c |
| + |
| + def _GenerateType(self, type_): |
| + """Given a Type object, returns the Code with the .dart for this |
| + type's definition. |
| + |
| + Assumes this type is a Parameter Type (creatable by user), and creates an |
| + object that extends ChromeObject. All parameters are specifiable as named |
| + arguments in the constructor, and all methods are wrapped with getters and |
| + setters that hide the JS() implementation. |
| + """ |
| + c = Code() |
| + (c.Append() |
| + .Concat(self._GenerateDocumentation(type_)) |
| + .Sblock('class %(type_name)s extends ChromeObject {') |
| + ) |
| + |
| + # Check whether this type has function members. If it does, don't allow |
| + # public construction. |
| + add_public_constructor = True |
| + for prop_name in type_.properties: |
| + if self._IsFunction(type_.properties[prop_name]): |
| + add_public_constructor = False |
| + break |
| + |
| + constructor_fields = [] |
| + for prop_name in type_.properties: |
| + constructor_fields.append( |
| + self._GeneratePropertySignature(type_.properties[prop_name], |
| + prependThis = False, |
| + omitBasicTypes = False)) |
| + |
| + # Add the public constructor. |
| + if add_public_constructor: |
| + (c.Append('/*') |
| + .Append(' * Public constructor') |
| + .Append(' */') |
|
not at google - send to devlin
2013/01/25 18:14:33
is generating these comments useful?
sashab
2013/01/29 08:27:13
I think it helps make the final code more readable
|
| + .Sblock('%(type_name)s({%(constructor_fields)s}) {') |
| + ) |
| + |
| + for prop_name in type_.properties: |
| + c.Append('this.%s = %s;' % (prop_name, prop_name)) |
| + (c.Eblock('}') |
| + .Append() |
| + ) |
| + |
| + # Add the private constructor. |
| + (c.Append('/*') |
| + .Append(' * Private constructor') |
| + .Append(' */') |
| + .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);') |
| + ) |
| + |
| + # Add an accessor (getter & setter) for each property. |
| + properties = [t for t in type_.properties.values() |
| + if not self._IsFunction(t)] |
| + if properties: |
| + (c.Append() |
| + .Append('/*') |
| + .Append(' * Public accessors') |
| + .Append(' */') |
| + ) |
| + for prop in properties: |
| + type_name = self._GetPropertyType(prop) |
| + prop_is_base_type = self._IsBaseType(prop) |
| + |
| + # Add the documentation for this property |
| + (c.Append() |
| + .Concat(self._GenerateDocumentation(prop)) |
| + ) |
| + |
| + # Check for custom hooks. |
| + add_getter = True |
| + add_setter = True |
| + |
| + hook_tuple = (type_.name, prop.name) |
| + if hook_tuple in self._hooks: |
| + for line in self._hooks[hook_tuple]: |
| + c.Append(line) |
| + add_getter = False |
| + add_setter = False |
| + |
| + # Add the getter. |
| + if add_getter: |
| + hook_tuple_get = (type_.name, prop.name + ' get') |
| + if hook_tuple_get in self._hooks: |
| + for line in self._hooks[hook_tuple_get]: |
| + c.Append(line) |
| + add_getter = False |
| + |
| + if add_getter: |
| + # TODO(sashab): Serialize generic Dart objects differently. |
| + if prop_is_base_type or self._IsObjectType(prop): |
| + c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" % |
| + (type_name, prop.name, type_name, prop.name)) |
| + elif self._IsReferencedType(prop): |
| + c.Append("%s get %s => new %s._proxy(JS('', '#.%s', this._jsObject));" |
| + % (type_name, prop.name, type_name, prop.name)) |
| + else: |
| + # TODO(sashab): What to do in this situation? Unserializable type. |
| + c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" % |
| + (type_name, prop.name, type_name, prop.name)) |
| + |
| + # Add the setter. |
| + if add_setter: |
| + hook_tuple_set = (type_.name, prop.name + ' set') |
| + if hook_tuple_set in self._hooks: |
| + for line in self._hooks[hook_tuple_set]: |
| + c.Append(line) |
| + add_setter = False |
| + |
| + if add_setter: |
| + wrapped_name = prop.name |
| + if not prop_is_base_type: |
| + wrapped_name = 'convertArgument(%s)' % prop.name |
| + |
| + (c.Append() |
| + .Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name)) |
| + .Append("JS('void', '#.%s = #', this._jsObject, %s);" % |
| + (prop.name, wrapped_name)) |
| + .Eblock("}") |
| + ) |
| + |
| + # Now add all the function properties. |
| + function_properties = [t for t in type_.properties.values() |
| + if self._IsFunction(t)] |
| + if function_properties: |
| + (c.Append() |
| + .Append('/*') |
| + .Append(' * Methods') |
| + .Append(' */') |
| + ) |
| + for prop in function_properties: |
| + c.Concat(self._GenerateFunction(prop)) |
| + |
| + (c.Eblock('}') |
| + .Substitute({ |
| + 'type_name': type_.simple_name, |
| + 'constructor_fields': ', '.join(constructor_fields) |
| + }) |
| + ) |
| + |
| + return c |
| + |
| + def _GenerateDocumentation(self, prop): |
| + """Given an object, generates the documentation for this object (as a |
| + code string) and returns the Code object. |
| + |
| + Returns an empty code object if the object has no documentation. |
| + |
| + Uses triple-quotes for the string. |
| + """ |
| + c = Code() |
| + if not hasattr(prop, 'description'): |
| + return c |
| + |
| + if prop.description: |
| + for line in prop.description.split('\n'): |
| + c.Append('/// %s' % line) |
| + return c |
|
not at google - send to devlin
2013/01/25 18:14:33
could you add this functionality to Code in a gene
sashab
2013/01/29 08:27:13
Saw the Comment method before I read this; a much
|
| + |
| + |
| + def _GenerateFunction(self, f): |
| + """Returns the Code object for the given function. |
| + """ |
| + return (Code() |
| + .Append() |
| + .Concat(self._GenerateDocumentation(f)) |
| + .Append("%s => %s;" % (self._GenerateFunctionSignature(f), |
| + self._GenerateProxyCall(f))) |
| + ) |
| + |
| + def _GenerateProxyCall(self, function, callTarget='this._jsObject'): |
| + """Given a function, generates the code to call that function via JS(). |
| + Returns a string. |
| + |
| + |callTarget| is the name of the object to call the function on. The default |
| + is this._jsObject. |
| + |
| + e.g. |
| + JS('void', '#.resizeTo(#, #)', this._jsObject, width, height) |
| + JS('void', '#.setBounds(#)', this._jsObject, convertArgument(bounds)) |
| + """ |
| + |
| + format = ("JS('%(return_type)s', " |
| + "'#.%(name)s(%(param_hashes)s)', " |
| + "%(target)s%(params)s)") |
| + |
| + params = "" |
| + if function.params: |
| + params_wrapped = [] |
| + for param in function.params: |
| + if not self._IsBaseType(param): |
| + params_wrapped.append('convertArgument(%s)' % param.name) |
| + else: |
| + params_wrapped.append(param.name) |
| + params = ', ' + ', '.join(params_wrapped) |
| + |
| + return format % { |
| + 'return_type': self._GetPropertyType(function.returns), |
| + 'name': function.name, |
| + 'param_hashes': ', '.join('#' for p in function.params), |
| + 'target': callTarget, |
| + 'params': params |
| + } |
| + |
| + def _GenerateEvent(self, event): |
| + """Given a Function object, returns the Code with the .dart for this event, |
| + represented by the function. |
| + |
| + All events extend the Event base type. |
| + """ |
| + c = Code() |
| + |
| + # Add documentation for this event. |
| + (c.Append() |
| + .Concat(self._GenerateDocumentation(event)) |
| + .Sblock('class Event_%(event_name)s extends Event {') |
| + ) |
| + |
| + # Override Event callback type definitions. |
| + for ret_type, event_func in (('void', 'addListener'), |
| + ('void', 'removeListener'), |
| + ('bool', 'hasListener')): |
| + |
| + param_list = self._GenerateParameterList(event.params, event.callback, |
| + allow_optional = False) |
| + |
| + c.Append('%s %s(void callback(%s)) => super.%s(callback);' % |
| + (ret_type, event_func, param_list, event_func)) |
| + |
| + # Generate the constructor. |
| + (c.Append() |
| + .Append('Event_%(event_name)s(jsObject) : ' |
| + 'super(jsObject, %(param_num)d);') |
| + ) |
| + |
| + (c.Eblock('}') |
| + .Substitute({ |
| + 'event_name': self._namespace.unix_name + '_' + event.name, |
| + 'param_num': len(event.params) |
| + }) |
| + ) |
| + |
| + return c |
| + |
| + def _GenerateMainClass(self): |
| + """Generates the main class for this file, which links to all functions |
| + and events. |
| + |
| + Includes a ChromeApi member variable to represent the connection. |
| + |
| + Returns a code object. |
| + """ |
| + c = Code() |
| + (c.Append() |
| + .Sblock('class API_%(namespace_name)s {') |
| + .Append('/*') |
| + .Append(' * API connection') |
| + .Append(' */') |
| + .Append('Object _jsObject;') |
| + ) |
| + |
| + # Add events. |
| + if self._namespace.events: |
| + (c.Append() |
| + .Append('/*') |
| + .Append(' * Events') |
| + .Append(' */') |
| + ) |
| + for event_name in self._namespace.events: |
| + c.Append('Event_%s_%s %s;' % (self._namespace.unix_name, event_name, |
| + event_name)) |
| + |
| + # Add functions. |
| + if self._namespace.functions: |
| + (c.Append() |
| + .Append('/*') |
| + .Append(' * Functions') |
| + .Append(' */') |
| + ) |
| + for function in self._namespace.functions.values(): |
| + c.Concat(self._GenerateFunction(function)) |
| + |
| + # Add the constructor. |
| + (c.Append() |
| + .Sblock('API_%(namespace_name)s(this._jsObject) {') |
| + ) |
| + |
| + # Add events to constructor. |
| + for event_name in self._namespace.events: |
| + c.Append("%s = new Event_%s_%s(JS('', '#.%s', this._jsObject));" % |
| + (event_name, self._namespace.unix_name, event_name, event_name)) |
| + c.Eblock('}') |
| + |
| + (c.Eblock('}') |
| + .Substitute({ |
| + 'namespace_name': self._namespace.unix_name |
| + }) |
| + ) |
| + |
| + return c |
| + |
| + def _GeneratePropertySignature(self, prop, prependThis = False, |
|
not at google - send to devlin
2013/01/25 18:14:33
style is prepend_this=False
i.e. underscore style
sashab
2013/01/29 08:27:13
Done, and fixed spacing issue for ='s everywhere i
|
| + omitBasicTypes = False, |
| + functionsAsObjects = False, |
| + withGetKeyword = False): |
| + """Given a property, returns a signature for that property. |
| + Recursively generates the signature for callbacks. |
| + Returns a String for the given property. |
| + |
| + * If |prependThis| is True, prepends "this." to all variable names. |
| + * If |omitBasicTypes| is True, only adds type names for function types. |
| + * If |functionsAsObjects| is True, treats callbacks as basic types and |
| + prepends the type 'Function'. |
| + * If |withGetKeyword| is True, adds the word 'get' between the property's |
| + type and name. Used for getters in dart. |
| + |
| + e.g. |
| + bool x |
| + void onClosed() |
| + void doSomething(bool x, void callback([String x])) |
| + |
| + e.g. If prependThis is True: |
| + bool this.x |
| + void this.onClosed() |
| + void this.doSomething(bool x, void callback([String x])) |
| + |
| + e.g. If omitBasicTypes is True: |
| + this.x |
| + void onClosed() |
| + void doSomething(bool x, void callback([String x])) |
| + |
| + e.g. If functionsAsObjects is True: |
| + bool x |
| + Function onClosed |
| + Function doSomething |
| + |
| + e.g. If withGetKeyword and functionsAsObjects is True: |
| + bool get x |
| + Function get onClosed |
| + Function get doSomething |
| + """ |
| + if self._IsFunction(prop) and not functionsAsObjects: |
| + return self._GenerateFunctionSignature(prop, prependThis) |
| + else: |
| + name_parts = [prop.simple_name] |
| + if prependThis: |
| + name_parts[0] = 'this.' + name_parts[0] |
| + if withGetKeyword: |
| + name_parts = ['get'] + name_parts |
| + |
| + name = ' '.join(name_parts) |
| + type_ = (self._GetPropertyType(prop) + ' ') if not omitBasicTypes else '' |
| + |
| + return '%(type)s%(name)s' % { |
| + 'type': type_, |
| + 'name': name |
| + } |
| + |
| + def _GenerateFunctionSignature(self, function, prependThis = False): |
| + """Given a function object, returns the signature for that function. |
| + Recursively generates the signature for callbacks. |
| + Returns a String for the given function. |
| + |
| + If prependThis is True, adds "this." to the function's name. |
| + |
| + e.g. |
| + void onClosed() |
| + bool isOpen([String type]) |
| + void doSomething(bool x, void callback([String x])) |
| + |
| + e.g. If prependThis is True: |
| + void this.onClosed() |
| + bool this.isOpen([String type]) |
| + void this.doSomething(bool x, void callback([String x])) |
| + """ |
| + sig = '%(return_type)s %(name)s(%(params)s)' |
| + |
| + # Get function return type. |
| + if function.returns: |
| + return_type = self._GetPropertyType(function.returns) |
| + else: |
| + return_type = 'void' |
| + |
| + # Get function name. |
| + function_name = function.simple_name |
| + if prependThis: |
| + function_name = 'this.' + function_name |
| + |
| + return sig % { |
| + 'return_type': return_type, |
| + 'name': function_name, |
| + 'params': self._GenerateParameterList(function.params, |
| + getattr(function, 'callback', None)) |
| + } |
| + |
| + def _GenerateParameterList(self, params, callback=None, |
| + allow_optional = True): |
| + """Given a list of function parameters, generates their signature (as a |
| + string). |
| + |
| + e.g. |
| + [String type] |
| + bool x, void callback([String x]) |
| + |
| + If allow_optional is False, ignores optional parameters. Useful for |
| + callbacks, where optional parameters are not used. |
| + """ |
| + # params lists (required & optional), to be joined with ,'s |
| + # FIXME(sashab): assuming all optional params come after required ones |
| + params_req = [] |
| + params_opt = [] |
| + for param in params: |
| + p_sig = self._GeneratePropertySignature(param) |
| + if allow_optional and param.optional: |
| + params_opt.append(p_sig) |
| + else: |
| + params_req.append(p_sig) |
| + |
| + # Add the callback, if it exists. |
| + if callback: |
| + c_sig = self._GenerateFunctionSignature(callback) |
| + if callback.optional: |
| + params_opt.append(c_sig) |
| + else: |
| + params_req.append(c_sig) |
| + |
| + # join params |
| + params = '' |
| + if params_req: |
| + params += ', '.join(params_req) |
| + if params_opt: |
| + params += ', ' |
| + if params_opt: |
| + params += '[' + ', '.join(params_opt) + ']' |
| + |
| + return params |
| + |
| + def _GetNamespace(self, name): |
| + """Given a name a.b.c, returns the namespace (in this case, a.b). |
| + """ |
| + return name.rsplit('.', 1)[0] |
| + |
| + def _GetBaseName(self, name): |
| + """Given a name a.b.c, returns the base name of the path (in this case, c). |
| + """ |
| + return name.rsplit('.', 1)[1] |
| + |
| + def _IsReferencedType(self, prop): |
| + """Given a model.Property, returns whether this type is a referenced type. |
| + """ |
| + return prop.type_ == PropertyType.REF |
|
not at google - send to devlin
2013/01/25 18:14:33
I think that the value of these methods isn't that
sashab
2013/01/29 08:27:13
Got rid of this one, but kept the other two (since
|
| + |
| + def _IsFunction(self, prop): |
| + """Given a model.Property, returns whether this type is a function. |
| + """ |
| + return prop.type_ == PropertyType.FUNCTION |
| + |
| + def _IsObjectType(self, prop): |
| + """Given a model.Property, returns whether this type is an object. |
| + """ |
| + return (prop.type_ == PropertyType.OBJECT or |
| + prop.type_ == PropertyType.ANY) |
| + |
| + def _IsBaseType(self, prop): |
| + """Given a model.Property, returns whether this type is a base type |
| + (string, number or boolean). |
| + """ |
| + base_type = self._GetPropertyType(prop) |
| + if base_type in ['bool', 'num', 'int', 'double', 'String']: |
| + return True |
| + return False |
| + |
| + def _GetReferencedType(self, name): |
| + """Given the name of a referenced type, returns the type object for that |
| + reference. |
| + |
| + Returns None if the type is not found. |
| + """ |
| + if name in self._namespace.types: |
| + return self._namespace.types[name] |
| + return None |
| + |
| + def _GetPropertyType(self, prop): |
|
not at google - send to devlin
2013/01/25 18:14:33
when you sync, _GetDartType(self, type_)
sashab
2013/01/29 08:27:13
You mean I should rename this function, and make i
|
| + """Given a model.Property object, returns its type as a Dart string. |
| + """ |
| + if prop == None: |
| + return 'void' |
| + |
| + dart_type = None |
| + type_ = prop.type_ |
| + |
| + if type_ == None: |
| + dart_type = 'void' |
| + elif type_ == PropertyType.REF: |
| + if self._GetNamespace(prop.ref_type) == self._namespace.name: |
| + # This type is in this namespace; just use its base name. |
| + dart_type = self._GetBaseName(prop.ref_type) |
| + else: |
| + # TODO(sashab): Work out how to import this foreign type. |
| + dart_type = prop.ref_type |
| + elif type_ == PropertyType.BOOLEAN: |
| + dart_type = 'bool' |
| + elif type_ == PropertyType.INTEGER: |
| + dart_type = 'int' |
| + elif type_ == PropertyType.INT64: |
| + dart_type = 'num' |
| + elif type_ == PropertyType.DOUBLE: |
| + dart_type = 'double' |
| + elif type_ == PropertyType.STRING: |
| + dart_type = 'String' |
| + elif type_ == PropertyType.ENUM: |
| + dart_type = 'String' |
| + elif type_ == PropertyType.ADDITIONAL_PROPERTIES: |
| + dart_type = 'Map' |
| + elif type_ == PropertyType.ANY: |
| + dart_type = 'Object' |
| + elif type_ == PropertyType.OBJECT: |
| + # TODO(sashab): Work out a mapped type name? |
| + dart_type = prop.instance_of or 'Object' |
| + elif type_ == PropertyType.FUNCTION: |
| + dart_type = 'Function' |
| + elif type_ == PropertyType.ARRAY: |
| + if hasattr(prop, 'item_type'): |
| + container_type = self._GetPropertyType(prop.item_type) |
| + dart_type = 'List<%s>' % container_type |
| + else: |
| + dart_type = 'List' |
| + elif type_ == PropertyType.BINARY: |
| + dart_type = 'String' |
| + else: |
| + raise NotImplementedError(type_) |
| + |
| + return dart_type.strip() |