diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/tools/json_schema_compiler | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/tools/json_schema_compiler')
52 files changed, 7140 insertions, 0 deletions
diff --git a/chromium/tools/json_schema_compiler/PRESUBMIT.py b/chromium/tools/json_schema_compiler/PRESUBMIT.py new file mode 100644 index 00000000000..b98649b3b4b --- /dev/null +++ b/chromium/tools/json_schema_compiler/PRESUBMIT.py @@ -0,0 +1,20 @@ +# 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. + +"""Presubmit script for changes affecting tools/json_schema_compiler/ + +See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts +for more details about the presubmit API built into gcl. +""" + +WHITELIST = [ r'.+_test.py$' ] + +def CheckChangeOnUpload(input_api, output_api): + return input_api.canned_checks.RunUnitTestsInDirectory( + input_api, output_api, '.', whitelist=WHITELIST) + + +def CheckChangeOnCommit(input_api, output_api): + return input_api.canned_checks.RunUnitTestsInDirectory( + input_api, output_api, '.', whitelist=WHITELIST) diff --git a/chromium/tools/json_schema_compiler/api_gen_util.gyp b/chromium/tools/json_schema_compiler/api_gen_util.gyp new file mode 100644 index 00000000000..54966cc6030 --- /dev/null +++ b/chromium/tools/json_schema_compiler/api_gen_util.gyp @@ -0,0 +1,20 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [{ + 'target_name': 'api_gen_util', + 'type': 'static_library', + 'sources': [ + 'util.cc', + ], + 'dependencies': ['<(DEPTH)/base/base.gyp:base'], + 'include_dirs': [ + '<(DEPTH)', + ], + }], +} diff --git a/chromium/tools/json_schema_compiler/cc_generator.py b/chromium/tools/json_schema_compiler/cc_generator.py new file mode 100644 index 00000000000..d7d565e3846 --- /dev/null +++ b/chromium/tools/json_schema_compiler/cc_generator.py @@ -0,0 +1,943 @@ +# 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. + +from code import Code +from model import PropertyType, Type +import cpp_util +import model +import schema_util +import sys +import util_cc_helper + +class CCGenerator(object): + def __init__(self, type_generator, cpp_namespace): + self._type_generator = type_generator + self._cpp_namespace = cpp_namespace + + def Generate(self, namespace): + return _Generator(namespace, + self._type_generator, + self._cpp_namespace).Generate() + +class _Generator(object): + """A .cc generator for a namespace. + """ + def __init__(self, namespace, cpp_type_generator, cpp_namespace): + self._namespace = namespace + self._type_helper = cpp_type_generator + self._cpp_namespace = cpp_namespace + self._target_namespace = ( + self._type_helper.GetCppNamespaceName(self._namespace)) + self._util_cc_helper = ( + util_cc_helper.UtilCCHelper(self._type_helper)) + self._generate_error_messages = namespace.compiler_options.get( + 'generate_error_messages', False) + + def Generate(self): + """Generates a Code object with the .cc for a single namespace. + """ + c = Code() + (c.Append(cpp_util.CHROMIUM_LICENSE) + .Append() + .Append(cpp_util.GENERATED_FILE_MESSAGE % self._namespace.source_file) + .Append() + .Append(self._util_cc_helper.GetIncludePath()) + .Append('#include "base/logging.h"') + .Append('#include "base/strings/string_number_conversions.h"') + .Append('#include "%s/%s.h"' % + (self._namespace.source_file_dir, self._namespace.unix_name)) + .Cblock(self._type_helper.GenerateIncludes(include_soft=True)) + .Append() + .Concat(cpp_util.OpenNamespace(self._cpp_namespace)) + .Cblock(self._type_helper.GetNamespaceStart()) + ) + if self._namespace.properties: + (c.Append('//') + .Append('// Properties') + .Append('//') + .Append() + ) + for property in self._namespace.properties.values(): + property_code = self._type_helper.GeneratePropertyValues( + property, + 'const %(type)s %(name)s = %(value)s;', + nodoc=True) + if property_code: + c.Cblock(property_code) + if self._namespace.types: + (c.Append('//') + .Append('// Types') + .Append('//') + .Append() + .Cblock(self._GenerateTypes(None, self._namespace.types.values())) + ) + if self._namespace.functions: + (c.Append('//') + .Append('// Functions') + .Append('//') + .Append() + ) + for function in self._namespace.functions.values(): + c.Cblock(self._GenerateFunction(function)) + if self._namespace.events: + (c.Append('//') + .Append('// Events') + .Append('//') + .Append() + ) + for event in self._namespace.events.values(): + c.Cblock(self._GenerateEvent(event)) + (c.Concat(self._type_helper.GetNamespaceEnd()) + .Cblock(cpp_util.CloseNamespace(self._cpp_namespace)) + ) + return c + + def _GenerateType(self, cpp_namespace, type_): + """Generates the function definitions for a type. + """ + classname = cpp_util.Classname(schema_util.StripNamespace(type_.name)) + c = Code() + + if type_.functions: + # Wrap functions within types in the type's namespace. + (c.Append('namespace %s {' % classname) + .Append()) + for function in type_.functions.values(): + c.Cblock(self._GenerateFunction(function)) + c.Append('} // namespace %s' % classname) + elif type_.property_type == PropertyType.ARRAY: + c.Cblock(self._GenerateType(cpp_namespace, type_.item_type)) + elif type_.property_type in (PropertyType.CHOICES, + PropertyType.OBJECT): + if cpp_namespace is None: + classname_in_namespace = classname + else: + classname_in_namespace = '%s::%s' % (cpp_namespace, classname) + + if type_.property_type == PropertyType.OBJECT: + c.Cblock(self._GeneratePropertyFunctions(classname_in_namespace, + type_.properties.values())) + else: + c.Cblock(self._GenerateTypes(classname_in_namespace, type_.choices)) + + (c.Append('%s::%s()' % (classname_in_namespace, classname)) + .Cblock(self._GenerateInitializersAndBody(type_)) + .Append('%s::~%s() {}' % (classname_in_namespace, classname)) + .Append() + ) + if type_.origin.from_json: + c.Cblock(self._GenerateTypePopulate(classname_in_namespace, type_)) + if cpp_namespace is None: # only generate for top-level types + c.Cblock(self._GenerateTypeFromValue(classname_in_namespace, type_)) + if type_.origin.from_client: + c.Cblock(self._GenerateTypeToValue(classname_in_namespace, type_)) + elif type_.property_type == PropertyType.ENUM: + (c.Cblock(self._GenerateEnumToString(cpp_namespace, type_)) + .Cblock(self._GenerateEnumFromString(cpp_namespace, type_)) + ) + + return c + + def _GenerateInitializersAndBody(self, type_): + items = [] + for prop in type_.properties.values(): + if prop.optional: + continue + + t = prop.type_ + if t.property_type == PropertyType.INTEGER: + items.append('%s(0)' % prop.unix_name) + elif t.property_type == PropertyType.DOUBLE: + items.append('%s(0.0)' % prop.unix_name) + elif t.property_type == PropertyType.BOOLEAN: + items.append('%s(false)' % prop.unix_name) + elif (t.property_type == PropertyType.ANY or + t.property_type == PropertyType.ARRAY or + t.property_type == PropertyType.BINARY or # mapped to std::string + t.property_type == PropertyType.CHOICES or + t.property_type == PropertyType.ENUM or + t.property_type == PropertyType.OBJECT or + t.property_type == PropertyType.FUNCTION or + t.property_type == PropertyType.REF or + t.property_type == PropertyType.STRING): + # TODO(miket): It would be nice to initialize CHOICES and ENUM, but we + # don't presently have the semantics to indicate which one of a set + # should be the default. + continue + else: + raise TypeError(t) + + if items: + s = ': %s' % (', '.join(items)) + else: + s = '' + s = s + ' {}' + return Code().Append(s) + + def _GenerateTypePopulate(self, cpp_namespace, type_): + """Generates the function for populating a type given a pointer to it. + + E.g for type "Foo", generates Foo::Populate() + """ + classname = cpp_util.Classname(schema_util.StripNamespace(type_.name)) + c = Code() + (c.Append('// static') + .Append('bool %(namespace)s::Populate(') + .Sblock(' %s) {' % self._GenerateParams( + ('const base::Value& value', '%(name)s* out')))) + + if type_.property_type == PropertyType.CHOICES: + for choice in type_.choices: + (c.Sblock('if (%s) {' % self._GenerateValueIsTypeExpression('value', + choice)) + .Concat(self._GeneratePopulateVariableFromValue( + choice, + '(&value)', + 'out->as_%s' % choice.unix_name, + 'false', + is_ptr=True)) + .Append('return true;') + .Eblock('}') + ) + (c.Concat(self._GenerateError( + '"expected %s, got " + %s' % + (" or ".join(choice.name for choice in type_.choices), + self._util_cc_helper.GetValueTypeString('value')))) + .Append('return false;')) + elif type_.property_type == PropertyType.OBJECT: + (c.Sblock('if (!value.IsType(base::Value::TYPE_DICTIONARY)) {') + .Concat(self._GenerateError( + '"expected dictionary, got " + ' + + self._util_cc_helper.GetValueTypeString('value'))) + .Append('return false;') + .Eblock('}')) + + if type_.properties or type_.additional_properties is not None: + c.Append('const base::DictionaryValue* dict = ' + 'static_cast<const base::DictionaryValue*>(&value);') + for prop in type_.properties.values(): + c.Concat(self._InitializePropertyToDefault(prop, 'out')) + for prop in type_.properties.values(): + c.Concat(self._GenerateTypePopulateProperty(prop, 'dict', 'out')) + if type_.additional_properties is not None: + if type_.additional_properties.property_type == PropertyType.ANY: + c.Append('out->additional_properties.MergeDictionary(dict);') + else: + cpp_type = self._type_helper.GetCppType(type_.additional_properties, + is_in_container=True) + (c.Append('for (base::DictionaryValue::Iterator it(*dict);') + .Sblock(' !it.IsAtEnd(); it.Advance()) {') + .Append('%s tmp;' % cpp_type) + .Concat(self._GeneratePopulateVariableFromValue( + type_.additional_properties, + '(&it.value())', + 'tmp', + 'false')) + .Append('out->additional_properties[it.key()] = tmp;') + .Eblock('}') + ) + c.Append('return true;') + (c.Eblock('}') + .Substitute({'namespace': cpp_namespace, 'name': classname})) + return c + + def _GenerateValueIsTypeExpression(self, var, type_): + real_type = self._type_helper.FollowRef(type_) + if real_type.property_type is PropertyType.CHOICES: + return '(%s)' % ' || '.join(self._GenerateValueIsTypeExpression(var, + choice) + for choice in real_type.choices) + return '%s.IsType(%s)' % (var, cpp_util.GetValueType(real_type)) + + def _GenerateTypePopulateProperty(self, prop, src, dst): + """Generate the code to populate a single property in a type. + + src: base::DictionaryValue* + dst: Type* + """ + c = Code() + value_var = prop.unix_name + '_value' + c.Append('const base::Value* %(value_var)s = NULL;') + if prop.optional: + (c.Sblock( + 'if (%(src)s->GetWithoutPathExpansion("%(key)s", &%(value_var)s)) {') + .Concat(self._GeneratePopulatePropertyFromValue( + prop, value_var, dst, 'false'))) + underlying_type = self._type_helper.FollowRef(prop.type_) + if underlying_type.property_type == PropertyType.ENUM: + (c.Append('} else {') + .Append('%%(dst)s->%%(name)s = %s;' % + self._type_helper.GetEnumNoneValue(prop.type_))) + c.Eblock('}') + else: + (c.Sblock( + 'if (!%(src)s->GetWithoutPathExpansion("%(key)s", &%(value_var)s)) {') + .Concat(self._GenerateError('"\'%%(key)s\' is required"')) + .Append('return false;') + .Eblock('}') + .Concat(self._GeneratePopulatePropertyFromValue( + prop, value_var, dst, 'false')) + ) + c.Append() + c.Substitute({ + 'value_var': value_var, + 'key': prop.name, + 'src': src, + 'dst': dst, + 'name': prop.unix_name + }) + return c + + def _GenerateTypeFromValue(self, cpp_namespace, type_): + classname = cpp_util.Classname(schema_util.StripNamespace(type_.name)) + c = Code() + (c.Append('// static') + .Append('scoped_ptr<%s> %s::FromValue(%s) {' % (classname, + cpp_namespace, self._GenerateParams(('const base::Value& value',)))) + .Append(' scoped_ptr<%s> out(new %s());' % (classname, classname)) + .Append(' if (!Populate(%s))' % self._GenerateArgs( + ('value', 'out.get()'))) + .Append(' return scoped_ptr<%s>();' % classname) + .Append(' return out.Pass();') + .Append('}') + ) + return c + + def _GenerateTypeToValue(self, cpp_namespace, type_): + """Generates a function that serializes the type into a base::Value. + E.g. for type "Foo" generates Foo::ToValue() + """ + if type_.property_type == PropertyType.OBJECT: + return self._GenerateObjectTypeToValue(cpp_namespace, type_) + elif type_.property_type == PropertyType.CHOICES: + return self._GenerateChoiceTypeToValue(cpp_namespace, type_) + else: + raise ValueError("Unsupported property type %s" % type_.type_) + + def _GenerateObjectTypeToValue(self, cpp_namespace, type_): + """Generates a function that serializes an object-representing type + into a base::DictionaryValue. + """ + c = Code() + (c.Sblock('scoped_ptr<base::DictionaryValue> %s::ToValue() const {' % + cpp_namespace) + .Append('scoped_ptr<base::DictionaryValue> value(' + 'new base::DictionaryValue());') + .Append() + ) + + for prop in type_.properties.values(): + if prop.optional: + # Optional enum values are generated with a NONE enum value. + underlying_type = self._type_helper.FollowRef(prop.type_) + if underlying_type.property_type == PropertyType.ENUM: + c.Sblock('if (%s != %s) {' % + (prop.unix_name, + self._type_helper.GetEnumNoneValue(prop.type_))) + else: + c.Sblock('if (%s.get()) {' % prop.unix_name) + + # ANY is a base::Value which is abstract and cannot be a direct member, so + # it will always be a pointer. + is_ptr = prop.optional or prop.type_.property_type == PropertyType.ANY + c.Append('value->SetWithoutPathExpansion("%s", %s);' % ( + prop.name, + self._CreateValueFromType(prop.type_, + 'this->%s' % prop.unix_name, + is_ptr=is_ptr))) + + if prop.optional: + c.Eblock('}'); + + if type_.additional_properties is not None: + if type_.additional_properties.property_type == PropertyType.ANY: + c.Append('value->MergeDictionary(&additional_properties);') + else: + # Non-copyable types will be wrapped in a linked_ptr for inclusion in + # maps, so we need to unwrap them. + needs_unwrap = ( + not self._type_helper.IsCopyable(type_.additional_properties)) + cpp_type = self._type_helper.GetCppType(type_.additional_properties, + is_in_container=True) + (c.Sblock('for (std::map<std::string, %s>::const_iterator it =' % + cpp_util.PadForGenerics(cpp_type)) + .Append(' additional_properties.begin();') + .Append(' it != additional_properties.end(); ++it) {') + .Append('value->SetWithoutPathExpansion(it->first, %s);' % + self._CreateValueFromType( + type_.additional_properties, + '%sit->second' % ('*' if needs_unwrap else ''))) + .Eblock('}') + ) + + return (c.Append() + .Append('return value.Pass();') + .Eblock('}')) + + def _GenerateChoiceTypeToValue(self, cpp_namespace, type_): + """Generates a function that serializes a choice-representing type + into a base::Value. + """ + c = Code() + c.Sblock('scoped_ptr<base::Value> %s::ToValue() const {' % cpp_namespace) + c.Append('scoped_ptr<base::Value> result;'); + for choice in type_.choices: + choice_var = 'as_%s' % choice.unix_name + (c.Sblock('if (%s) {' % choice_var) + .Append('DCHECK(!result) << "Cannot set multiple choices for %s";' % + type_.unix_name) + .Append('result.reset(%s);' % + self._CreateValueFromType(choice, '*%s' % choice_var)) + .Eblock('}') + ) + (c.Append('DCHECK(result) << "Must set at least one choice for %s";' % + type_.unix_name) + .Append('return result.Pass();') + .Eblock('}') + ) + return c + + def _GenerateFunction(self, function): + """Generates the definitions for function structs. + """ + c = Code() + + # TODO(kalman): use function.unix_name not Classname. + function_namespace = cpp_util.Classname(function.name) + """Windows has a #define for SendMessage, so to avoid any issues, we need + to not use the name. + """ + if function_namespace == 'SendMessage': + function_namespace = 'PassMessage' + (c.Append('namespace %s {' % function_namespace) + .Append() + ) + + # Params::Populate function + if function.params: + c.Concat(self._GeneratePropertyFunctions('Params', function.params)) + (c.Append('Params::Params() {}') + .Append('Params::~Params() {}') + .Append() + .Cblock(self._GenerateFunctionParamsCreate(function)) + ) + + # Results::Create function + if function.callback: + c.Concat(self._GenerateCreateCallbackArguments('Results', + function.callback)) + + c.Append('} // namespace %s' % function_namespace) + return c + + def _GenerateEvent(self, event): + # TODO(kalman): use event.unix_name not Classname. + c = Code() + event_namespace = cpp_util.Classname(event.name) + (c.Append('namespace %s {' % event_namespace) + .Append() + .Cblock(self._GenerateEventNameConstant(None, event)) + .Cblock(self._GenerateCreateCallbackArguments(None, event)) + .Append('} // namespace %s' % event_namespace) + ) + return c + + def _CreateValueFromType(self, type_, var, is_ptr=False): + """Creates a base::Value given a type. Generated code passes ownership + to caller. + + var: variable or variable* + + E.g for std::string, generate new base::StringValue(var) + """ + underlying_type = self._type_helper.FollowRef(type_) + if (underlying_type.property_type == PropertyType.CHOICES or + underlying_type.property_type == PropertyType.OBJECT): + if is_ptr: + return '(%s)->ToValue().release()' % var + else: + return '(%s).ToValue().release()' % var + elif (underlying_type.property_type == PropertyType.ANY or + underlying_type.property_type == PropertyType.FUNCTION): + if is_ptr: + vardot = '(%s)->' % var + else: + vardot = '(%s).' % var + return '%sDeepCopy()' % vardot + elif underlying_type.property_type == PropertyType.ENUM: + return 'new base::StringValue(ToString(%s))' % var + elif underlying_type.property_type == PropertyType.BINARY: + if is_ptr: + vardot = var + '->' + else: + vardot = var + '.' + return ('base::BinaryValue::CreateWithCopiedBuffer(%sdata(), %ssize())' % + (vardot, vardot)) + elif underlying_type.property_type == PropertyType.ARRAY: + return '%s.release()' % self._util_cc_helper.CreateValueFromArray( + underlying_type, + var, + is_ptr) + elif underlying_type.property_type.is_fundamental: + if is_ptr: + var = '*%s' % var + if underlying_type.property_type == PropertyType.STRING: + return 'new base::StringValue(%s)' % var + else: + return 'new base::FundamentalValue(%s)' % var + else: + raise NotImplementedError('Conversion of %s to base::Value not ' + 'implemented' % repr(type_.type_)) + + def _GenerateParamsCheck(self, function, var): + """Generates a check for the correct number of arguments when creating + Params. + """ + c = Code() + num_required = 0 + for param in function.params: + if not param.optional: + num_required += 1 + if num_required == len(function.params): + c.Sblock('if (%(var)s.GetSize() != %(total)d) {') + elif not num_required: + c.Sblock('if (%(var)s.GetSize() > %(total)d) {') + else: + c.Sblock('if (%(var)s.GetSize() < %(required)d' + ' || %(var)s.GetSize() > %(total)d) {') + (c.Concat(self._GenerateError( + '"expected %%(total)d arguments, got " ' + '+ base::IntToString(%%(var)s.GetSize())')) + .Append('return scoped_ptr<Params>();') + .Eblock('}') + .Substitute({ + 'var': var, + 'required': num_required, + 'total': len(function.params), + })) + return c + + def _GenerateFunctionParamsCreate(self, function): + """Generate function to create an instance of Params. The generated + function takes a base::ListValue of arguments. + + E.g for function "Bar", generate Bar::Params::Create() + """ + c = Code() + (c.Append('// static') + .Sblock('scoped_ptr<Params> Params::Create(%s) {' % self._GenerateParams( + ['const base::ListValue& args'])) + .Concat(self._GenerateParamsCheck(function, 'args')) + .Append('scoped_ptr<Params> params(new Params());')) + + for param in function.params: + c.Concat(self._InitializePropertyToDefault(param, 'params')) + + for i, param in enumerate(function.params): + # Any failure will cause this function to return. If any argument is + # incorrect or missing, those following it are not processed. Note that + # for optional arguments, we allow missing arguments and proceed because + # there may be other arguments following it. + failure_value = 'scoped_ptr<Params>()' + c.Append() + value_var = param.unix_name + '_value' + (c.Append('const base::Value* %(value_var)s = NULL;') + .Append('if (args.Get(%(i)s, &%(value_var)s) &&') + .Sblock(' !%(value_var)s->IsType(base::Value::TYPE_NULL)) {') + .Concat(self._GeneratePopulatePropertyFromValue( + param, value_var, 'params', failure_value)) + .Eblock('}') + ) + if not param.optional: + (c.Sblock('else {') + .Concat(self._GenerateError('"\'%%(key)s\' is required"')) + .Append('return %s;' % failure_value) + .Eblock('}')) + c.Substitute({'value_var': value_var, 'i': i, 'key': param.name}) + (c.Append() + .Append('return params.Pass();') + .Eblock('}') + .Append() + ) + + return c + + def _GeneratePopulatePropertyFromValue(self, + prop, + src_var, + dst_class_var, + failure_value): + """Generates code to populate property |prop| of |dst_class_var| (a + pointer) from a Value*. See |_GeneratePopulateVariableFromValue| for + semantics. + """ + return self._GeneratePopulateVariableFromValue(prop.type_, + src_var, + '%s->%s' % (dst_class_var, + prop.unix_name), + failure_value, + is_ptr=prop.optional) + + def _GeneratePopulateVariableFromValue(self, + type_, + src_var, + dst_var, + failure_value, + is_ptr=False): + """Generates code to populate a variable |dst_var| of type |type_| from a + Value* at |src_var|. The Value* is assumed to be non-NULL. In the generated + code, if |dst_var| fails to be populated then Populate will return + |failure_value|. + """ + c = Code() + c.Sblock('{') + + underlying_type = self._type_helper.FollowRef(type_) + + if underlying_type.property_type.is_fundamental: + if is_ptr: + (c.Append('%(cpp_type)s temp;') + .Sblock('if (!%s) {' % cpp_util.GetAsFundamentalValue( + self._type_helper.FollowRef(type_), src_var, '&temp')) + .Concat(self._GenerateError( + '"\'%%(key)s\': expected ' + '%s, got " + %s' % ( + type_.name, + self._util_cc_helper.GetValueTypeString( + '%%(src_var)s', True)))) + .Append('return %(failure_value)s;') + .Eblock('}') + .Append('%(dst_var)s.reset(new %(cpp_type)s(temp));') + ) + else: + (c.Sblock('if (!%s) {' % cpp_util.GetAsFundamentalValue( + self._type_helper.FollowRef(type_), + src_var, + '&%s' % dst_var)) + .Concat(self._GenerateError( + '"\'%%(key)s\': expected ' + '%s, got " + %s' % ( + type_.name, + self._util_cc_helper.GetValueTypeString( + '%%(src_var)s', True)))) + .Append('return %(failure_value)s;') + .Eblock('}') + ) + elif underlying_type.property_type == PropertyType.OBJECT: + if is_ptr: + (c.Append('const base::DictionaryValue* dictionary = NULL;') + .Sblock('if (!%(src_var)s->GetAsDictionary(&dictionary)) {') + .Concat(self._GenerateError( + '"\'%%(key)s\': expected dictionary, got " + ' + + self._util_cc_helper.GetValueTypeString('%%(src_var)s', True))) + .Append('return %(failure_value)s;') + .Eblock('}') + .Append('scoped_ptr<%(cpp_type)s> temp(new %(cpp_type)s());') + .Append('if (!%%(cpp_type)s::Populate(%s)) {' % self._GenerateArgs( + ('*dictionary', 'temp.get()'))) + .Append(' return %(failure_value)s;') + .Append('}') + .Append('%(dst_var)s = temp.Pass();') + ) + else: + (c.Append('const base::DictionaryValue* dictionary = NULL;') + .Sblock('if (!%(src_var)s->GetAsDictionary(&dictionary)) {') + .Concat(self._GenerateError( + '"\'%%(key)s\': expected dictionary, got " + ' + + self._util_cc_helper.GetValueTypeString('%%(src_var)s', True))) + .Append('return %(failure_value)s;') + .Eblock('}') + .Append('if (!%%(cpp_type)s::Populate(%s)) {' % self._GenerateArgs( + ('*dictionary', '&%(dst_var)s'))) + .Append(' return %(failure_value)s;') + .Append('}') + ) + elif underlying_type.property_type == PropertyType.FUNCTION: + if is_ptr: + c.Append('%(dst_var)s.reset(new base::DictionaryValue());') + elif underlying_type.property_type == PropertyType.ANY: + c.Append('%(dst_var)s.reset(%(src_var)s->DeepCopy());') + elif underlying_type.property_type == PropertyType.ARRAY: + # util_cc_helper deals with optional and required arrays + (c.Append('const base::ListValue* list = NULL;') + .Sblock('if (!%(src_var)s->GetAsList(&list)) {') + .Concat(self._GenerateError( + '"\'%%(key)s\': expected list, got " + ' + + self._util_cc_helper.GetValueTypeString('%%(src_var)s', True))) + .Append('return %(failure_value)s;') + .Eblock('}')) + item_type = self._type_helper.FollowRef(underlying_type.item_type) + if item_type.property_type == PropertyType.ENUM: + c.Concat(self._GenerateListValueToEnumArrayConversion( + item_type, + 'list', + dst_var, + failure_value, + is_ptr=is_ptr)) + else: + (c.Sblock('if (!%s) {' % self._util_cc_helper.PopulateArrayFromList( + underlying_type, + 'list', + dst_var, + is_ptr)) + .Concat(self._GenerateError( + '"unable to populate array \'%%(parent_key)s\'"')) + .Append('return %(failure_value)s;') + .Eblock('}') + ) + elif underlying_type.property_type == PropertyType.CHOICES: + if is_ptr: + (c.Append('scoped_ptr<%(cpp_type)s> temp(new %(cpp_type)s());') + .Append('if (!%%(cpp_type)s::Populate(%s))' % self._GenerateArgs( + ('*%(src_var)s', 'temp.get()'))) + .Append(' return %(failure_value)s;') + .Append('%(dst_var)s = temp.Pass();') + ) + else: + (c.Append('if (!%%(cpp_type)s::Populate(%s))' % self._GenerateArgs( + ('*%(src_var)s', '&%(dst_var)s'))) + .Append(' return %(failure_value)s;')) + elif underlying_type.property_type == PropertyType.ENUM: + c.Concat(self._GenerateStringToEnumConversion(type_, + src_var, + dst_var, + failure_value)) + elif underlying_type.property_type == PropertyType.BINARY: + (c.Sblock('if (!%(src_var)s->IsType(base::Value::TYPE_BINARY)) {') + .Concat(self._GenerateError( + '"\'%%(key)s\': expected binary, got " + ' + + self._util_cc_helper.GetValueTypeString('%%(src_var)s', True))) + .Append('return %(failure_value)s;') + .Eblock('}') + .Append('const base::BinaryValue* binary_value =') + .Append(' static_cast<const base::BinaryValue*>(%(src_var)s);') + ) + if is_ptr: + (c.Append('%(dst_var)s.reset(') + .Append(' new std::string(binary_value->GetBuffer(),') + .Append(' binary_value->GetSize()));') + ) + else: + (c.Append('%(dst_var)s.assign(binary_value->GetBuffer(),') + .Append(' binary_value->GetSize());') + ) + else: + raise NotImplementedError(type_) + return c.Eblock('}').Substitute({ + 'cpp_type': self._type_helper.GetCppType(type_), + 'src_var': src_var, + 'dst_var': dst_var, + 'failure_value': failure_value, + 'key': type_.name, + 'parent_key': type_.parent.name + }) + + def _GenerateListValueToEnumArrayConversion(self, + item_type, + src_var, + dst_var, + failure_value, + is_ptr=False): + """Returns Code that converts a ListValue of string constants from + |src_var| into an array of enums of |type_| in |dst_var|. On failure, + returns |failure_value|. + """ + c = Code() + accessor = '.' + if is_ptr: + accessor = '->' + cpp_type = self._type_helper.GetCppType(item_type, is_in_container=True) + c.Append('%s.reset(new std::vector<%s>);' % + (dst_var, cpp_util.PadForGenerics(cpp_type))) + (c.Sblock('for (base::ListValue::const_iterator it = %s->begin(); ' + 'it != %s->end(); ++it) {' % (src_var, src_var)) + .Append('%s tmp;' % self._type_helper.GetCppType(item_type)) + .Concat(self._GenerateStringToEnumConversion(item_type, + '(*it)', + 'tmp', + failure_value)) + .Append('%s%spush_back(tmp);' % (dst_var, accessor)) + .Eblock('}') + ) + return c + + def _GenerateStringToEnumConversion(self, + type_, + src_var, + dst_var, + failure_value): + """Returns Code that converts a string type in |src_var| to an enum with + type |type_| in |dst_var|. In the generated code, if |src_var| is not + a valid enum name then the function will return |failure_value|. + """ + c = Code() + enum_as_string = '%s_as_string' % type_.unix_name + (c.Append('std::string %s;' % enum_as_string) + .Sblock('if (!%s->GetAsString(&%s)) {' % (src_var, enum_as_string)) + .Concat(self._GenerateError( + '"\'%%(key)s\': expected string, got " + ' + + self._util_cc_helper.GetValueTypeString('%%(src_var)s', True))) + .Append('return %s;' % failure_value) + .Eblock('}') + .Append('%s = Parse%s(%s);' % (dst_var, + self._type_helper.GetCppType(type_), + enum_as_string)) + .Sblock('if (%s == %s) {' % (dst_var, + self._type_helper.GetEnumNoneValue(type_))) + .Concat(self._GenerateError( + '\"\'%%(key)s\': expected \\"' + + '\\" or \\"'.join(self._type_helper.FollowRef(type_).enum_values) + + '\\", got \\"" + %s + "\\""' % enum_as_string)) + .Append('return %s;' % failure_value) + .Eblock('}') + .Substitute({'src_var': src_var, 'key': type_.name}) + ) + return c + + def _GeneratePropertyFunctions(self, namespace, params): + """Generates the member functions for a list of parameters. + """ + return self._GenerateTypes(namespace, (param.type_ for param in params)) + + def _GenerateTypes(self, namespace, types): + """Generates the member functions for a list of types. + """ + c = Code() + for type_ in types: + c.Cblock(self._GenerateType(namespace, type_)) + return c + + def _GenerateEnumToString(self, cpp_namespace, type_): + """Generates ToString() which gets the string representation of an enum. + """ + c = Code() + classname = cpp_util.Classname(schema_util.StripNamespace(type_.name)) + + if cpp_namespace is not None: + c.Append('// static') + maybe_namespace = '' if cpp_namespace is None else '%s::' % cpp_namespace + + c.Sblock('std::string %sToString(%s enum_param) {' % + (maybe_namespace, classname)) + c.Sblock('switch (enum_param) {') + for enum_value in self._type_helper.FollowRef(type_).enum_values: + (c.Append('case %s: ' % self._type_helper.GetEnumValue(type_, enum_value)) + .Append(' return "%s";' % enum_value)) + (c.Append('case %s:' % self._type_helper.GetEnumNoneValue(type_)) + .Append(' return "";') + .Eblock('}') + .Append('NOTREACHED();') + .Append('return "";') + .Eblock('}') + ) + return c + + def _GenerateEnumFromString(self, cpp_namespace, type_): + """Generates FromClassNameString() which gets an enum from its string + representation. + """ + c = Code() + classname = cpp_util.Classname(schema_util.StripNamespace(type_.name)) + + if cpp_namespace is not None: + c.Append('// static') + maybe_namespace = '' if cpp_namespace is None else '%s::' % cpp_namespace + + c.Sblock('%s%s %sParse%s(const std::string& enum_string) {' % + (maybe_namespace, classname, maybe_namespace, classname)) + for i, enum_value in enumerate( + self._type_helper.FollowRef(type_).enum_values): + # This is broken up into all ifs with no else ifs because we get + # "fatal error C1061: compiler limit : blocks nested too deeply" + # on Windows. + (c.Append('if (enum_string == "%s")' % enum_value) + .Append(' return %s;' % + self._type_helper.GetEnumValue(type_, enum_value))) + (c.Append('return %s;' % self._type_helper.GetEnumNoneValue(type_)) + .Eblock('}') + ) + return c + + def _GenerateCreateCallbackArguments(self, function_scope, callback): + """Generate all functions to create Value parameters for a callback. + + E.g for function "Bar", generate Bar::Results::Create + E.g for event "Baz", generate Baz::Create + + function_scope: the function scope path, e.g. Foo::Bar for the function + Foo::Bar::Baz(). May be None if there is no function scope. + callback: the Function object we are creating callback arguments for. + """ + c = Code() + params = callback.params + c.Concat(self._GeneratePropertyFunctions(function_scope, params)) + + (c.Sblock('scoped_ptr<base::ListValue> %(function_scope)s' + 'Create(%(declaration_list)s) {') + .Append('scoped_ptr<base::ListValue> create_results(' + 'new base::ListValue());') + ) + declaration_list = [] + for param in params: + declaration_list.append(cpp_util.GetParameterDeclaration( + param, self._type_helper.GetCppType(param.type_))) + c.Append('create_results->Append(%s);' % + self._CreateValueFromType(param.type_, param.unix_name)) + c.Append('return create_results.Pass();') + c.Eblock('}') + c.Substitute({ + 'function_scope': ('%s::' % function_scope) if function_scope else '', + 'declaration_list': ', '.join(declaration_list), + 'param_names': ', '.join(param.unix_name for param in params) + }) + return c + + def _GenerateEventNameConstant(self, function_scope, event): + """Generates a constant string array for the event name. + """ + c = Code() + c.Append('const char kEventName[] = "%s.%s";' % ( + self._namespace.name, event.name)) + return c + + def _InitializePropertyToDefault(self, prop, dst): + """Initialize a model.Property to its default value inside an object. + + E.g for optional enum "state", generate dst->state = STATE_NONE; + + dst: Type* + """ + c = Code() + underlying_type = self._type_helper.FollowRef(prop.type_) + if (underlying_type.property_type == PropertyType.ENUM and + prop.optional): + c.Append('%s->%s = %s;' % ( + dst, + prop.unix_name, + self._type_helper.GetEnumNoneValue(prop.type_))) + return c + + def _GenerateError(self, body): + """Generates an error message pertaining to population failure. + + E.g 'expected bool, got int' + """ + c = Code() + if not self._generate_error_messages: + return c + (c.Append('if (error)') + .Append(' *error = ' + body + ';')) + return c + + def _GenerateParams(self, params): + """Builds the parameter list for a function, given an array of parameters. + """ + if self._generate_error_messages: + params = list(params) + ['std::string* error'] + return ', '.join(str(p) for p in params) + + def _GenerateArgs(self, args): + """Builds the argument list for a function, given an array of arguments. + """ + if self._generate_error_messages: + args = list(args) + ['error'] + return ', '.join(str(a) for a in args) diff --git a/chromium/tools/json_schema_compiler/code.py b/chromium/tools/json_schema_compiler/code.py new file mode 100644 index 00000000000..3622237a84a --- /dev/null +++ b/chromium/tools/json_schema_compiler/code.py @@ -0,0 +1,141 @@ +# 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. + +class Code(object): + """A convenience object for constructing code. + + Logically each object should be a block of code. All methods except |Render| + and |IsEmpty| return self. + """ + def __init__(self, indent_size=2, comment_length=80): + self._code = [] + self._indent_level = 0 + self._indent_size = indent_size + self._comment_length = comment_length + + def Append(self, line='', substitute=True, indent_level=None): + """Appends a line of code at the current indent level or just a newline if + line is not specified. Trailing whitespace is stripped. + + substitute: indicated whether this line should be affected by + code.Substitute(). + """ + if indent_level is None: + indent_level = self._indent_level + self._code.append(Line(((' ' * indent_level) + line).rstrip(), + substitute=substitute)) + return self + + def IsEmpty(self): + """Returns True if the Code object is empty. + """ + return not bool(self._code) + + def Concat(self, obj): + """Concatenate another Code object onto this one. Trailing whitespace is + stripped. + + Appends the code at the current indent level. Will fail if there are any + un-interpolated format specifiers eg %s, %(something)s which helps + isolate any strings that haven't been substituted. + """ + if not isinstance(obj, Code): + raise TypeError(type(obj)) + assert self is not obj + for line in obj._code: + try: + # line % () will fail if any substitution tokens are left in line + if line.substitute: + line.value %= () + except TypeError: + raise TypeError('Unsubstituted value when concatting\n' + line.value) + except ValueError: + raise ValueError('Stray % character when concatting\n' + line.value) + self.Append(line.value, line.substitute) + + return self + + def Cblock(self, code): + """Concatenates another Code object |code| onto this one followed by a + blank line, if |code| is non-empty.""" + if not code.IsEmpty(): + self.Concat(code).Append() + return self + + def Sblock(self, line=None): + """Starts a code block. + + Appends a line of code and then increases the indent level. + """ + if line is not None: + self.Append(line) + self._indent_level += self._indent_size + return self + + def Eblock(self, line=None): + """Ends a code block by decreasing and then appending a line (or a blank + line if not given). + """ + # TODO(calamity): Decide if type checking is necessary + #if not isinstance(line, basestring): + # raise TypeError + self._indent_level -= self._indent_size + if line is not None: + self.Append(line) + return self + + def Comment(self, comment, comment_prefix='// '): + """Adds the given string as a comment. + + Will split the comment if it's too long. Use mainly for variable length + comments. Otherwise just use code.Append('// ...') for comments. + + Unaffected by code.Substitute(). + """ + max_len = self._comment_length - self._indent_level - len(comment_prefix) + while len(comment) >= max_len: + line = comment[0:max_len] + last_space = line.rfind(' ') + if last_space != -1: + line = line[0:last_space] + comment = comment[last_space + 1:] + else: + comment = comment[max_len:] + self.Append(comment_prefix + line, substitute=False) + self.Append(comment_prefix + comment, substitute=False) + return self + + def Substitute(self, d): + """Goes through each line and interpolates using the given dict. + + Raises type error if passed something that isn't a dict + + Use for long pieces of code using interpolation with the same variables + repeatedly. This will reduce code and allow for named placeholders which + are more clear. + """ + if not isinstance(d, dict): + raise TypeError('Passed argument is not a dictionary: ' + d) + for i, line in enumerate(self._code): + if self._code[i].substitute: + # Only need to check %s because arg is a dict and python will allow + # '%s %(named)s' but just about nothing else + if '%s' in self._code[i].value or '%r' in self._code[i].value: + raise TypeError('"%s" or "%r" found in substitution. ' + 'Named arguments only. Use "%" to escape') + self._code[i].value = line.value % d + self._code[i].substitute = False + return self + + def Render(self): + """Renders Code as a string. + """ + return '\n'.join([l.value for l in self._code]) + +class Line(object): + """A line of code. + """ + def __init__(self, value, substitute=True): + self.value = value + self.substitute = substitute diff --git a/chromium/tools/json_schema_compiler/code_test.py b/chromium/tools/json_schema_compiler/code_test.py new file mode 100755 index 00000000000..ca3652420f9 --- /dev/null +++ b/chromium/tools/json_schema_compiler/code_test.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python +# 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. + +from code import Code +import unittest + +class CodeTest(unittest.TestCase): + def testAppend(self): + c = Code() + c.Append('line') + self.assertEquals('line', c.Render()) + + def testBlock(self): + c = Code() + (c.Append('line') + .Sblock('sblock') + .Append('inner') + .Append('moreinner') + .Sblock('moresblock') + .Append('inner') + .Eblock('out') + .Append('inner') + .Eblock('out') + ) + self.assertEquals( + 'line\n' + 'sblock\n' + ' inner\n' + ' moreinner\n' + ' moresblock\n' + ' inner\n' + ' out\n' + ' inner\n' + 'out', + c.Render()) + + def testConcat(self): + b = Code() + (b.Sblock('2') + .Append('2') + .Eblock('2') + ) + c = Code() + (c.Sblock('1') + .Concat(b) + .Append('1') + .Eblock('1') + ) + self.assertEquals( + '1\n' + ' 2\n' + ' 2\n' + ' 2\n' + ' 1\n' + '1', + c.Render()) + d = Code() + a = Code() + a.Concat(d) + self.assertEquals('', a.Render()) + a.Concat(c) + self.assertEquals( + '1\n' + ' 2\n' + ' 2\n' + ' 2\n' + ' 1\n' + '1', + a.Render()) + + def testConcatErrors(self): + c = Code() + d = Code() + d.Append('%s') + self.assertRaises(TypeError, c.Concat, d) + d = Code() + d.Append('%(classname)s') + self.assertRaises(TypeError, c.Concat, d) + d = 'line of code' + self.assertRaises(TypeError, c.Concat, d) + + def testSubstitute(self): + c = Code() + c.Append('%(var1)s %(var2)s %(var1)s') + c.Substitute({'var1': 'one', 'var2': 'two'}) + self.assertEquals('one two one', c.Render()) + c.Append('%(var1)s %(var2)s %(var3)s') + c.Append('%(var2)s %(var1)s %(var3)s') + c.Substitute({'var1': 'one', 'var2': 'two', 'var3': 'three'}) + self.assertEquals( + 'one two one\n' + 'one two three\n' + 'two one three', + c.Render()) + + def testSubstituteErrors(self): + # No unnamed placeholders allowed when substitute is run + c = Code() + c.Append('%s %s') + self.assertRaises(TypeError, c.Substitute, ('var1', 'one')) + c = Code() + c.Append('%s %(var1)s') + self.assertRaises(TypeError, c.Substitute, {'var1': 'one'}) + c = Code() + c.Append('%s %(var1)s') + self.assertRaises(TypeError, c.Substitute, {'var1': 'one'}) + c = Code() + c.Append('%(var1)s') + self.assertRaises(KeyError, c.Substitute, {'clearlynotvar1': 'one'}) + + def testIsEmpty(self): + c = Code() + self.assertTrue(c.IsEmpty()) + c.Append('asdf') + self.assertFalse(c.IsEmpty()) + + def testComment(self): + long_comment = ('This comment is eighty nine characters in longness, ' + 'that is, to use another word, length') + c = Code() + c.Comment(long_comment) + self.assertEquals( + '// This comment is eighty nine characters ' + 'in longness, that is, to use another\n' + '// word, length', + c.Render()) + c = Code() + c.Sblock('sblock') + c.Comment(long_comment) + c.Eblock('eblock') + c.Comment(long_comment) + self.assertEquals( + 'sblock\n' + ' // This comment is eighty nine characters ' + 'in longness, that is, to use\n' + ' // another word, length\n' + 'eblock\n' + '// This comment is eighty nine characters in ' + 'longness, that is, to use another\n' + '// word, length', + c.Render()) + long_word = 'x' * 100 + c = Code() + c.Comment(long_word) + self.assertEquals( + '// ' + 'x' * 77 + '\n' + '// ' + 'x' * 23, + c.Render()) + + def testCommentWithSpecialCharacters(self): + c = Code() + c.Comment('20% of 80%s') + c.Substitute({}) + self.assertEquals('// 20% of 80%s', c.Render()) + d = Code() + d.Append('90') + d.Concat(c) + self.assertEquals('90\n' + '// 20% of 80%s', + d.Render()) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/compiler.py b/chromium/tools/json_schema_compiler/compiler.py new file mode 100755 index 00000000000..acf64acd91d --- /dev/null +++ b/chromium/tools/json_schema_compiler/compiler.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# 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 for C++ structs from api json files. + +The purpose of this tool is to remove the need for hand-written code that +converts to and from base::Value types when receiving javascript api calls. +Originally written for generating code for extension apis. Reference schemas +are in chrome/common/extensions/api. + +Usage example: + compiler.py --root /home/Work/src --namespace extensions windows.json + tabs.json + compiler.py --destdir gen --root /home/Work/src + --namespace extensions windows.json tabs.json +""" + +import optparse +import os +import sys + +from cpp_bundle_generator import CppBundleGenerator +from cpp_generator import CppGenerator +from cpp_type_generator import CppTypeGenerator +from dart_generator import DartGenerator +import json_schema +from model import Model, UnixName +from schema_loader import SchemaLoader + +# Names of supported code generators, as specified on the command-line. +# First is default. +GENERATORS = ['cpp', 'cpp-bundle', 'dart'] + +def GenerateSchema(generator, + filenames, + root, + destdir, + root_namespace, + dart_overrides_dir): + schema_loader = SchemaLoader(os.path.dirname(os.path.relpath( + os.path.normpath(filenames[0]), root))) + # Merge the source files into a single list of schemas. + api_defs = [] + for filename in filenames: + schema = os.path.normpath(filename) + schema_filename, schema_extension = os.path.splitext(schema) + path, short_filename = os.path.split(schema_filename) + api_def = schema_loader.LoadSchema(schema) + + # If compiling the C++ model code, delete 'nocompile' nodes. + if generator == 'cpp': + api_def = json_schema.DeleteNodes(api_def, 'nocompile') + api_defs.extend(api_def) + + api_model = Model() + + # For single-schema compilation make sure that the first (i.e. only) schema + # is the default one. + default_namespace = None + + # Load the actual namespaces into the model. + for target_namespace, schema_filename in zip(api_defs, filenames): + relpath = os.path.relpath(os.path.normpath(schema_filename), root) + namespace = api_model.AddNamespace(target_namespace, + relpath, + include_compiler_options=True) + if default_namespace is None: + default_namespace = namespace + + path, filename = os.path.split(schema_filename) + short_filename, extension = os.path.splitext(filename) + + # Filenames are checked against the unix_names of the namespaces they + # generate because the gyp uses the names of the JSON files to generate + # the names of the .cc and .h files. We want these to be using unix_names. + if namespace.unix_name != short_filename: + sys.exit("Filename %s is illegal. Name files using unix_hacker style." % + schema_filename) + + # The output filename must match the input filename for gyp to deal with it + # properly. + out_file = namespace.unix_name + + # Construct the type generator with all the namespaces in this model. + type_generator = CppTypeGenerator(api_model, + schema_loader, + default_namespace=default_namespace) + + if generator == 'cpp-bundle': + cpp_bundle_generator = CppBundleGenerator(root, + api_model, + api_defs, + type_generator, + root_namespace) + generators = [ + ('generated_api.cc', cpp_bundle_generator.api_cc_generator), + ('generated_api.h', cpp_bundle_generator.api_h_generator), + ('generated_schemas.cc', cpp_bundle_generator.schemas_cc_generator), + ('generated_schemas.h', cpp_bundle_generator.schemas_h_generator) + ] + elif generator == 'cpp': + cpp_generator = CppGenerator(type_generator, root_namespace) + generators = [ + ('%s.h' % namespace.unix_name, cpp_generator.h_generator), + ('%s.cc' % namespace.unix_name, cpp_generator.cc_generator) + ] + elif generator == 'dart': + generators = [ + ('%s.dart' % namespace.unix_name, DartGenerator( + dart_overrides_dir)) + ] + else: + raise Exception('Unrecognised generator %s' % generator) + + output_code = [] + for filename, generator in generators: + code = generator.Generate(namespace).Render() + if destdir: + with open(os.path.join(destdir, namespace.source_file_dir, + filename), 'w') as f: + f.write(code) + output_code += [filename, '', code, ''] + + return '\n'.join(output_code) + +if __name__ == '__main__': + parser = optparse.OptionParser( + description='Generates a C++ model of an API from JSON schema', + usage='usage: %prog [option]... schema') + parser.add_option('-r', '--root', default='.', + help='logical include root directory. Path to schema files from specified' + 'dir will be the include path.') + parser.add_option('-d', '--destdir', + help='root directory to output generated files.') + parser.add_option('-n', '--namespace', default='generated_api_schemas', + help='C++ namespace for generated files. e.g extensions::api.') + parser.add_option('-g', '--generator', default=GENERATORS[0], + choices=GENERATORS, + help='The generator to use to build the output code. Supported values are' + ' %s' % GENERATORS) + parser.add_option('-D', '--dart-overrides-dir', dest='dart_overrides_dir', + help='Adds custom dart from files in the given directory (Dart only).') + + (opts, filenames) = parser.parse_args() + + if not filenames: + sys.exit(0) # This is OK as a no-op + + # Unless in bundle mode, only one file should be specified. + if opts.generator != 'cpp-bundle' and len(filenames) > 1: + # TODO(sashab): Could also just use filenames[0] here and not complain. + raise Exception( + "Unless in bundle mode, only one file can be specified at a time.") + + result = GenerateSchema(opts.generator, filenames, opts.root, opts.destdir, + opts.namespace, opts.dart_overrides_dir) + if not opts.destdir: + print result diff --git a/chromium/tools/json_schema_compiler/cpp_bundle_generator.py b/chromium/tools/json_schema_compiler/cpp_bundle_generator.py new file mode 100644 index 00000000000..580bca85208 --- /dev/null +++ b/chromium/tools/json_schema_compiler/cpp_bundle_generator.py @@ -0,0 +1,291 @@ +# 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. + +import code +import cpp_util +from model import Platforms +from schema_util import CapitalizeFirstLetter +from schema_util import JsFunctionNameToClassName + +import json +import os +import re + +# TODO(miket/asargent) - parameterize this. +SOURCE_BASE_PATH = 'chrome/common/extensions/api' + +def _RemoveDescriptions(node): + """Returns a copy of |schema| with "description" fields removed. + """ + if isinstance(node, dict): + result = {} + for key, value in node.items(): + # Some schemas actually have properties called "description", so only + # remove descriptions that have string values. + if key == 'description' and isinstance(value, basestring): + continue + result[key] = _RemoveDescriptions(value) + return result + if isinstance(node, list): + return [_RemoveDescriptions(v) for v in node] + return node + +class CppBundleGenerator(object): + """This class contains methods to generate code based on multiple schemas. + """ + + def __init__(self, root, model, api_defs, cpp_type_generator, cpp_namespace): + self._root = root; + self._model = model + self._api_defs = api_defs + self._cpp_type_generator = cpp_type_generator + self._cpp_namespace = cpp_namespace + + self.api_cc_generator = _APICCGenerator(self) + self.api_h_generator = _APIHGenerator(self) + self.schemas_cc_generator = _SchemasCCGenerator(self) + self.schemas_h_generator = _SchemasHGenerator(self) + + def _GenerateHeader(self, file_base, body_code): + """Generates a code.Code object for a header file + + Parameters: + - |file_base| - the base of the filename, e.g. 'foo' (for 'foo.h') + - |body_code| - the code to put in between the multiple inclusion guards""" + c = code.Code() + c.Append(cpp_util.CHROMIUM_LICENSE) + c.Append() + c.Append(cpp_util.GENERATED_BUNDLE_FILE_MESSAGE % SOURCE_BASE_PATH) + ifndef_name = cpp_util.GenerateIfndefName(SOURCE_BASE_PATH, file_base) + c.Append() + c.Append('#ifndef %s' % ifndef_name) + c.Append('#define %s' % ifndef_name) + c.Append() + c.Concat(body_code) + c.Append() + c.Append('#endif // %s' % ifndef_name) + c.Append() + return c + + def _GetPlatformIfdefs(self, model_object): + """Generates the "defined" conditional for an #if check if |model_object| + has platform restrictions. Returns None if there are no restrictions. + """ + if model_object.platforms is None: + return None + ifdefs = [] + for platform in model_object.platforms: + if platform == Platforms.CHROMEOS: + ifdefs.append('defined(OS_CHROMEOS)') + else: + raise ValueError("Unsupported platform ifdef: %s" % platform.name) + return ' and '.join(ifdefs) + + def _GenerateRegisterFunctions(self, namespace_name, function): + c = code.Code() + function_ifdefs = self._GetPlatformIfdefs(function) + if function_ifdefs is not None: + c.Append("#if %s" % function_ifdefs, indent_level=0) + + function_name = JsFunctionNameToClassName(namespace_name, function.name) + c.Append("registry->RegisterFunction<%sFunction>();" % ( + function_name)) + + if function_ifdefs is not None: + c.Append("#endif // %s" % function_ifdefs, indent_level=0) + return c + + def _GenerateFunctionRegistryRegisterAll(self): + c = code.Code() + c.Append('// static') + c.Sblock('void GeneratedFunctionRegistry::RegisterAll(' + 'ExtensionFunctionRegistry* registry) {') + for namespace in self._model.namespaces.values(): + namespace_ifdefs = self._GetPlatformIfdefs(namespace) + if namespace_ifdefs is not None: + c.Append("#if %s" % namespace_ifdefs, indent_level=0) + + namespace_name = CapitalizeFirstLetter(namespace.name.replace( + "experimental.", "")) + for function in namespace.functions.values(): + if function.nocompile: + continue + c.Concat(self._GenerateRegisterFunctions(namespace.name, function)) + + for type_ in namespace.types.values(): + for function in type_.functions.values(): + if function.nocompile: + continue + namespace_types_name = JsFunctionNameToClassName( + namespace.name, type_.name) + c.Concat(self._GenerateRegisterFunctions(namespace_types_name, + function)) + + if namespace_ifdefs is not None: + c.Append("#endif // %s" % namespace_ifdefs, indent_level=0) + c.Eblock("}") + return c + +class _APIHGenerator(object): + """Generates the header for API registration / declaration""" + def __init__(self, cpp_bundle): + self._bundle = cpp_bundle + + def Generate(self, namespace): + c = code.Code() + + c.Append('#include <string>') + c.Append() + c.Append('#include "base/basictypes.h"') + c.Append() + c.Append("class ExtensionFunctionRegistry;") + c.Append() + c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace)) + c.Append() + c.Append('class GeneratedFunctionRegistry {') + c.Sblock(' public:') + c.Append('static void RegisterAll(' + 'ExtensionFunctionRegistry* registry);') + c.Eblock('};'); + c.Append() + c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace)) + return self._bundle._GenerateHeader('generated_api', c) + +class _APICCGenerator(object): + """Generates a code.Code object for the generated API .cc file""" + + def __init__(self, cpp_bundle): + self._bundle = cpp_bundle + + def Generate(self, namespace): + c = code.Code() + c.Append(cpp_util.CHROMIUM_LICENSE) + c.Append() + c.Append('#include "%s"' % (os.path.join(SOURCE_BASE_PATH, + 'generated_api.h'))) + c.Append() + for namespace in self._bundle._model.namespaces.values(): + namespace_name = namespace.unix_name.replace("experimental_", "") + implementation_header = namespace.compiler_options.get( + "implemented_in", + "chrome/browser/extensions/api/%s/%s_api.h" % (namespace_name, + namespace_name)) + if not os.path.exists( + os.path.join(self._bundle._root, + os.path.normpath(implementation_header))): + if "implemented_in" in namespace.compiler_options: + raise ValueError('Header file for namespace "%s" specified in ' + 'compiler_options not found: %s' % + (namespace.unix_name, implementation_header)) + continue + ifdefs = self._bundle._GetPlatformIfdefs(namespace) + if ifdefs is not None: + c.Append("#if %s" % ifdefs, indent_level=0) + + c.Append('#include "%s"' % implementation_header) + + if ifdefs is not None: + c.Append("#endif // %s" % ifdefs, indent_level=0) + c.Append() + c.Append('#include ' + '"chrome/browser/extensions/extension_function_registry.h"') + c.Append() + c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace)) + c.Append() + c.Concat(self._bundle._GenerateFunctionRegistryRegisterAll()) + c.Append() + c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace)) + c.Append() + return c + +class _SchemasHGenerator(object): + """Generates a code.Code object for the generated schemas .h file""" + def __init__(self, cpp_bundle): + self._bundle = cpp_bundle + + def Generate(self, namespace): + c = code.Code() + c.Append('#include <map>') + c.Append('#include <string>') + c.Append(); + c.Append('#include "base/strings/string_piece.h"') + c.Append() + c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace)) + c.Append() + c.Append('class GeneratedSchemas {') + c.Sblock(' public:') + c.Append('// Determines if schema named |name| is generated.') + c.Append('static bool IsGenerated(std::string name);') + c.Append() + c.Append('// Gets the API schema named |name|.') + c.Append('static base::StringPiece Get(const std::string& name);') + c.Eblock('};'); + c.Append() + c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace)) + return self._bundle._GenerateHeader('generated_schemas', c) + +def _FormatNameAsConstant(name): + """Formats a name to be a C++ constant of the form kConstantName""" + name = '%s%s' % (name[0].upper(), name[1:]) + return 'k%s' % re.sub('_[a-z]', + lambda m: m.group(0)[1].upper(), + name.replace('.', '_')) + +class _SchemasCCGenerator(object): + """Generates a code.Code object for the generated schemas .cc file""" + + def __init__(self, cpp_bundle): + self._bundle = cpp_bundle + + def Generate(self, namespace): + c = code.Code() + c.Append(cpp_util.CHROMIUM_LICENSE) + c.Append() + c.Append('#include "%s"' % (os.path.join(SOURCE_BASE_PATH, + 'generated_schemas.h'))) + c.Append() + c.Append('#include "base/lazy_instance.h"') + c.Append() + c.Append('namespace {') + for api in self._bundle._api_defs: + namespace = self._bundle._model.namespaces[api.get('namespace')] + # JSON parsing code expects lists of schemas, so dump a singleton list. + json_content = json.dumps([_RemoveDescriptions(api)], + separators=(',', ':')) + # Escape all double-quotes and backslashes. For this to output a valid + # JSON C string, we need to escape \ and ". + json_content = json_content.replace('\\', '\\\\').replace('"', '\\"') + c.Append('const char %s[] = "%s";' % + (_FormatNameAsConstant(namespace.name), json_content)) + c.Append('}') + c.Concat(cpp_util.OpenNamespace(self._bundle._cpp_namespace)) + c.Append() + c.Sblock('struct Static {') + c.Sblock('Static() {') + for api in self._bundle._api_defs: + namespace = self._bundle._model.namespaces[api.get('namespace')] + c.Append('schemas["%s"] = %s;' % (namespace.name, + _FormatNameAsConstant(namespace.name))) + c.Eblock('}'); + c.Append() + c.Append('std::map<std::string, const char*> schemas;') + c.Eblock('};'); + c.Append() + c.Append('base::LazyInstance<Static> g_lazy_instance;') + c.Append() + c.Append('// static') + c.Sblock('base::StringPiece GeneratedSchemas::Get(' + 'const std::string& name) {') + c.Append('return IsGenerated(name) ? ' + 'g_lazy_instance.Get().schemas[name] : "";') + c.Eblock('}') + c.Append() + c.Append('// static') + c.Sblock('bool GeneratedSchemas::IsGenerated(std::string name) {') + c.Append('return g_lazy_instance.Get().schemas.count(name) > 0;') + c.Eblock('}') + c.Append() + c.Concat(cpp_util.CloseNamespace(self._bundle._cpp_namespace)) + c.Append() + return c diff --git a/chromium/tools/json_schema_compiler/cpp_generator.py b/chromium/tools/json_schema_compiler/cpp_generator.py new file mode 100644 index 00000000000..6dc05137205 --- /dev/null +++ b/chromium/tools/json_schema_compiler/cpp_generator.py @@ -0,0 +1,11 @@ +# 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. + +from cc_generator import CCGenerator +from h_generator import HGenerator + +class CppGenerator(object): + def __init__(self, type_generator, cpp_namespace): + self.h_generator = HGenerator(type_generator, cpp_namespace) + self.cc_generator = CCGenerator(type_generator, cpp_namespace) diff --git a/chromium/tools/json_schema_compiler/cpp_type_generator.py b/chromium/tools/json_schema_compiler/cpp_type_generator.py new file mode 100644 index 00000000000..efb712ee728 --- /dev/null +++ b/chromium/tools/json_schema_compiler/cpp_type_generator.py @@ -0,0 +1,276 @@ +# 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. + +from code import Code +from model import Namespace, PropertyType, Type +import cpp_util +from json_parse import OrderedDict +from operator import attrgetter +import schema_util + +class _TypeDependency(object): + """Contains information about a dependency a namespace has on a type: the + type's model, and whether that dependency is "hard" meaning that it cannot be + forward declared. + """ + def __init__(self, type_, hard=False): + self.type_ = type_ + self.hard = hard + + def GetSortKey(self): + return '%s.%s' % (self.type_.namespace.name, self.type_.name) + +class CppTypeGenerator(object): + """Manages the types of properties and provides utilities for getting the + C++ type out of a model.Property + """ + def __init__(self, model, schema_loader, default_namespace=None): + """Creates a cpp_type_generator. The given root_namespace should be of the + format extensions::api::sub. The generator will generate code suitable for + use in the given model's namespace. + """ + self._default_namespace = default_namespace + if self._default_namespace is None: + self._default_namespace = model.namespaces.values()[0] + self._schema_loader = schema_loader + + def GetCppNamespaceName(self, namespace): + """Gets the mapped C++ namespace name for the given namespace relative to + the root namespace. + """ + return namespace.unix_name + + def GetNamespaceStart(self): + """Get opening self._default_namespace namespace declaration. + """ + return Code().Append('namespace %s {' % + self.GetCppNamespaceName(self._default_namespace)) + + def GetNamespaceEnd(self): + """Get closing self._default_namespace namespace declaration. + """ + return Code().Append('} // %s' % + self.GetCppNamespaceName(self._default_namespace)) + + def GetEnumNoneValue(self, type_): + """Gets the enum value in the given model.Property indicating no value has + been set. + """ + return '%s_NONE' % self.FollowRef(type_).unix_name.upper() + + def GetEnumValue(self, type_, enum_value): + """Gets the enum value of the given model.Property of the given type. + + e.g VAR_STRING + """ + value = '%s_%s' % (self.FollowRef(type_).unix_name.upper(), + cpp_util.Classname(enum_value.upper())) + # To avoid collisions with built-in OS_* preprocessor definitions, we add a + # trailing slash to enum names that start with OS_. + if value.startswith("OS_"): + value += "_" + return value + + def GetCppType(self, type_, is_ptr=False, is_in_container=False): + """Translates a model.Property or model.Type into its C++ type. + + If REF types from different namespaces are referenced, will resolve + using self._schema_loader. + + Use |is_ptr| if the type is optional. This will wrap the type in a + scoped_ptr if possible (it is not possible to wrap an enum). + + Use |is_in_container| if the type is appearing in a collection, e.g. a + std::vector or std::map. This will wrap it in the correct type with spacing. + """ + cpp_type = None + if type_.property_type == PropertyType.REF: + ref_type = self._FindType(type_.ref_type) + if ref_type is None: + raise KeyError('Cannot find referenced type: %s' % type_.ref_type) + if self._default_namespace is ref_type.namespace: + cpp_type = ref_type.name + else: + cpp_type = '%s::%s' % (ref_type.namespace.unix_name, ref_type.name) + elif type_.property_type == PropertyType.BOOLEAN: + cpp_type = 'bool' + elif type_.property_type == PropertyType.INTEGER: + cpp_type = 'int' + elif type_.property_type == PropertyType.INT64: + cpp_type = 'int64' + elif type_.property_type == PropertyType.DOUBLE: + cpp_type = 'double' + elif type_.property_type == PropertyType.STRING: + cpp_type = 'std::string' + elif type_.property_type == PropertyType.ENUM: + cpp_type = cpp_util.Classname(type_.name) + elif type_.property_type == PropertyType.ANY: + cpp_type = 'base::Value' + elif (type_.property_type == PropertyType.OBJECT or + type_.property_type == PropertyType.CHOICES): + cpp_type = cpp_util.Classname(type_.name) + elif type_.property_type == PropertyType.FUNCTION: + # Functions come into the json schema compiler as empty objects. We can + # record these as empty DictionaryValues so that we know if the function + # was passed in or not. + cpp_type = 'base::DictionaryValue' + elif type_.property_type == PropertyType.ARRAY: + item_cpp_type = self.GetCppType(type_.item_type, is_in_container=True) + cpp_type = 'std::vector<%s>' % cpp_util.PadForGenerics(item_cpp_type) + elif type_.property_type == PropertyType.BINARY: + cpp_type = 'std::string' + else: + raise NotImplementedError('Cannot get type of %s' % type_.property_type) + + # HACK: optional ENUM is represented elsewhere with a _NONE value, so it + # never needs to be wrapped in pointer shenanigans. + # TODO(kalman): change this - but it's an exceedingly far-reaching change. + if not self.FollowRef(type_).property_type == PropertyType.ENUM: + if is_in_container and (is_ptr or not self.IsCopyable(type_)): + cpp_type = 'linked_ptr<%s>' % cpp_util.PadForGenerics(cpp_type) + elif is_ptr: + cpp_type = 'scoped_ptr<%s>' % cpp_util.PadForGenerics(cpp_type) + + return cpp_type + + def IsCopyable(self, type_): + return not (self.FollowRef(type_).property_type in (PropertyType.ANY, + PropertyType.ARRAY, + PropertyType.OBJECT, + PropertyType.CHOICES)) + + def GenerateForwardDeclarations(self): + """Returns the forward declarations for self._default_namespace. + """ + c = Code() + + for namespace, dependencies in self._NamespaceTypeDependencies().items(): + c.Append('namespace %s {' % namespace.unix_name) + for dependency in dependencies: + # No point forward-declaring hard dependencies. + if dependency.hard: + continue + # Add more ways to forward declare things as necessary. + if dependency.type_.property_type in (PropertyType.CHOICES, + PropertyType.OBJECT): + c.Append('struct %s;' % dependency.type_.name) + c.Append('}') + + return c + + def GenerateIncludes(self, include_soft=False): + """Returns the #include lines for self._default_namespace. + """ + c = Code() + for namespace, dependencies in self._NamespaceTypeDependencies().items(): + for dependency in dependencies: + if dependency.hard or include_soft: + c.Append('#include "%s/%s.h"' % (namespace.source_file_dir, + namespace.unix_name)) + return c + + def _FindType(self, full_name): + """Finds the model.Type with name |qualified_name|. If it's not from + |self._default_namespace| then it needs to be qualified. + """ + namespace = self._schema_loader.ResolveType(full_name, + self._default_namespace) + if namespace is None: + raise KeyError('Cannot resolve type %s. Maybe it needs a prefix ' + 'if it comes from another namespace?' % full_name) + return namespace.types[schema_util.StripNamespace(full_name)] + + def FollowRef(self, type_): + """Follows $ref link of types to resolve the concrete type a ref refers to. + + If the property passed in is not of type PropertyType.REF, it will be + returned unchanged. + """ + if type_.property_type != PropertyType.REF: + return type_ + return self.FollowRef(self._FindType(type_.ref_type)) + + def _NamespaceTypeDependencies(self): + """Returns a dict ordered by namespace name containing a mapping of + model.Namespace to every _TypeDependency for |self._default_namespace|, + sorted by the type's name. + """ + dependencies = set() + for function in self._default_namespace.functions.values(): + for param in function.params: + dependencies |= self._TypeDependencies(param.type_, + hard=not param.optional) + if function.callback: + for param in function.callback.params: + dependencies |= self._TypeDependencies(param.type_, + hard=not param.optional) + for type_ in self._default_namespace.types.values(): + for prop in type_.properties.values(): + dependencies |= self._TypeDependencies(prop.type_, + hard=not prop.optional) + for event in self._default_namespace.events.values(): + for param in event.params: + dependencies |= self._TypeDependencies(param.type_, + hard=not param.optional) + + # Make sure that the dependencies are returned in alphabetical order. + dependency_namespaces = OrderedDict() + for dependency in sorted(dependencies, key=_TypeDependency.GetSortKey): + namespace = dependency.type_.namespace + if namespace is self._default_namespace: + continue + if namespace not in dependency_namespaces: + dependency_namespaces[namespace] = [] + dependency_namespaces[namespace].append(dependency) + + return dependency_namespaces + + def _TypeDependencies(self, type_, hard=False): + """Gets all the type dependencies of a property. + """ + deps = set() + if type_.property_type == PropertyType.REF: + deps.add(_TypeDependency(self._FindType(type_.ref_type), hard=hard)) + elif type_.property_type == PropertyType.ARRAY: + # Non-copyable types are not hard because they are wrapped in linked_ptrs + # when generated. Otherwise they're typedefs, so they're hard (though we + # could generate those typedefs in every dependent namespace, but that + # seems weird). + deps = self._TypeDependencies(type_.item_type, + hard=self.IsCopyable(type_.item_type)) + elif type_.property_type == PropertyType.CHOICES: + for type_ in type_.choices: + deps |= self._TypeDependencies(type_, hard=self.IsCopyable(type_)) + elif type_.property_type == PropertyType.OBJECT: + for p in type_.properties.values(): + deps |= self._TypeDependencies(p.type_, hard=not p.optional) + return deps + + def GeneratePropertyValues(self, property, line, nodoc=False): + """Generates the Code to display all value-containing properties. + """ + c = Code() + if not nodoc: + c.Comment(property.description) + + if property.value is not None: + c.Append(line % { + "type": self.GetCppType(property.type_), + "name": property.name, + "value": property.value + }) + else: + has_child_code = False + c.Sblock('namespace %s {' % property.name) + for child_property in property.type_.properties.values(): + child_code = self.GeneratePropertyValues(child_property, + line, + nodoc=nodoc) + if child_code: + has_child_code = True + c.Concat(child_code) + c.Eblock('} // namespace %s' % property.name) + if not has_child_code: + c = None + return c diff --git a/chromium/tools/json_schema_compiler/cpp_type_generator_test.py b/chromium/tools/json_schema_compiler/cpp_type_generator_test.py new file mode 100755 index 00000000000..7782b234a4c --- /dev/null +++ b/chromium/tools/json_schema_compiler/cpp_type_generator_test.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +# 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. + +from cpp_type_generator import CppTypeGenerator +from json_schema import CachedLoad +import model +import unittest + +from collections import defaultdict + +class _FakeSchemaLoader(object): + def __init__(self, model): + self._model = model + + def ResolveType(self, type_name, default): + parts = type_name.rsplit('.', 1) + if len(parts) == 1: + return default if type_name in default.types else None + return self._model.namespaces[parts[0]] + +class CppTypeGeneratorTest(unittest.TestCase): + def setUp(self): + self.models = defaultdict(model.Model) + + self.forbidden_json = CachedLoad('test/forbidden.json') + self.forbidden = self.models['forbidden'].AddNamespace( + self.forbidden_json[0], 'path/to/forbidden.json') + self.permissions_json = CachedLoad('test/permissions.json') + self.permissions = self.models['permissions'].AddNamespace( + self.permissions_json[0], 'path/to/permissions.json') + self.windows_json = CachedLoad('test/windows.json') + self.windows = self.models['windows'].AddNamespace(self.windows_json[0], + 'path/to/window.json') + self.tabs_json = CachedLoad('test/tabs.json') + self.tabs = self.models['tabs'].AddNamespace(self.tabs_json[0], + 'path/to/tabs.json') + self.browser_action_json = CachedLoad('test/browser_action.json') + self.browser_action = self.models['browser_action'].AddNamespace( + self.browser_action_json[0], 'path/to/browser_action.json') + self.font_settings_json = CachedLoad('test/font_settings.json') + self.font_settings = self.models['font_settings'].AddNamespace( + self.font_settings_json[0], 'path/to/font_settings.json') + self.dependency_tester_json = CachedLoad('test/dependency_tester.json') + self.dependency_tester = self.models['dependency_tester'].AddNamespace( + self.dependency_tester_json[0], 'path/to/dependency_tester.json') + self.content_settings_json = CachedLoad('test/content_settings.json') + self.content_settings = self.models['content_settings'].AddNamespace( + self.content_settings_json[0], 'path/to/content_settings.json') + + def testGenerateIncludesAndForwardDeclarations(self): + m = model.Model() + m.AddNamespace(self.windows_json[0], 'path/to/windows.json') + m.AddNamespace(self.tabs_json[0], 'path/to/tabs.json') + manager = CppTypeGenerator(m, _FakeSchemaLoader(m)) + + self.assertEquals('', manager.GenerateIncludes().Render()) + self.assertEquals('#include "path/to/tabs.h"', + manager.GenerateIncludes(include_soft=True).Render()) + self.assertEquals('namespace tabs {\n' + 'struct Tab;\n' + '}', + manager.GenerateForwardDeclarations().Render()) + manager = CppTypeGenerator(self.models.get('permissions'), + _FakeSchemaLoader(m)) + self.assertEquals('', manager.GenerateIncludes().Render()) + self.assertEquals('', manager.GenerateIncludes().Render()) + self.assertEquals('', manager.GenerateForwardDeclarations().Render()) + manager = CppTypeGenerator(self.models.get('content_settings'), + _FakeSchemaLoader(m)) + self.assertEquals('', manager.GenerateIncludes().Render()) + + def testGenerateIncludesAndForwardDeclarationsDependencies(self): + m = model.Model() + # Insert 'font_settings' before 'browser_action' in order to test that + # CppTypeGenerator sorts them properly. + m.AddNamespace(self.font_settings_json[0], 'path/to/font_settings.json') + m.AddNamespace(self.browser_action_json[0], 'path/to/browser_action.json') + dependency_tester = m.AddNamespace(self.dependency_tester_json[0], + 'path/to/dependency_tester.json') + manager = CppTypeGenerator(m, + _FakeSchemaLoader(m), + default_namespace=dependency_tester) + self.assertEquals('#include "path/to/browser_action.h"\n' + '#include "path/to/font_settings.h"', + manager.GenerateIncludes().Render()) + self.assertEquals('namespace browser_action {\n' + '}\n' + 'namespace font_settings {\n' + '}', + manager.GenerateForwardDeclarations().Render()) + + def testGetCppTypeSimple(self): + manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None)) + self.assertEquals( + 'int', + manager.GetCppType(self.tabs.types['Tab'].properties['id'].type_)) + self.assertEquals( + 'std::string', + manager.GetCppType(self.tabs.types['Tab'].properties['status'].type_)) + self.assertEquals( + 'bool', + manager.GetCppType(self.tabs.types['Tab'].properties['selected'].type_)) + + def testStringAsType(self): + manager = CppTypeGenerator(self.models.get('font_settings'), + _FakeSchemaLoader(None)) + self.assertEquals( + 'std::string', + manager.GetCppType(self.font_settings.types['FakeStringType'])) + + def testArrayAsType(self): + manager = CppTypeGenerator(self.models.get('browser_action'), + _FakeSchemaLoader(None)) + self.assertEquals( + 'std::vector<int>', + manager.GetCppType(self.browser_action.types['ColorArray'])) + + def testGetCppTypeArray(self): + manager = CppTypeGenerator(self.models.get('windows'), + _FakeSchemaLoader(None)) + self.assertEquals( + 'std::vector<linked_ptr<Window> >', + manager.GetCppType( + self.windows.functions['getAll'].callback.params[0].type_)) + manager = CppTypeGenerator(self.models.get('permissions'), + _FakeSchemaLoader(None)) + self.assertEquals( + 'std::vector<std::string>', + manager.GetCppType( + self.permissions.types['Permissions'].properties['origins'].type_)) + + def testGetCppTypeLocalRef(self): + manager = CppTypeGenerator(self.models.get('tabs'), _FakeSchemaLoader(None)) + self.assertEquals( + 'Tab', + manager.GetCppType(self.tabs.functions['get'].callback.params[0].type_)) + + def testGetCppTypeIncludedRef(self): + m = model.Model() + m.AddNamespace(self.windows_json[0], 'path/to/windows.json') + m.AddNamespace(self.tabs_json[0], 'path/to/tabs.json') + manager = CppTypeGenerator(m, _FakeSchemaLoader(m)) + self.assertEquals( + 'std::vector<linked_ptr<tabs::Tab> >', + manager.GetCppType( + self.windows.types['Window'].properties['tabs'].type_)) + + def testGetCppTypeWithPadForGeneric(self): + manager = CppTypeGenerator(self.models.get('permissions'), + _FakeSchemaLoader(None)) + self.assertEquals('std::vector<std::string>', + manager.GetCppType( + self.permissions.types['Permissions'].properties['origins'].type_, + is_in_container=False)) + self.assertEquals('linked_ptr<std::vector<std::string> >', + manager.GetCppType( + self.permissions.types['Permissions'].properties['origins'].type_, + is_in_container=True)) + self.assertEquals('bool', + manager.GetCppType( + self.permissions.functions['contains'].callback.params[0].type_, + is_in_container=True)) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/cpp_util.py b/chromium/tools/json_schema_compiler/cpp_util.py new file mode 100644 index 00000000000..2de42293fba --- /dev/null +++ b/chromium/tools/json_schema_compiler/cpp_util.py @@ -0,0 +1,113 @@ +# 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. + +"""Utilies and constants specific to Chromium C++ code. +""" + +from code import Code +from datetime import datetime +from model import Property, PropertyType, Type +import os +import re + +CHROMIUM_LICENSE = ( +"""// Copyright (c) %d 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.""" % datetime.now().year +) +GENERATED_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITION IN +// %s +// DO NOT EDIT. +""" +GENERATED_BUNDLE_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITIONS IN +// %s +// DO NOT EDIT. +""" + +def Classname(s): + """Translates a namespace name or function name into something more + suited to C++. + + eg experimental.downloads -> Experimental_Downloads + updateAll -> UpdateAll. + """ + return '_'.join([x[0].upper() + x[1:] for x in re.split('\W', s)]) + +def GetAsFundamentalValue(type_, src, dst): + """Returns the C++ code for retrieving a fundamental type from a + Value into a variable. + + src: Value* + dst: Property* + """ + return { + PropertyType.BOOLEAN: '%s->GetAsBoolean(%s)', + PropertyType.DOUBLE: '%s->GetAsDouble(%s)', + PropertyType.INTEGER: '%s->GetAsInteger(%s)', + PropertyType.STRING: '%s->GetAsString(%s)', + }[type_.property_type] % (src, dst) + +def GetValueType(type_): + """Returns the Value::Type corresponding to the model.Type. + """ + return { + PropertyType.ARRAY: 'base::Value::TYPE_LIST', + PropertyType.BINARY: 'base::Value::TYPE_BINARY', + PropertyType.BOOLEAN: 'base::Value::TYPE_BOOLEAN', + # PropertyType.CHOICES can be any combination of types. + PropertyType.DOUBLE: 'base::Value::TYPE_DOUBLE', + PropertyType.ENUM: 'base::Value::TYPE_STRING', + PropertyType.FUNCTION: 'base::Value::TYPE_DICTIONARY', + PropertyType.INTEGER: 'base::Value::TYPE_INTEGER', + PropertyType.OBJECT: 'base::Value::TYPE_DICTIONARY', + PropertyType.STRING: 'base::Value::TYPE_STRING', + }[type_.property_type] + +def GetParameterDeclaration(param, type_): + """Gets a parameter declaration of a given model.Property and its C++ + type. + """ + if param.type_.property_type in (PropertyType.ANY, + PropertyType.ARRAY, + PropertyType.CHOICES, + PropertyType.OBJECT, + PropertyType.REF, + PropertyType.STRING): + arg = 'const %(type)s& %(name)s' + else: + arg = '%(type)s %(name)s' + return arg % { + 'type': type_, + 'name': param.unix_name, + } + +def GenerateIfndefName(path, filename): + """Formats a path and filename as a #define name. + + e.g chrome/extensions/gen, file.h becomes CHROME_EXTENSIONS_GEN_FILE_H__. + """ + return (('%s_%s_H__' % (path, filename)) + .upper().replace(os.sep, '_').replace('/', '_')) + +def PadForGenerics(var): + """Appends a space to |var| if it ends with a >, so that it can be compiled + within generic types. + """ + return ('%s ' % var) if var.endswith('>') else var + +def OpenNamespace(namespace): + """Get opening root namespace declarations. + """ + c = Code() + for component in namespace.split('::'): + c.Append('namespace %s {' % component) + return c + +def CloseNamespace(namespace): + """Get closing root namespace declarations. + """ + c = Code() + for component in reversed(namespace.split('::')): + c.Append('} // namespace %s' % component) + return c diff --git a/chromium/tools/json_schema_compiler/cpp_util_test.py b/chromium/tools/json_schema_compiler/cpp_util_test.py new file mode 100755 index 00000000000..ede309587f3 --- /dev/null +++ b/chromium/tools/json_schema_compiler/cpp_util_test.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +# 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. + +import cpp_util +import unittest + +class CppUtilTest(unittest.TestCase): + def testClassname(self): + self.assertEquals('Permissions', cpp_util.Classname('permissions')) + self.assertEquals('UpdateAllTheThings', + cpp_util.Classname('updateAllTheThings')) + self.assertEquals('Aa_Bb_Cc', cpp_util.Classname('aa.bb.cc')) + + def testNamespaceDeclaration(self): + self.assertEquals('namespace extensions {', + cpp_util.OpenNamespace('extensions').Render()) + self.assertEquals('} // namespace extensions', + cpp_util.CloseNamespace('extensions').Render()) + self.assertEquals('namespace extensions {\n' + 'namespace gen {\n' + 'namespace api {', + cpp_util.OpenNamespace('extensions::gen::api').Render()) + self.assertEquals('} // namespace api\n' + '} // namespace gen\n' + '} // namespace extensions', + cpp_util.CloseNamespace('extensions::gen::api').Render()) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/dart_generator.py b/chromium/tools/json_schema_compiler/dart_generator.py new file mode 100644 index 00000000000..3a4c2e33c10 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_generator.py @@ -0,0 +1,762 @@ +# 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. +""" + +from code import Code +from model import * +from schema_util import * + +import os +from datetime import datetime + +LICENSE = ( +"""// Copyright (c) %s, the Dart project authors. Please see the AUTHORS file +// 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.""" % + datetime.now().year) + +class DartGenerator(object): + def __init__(self, dart_overrides_dir=None): + self._dart_overrides_dir = dart_overrides_dir + + def Generate(self, namespace): + return _Generator(namespace, self._dart_overrides_dir).Generate() + +class _Generator(object): + """A .dart generator for a namespace. + """ + + def __init__(self, namespace, dart_overrides_dir=None): + self._namespace = namespace + # TODO(sashab): Once inline type definitions start being added to + # self._types, make a _FindType(self, type_) function that looks at + # self._namespace.types. + self._types = namespace.types + + # Build a dictionary of Type Name --> Custom Dart code. + self._type_overrides = {} + if dart_overrides_dir is not None: + for filename in os.listdir(dart_overrides_dir): + if filename.startswith(namespace.unix_name): + with open(os.path.join(dart_overrides_dir, filename)) as f: + # Split off the namespace and file extension, leaving just the type. + type_path = '.'.join(filename.split('.')[1:-1]) + self._type_overrides[type_path] = f.read() + + # TODO(sashab): Add all inline type definitions to the global Types + # dictionary here, so they have proper names, and are implemented along with + # all other types. Also update the parameters/members with these types + # to reference these new types instead. + + def Generate(self): + """Generates a Code object with the .dart for the entire namespace. + """ + c = Code() + (c.Append(LICENSE) + .Append() + .Append('// Generated from namespace: %s' % self._namespace.name) + .Append() + .Append('part of chrome;')) + + if self._types: + (c.Append() + .Append('/**') + .Append(' * Types') + .Append(' */') + .Append() + ) + for type_ in self._types.values(): + # Check for custom dart for this whole type. + override = self._GetOverride([type_.name], document_with=type_) + c.Cblock(override if override is not None else self._GenerateType(type_)) + + if self._namespace.events: + (c.Append('/**') + .Append(' * Events') + .Append(' */') + .Append() + ) + for event_name in self._namespace.events: + c.Cblock(self._GenerateEvent(self._namespace.events[event_name])) + + (c.Append('/**') + .Append(' * Functions') + .Append(' */') + .Append() + ) + c.Cblock(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() + + # Since enums are just treated as strings for now, don't generate their + # type. + # TODO(sashab): Find a nice way to wrap enum objects. + if type_.property_type is PropertyType.ENUM: + return c + + (c.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 = all(not self._IsFunction(p.type_) + for p in type_.properties.values()) + constructor_fields = [self._GeneratePropertySignature(p) + for p in type_.properties.values()] + + if add_public_constructor: + (c.Append('/*') + .Append(' * Public constructor') + .Append(' */') + .Sblock('%(type_name)s({%(constructor_fields)s}) {') + ) + + for prop_name in type_.properties: + (c.Sblock('if (%s != null)' % prop_name) + .Append('this.%s = %s;' % (prop_name, prop_name)) + .Eblock() + ) + (c.Eblock('}') + .Append() + ) + + (c.Append('/*') + .Append(' * Private constructor') + .Append(' */') + .Append('%(type_name)s._proxy(_jsObject) : super._proxy(_jsObject);') + ) + + # Add an accessor (getter & setter) for each property. + properties = [p for p in type_.properties.values() + if not self._IsFunction(p.type_)] + if properties: + (c.Append() + .Append('/*') + .Append(' * Public accessors') + .Append(' */') + ) + for prop in properties: + override = self._GetOverride([type_.name, prop.name], document_with=prop) + c.Concat(override if override is not None + else self._GenerateGetterAndSetter(type_, prop)) + + # Now add all the methods. + methods = [t for t in type_.properties.values() + if self._IsFunction(t.type_)] + if methods: + (c.Append() + .Append('/*') + .Append(' * Methods') + .Append(' */') + ) + for prop in methods: + # Check if there's an override for this method. + override = self._GetOverride([type_.name, prop.name], document_with=prop) + c.Cblock(override if override is not None + else self._GenerateFunction(prop.type_.function)) + + (c.Eblock('}') + .Substitute({ + 'type_name': self._AddPrefix(type_.simple_name), + 'constructor_fields': ', '.join(constructor_fields) + }) + ) + + return c + + def _GenerateGetterAndSetter(self, type_, prop): + """Given a Type and Property, returns the Code object for the getter and + setter for that property. + """ + c = Code() + override = self._GetOverride([type_.name, prop.name, '.get'], + document_with=prop) + c.Cblock(override if override is not None + else self._GenerateGetter(type_, prop)) + override = self._GetOverride([type_.name, prop.name, '.set']) + c.Cblock(override if override is not None + else self._GenerateSetter(type_, prop)) + return c + + def _GenerateGetter(self, type_, prop): + """Given a Type and Property, returns the Code object for the getter for + that property. + + Also adds the documentation for this property before the method. + """ + c = Code() + c.Concat(self._GenerateDocumentation(prop)) + + type_name = self._GetDartType(prop.type_) + if (self._IsBaseType(prop.type_)): + c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" % + (type_name, prop.name, type_name, prop.name)) + elif self._IsSerializableObjectType(prop.type_): + c.Append("%s get %s => new %s._proxy(JS('', '#.%s', " + "this._jsObject));" % + (type_name, prop.name, type_name, prop.name)) + elif self._IsListOfSerializableObjects(prop.type_): + (c.Sblock('%s get %s {' % (type_name, prop.name)) + .Append('%s __proxy_%s = new %s();' % (type_name, prop.name, + type_name)) + .Append("int count = JS('int', '#.%s.length', this._jsObject);" % + prop.name) + .Sblock("for (int i = 0; i < count; i++) {") + .Append("var item = JS('', '#.%s[#]', this._jsObject, i);" % prop.name) + .Append('__proxy_%s.add(new %s._proxy(item));' % (prop.name, + self._GetDartType(prop.type_.item_type))) + .Eblock('}') + .Append('return __proxy_%s;' % prop.name) + .Eblock('}') + ) + elif self._IsObjectType(prop.type_): + # TODO(sashab): Think of a way to serialize generic Dart objects. + if type_name in self._types: + c.Append("%s get %s => new %s._proxy(JS('%s', '#.%s', " + "this._jsObject));" % + (type_name, prop.name, type_name, type_name, prop.name)) + else: + c.Append("%s get %s => JS('%s', '#.%s', this._jsObject);" % + (type_name, prop.name, type_name, prop.name)) + else: + raise Exception( + "Could not generate wrapper for %s.%s: unserializable type %s" % + (type_.name, prop.name, type_name) + ) + return c + + def _GenerateSetter(self, type_, prop): + """Given a Type and Property, returns the Code object for the setter for + that property. + """ + c = Code() + type_name = self._GetDartType(prop.type_) + wrapped_name = prop.name + if not self._IsBaseType(prop.type_): + wrapped_name = 'convertArgument(%s)' % prop.name + + (c.Sblock("void set %s(%s %s) {" % (prop.name, type_name, prop.name)) + .Append("JS('void', '#.%s = #', this._jsObject, %s);" % + (prop.name, wrapped_name)) + .Eblock("}") + ) + 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 prop.description is not None: + for line in prop.description.split('\n'): + c.Comment(line, comment_prefix='/// ') + return c + + def _GenerateFunction(self, f): + """Returns the Code object for the given function. + """ + c = Code() + c.Concat(self._GenerateDocumentation(f)) + + if not self._NeedsProxiedCallback(f): + c.Append("%s => %s;" % (self._GenerateFunctionSignature(f), + self._GenerateProxyCall(f))) + return c + + (c.Sblock("%s {" % self._GenerateFunctionSignature(f)) + .Concat(self._GenerateProxiedFunction(f.callback, f.callback.name)) + .Append('%s;' % self._GenerateProxyCall(f)) + .Eblock('}') + ) + + return c + + def _GenerateProxiedFunction(self, f, callback_name): + """Given a function (assumed to be a callback), generates the proxied + version of this function, which calls |callback_name| if it is defined. + + Returns a Code object. + """ + c = Code() + proxied_params = [] + # A list of Properties, containing List<*> objects that need proxying for + # their members (by copying out each member and proxying it). + lists_to_proxy = [] + for p in f.params: + if self._IsBaseType(p.type_): + proxied_params.append(p.name) + elif self._IsSerializableObjectType(p.type_): + proxied_params.append('new %s._proxy(%s)' % ( + self._GetDartType(p.type_), p.name)) + elif self._IsListOfSerializableObjects(p.type_): + proxied_params.append('__proxy_%s' % p.name) + lists_to_proxy.append(p) + elif self._IsObjectType(p.type_): + # TODO(sashab): Find a way to build generic JS objects back in Dart. + proxied_params.append('%s' % p.name) + elif p.type_.property_type is PropertyType.ARRAY: + # TODO(sashab): This might be okay - what if this is a list of + # FileEntry elements? In this case, a basic list will proxy the objects + # fine. + proxied_params.append('%s' % p.name) + else: + raise Exception( + "Cannot automatically create proxy; can't wrap %s, type %s" % ( + self._GenerateFunctionSignature(f), self._GetDartType(p.type_))) + + (c.Sblock("void __proxy_callback(%s) {" % ', '.join(p.name for p in + f.params)) + .Sblock('if (%s != null) {' % callback_name) + ) + + # Add the proxied lists. + for list_to_proxy in lists_to_proxy: + (c.Append("%s __proxy_%s = new %s();" % ( + self._GetDartType(list_to_proxy.type_), + list_to_proxy.name, + self._GetDartType(list_to_proxy.type_))) + .Sblock("for (var o in %s) {" % list_to_proxy.name) + .Append('__proxy_%s.add(new %s._proxy(o));' % (list_to_proxy.name, + self._GetDartType(list_to_proxy.type_.item_type))) + .Eblock("}") + ) + + (c.Append("%s(%s);" % (callback_name, ', '.join(proxied_params))) + .Eblock('}') + .Eblock('}') + ) + return c + + def _NeedsProxiedCallback(self, f): + """Given a function, returns True if this function's callback needs to be + proxied, False if not. + + Function callbacks need to be proxied if they have at least one + non-base-type parameter. + """ + return f.callback and self._NeedsProxy(f.callback) + + def _NeedsProxy(self, f): + """Given a function, returns True if it needs to be proxied, False if not. + + A function needs to be proxied if any of its members are non-base types. + This means that, when the function object is passed to Javascript, it + needs to be wrapped in a "proxied" call that converts the JS inputs to Dart + objects explicitly, before calling the real function with these new objects. + """ + return any(not self._IsBaseType(p.type_) for p in f.params) + + def _GenerateProxyCall(self, function, call_target='this._jsObject'): + """Given a function, generates the code to call that function via JS(). + Returns a string. + + |call_target| 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)) + """ + n_params = len(function.params) + if function.callback: + n_params += 1 + + return_type_str = self._GetDartType(function.returns) + params = [] + + # If this object is serializable, don't convert the type from JS - pass the + # JS object straight into the proxy. + if self._IsSerializableObjectType(function.returns): + params.append("''") + else: + params.append("'%s'" % return_type_str) + + params.append("'#.%s(%s)'" % (function.name, ', '.join(['#'] * n_params))) + params.append(call_target) + + for param in function.params: + if not self._IsBaseType(param.type_): + params.append('convertArgument(%s)' % param.name) + else: + params.append(param.name) + if function.callback: + # If this isn't a base type, we need a proxied callback. + callback_name = function.callback.name + if self._NeedsProxiedCallback(function): + callback_name = "__proxy_callback" + params.append('convertDartClosureToJS(%s, %s)' % (callback_name, + len(function.callback.params))) + + # If the object is serializable, call the proxy constructor for this type. + proxy_call = 'JS(%s)' % ', '.join(params) + if self._IsSerializableObjectType(function.returns): + proxy_call = 'new %s._proxy(%s)' % (return_type_str, proxy_call) + + return proxy_call + + 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.Concat(self._GenerateDocumentation(event)) + .Sblock('class Event_%(event_name)s extends Event {') + ) + + # If this event needs a proxy, all calls need to be proxied. + needs_proxy = self._NeedsProxy(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, + convert_optional=True) + if needs_proxy: + (c.Sblock('%s %s(void callback(%s)) {' % (ret_type, event_func, + param_list)) + .Concat(self._GenerateProxiedFunction(event, 'callback')) + .Append('super.%s(__proxy_callback);' % event_func) + .Eblock('}') + ) + else: + c.Append('%s %s(void callback(%s)) => super.%s(callback);' % + (ret_type, event_func, param_list, event_func)) + c.Append() + + # Generate the constructor. + (c.Append('Event_%(event_name)s(jsObject) : ' + 'super._(jsObject, %(param_num)d);') + .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. + + Returns a code object. + """ + c = Code() + (c.Sblock('class API_%s {' % self._namespace.unix_name) + .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(): + # Check for custom dart for this whole property. + override = self._GetOverride([function.name], document_with=function) + c.Cblock(override if override is not None + else self._GenerateFunction(function)) + + # Add the constructor. + c.Sblock('API_%s(this._jsObject) {' % self._namespace.unix_name) + + # 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('}') + .Eblock('}') + ) + return c + + def _GeneratePropertySignature(self, prop): + """Given a property, returns a signature for that property. + Recursively generates the signature for callbacks. + Returns a String for the given property. + + e.g. + bool x + void onClosed() + void doSomething(bool x, void callback([String x])) + """ + if self._IsFunction(prop.type_): + return self._GenerateFunctionSignature(prop.type_.function) + return '%(type)s %(name)s' % { + 'type': self._GetDartType(prop.type_), + 'name': prop.simple_name + } + + def _GenerateFunctionSignature(self, function, convert_optional=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 convert_optional is True, changes optional parameters to be required. + + e.g. + void onClosed() + bool isOpen([String type]) + void doSomething(bool x, void callback([String x])) + """ + sig = '%(return_type)s %(name)s(%(params)s)' + + if function.returns: + return_type = self._GetDartType(function.returns) + else: + return_type = 'void' + + return sig % { + 'return_type': return_type, + 'name': function.simple_name, + 'params': self._GenerateParameterList(function.params, + function.callback, + convert_optional=convert_optional) + } + + def _GenerateParameterList(self, + params, + callback=None, + convert_optional=False): + """Given a list of function parameters, generates their signature (as a + string). + + e.g. + [String type] + bool x, void callback([String x]) + + If convert_optional is True, changes optional parameters to be required. + Useful for callbacks, where optional parameters are treated as required. + """ + # Params lists (required & optional), to be joined with commas. + # TODO(sashab): Don't assume optional params always come after required + # ones. + params_req = [] + params_opt = [] + for param in params: + p_sig = self._GeneratePropertySignature(param) + if param.optional and not convert_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, convert_optional=True) + if callback.optional: + params_opt.append(c_sig) + else: + params_req.append(c_sig) + + # Join the parameters with commas. + # Optional parameters have to be in square brackets, e.g.: + # + # required params | optional params | output + # [] | [] | '' + # [x, y] | [] | 'x, y' + # [] | [a, b] | '[a, b]' + # [x, y] | [a, b] | 'x, y, [a, b]' + if params_opt: + params_opt[0] = '[%s' % params_opt[0] + params_opt[-1] = '%s]' % params_opt[-1] + param_sets = [', '.join(params_req), ', '.join(params_opt)] + + # The 'if p' part here is needed to prevent commas where there are no + # parameters of a certain type. + # If there are no optional parameters, this prevents a _trailing_ comma, + # e.g. '(x, y,)'. Similarly, if there are no required parameters, this + # prevents a leading comma, e.g. '(, [a, b])'. + return ', '.join(p for p in param_sets if p) + + def _GetOverride(self, key_chain, document_with=None): + """Given a list of keys, joins them with periods and searches for them in + the custom dart overrides. + If there is an override for that key, finds the override code and returns + the Code object. If not, returns None. + + If document_with is not None, adds the documentation for this property + before the override code. + """ + c = Code() + contents = self._type_overrides.get('.'.join(key_chain)) + if contents is None: + return None + + if document_with is not None: + c.Concat(self._GenerateDocumentation(document_with)) + for line in contents.strip('\n').split('\n'): + c.Append(line) + return c + + def _AddPrefix(self, name): + """Given the name of a type, prefixes the namespace (as camelcase) and + returns the new name. + """ + # TODO(sashab): Split the dart library into multiple files, avoiding the + # need for this prefixing. + return ('%s%s' % ( + ''.join(s.capitalize() for s in self._namespace.name.split('.')), + name)) + + def _IsFunction(self, type_): + """Given a model.Type, returns whether this type is a function. + """ + return type_.property_type == PropertyType.FUNCTION + + def _IsSerializableObjectType(self, type_): + """Given a model.Type, returns whether this type is a serializable object. + Serializable objects are custom types defined in this namespace. + + If this object is a reference to something not in this namespace, assumes + its a serializable object. + """ + if type_ is None: + return False + if type_.property_type is PropertyType.CHOICES: + return all(self._IsSerializableObjectType(c) for c in type_.choices) + if type_.property_type is PropertyType.REF: + if type_.ref_type in self._types: + return self._IsObjectType(self._types[type_.ref_type]) + return True + if (type_.property_type == PropertyType.OBJECT + and type_.instance_of in self._types): + return self._IsObjectType(self._types[type_.instance_of]) + return False + + def _IsObjectType(self, type_): + """Given a model.Type, returns whether this type is an object. + """ + return (self._IsSerializableObjectType(type_) + or type_.property_type in [PropertyType.OBJECT, PropertyType.ANY]) + + def _IsListOfSerializableObjects(self, type_): + """Given a model.Type, returns whether this type is a list of serializable + objects (or regular objects, if this list is treated as a type - in this + case, the item type was defined inline). + + If this type is a reference to something not in this namespace, assumes + it is not a list of serializable objects. + """ + if type_.property_type is PropertyType.CHOICES: + return all(self._IsListOfSerializableObjects(c) for c in type_.choices) + if type_.property_type is PropertyType.REF: + if type_.ref_type in self._types: + return self._IsListOfSerializableObjects(self._types[type_.ref_type]) + return False + return (type_.property_type is PropertyType.ARRAY and + (self._IsSerializableObjectType(type_.item_type))) + + def _IsListOfBaseTypes(self, type_): + """Given a model.Type, returns whether this type is a list of base type + objects (PropertyType.REF types). + """ + if type_.property_type is PropertyType.CHOICES: + return all(self._IsListOfBaseTypes(c) for c in type_.choices) + return (type_.property_type is PropertyType.ARRAY and + self._IsBaseType(type_.item_type)) + + def _IsBaseType(self, type_): + """Given a model.type_, returns whether this type is a base type + (string, number, boolean, or a list of these). + + If type_ is a Choices object, returns True if all possible choices are base + types. + """ + # TODO(sashab): Remove 'Choices' as a base type once they are wrapped in + # native Dart classes. + if type_.property_type is PropertyType.CHOICES: + return all(self._IsBaseType(c) for c in type_.choices) + return ( + (self._GetDartType(type_) in ['bool', 'num', 'int', 'double', 'String']) + or (type_.property_type is PropertyType.ARRAY + and self._IsBaseType(type_.item_type)) + ) + + def _GetDartType(self, type_): + """Given a model.Type object, returns its type as a Dart string. + """ + if type_ is None: + return 'void' + + prop_type = type_.property_type + if prop_type is PropertyType.REF: + if type_.ref_type in self._types: + return self._GetDartType(self._types[type_.ref_type]) + # TODO(sashab): If the type is foreign, it might have to be imported. + return StripNamespace(type_.ref_type) + elif prop_type is PropertyType.BOOLEAN: + return 'bool' + elif prop_type is PropertyType.INTEGER: + return 'int' + elif prop_type is PropertyType.INT64: + return 'num' + elif prop_type is PropertyType.DOUBLE: + return 'double' + elif prop_type is PropertyType.STRING: + return 'String' + elif prop_type is PropertyType.ENUM: + return 'String' + elif prop_type is PropertyType.CHOICES: + # TODO(sashab): Think of a nice way to generate code for Choices objects + # in Dart. + return 'Object' + elif prop_type is PropertyType.ANY: + return 'Object' + elif prop_type is PropertyType.OBJECT: + # TODO(sashab): type_.name is the name of the function's parameter for + # inline types defined in functions. Think of a way to generate names + # for this, or remove all inline type definitions at the start. + if type_.instance_of is not None: + return type_.instance_of + if not isinstance(type_.parent, Function): + return self._AddPrefix(type_.name) + return 'Object' + elif prop_type is PropertyType.FUNCTION: + return 'Function' + elif prop_type is PropertyType.ARRAY: + return 'List<%s>' % self._GetDartType(type_.item_type) + elif prop_type is PropertyType.BINARY: + return 'String' + else: + raise NotImplementedError(prop_type) + diff --git a/chromium/tools/json_schema_compiler/dart_generator_test.py b/chromium/tools/json_schema_compiler/dart_generator_test.py new file mode 100755 index 00000000000..65ec7f1e638 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_generator_test.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python +# 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. + +import os +import sys +import unittest +import glob + +from dart_generator import DartGenerator +from compiler import GenerateSchema + +# If --rebase is passed to this test, this is set to True, indicating the test +# output should be re-generated for each test (rather than running the tests +# themselves). +REBASE_MODE = False + +# The directory containing the input and expected output files corresponding +# to each test name. +TESTS_DIR = 'dart_test' + +class DartTest(unittest.TestCase): + + def _RunTest(self, test_filename): + '''Given the name of a test, runs compiler.py on the file: + TESTS_DIR/test_filename.idl + and compares it to the output in the file: + TESTS_DIR/test_filename.dart + ''' + file_rel = os.path.join(TESTS_DIR, test_filename) + + output_dir = None + if REBASE_MODE: + output_dir = TESTS_DIR + output_code = GenerateSchema('dart', ['%s.idl' % file_rel], TESTS_DIR, + output_dir, None, None) + + if not REBASE_MODE: + with open('%s.dart' % file_rel) as f: + expected_output = f.read() + # Remove the first line of the output code (as it contains the filename). + # Also remove all blank lines, ignoring them from the comparison. + # Compare with lists instead of strings for clearer diffs (especially with + # whitespace) when a test fails. + self.assertEqual([l for l in expected_output.split('\n') if l], + [l for l in output_code.split('\n')[1:] if l]) + + def setUp(self): + # Increase the maximum diff amount to see the full diff on a failed test. + self.maxDiff = 2000 + + def testComments(self): + self._RunTest('comments') + + def testDictionaries(self): + self._RunTest('dictionaries') + + def testEmptyNamespace(self): + self._RunTest('empty_namespace') + + def testEmptyType(self): + self._RunTest('empty_type') + + def testEvents(self): + self._RunTest('enums') + + def testEvents(self): + self._RunTest('events') + + def testBasicFunction(self): + self._RunTest('functions') + + def testOpratableType(self): + self._RunTest('operatable_type') + + def testTags(self): + self._RunTest('tags') + +if __name__ == '__main__': + if '--rebase' in sys.argv: + print "Running in rebase mode." + REBASE_MODE = True + sys.argv.remove('--rebase') + unittest.main() diff --git a/chromium/tools/json_schema_compiler/dart_test/comments.dart b/chromium/tools/json_schema_compiler/dart_test/comments.dart new file mode 100644 index 00000000000..d734d56f522 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/comments.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: comments + +part of chrome; +/** + * Functions + */ + +class API_comments { + /* + * API connection + */ + Object _jsObject; + + /* + * Functions + */ + /// There's a blank line at the start of this comment. Documentation for + /// basicFunction. BasicFunction() is a great function. There is a newline + /// after this.<br/><br/> It works like so: +-----+ | | + /// +--+ | | | | +-----+ --> +--+<br/><br/> Some other + /// stuff here. This paragraph starts with whitespace. Overall, its a + /// great function. There's also a blank line at the end of this comment. + void basicFunction() => JS('void', '#.basicFunction()', this._jsObject); + + API_comments(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/comments.idl b/chromium/tools/json_schema_compiler/dart_test/comments.idl new file mode 100644 index 00000000000..867d289d264 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/comments.idl @@ -0,0 +1,32 @@ +// 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. + +// This comment is for the comments namespace. +namespace comments { + // This comments the "Functions" block, and should be ignored. + interface Functions { + // This comment is separated by at least one blank line from the start of + // the function, and should be ignored. + + // + // There's a blank line at the start of this comment. + // + // Documentation for basicFunction. + // BasicFunction() is a great function. + // There is a newline after this. + // + // It works like so: + // +-----+ + // | | +--+ + // | | | | + // +-----+ --> +--+ + // + // Some other stuff here. + // This paragraph starts with whitespace. + // Overall, its a great function. + // There's also a blank line at the end of this comment. + // + static void basicFunction(); + }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/dictionaries.dart b/chromium/tools/json_schema_compiler/dart_test/dictionaries.dart new file mode 100644 index 00000000000..ebe92a6c1eb --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/dictionaries.dart @@ -0,0 +1,235 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: dictionaries + +part of chrome; + +/** + * Types + */ + +class DictionariesInnerType extends ChromeObject { + /* + * Public constructor + */ + DictionariesInnerType({String s, int b, int i, int l, double d, FileEntry f, String os, int ob, int oi, int ol, double od, FileEntry of}) { + if (s != null) + this.s = s; + if (b != null) + this.b = b; + if (i != null) + this.i = i; + if (l != null) + this.l = l; + if (d != null) + this.d = d; + if (f != null) + this.f = f; + if (os != null) + this.os = os; + if (ob != null) + this.ob = ob; + if (oi != null) + this.oi = oi; + if (ol != null) + this.ol = ol; + if (od != null) + this.od = od; + if (of != null) + this.of = of; + } + + /* + * Private constructor + */ + DictionariesInnerType._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// Documentation for the String s. + String get s => JS('String', '#.s', this._jsObject); + + void set s(String s) { + JS('void', '#.s = #', this._jsObject, s); + } + + /// Documentation for the boolean b. + int get b => JS('int', '#.b', this._jsObject); + + void set b(int b) { + JS('void', '#.b = #', this._jsObject, b); + } + + /// Documentation for the int i. + int get i => JS('int', '#.i', this._jsObject); + + void set i(int i) { + JS('void', '#.i = #', this._jsObject, i); + } + + /// Documentation for the long l. + int get l => JS('int', '#.l', this._jsObject); + + void set l(int l) { + JS('void', '#.l = #', this._jsObject, l); + } + + /// Documentation for the double d. + double get d => JS('double', '#.d', this._jsObject); + + void set d(double d) { + JS('void', '#.d = #', this._jsObject, d); + } + + /// Documentation for the file entry f. + FileEntry get f => JS('FileEntry', '#.f', this._jsObject); + + void set f(FileEntry f) { + JS('void', '#.f = #', this._jsObject, convertArgument(f)); + } + + /// Documentation for the optional String s. + String get os => JS('String', '#.os', this._jsObject); + + void set os(String os) { + JS('void', '#.os = #', this._jsObject, os); + } + + /// Documentation for the optional boolean ob. + int get ob => JS('int', '#.ob', this._jsObject); + + void set ob(int ob) { + JS('void', '#.ob = #', this._jsObject, ob); + } + + /// Documentation for the optional int i. + int get oi => JS('int', '#.oi', this._jsObject); + + void set oi(int oi) { + JS('void', '#.oi = #', this._jsObject, oi); + } + + /// Documentation for the optional long l. + int get ol => JS('int', '#.ol', this._jsObject); + + void set ol(int ol) { + JS('void', '#.ol = #', this._jsObject, ol); + } + + /// Documentation for the optional double d. + double get od => JS('double', '#.od', this._jsObject); + + void set od(double od) { + JS('void', '#.od = #', this._jsObject, od); + } + + /// Documentation for the optional file entry f. + FileEntry get of => JS('FileEntry', '#.of', this._jsObject); + + void set of(FileEntry of) { + JS('void', '#.of = #', this._jsObject, convertArgument(of)); + } + +} + +class DictionariesOuterType extends ChromeObject { + /* + * Public constructor + */ + DictionariesOuterType({List<DictionariesInnerType> items, List<DictionariesInnerType> oitems}) { + if (items != null) + this.items = items; + if (oitems != null) + this.oitems = oitems; + } + + /* + * Private constructor + */ + DictionariesOuterType._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// Documentation for the array of InnerTypes items. + List<DictionariesInnerType> get items { + List<DictionariesInnerType> __proxy_items = new List<DictionariesInnerType>(); + int count = JS('int', '#.items.length', this._jsObject); + for (int i = 0; i < count; i++) { + var item = JS('', '#.items[#]', this._jsObject, i); + __proxy_items.add(new DictionariesInnerType._proxy(item)); + } + return __proxy_items; + } + + void set items(List<DictionariesInnerType> items) { + JS('void', '#.items = #', this._jsObject, convertArgument(items)); + } + + /// Documentation for the optional array of Inner Types oitems. + List<DictionariesInnerType> get oitems { + List<DictionariesInnerType> __proxy_oitems = new List<DictionariesInnerType>(); + int count = JS('int', '#.oitems.length', this._jsObject); + for (int i = 0; i < count; i++) { + var item = JS('', '#.oitems[#]', this._jsObject, i); + __proxy_oitems.add(new DictionariesInnerType._proxy(item)); + } + return __proxy_oitems; + } + + void set oitems(List<DictionariesInnerType> oitems) { + JS('void', '#.oitems = #', this._jsObject, convertArgument(oitems)); + } + +} + +class DictionariesComplexType extends ChromeObject { + /* + * Public constructor + */ + DictionariesComplexType({int i, DictionariesComplexType c}) { + if (i != null) + this.i = i; + if (c != null) + this.c = c; + } + + /* + * Private constructor + */ + DictionariesComplexType._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// Documentation for the int i. + int get i => JS('int', '#.i', this._jsObject); + + void set i(int i) { + JS('void', '#.i = #', this._jsObject, i); + } + + /// Documentation for the ComplexType c. + DictionariesComplexType get c => new DictionariesComplexType._proxy(JS('', '#.c', this._jsObject)); + + void set c(DictionariesComplexType c) { + JS('void', '#.c = #', this._jsObject, convertArgument(c)); + } + +} + +/** + * Functions + */ + +class API_dictionaries { + /* + * API connection + */ + Object _jsObject; + API_dictionaries(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/dictionaries.idl b/chromium/tools/json_schema_compiler/dart_test/dictionaries.idl new file mode 100644 index 00000000000..1eb9e25e0a0 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/dictionaries.idl @@ -0,0 +1,61 @@ +// 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. + +// This comment is for the dictionaries namespace. +namespace dictionaries { + // Documentation for ComplexType. + dictionary InnerType { + // Documentation for the String s. + DOMString s; + + // Documentation for the boolean b. + int b; + + // Documentation for the int i. + int i; + + // Documentation for the long l. + long l; + + // Documentation for the double d. + double d; + + // Documentation for the file entry f. + [instanceOf=FileEntry] object f; + + // Documentation for the optional String s. + DOMString? os; + + // Documentation for the optional boolean ob. + int ob; + + // Documentation for the optional int i. + int? oi; + + // Documentation for the optional long l. + long? ol; + + // Documentation for the optional double d. + double? od; + + // Documentation for the optional file entry f. + [instanceOf=FileEntry] object? of; + }; + + dictionary OuterType { + // Documentation for the array of InnerTypes items. + InnerType[] items; + + // Documentation for the optional array of Inner Types oitems. + InnerType[]? oitems; + }; + + dictionary ComplexType { + // Documentation for the int i. + int i; + + // Documentation for the ComplexType c. + ComplexType c; + }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/empty_namespace.dart b/chromium/tools/json_schema_compiler/dart_test/empty_namespace.dart new file mode 100644 index 00000000000..d2378a2860c --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/empty_namespace.dart @@ -0,0 +1,19 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: empty_namespace + +part of chrome; +/** + * Functions + */ + +class API_empty_namespace { + /* + * API connection + */ + Object _jsObject; + API_empty_namespace(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/empty_namespace.idl b/chromium/tools/json_schema_compiler/dart_test/empty_namespace.idl new file mode 100644 index 00000000000..824de2d80e7 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/empty_namespace.idl @@ -0,0 +1,7 @@ +// 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. + +// An empty comment is required for an empty namespace. +namespace empty_namespace { +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/empty_type.dart b/chromium/tools/json_schema_compiler/dart_test/empty_type.dart new file mode 100644 index 00000000000..ebe8e69659a --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/empty_type.dart @@ -0,0 +1,37 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: empty_type + +part of chrome; + +/** + * Types + */ + +class Empty_typeEmptyType extends ChromeObject { + /* + * Public constructor + */ + Empty_typeEmptyType({}) { + } + + /* + * Private constructor + */ + Empty_typeEmptyType._proxy(_jsObject) : super._proxy(_jsObject); +} + +/** + * Functions + */ + +class API_empty_type { + /* + * API connection + */ + Object _jsObject; + API_empty_type(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/empty_type.idl b/chromium/tools/json_schema_compiler/dart_test/empty_type.idl new file mode 100644 index 00000000000..9d7de6f7a70 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/empty_type.idl @@ -0,0 +1,10 @@ +// 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. + +// Namespace-level comment for EmptyType. +namespace empty_type { + // Documentation for EmptyType. + dictionary EmptyType { + }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/enums.idl b/chromium/tools/json_schema_compiler/dart_test/enums.idl new file mode 100644 index 00000000000..1c82d00824a --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/enums.idl @@ -0,0 +1,12 @@ +// Copyright 2013 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. + +// A comment for the enums namespace. +namespace enums { + // A basic enumeration. + enum Enum1 { a, b }; + + // Another basic enumeration. + enum Enum2 { ab, bc, de, ef, fg, hi }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/events.dart b/chromium/tools/json_schema_compiler/dart_test/events.dart new file mode 100644 index 00000000000..d2e79ba1b31 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/events.dart @@ -0,0 +1,282 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: events + +part of chrome; + +/** + * Types + */ + +class EventsEventArgumentElement extends ChromeObject { + /* + * Public constructor + */ + EventsEventArgumentElement({String elementStringArg}) { + if (elementStringArg != null) + this.elementStringArg = elementStringArg; + } + + /* + * Private constructor + */ + EventsEventArgumentElement._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + String get elementStringArg => JS('String', '#.elementStringArg', this._jsObject); + + void set elementStringArg(String elementStringArg) { + JS('void', '#.elementStringArg = #', this._jsObject, elementStringArg); + } + +} + +class EventsEventArgument extends ChromeObject { + /* + * Public constructor + */ + EventsEventArgument({FileEntry entryArg, String stringArg, int intArg, List<EventsEventArgumentElement> elements, FileEntry optionalEntryArg, String optionalStringArg, int optionalIntArg, List<EventsEventArgumentElement> optionalElements}) { + if (entryArg != null) + this.entryArg = entryArg; + if (stringArg != null) + this.stringArg = stringArg; + if (intArg != null) + this.intArg = intArg; + if (elements != null) + this.elements = elements; + if (optionalEntryArg != null) + this.optionalEntryArg = optionalEntryArg; + if (optionalStringArg != null) + this.optionalStringArg = optionalStringArg; + if (optionalIntArg != null) + this.optionalIntArg = optionalIntArg; + if (optionalElements != null) + this.optionalElements = optionalElements; + } + + /* + * Private constructor + */ + EventsEventArgument._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// A file entry + FileEntry get entryArg => JS('FileEntry', '#.entryArg', this._jsObject); + + void set entryArg(FileEntry entryArg) { + JS('void', '#.entryArg = #', this._jsObject, convertArgument(entryArg)); + } + + /// A string + String get stringArg => JS('String', '#.stringArg', this._jsObject); + + void set stringArg(String stringArg) { + JS('void', '#.stringArg = #', this._jsObject, stringArg); + } + + /// A primitive + int get intArg => JS('int', '#.intArg', this._jsObject); + + void set intArg(int intArg) { + JS('void', '#.intArg = #', this._jsObject, intArg); + } + + /// An array + List<EventsEventArgumentElement> get elements { + List<EventsEventArgumentElement> __proxy_elements = new List<EventsEventArgumentElement>(); + int count = JS('int', '#.elements.length', this._jsObject); + for (int i = 0; i < count; i++) { + var item = JS('', '#.elements[#]', this._jsObject, i); + __proxy_elements.add(new EventsEventArgumentElement._proxy(item)); + } + return __proxy_elements; + } + + void set elements(List<EventsEventArgumentElement> elements) { + JS('void', '#.elements = #', this._jsObject, convertArgument(elements)); + } + + /// Optional file entry + FileEntry get optionalEntryArg => JS('FileEntry', '#.optionalEntryArg', this._jsObject); + + void set optionalEntryArg(FileEntry optionalEntryArg) { + JS('void', '#.optionalEntryArg = #', this._jsObject, convertArgument(optionalEntryArg)); + } + + /// A string + String get optionalStringArg => JS('String', '#.optionalStringArg', this._jsObject); + + void set optionalStringArg(String optionalStringArg) { + JS('void', '#.optionalStringArg = #', this._jsObject, optionalStringArg); + } + + /// A primitive + int get optionalIntArg => JS('int', '#.optionalIntArg', this._jsObject); + + void set optionalIntArg(int optionalIntArg) { + JS('void', '#.optionalIntArg = #', this._jsObject, optionalIntArg); + } + + /// An array + List<EventsEventArgumentElement> get optionalElements { + List<EventsEventArgumentElement> __proxy_optionalElements = new List<EventsEventArgumentElement>(); + int count = JS('int', '#.optionalElements.length', this._jsObject); + for (int i = 0; i < count; i++) { + var item = JS('', '#.optionalElements[#]', this._jsObject, i); + __proxy_optionalElements.add(new EventsEventArgumentElement._proxy(item)); + } + return __proxy_optionalElements; + } + + void set optionalElements(List<EventsEventArgumentElement> optionalElements) { + JS('void', '#.optionalElements = #', this._jsObject, convertArgument(optionalElements)); + } + +} + +/** + * Events + */ + +/// Documentation for the first basic event. +class Event_events_firstBasicEvent extends Event { + void addListener(void callback()) => super.addListener(callback); + + void removeListener(void callback()) => super.removeListener(callback); + + bool hasListener(void callback()) => super.hasListener(callback); + + Event_events_firstBasicEvent(jsObject) : super._(jsObject, 0); +} + +/// Documentation for the second basic event. +class Event_events_secondBasicEvent extends Event { + void addListener(void callback()) => super.addListener(callback); + + void removeListener(void callback()) => super.removeListener(callback); + + bool hasListener(void callback()) => super.hasListener(callback); + + Event_events_secondBasicEvent(jsObject) : super._(jsObject, 0); +} + +/// Documentation for an event with a non-optional primitive argument. +class Event_events_nonOptionalPrimitiveArgEvent extends Event { + void addListener(void callback(int argument)) => super.addListener(callback); + + void removeListener(void callback(int argument)) => super.removeListener(callback); + + bool hasListener(void callback(int argument)) => super.hasListener(callback); + + Event_events_nonOptionalPrimitiveArgEvent(jsObject) : super._(jsObject, 1); +} + +/// Documentation for an event with an optional primitive argument. +class Event_events_optionalPrimitiveArgEvent extends Event { + void addListener(void callback(int argument)) => super.addListener(callback); + + void removeListener(void callback(int argument)) => super.removeListener(callback); + + bool hasListener(void callback(int argument)) => super.hasListener(callback); + + Event_events_optionalPrimitiveArgEvent(jsObject) : super._(jsObject, 1); +} + +/// Documentation for an event with a non-optional dictionary argument. +class Event_events_nonOptionalDictArgEvent extends Event { + void addListener(void callback(EventsEventArgument argument)) { + void __proxy_callback(argument) { + if (callback != null) { + callback(new EventsEventArgument._proxy(argument)); + } + } + super.addListener(__proxy_callback); + } + + void removeListener(void callback(EventsEventArgument argument)) { + void __proxy_callback(argument) { + if (callback != null) { + callback(new EventsEventArgument._proxy(argument)); + } + } + super.removeListener(__proxy_callback); + } + + bool hasListener(void callback(EventsEventArgument argument)) { + void __proxy_callback(argument) { + if (callback != null) { + callback(new EventsEventArgument._proxy(argument)); + } + } + super.hasListener(__proxy_callback); + } + + Event_events_nonOptionalDictArgEvent(jsObject) : super._(jsObject, 1); +} + +/// Documentation for an event with a optional dictionary argument. +class Event_events_optionalDictArgEvent extends Event { + void addListener(void callback(EventsEventArgument argument)) { + void __proxy_callback(argument) { + if (callback != null) { + callback(new EventsEventArgument._proxy(argument)); + } + } + super.addListener(__proxy_callback); + } + + void removeListener(void callback(EventsEventArgument argument)) { + void __proxy_callback(argument) { + if (callback != null) { + callback(new EventsEventArgument._proxy(argument)); + } + } + super.removeListener(__proxy_callback); + } + + bool hasListener(void callback(EventsEventArgument argument)) { + void __proxy_callback(argument) { + if (callback != null) { + callback(new EventsEventArgument._proxy(argument)); + } + } + super.hasListener(__proxy_callback); + } + + Event_events_optionalDictArgEvent(jsObject) : super._(jsObject, 1); +} + +/** + * Functions + */ + +class API_events { + /* + * API connection + */ + Object _jsObject; + + /* + * Events + */ + Event_events_firstBasicEvent firstBasicEvent; + Event_events_secondBasicEvent secondBasicEvent; + Event_events_nonOptionalPrimitiveArgEvent nonOptionalPrimitiveArgEvent; + Event_events_optionalPrimitiveArgEvent optionalPrimitiveArgEvent; + Event_events_nonOptionalDictArgEvent nonOptionalDictArgEvent; + Event_events_optionalDictArgEvent optionalDictArgEvent; + API_events(this._jsObject) { + firstBasicEvent = new Event_events_firstBasicEvent(JS('', '#.firstBasicEvent', this._jsObject)); + secondBasicEvent = new Event_events_secondBasicEvent(JS('', '#.secondBasicEvent', this._jsObject)); + nonOptionalPrimitiveArgEvent = new Event_events_nonOptionalPrimitiveArgEvent(JS('', '#.nonOptionalPrimitiveArgEvent', this._jsObject)); + optionalPrimitiveArgEvent = new Event_events_optionalPrimitiveArgEvent(JS('', '#.optionalPrimitiveArgEvent', this._jsObject)); + nonOptionalDictArgEvent = new Event_events_nonOptionalDictArgEvent(JS('', '#.nonOptionalDictArgEvent', this._jsObject)); + optionalDictArgEvent = new Event_events_optionalDictArgEvent(JS('', '#.optionalDictArgEvent', this._jsObject)); + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/events.idl b/chromium/tools/json_schema_compiler/dart_test/events.idl new file mode 100644 index 00000000000..f1fb4b67dcc --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/events.idl @@ -0,0 +1,56 @@ +// Copyright 2013 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. + +// This comment is for the events namespace. +namespace events { + dictionary EventArgumentElement { + DOMString elementStringArg; + }; + + dictionary EventArgument { + // A file entry + [instanceOf=FileEntry] object entryArg; + + // A string + DOMString stringArg; + + // A primitive + int intArg; + + // An array + EventArgumentElement[] elements; + + // Optional file entry + [instanceOf=FileEntry] object? optionalEntryArg; + + // A string + DOMString? optionalStringArg; + + // A primitive + int? optionalIntArg; + + // An array + EventArgumentElement[]? optionalElements; + }; + + interface Events { + // Documentation for the first basic event. + static void firstBasicEvent(); + + // Documentation for the second basic event. + static void secondBasicEvent(); + + // Documentation for an event with a non-optional primitive argument. + static void nonOptionalPrimitiveArgEvent(int argument); + + // Documentation for an event with an optional primitive argument. + static void optionalPrimitiveArgEvent(optional int argument); + + // Documentation for an event with a non-optional dictionary argument. + static void nonOptionalDictArgEvent(EventArgument argument); + + // Documentation for an event with a optional dictionary argument. + static void optionalDictArgEvent(EventArgument argument); + }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/functions.dart b/chromium/tools/json_schema_compiler/dart_test/functions.dart new file mode 100644 index 00000000000..2b4f02bc1c6 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/functions.dart @@ -0,0 +1,93 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: functions + +part of chrome; + +/** + * Types + */ + +class FunctionsDictType extends ChromeObject { + /* + * Private constructor + */ + FunctionsDictType._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// A field. + int get a => JS('int', '#.a', this._jsObject); + + void set a(int a) { + JS('void', '#.a = #', this._jsObject, a); + } + + + /* + * Methods + */ + /// A parameter. + void voidFunc() => JS('void', '#.voidFunc()', this._jsObject); + +} + +/** + * Functions + */ + +class API_functions { + /* + * API connection + */ + Object _jsObject; + + /* + * Functions + */ + /// Simple function. + void voidFunc() => JS('void', '#.voidFunc()', this._jsObject); + + /// Function taking a non-optional argument. + void argFunc(String s) => JS('void', '#.argFunc(#)', this._jsObject, s); + + /// Function taking an optional argument. + void optionalArgFunc([String s]) => JS('void', '#.optionalArgFunc(#)', this._jsObject, s); + + /// Function taking a non-optional dictionary argument. + void dictArgFunc(FunctionsDictType d) => JS('void', '#.dictArgFunc(#)', this._jsObject, convertArgument(d)); + + /// Function taking an optional dictionary argument. + void optionalDictArgFunc([FunctionsDictType d]) => JS('void', '#.optionalDictArgFunc(#)', this._jsObject, convertArgument(d)); + + /// Function taking an entry argument. + void entryArgFunc(Object entry) => JS('void', '#.entryArgFunc(#)', this._jsObject, convertArgument(entry)); + + /// Function taking a simple callback. + void callbackFunc(void c()) => JS('void', '#.callbackFunc(#)', this._jsObject, convertDartClosureToJS(c, 0)); + + /// Function taking an optional simple callback. + void optionalCallbackFunc([void c()]) => JS('void', '#.optionalCallbackFunc(#)', this._jsObject, convertDartClosureToJS(c, 0)); + + /// Function taking a primitive callback. + void primitiveCallbackFunc(void c(int i)) => JS('void', '#.primitiveCallbackFunc(#)', this._jsObject, convertDartClosureToJS(c, 1)); + + /// Function taking a dictionary callback. + void dictCallbackFunc(void c(DictType dict)) { + void __proxy_callback(dict) { + if (c != null) { + c(new DictType._proxy(dict)); + } + } + JS('void', '#.dictCallbackFunc(#)', this._jsObject, convertDartClosureToJS(__proxy_callback, 1)); + } + + /// Function returning a dictionary. + FunctionsDictType dictRetFunc() => new FunctionsDictType._proxy(JS('', '#.dictRetFunc()', this._jsObject)); + + API_functions(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/functions.idl b/chromium/tools/json_schema_compiler/dart_test/functions.idl new file mode 100644 index 00000000000..e303d0de022 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/functions.idl @@ -0,0 +1,55 @@ +// Copyright 2013 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. + +// A comment for the functions namespace. +namespace functions { + callback SimpleCallback = void (); + + callback PrimitiveCallback = void (int i); + + callback DictCallback = void ([instanceOf=DictType] object dict); + + dictionary DictType { + // A field. + int a; + + // A parameter. + static void voidFunc(); + }; + + interface Functions { + // Simple function. + static void voidFunc(); + + // Function taking a non-optional argument. + static void argFunc(DOMString s); + + // Function taking an optional argument. + static void optionalArgFunc(optional DOMString s); + + // Function taking a non-optional dictionary argument. + static void dictArgFunc(DictType d); + + // Function taking an optional dictionary argument. + static void optionalDictArgFunc(optional DictType d); + + // Function taking an entry argument. + static void entryArgFunc([intanceOf=FileEntry] object entry); + + // Function taking a simple callback. + static void callbackFunc(SimpleCallback c); + + // Function taking an optional simple callback. + static void optionalCallbackFunc(optional SimpleCallback c); + + // Function taking a primitive callback. + static void primitiveCallbackFunc(PrimitiveCallback c); + + // Function taking a dictionary callback. + static void dictCallbackFunc(DictCallback c); + + // Function returning a dictionary. + static DictType dictRetFunc(); + }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/operatable_type.dart b/chromium/tools/json_schema_compiler/dart_test/operatable_type.dart new file mode 100644 index 00000000000..725d6a68882 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/operatable_type.dart @@ -0,0 +1,94 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: operatable_type + +part of chrome; + +/** + * Types + */ + +class Operatable_typeDictType extends ChromeObject { + /* + * Public constructor + */ + Operatable_typeDictType({int x, int y}) { + if (x != null) + this.x = x; + if (y != null) + this.y = y; + } + + /* + * Private constructor + */ + Operatable_typeDictType._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + int get x => JS('int', '#.x', this._jsObject); + + void set x(int x) { + JS('void', '#.x = #', this._jsObject, x); + } + + int get y => JS('int', '#.y', this._jsObject); + + void set y(int y) { + JS('void', '#.y = #', this._jsObject, y); + } + +} + +class Operatable_typeOperatableType extends ChromeObject { + /* + * Private constructor + */ + Operatable_typeOperatableType._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// Documentation for the String t. + String get t => JS('String', '#.t', this._jsObject); + + void set t(String t) { + JS('void', '#.t = #', this._jsObject, t); + } + + + /* + * Methods + */ + /// Function returning nothing, taking nothing. + void voidFunc() => JS('void', '#.voidFunc()', this._jsObject); + + /// Function returning primitive type. + int intRetFunc() => new int._proxy(JS('', '#.intRetFunc()', this._jsObject)); + + /// Function returning dictionary. + Operatable_typeDictType dictRetFunc() => new Operatable_typeDictType._proxy(JS('', '#.dictRetFunc()', this._jsObject)); + + /// Function taking primitive type. + void intArgFunc(int i) => JS('void', '#.intArgFunc(#)', this._jsObject, i); + + /// Function taking dict type. + void dictArgFunc(Operatable_typeDictType d) => JS('void', '#.dictArgFunc(#)', this._jsObject, convertArgument(d)); + +} + +/** + * Functions + */ + +class API_operatable_type { + /* + * API connection + */ + Object _jsObject; + API_operatable_type(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/operatable_type.idl b/chromium/tools/json_schema_compiler/dart_test/operatable_type.idl new file mode 100644 index 00000000000..9c5f53cc0ef --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/operatable_type.idl @@ -0,0 +1,32 @@ +// 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. + +// Top-level namespace-comment for operatableType +namespace operatable_type { + dictionary DictType { + int x; + int y; + }; + + // Documentation for OperatableType. + dictionary OperatableType { + // Documentation for the String t. + DOMString t; + + // Function returning nothing, taking nothing. + static void voidFunc(); + + // Function returning primitive type. + static int intRetFunc(); + + // Function returning dictionary. + static DictType dictRetFunc(); + + // Function taking primitive type. + static void intArgFunc(int i); + + // Function taking dict type. + static void dictArgFunc(DictType d); + }; +}; diff --git a/chromium/tools/json_schema_compiler/dart_test/tags.dart b/chromium/tools/json_schema_compiler/dart_test/tags.dart new file mode 100644 index 00000000000..4c3514aeebf --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/tags.dart @@ -0,0 +1,116 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// 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. + +// Generated from namespace: tags + +part of chrome; + +/** + * Types + */ + +class TagsInlineDoc extends ChromeObject { + /* + * Public constructor + */ + TagsInlineDoc({}) { + } + + /* + * Private constructor + */ + TagsInlineDoc._proxy(_jsObject) : super._proxy(_jsObject); +} + +class TagsNodoc extends ChromeObject { + /* + * Public constructor + */ + TagsNodoc({}) { + } + + /* + * Private constructor + */ + TagsNodoc._proxy(_jsObject) : super._proxy(_jsObject); +} + +class TagsNocompile extends ChromeObject { + /* + * Public constructor + */ + TagsNocompile({}) { + } + + /* + * Private constructor + */ + TagsNocompile._proxy(_jsObject) : super._proxy(_jsObject); +} + +class TagsPlainDict extends ChromeObject { + /* + * Public constructor + */ + TagsPlainDict({int inline_doc, String nodoc, double nocompile, fileEntry instance_of_tag}) { + if (inline_doc != null) + this.inline_doc = inline_doc; + if (nodoc != null) + this.nodoc = nodoc; + if (nocompile != null) + this.nocompile = nocompile; + if (instance_of_tag != null) + this.instance_of_tag = instance_of_tag; + } + + /* + * Private constructor + */ + TagsPlainDict._proxy(_jsObject) : super._proxy(_jsObject); + + /* + * Public accessors + */ + /// This int has the property [inline_doc]. + int get inline_doc => JS('int', '#.inline_doc', this._jsObject); + + void set inline_doc(int inline_doc) { + JS('void', '#.inline_doc = #', this._jsObject, inline_doc); + } + + /// This String has the property [nodoc]. + String get nodoc => JS('String', '#.nodoc', this._jsObject); + + void set nodoc(String nodoc) { + JS('void', '#.nodoc = #', this._jsObject, nodoc); + } + + /// This double has the property [nocompile]. + double get nocompile => JS('double', '#.nocompile', this._jsObject); + + void set nocompile(double nocompile) { + JS('void', '#.nocompile = #', this._jsObject, nocompile); + } + + /// This object has the property [instanceOf=fileEntry]. + fileEntry get instance_of_tag => JS('fileEntry', '#.instance_of_tag', this._jsObject); + + void set instance_of_tag(fileEntry instance_of_tag) { + JS('void', '#.instance_of_tag = #', this._jsObject, convertArgument(instance_of_tag)); + } + +} + +/** + * Functions + */ + +class API_tags { + /* + * API connection + */ + Object _jsObject; + API_tags(this._jsObject) { + } +} diff --git a/chromium/tools/json_schema_compiler/dart_test/tags.idl b/chromium/tools/json_schema_compiler/dart_test/tags.idl new file mode 100644 index 00000000000..7a029cd70e3 --- /dev/null +++ b/chromium/tools/json_schema_compiler/dart_test/tags.idl @@ -0,0 +1,33 @@ +// 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. + +// A comment describing tags. +namespace tags { + // This dictionary has the property [inline_doc]. + [inline_doc] dictionary InlineDoc { + }; + + // This dictionary has the property [nodoc]. + [nodoc] dictionary Nodoc { + }; + + // This dictionary has the property [nocompile]. + [nocompile] dictionary Nocompile { + }; + + // This dictionary has no tags on the dictionary itself. + dictionary PlainDict { + // This int has the property [inline_doc]. + [inline_doc] int inline_doc; + + // This String has the property [nodoc]. + [nodoc] String nodoc; + + // This double has the property [nocompile]. + [nocompile] double nocompile; + + // This object has the property [instanceOf=fileEntry]. + [instanceOf=fileEntry] object instance_of_tag; + }; +}; diff --git a/chromium/tools/json_schema_compiler/h_generator.py b/chromium/tools/json_schema_compiler/h_generator.py new file mode 100644 index 00000000000..9a348fe51dc --- /dev/null +++ b/chromium/tools/json_schema_compiler/h_generator.py @@ -0,0 +1,397 @@ +# 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. + +from code import Code +from model import PropertyType, Type +import cpp_util +import schema_util + +class HGenerator(object): + def __init__(self, type_generator, cpp_namespace): + self._type_generator = type_generator + self._cpp_namespace = cpp_namespace + + def Generate(self, namespace): + return _Generator(namespace, + self._type_generator, + self._cpp_namespace).Generate() + +class _Generator(object): + """A .h generator for a namespace. + """ + def __init__(self, namespace, cpp_type_generator, cpp_namespace): + self._namespace = namespace + self._type_helper = cpp_type_generator + self._cpp_namespace = cpp_namespace + self._target_namespace = ( + self._type_helper.GetCppNamespaceName(self._namespace)) + self._generate_error_messages = namespace.compiler_options.get( + 'generate_error_messages', False) + + def Generate(self): + """Generates a Code object with the .h for a single namespace. + """ + c = Code() + (c.Append(cpp_util.CHROMIUM_LICENSE) + .Append() + .Append(cpp_util.GENERATED_FILE_MESSAGE % self._namespace.source_file) + .Append() + ) + + ifndef_name = cpp_util.GenerateIfndefName(self._namespace.source_file_dir, + self._target_namespace) + (c.Append('#ifndef %s' % ifndef_name) + .Append('#define %s' % ifndef_name) + .Append() + .Append('#include <map>') + .Append('#include <string>') + .Append('#include <vector>') + .Append() + .Append('#include "base/basictypes.h"') + .Append('#include "base/logging.h"') + .Append('#include "base/memory/linked_ptr.h"') + .Append('#include "base/memory/scoped_ptr.h"') + .Append('#include "base/values.h"') + .Cblock(self._type_helper.GenerateIncludes()) + .Append() + ) + + c.Concat(cpp_util.OpenNamespace(self._cpp_namespace)) + # TODO(calamity): These forward declarations should be #includes to allow + # $ref types from other files to be used as required params. This requires + # some detangling of windows and tabs which will currently lead to circular + # #includes. + forward_declarations = ( + self._type_helper.GenerateForwardDeclarations()) + if not forward_declarations.IsEmpty(): + (c.Append() + .Cblock(forward_declarations) + ) + + c.Concat(self._type_helper.GetNamespaceStart()) + c.Append() + if self._namespace.properties: + (c.Append('//') + .Append('// Properties') + .Append('//') + .Append() + ) + for property in self._namespace.properties.values(): + property_code = self._type_helper.GeneratePropertyValues( + property, + 'extern const %(type)s %(name)s;') + if property_code: + c.Cblock(property_code) + if self._namespace.types: + (c.Append('//') + .Append('// Types') + .Append('//') + .Append() + .Cblock(self._GenerateTypes(self._FieldDependencyOrder(), + is_toplevel=True, + generate_typedefs=True)) + ) + if self._namespace.functions: + (c.Append('//') + .Append('// Functions') + .Append('//') + .Append() + ) + for function in self._namespace.functions.values(): + c.Cblock(self._GenerateFunction(function)) + if self._namespace.events: + (c.Append('//') + .Append('// Events') + .Append('//') + .Append() + ) + for event in self._namespace.events.values(): + c.Cblock(self._GenerateEvent(event)) + (c.Concat(self._type_helper.GetNamespaceEnd()) + .Concat(cpp_util.CloseNamespace(self._cpp_namespace)) + .Append('#endif // %s' % ifndef_name) + .Append() + ) + return c + + def _FieldDependencyOrder(self): + """Generates the list of types in the current namespace in an order in which + depended-upon types appear before types which depend on them. + """ + dependency_order = [] + + def ExpandType(path, type_): + if type_ in path: + raise ValueError("Illegal circular dependency via cycle " + + ", ".join(map(lambda x: x.name, path + [type_]))) + for prop in type_.properties.values(): + if (prop.type_ == PropertyType.REF and + schema_util.GetNamespace(prop.ref_type) == self._namespace.name): + ExpandType(path + [type_], self._namespace.types[prop.ref_type]) + if not type_ in dependency_order: + dependency_order.append(type_) + + for type_ in self._namespace.types.values(): + ExpandType([], type_) + return dependency_order + + def _GenerateEnumDeclaration(self, enum_name, type_): + """Generate the declaration of a C++ enum. + """ + c = Code() + c.Sblock('enum %s {' % enum_name) + c.Append(self._type_helper.GetEnumNoneValue(type_) + ',') + for value in type_.enum_values: + c.Append(self._type_helper.GetEnumValue(type_, value) + ',') + return c.Eblock('};') + + def _GenerateFields(self, props): + """Generates the field declarations when declaring a type. + """ + c = Code() + needs_blank_line = False + for prop in props: + if needs_blank_line: + c.Append() + needs_blank_line = True + if prop.description: + c.Comment(prop.description) + # ANY is a base::Value which is abstract and cannot be a direct member, so + # we always need to wrap it in a scoped_ptr. + is_ptr = prop.optional or prop.type_.property_type == PropertyType.ANY + (c.Append('%s %s;' % ( + self._type_helper.GetCppType(prop.type_, is_ptr=is_ptr), + prop.unix_name)) + ) + return c + + def _GenerateType(self, type_, is_toplevel=False, generate_typedefs=False): + """Generates a struct for |type_|. + + |is_toplevel| implies that the type was declared in the "types" field + of an API schema. This determines the correct function + modifier(s). + |generate_typedefs| controls whether primitive types should be generated as + a typedef. This may not always be desired. If false, + primitive types are ignored. + """ + classname = cpp_util.Classname(schema_util.StripNamespace(type_.name)) + c = Code() + + if type_.functions: + # Wrap functions within types in the type's namespace. + (c.Append('namespace %s {' % classname) + .Append() + ) + for function in type_.functions.values(): + c.Cblock(self._GenerateFunction(function)) + c.Append('} // namespace %s' % classname) + elif type_.property_type == PropertyType.ARRAY: + if generate_typedefs and type_.description: + c.Comment(type_.description) + c.Cblock(self._GenerateType(type_.item_type)) + if generate_typedefs: + (c.Append('typedef std::vector<%s > %s;' % ( + self._type_helper.GetCppType(type_.item_type), + classname)) + ) + elif type_.property_type == PropertyType.STRING: + if generate_typedefs: + if type_.description: + c.Comment(type_.description) + c.Append('typedef std::string %(classname)s;') + elif type_.property_type == PropertyType.ENUM: + if type_.description: + c.Comment(type_.description) + c.Sblock('enum %(classname)s {') + c.Append('%s,' % self._type_helper.GetEnumNoneValue(type_)) + for value in type_.enum_values: + c.Append('%s,' % self._type_helper.GetEnumValue(type_, value)) + # Top level enums are in a namespace scope so the methods shouldn't be + # static. On the other hand, those declared inline (e.g. in an object) do. + maybe_static = '' if is_toplevel else 'static ' + (c.Eblock('};') + .Append() + .Append('%sstd::string ToString(%s as_enum);' % + (maybe_static, classname)) + .Append('%s%s Parse%s(const std::string& as_string);' % + (maybe_static, classname, classname)) + ) + elif type_.property_type in (PropertyType.CHOICES, + PropertyType.OBJECT): + if type_.description: + c.Comment(type_.description) + (c.Sblock('struct %(classname)s {') + .Append('%(classname)s();') + .Append('~%(classname)s();') + ) + if type_.origin.from_json: + (c.Append() + .Comment('Populates a %s object from a base::Value. Returns' + ' whether |out| was successfully populated.' % classname) + .Append('static bool Populate(%s);' % self._GenerateParams( + ('const base::Value& value', '%s* out' % classname))) + ) + if is_toplevel: + (c.Append() + .Comment('Creates a %s object from a base::Value, or NULL on ' + 'failure.' % classname) + .Append('static scoped_ptr<%s> FromValue(%s);' % ( + classname, self._GenerateParams(('const base::Value& value',)))) + ) + if type_.origin.from_client: + value_type = ('base::Value' + if type_.property_type is PropertyType.CHOICES else + 'base::DictionaryValue') + (c.Append() + .Comment('Returns a new %s representing the serialized form of this ' + '%s object.' % (value_type, classname)) + .Append('scoped_ptr<%s> ToValue() const;' % value_type) + ) + if type_.property_type == PropertyType.CHOICES: + # Choices are modelled with optional fields for each choice. Exactly one + # field of the choice is guaranteed to be set by the compiler. + c.Cblock(self._GenerateTypes(type_.choices)) + c.Append('// Choices:') + for choice_type in type_.choices: + c.Append('%s as_%s;' % ( + self._type_helper.GetCppType(choice_type, is_ptr=True), + choice_type.unix_name)) + else: + properties = type_.properties.values() + (c.Append() + .Cblock(self._GenerateTypes(p.type_ for p in properties)) + .Cblock(self._GenerateFields(properties))) + if type_.additional_properties is not None: + # Most additionalProperties actually have type "any", which is better + # modelled as a DictionaryValue rather than a map of string -> Value. + if type_.additional_properties.property_type == PropertyType.ANY: + c.Append('base::DictionaryValue additional_properties;') + else: + (c.Cblock(self._GenerateType(type_.additional_properties)) + .Append('std::map<std::string, %s> additional_properties;' % + cpp_util.PadForGenerics( + self._type_helper.GetCppType(type_.additional_properties, + is_in_container=True))) + ) + (c.Eblock() + .Append() + .Sblock(' private:') + .Append('DISALLOW_COPY_AND_ASSIGN(%(classname)s);') + .Eblock('};') + ) + return c.Substitute({'classname': classname}) + + def _GenerateEvent(self, event): + """Generates the namespaces for an event. + """ + c = Code() + # TODO(kalman): use event.unix_name not Classname. + event_namespace = cpp_util.Classname(event.name) + (c.Append('namespace %s {' % event_namespace) + .Append() + .Concat(self._GenerateEventNameConstant(event)) + .Concat(self._GenerateCreateCallbackArguments(event)) + .Eblock('} // namespace %s' % event_namespace) + ) + return c + + def _GenerateFunction(self, function): + """Generates the namespaces and structs for a function. + """ + c = Code() + # TODO(kalman): Use function.unix_name not Classname here. + function_namespace = cpp_util.Classname(function.name) + """Windows has a #define for SendMessage, so to avoid any issues, we need + to not use the name. + """ + if function_namespace == 'SendMessage': + function_namespace = 'PassMessage' + (c.Append('namespace %s {' % function_namespace) + .Append() + .Cblock(self._GenerateFunctionParams(function)) + ) + if function.callback: + c.Cblock(self._GenerateFunctionResults(function.callback)) + c.Append('} // namespace %s' % function_namespace) + return c + + def _GenerateFunctionParams(self, function): + """Generates the struct for passing parameters from JSON to a function. + """ + if not function.params: + return Code() + + c = Code() + (c.Sblock('struct Params {') + .Append('static scoped_ptr<Params> Create(%s);' % self._GenerateParams( + ('const base::ListValue& args',))) + .Append('~Params();') + .Append() + .Cblock(self._GenerateTypes(p.type_ for p in function.params)) + .Cblock(self._GenerateFields(function.params)) + .Eblock() + .Append() + .Sblock(' private:') + .Append('Params();') + .Append() + .Append('DISALLOW_COPY_AND_ASSIGN(Params);') + .Eblock('};') + ) + return c + + def _GenerateTypes(self, types, is_toplevel=False, generate_typedefs=False): + """Generate the structures required by a property such as OBJECT classes + and enums. + """ + c = Code() + for type_ in types: + c.Cblock(self._GenerateType(type_, + is_toplevel=is_toplevel, + generate_typedefs=generate_typedefs)) + return c + + def _GenerateCreateCallbackArguments(self, function): + """Generates functions for passing parameters to a callback. + """ + c = Code() + params = function.params + c.Cblock(self._GenerateTypes((p.type_ for p in params), is_toplevel=True)) + + declaration_list = [] + for param in params: + if param.description: + c.Comment(param.description) + declaration_list.append(cpp_util.GetParameterDeclaration( + param, self._type_helper.GetCppType(param.type_))) + c.Append('scoped_ptr<base::ListValue> Create(%s);' % + ', '.join(declaration_list)) + return c + + def _GenerateEventNameConstant(self, event): + """Generates a constant string array for the event name. + """ + c = Code() + c.Append('extern const char kEventName[]; // "%s.%s"' % ( + self._namespace.name, event.name)) + c.Append() + return c + + def _GenerateFunctionResults(self, callback): + """Generates namespace for passing a function's result back. + """ + c = Code() + (c.Append('namespace Results {') + .Append() + .Concat(self._GenerateCreateCallbackArguments(callback)) + .Append('} // namespace Results') + ) + return c + + def _GenerateParams(self, params): + """Builds the parameter list for a function, given an array of parameters. + """ + if self._generate_error_messages: + params += ('std::string* error = NULL',) + return ', '.join(str(p) for p in params) diff --git a/chromium/tools/json_schema_compiler/highlighters/__init__.py b/chromium/tools/json_schema_compiler/highlighters/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/chromium/tools/json_schema_compiler/highlighters/__init__.py diff --git a/chromium/tools/json_schema_compiler/highlighters/hilite_me_highlighter.py b/chromium/tools/json_schema_compiler/highlighters/hilite_me_highlighter.py new file mode 100644 index 00000000000..af0484723cd --- /dev/null +++ b/chromium/tools/json_schema_compiler/highlighters/hilite_me_highlighter.py @@ -0,0 +1,30 @@ +# 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. + +import urllib +import urllib2 + +class HiliteMeHighlighter(object): + """Highlighter that calls the http://hilite.me API to highlight code. + """ + def GetCSS(self, style): + return '' + + def GetCodeElement(self, code, style): + # Call hilite.me API to do syntax highlighting + return urllib2.urlopen('http://hilite.me/api', + urllib.urlencode([ + ('code', code), + ('lexer', 'cpp'), + ('style', style), + ('linenos', 1)]) + ).read() + + def DisplayName(self): + return 'hilite.me (slow, requires internet)' + + def GetStyles(self): + return ['monokai', 'manni', 'perldoc', 'borland', 'colorful', 'default', + 'murphy', 'vs', 'trac', 'tango', 'fruity', 'autumn', 'bw', 'emacs', + 'vim', 'pastie', 'friendly', 'native'] diff --git a/chromium/tools/json_schema_compiler/highlighters/none_highlighter.py b/chromium/tools/json_schema_compiler/highlighters/none_highlighter.py new file mode 100644 index 00000000000..ac1cc2b4b26 --- /dev/null +++ b/chromium/tools/json_schema_compiler/highlighters/none_highlighter.py @@ -0,0 +1,20 @@ +# 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. + +import cgi + +class NoneHighlighter(object): + """Highlighter that just wraps code in a <pre>. + """ + def GetCSS(self, style): + return '' + + def GetCodeElement(self, code, style): + return '<pre>' + cgi.escape(code) + '</pre>' + + def DisplayName(self): + return 'none' + + def GetStyles(self): + return [] diff --git a/chromium/tools/json_schema_compiler/highlighters/pygments_highlighter.py b/chromium/tools/json_schema_compiler/highlighters/pygments_highlighter.py new file mode 100644 index 00000000000..06abd33c790 --- /dev/null +++ b/chromium/tools/json_schema_compiler/highlighters/pygments_highlighter.py @@ -0,0 +1,37 @@ +# 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. + +import sys +try: + import pygments + from pygments.lexers import CppLexer + from pygments.formatters import HtmlFormatter + PYGMENTS_IMPORTED = True +except ImportError: + print('It appears that Pygments is not installed. ' + 'Can be installed using easy_install Pygments or from http://pygments.org.') + PYGMENTS_IMPORTED = False + +class PygmentsHighlighter(object): + def __init__(self): + if not PYGMENTS_IMPORTED: + raise ImportError('Pygments not installed') + + """Highlighter that uses the python pygments library to highlight code. + """ + def GetCSS(self, style): + formatter = HtmlFormatter(linenos=True, + style=pygments.styles.get_style_by_name(style)) + return formatter.get_style_defs('.highlight') + + def GetCodeElement(self, code, style): + formatter = HtmlFormatter(linenos=True, + style=pygments.styles.get_style_by_name(style)) + return pygments.highlight(code, CppLexer(), formatter) + + def DisplayName(self): + return 'pygments' + ('' if PYGMENTS_IMPORTED else ' (not installed)') + + def GetStyles(self): + return list(pygments.styles.get_all_styles()) diff --git a/chromium/tools/json_schema_compiler/idl_schema.py b/chromium/tools/json_schema_compiler/idl_schema.py new file mode 100644 index 00000000000..0556236be4f --- /dev/null +++ b/chromium/tools/json_schema_compiler/idl_schema.py @@ -0,0 +1,417 @@ +#! /usr/bin/env python +# 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. + +import itertools +import json +import os.path +import re +import sys + +from json_parse import OrderedDict +import schema_util + +# This file is a peer to json_schema.py. Each of these files understands a +# certain format describing APIs (either JSON or IDL), reads files written +# in that format into memory, and emits them as a Python array of objects +# corresponding to those APIs, where the objects are formatted in a way that +# the JSON schema compiler understands. compiler.py drives both idl_schema.py +# and json_schema.py. + +# idl_parser expects to be able to import certain files in its directory, +# so let's set things up the way it wants. +_idl_generators_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), + os.pardir, os.pardir, 'ppapi', 'generators') +if _idl_generators_path in sys.path: + import idl_parser +else: + sys.path.insert(0, _idl_generators_path) + try: + import idl_parser + finally: + sys.path.pop(0) + +def ProcessComment(comment): + ''' + Convert a comment into a parent comment and a list of parameter comments. + + Function comments are of the form: + Function documentation. May contain HTML and multiple lines. + + |arg1_name|: Description of arg1. Use <var>argument</var> to refer + to other arguments. + |arg2_name|: Description of arg2... + + Newlines are removed, and leading and trailing whitespace is stripped. + + Args: + comment: The string from a Comment node. + + Returns: A tuple that looks like: + ( + "The processed comment, minus all |parameter| mentions.", + { + 'parameter_name_1': "The comment that followed |parameter_name_1|:", + ... + } + ) + ''' + # Find all the parameter comments of the form '|name|: comment'. + parameter_starts = list(re.finditer(r' *\|([^|]*)\| *: *', comment)) + + # Get the parent comment (everything before the first parameter comment. + first_parameter_location = (parameter_starts[0].start() + if parameter_starts else len(comment)) + parent_comment = comment[:first_parameter_location] + + # We replace \n\n with <br/><br/> here and below, because the documentation + # needs to know where the newlines should be, and this is easier than + # escaping \n. + parent_comment = (parent_comment.strip().replace('\n\n', '<br/><br/>') + .replace('\n', '')) + + params = OrderedDict() + for (cur_param, next_param) in itertools.izip_longest(parameter_starts, + parameter_starts[1:]): + param_name = cur_param.group(1) + + # A parameter's comment goes from the end of its introduction to the + # beginning of the next parameter's introduction. + param_comment_start = cur_param.end() + param_comment_end = next_param.start() if next_param else len(comment) + params[param_name] = (comment[param_comment_start:param_comment_end + ].strip().replace('\n\n', '<br/><br/>') + .replace('\n', '')) + return (parent_comment, params) + +class Callspec(object): + ''' + Given a Callspec node representing an IDL function declaration, converts into + a tuple: + (name, list of function parameters, return type) + ''' + def __init__(self, callspec_node, comment): + self.node = callspec_node + self.comment = comment + + def process(self, callbacks): + parameters = [] + return_type = None + if self.node.GetProperty('TYPEREF') not in ('void', None): + return_type = Typeref(self.node.GetProperty('TYPEREF'), + self.node, + {'name': self.node.GetName()}).process(callbacks) + # The IDL parser doesn't allow specifying return types as optional. + # Instead we infer any object return values to be optional. + # TODO(asargent): fix the IDL parser to support optional return types. + if return_type.get('type') == 'object' or '$ref' in return_type: + return_type['optional'] = True; + for node in self.node.children: + parameter = Param(node).process(callbacks) + if parameter['name'] in self.comment: + parameter['description'] = self.comment[parameter['name']] + parameters.append(parameter) + return (self.node.GetName(), parameters, return_type) + +class Param(object): + ''' + Given a Param node representing a function parameter, converts into a Python + dictionary that the JSON schema compiler expects to see. + ''' + def __init__(self, param_node): + self.node = param_node + + def process(self, callbacks): + return Typeref(self.node.GetProperty('TYPEREF'), + self.node, + {'name': self.node.GetName()}).process(callbacks) + +class Dictionary(object): + ''' + Given an IDL Dictionary node, converts into a Python dictionary that the JSON + schema compiler expects to see. + ''' + def __init__(self, dictionary_node): + self.node = dictionary_node + + def process(self, callbacks): + properties = OrderedDict() + for node in self.node.children: + if node.cls == 'Member': + k, v = Member(node).process(callbacks) + properties[k] = v + result = {'id': self.node.GetName(), + 'properties': properties, + 'type': 'object'} + if self.node.GetProperty('inline_doc'): + result['inline_doc'] = True + elif self.node.GetProperty('noinline_doc'): + result['noinline_doc'] = True + return result + + +class Member(object): + ''' + Given an IDL dictionary or interface member, converts into a name/value pair + where the value is a Python dictionary that the JSON schema compiler expects + to see. + ''' + def __init__(self, member_node): + self.node = member_node + + def process(self, callbacks): + properties = OrderedDict() + name = self.node.GetName() + for property_name in ('OPTIONAL', 'nodoc', 'nocompile', 'nodart'): + if self.node.GetProperty(property_name): + properties[property_name.lower()] = True + for option_name, sanitizer in [ + ('maxListeners', int), + ('supportsFilters', lambda s: s == 'true'), + ('supportsListeners', lambda s: s == 'true'), + ('supportsRules', lambda s: s == 'true')]: + if self.node.GetProperty(option_name): + if 'options' not in properties: + properties['options'] = {} + properties['options'][option_name] = sanitizer(self.node.GetProperty( + option_name)) + is_function = False + parameter_comments = OrderedDict() + for node in self.node.children: + if node.cls == 'Comment': + (parent_comment, parameter_comments) = ProcessComment(node.GetName()) + properties['description'] = parent_comment + elif node.cls == 'Callspec': + is_function = True + name, parameters, return_type = (Callspec(node, parameter_comments) + .process(callbacks)) + properties['parameters'] = parameters + if return_type is not None: + properties['returns'] = return_type + properties['name'] = name + if is_function: + properties['type'] = 'function' + else: + properties = Typeref(self.node.GetProperty('TYPEREF'), + self.node, properties).process(callbacks) + enum_values = self.node.GetProperty('legalValues') + if enum_values: + if properties['type'] == 'integer': + enum_values = map(int, enum_values) + elif properties['type'] == 'double': + enum_values = map(float, enum_values) + properties['enum'] = enum_values + return name, properties + +class Typeref(object): + ''' + Given a TYPEREF property representing the type of dictionary member or + function parameter, converts into a Python dictionary that the JSON schema + compiler expects to see. + ''' + def __init__(self, typeref, parent, additional_properties=OrderedDict()): + self.typeref = typeref + self.parent = parent + self.additional_properties = additional_properties + + def process(self, callbacks): + properties = self.additional_properties + result = properties + + if self.parent.GetProperty('OPTIONAL', False): + properties['optional'] = True + + # The IDL parser denotes array types by adding a child 'Array' node onto + # the Param node in the Callspec. + for sibling in self.parent.GetChildren(): + if sibling.cls == 'Array' and sibling.GetName() == self.parent.GetName(): + properties['type'] = 'array' + properties['items'] = OrderedDict() + properties = properties['items'] + break + + if self.typeref == 'DOMString': + properties['type'] = 'string' + elif self.typeref == 'boolean': + properties['type'] = 'boolean' + elif self.typeref == 'double': + properties['type'] = 'number' + elif self.typeref == 'long': + properties['type'] = 'integer' + elif self.typeref == 'any': + properties['type'] = 'any' + elif self.typeref == 'object': + properties['type'] = 'object' + if 'additionalProperties' not in properties: + properties['additionalProperties'] = OrderedDict() + properties['additionalProperties']['type'] = 'any' + instance_of = self.parent.GetProperty('instanceOf') + if instance_of: + properties['isInstanceOf'] = instance_of + elif self.typeref == 'ArrayBuffer': + properties['type'] = 'binary' + properties['isInstanceOf'] = 'ArrayBuffer' + elif self.typeref == 'FileEntry': + properties['type'] = 'object' + properties['isInstanceOf'] = 'FileEntry' + if 'additionalProperties' not in properties: + properties['additionalProperties'] = OrderedDict() + properties['additionalProperties']['type'] = 'any' + elif self.typeref is None: + properties['type'] = 'function' + else: + if self.typeref in callbacks: + # Do not override name and description if they are already specified. + name = properties.get('name', None) + description = properties.get('description', None) + properties.update(callbacks[self.typeref]) + if description is not None: + properties['description'] = description + if name is not None: + properties['name'] = name + else: + properties['$ref'] = self.typeref + return result + + +class Enum(object): + ''' + Given an IDL Enum node, converts into a Python dictionary that the JSON + schema compiler expects to see. + ''' + def __init__(self, enum_node): + self.node = enum_node + self.description = '' + + def process(self, callbacks): + enum = [] + for node in self.node.children: + if node.cls == 'EnumItem': + enum.append(node.GetName()) + elif node.cls == 'Comment': + self.description = ProcessComment(node.GetName())[0] + else: + sys.exit('Did not process %s %s' % (node.cls, node)) + result = {'id' : self.node.GetName(), + 'description': self.description, + 'type': 'string', + 'enum': enum} + for property_name in ('inline_doc', 'noinline_doc', 'nodoc'): + if self.node.GetProperty(property_name): + result[property_name] = True + return result + + +class Namespace(object): + ''' + Given an IDLNode representing an IDL namespace, converts into a Python + dictionary that the JSON schema compiler expects to see. + ''' + + def __init__(self, namespace_node, description, nodoc=False, internal=False): + self.namespace = namespace_node + self.nodoc = nodoc + self.internal = internal + self.events = [] + self.functions = [] + self.types = [] + self.callbacks = OrderedDict() + self.description = description + + def process(self): + for node in self.namespace.children: + if node.cls == 'Dictionary': + self.types.append(Dictionary(node).process(self.callbacks)) + elif node.cls == 'Callback': + k, v = Member(node).process(self.callbacks) + self.callbacks[k] = v + elif node.cls == 'Interface' and node.GetName() == 'Functions': + self.functions = self.process_interface(node) + elif node.cls == 'Interface' and node.GetName() == 'Events': + self.events = self.process_interface(node) + elif node.cls == 'Enum': + self.types.append(Enum(node).process(self.callbacks)) + else: + sys.exit('Did not process %s %s' % (node.cls, node)) + return {'namespace': self.namespace.GetName(), + 'description': self.description, + 'nodoc': self.nodoc, + 'types': self.types, + 'functions': self.functions, + 'internal': self.internal, + 'events': self.events} + + def process_interface(self, node): + members = [] + for member in node.children: + if member.cls == 'Member': + name, properties = Member(member).process(self.callbacks) + members.append(properties) + return members + +class IDLSchema(object): + ''' + Given a list of IDLNodes and IDLAttributes, converts into a Python list + of api_defs that the JSON schema compiler expects to see. + ''' + + def __init__(self, idl): + self.idl = idl + + def process(self): + namespaces = [] + nodoc = False + internal = False + description = None + for node in self.idl: + if node.cls == 'Namespace': + if not description: + # TODO(kalman): Go back to throwing an error here. + print('%s must have a namespace-level comment. This will ' + 'appear on the API summary page.' % node.GetName()) + description = '' + namespace = Namespace(node, description, nodoc, internal) + namespaces.append(namespace.process()) + nodoc = False + internal = False + elif node.cls == 'Copyright': + continue + elif node.cls == 'Comment': + description = node.GetName() + elif node.cls == 'ExtAttribute': + if node.name == 'nodoc': + nodoc = bool(node.value) + elif node.name == 'internal': + internal = bool(node.value) + else: + continue + else: + sys.exit('Did not process %s %s' % (node.cls, node)) + return namespaces + +def Load(filename): + ''' + Given the filename of an IDL file, parses it and returns an equivalent + Python dictionary in a format that the JSON schema compiler expects to see. + ''' + + f = open(filename, 'r') + contents = f.read() + f.close() + + idl = idl_parser.IDLParser().ParseData(contents, filename) + idl_schema = IDLSchema(idl) + return idl_schema.process() + +def Main(): + ''' + Dump a json serialization of parse result for the IDL files whose names + were passed in on the command line. + ''' + for filename in sys.argv[1:]: + schema = Load(filename) + print json.dumps(schema, indent=2) + +if __name__ == '__main__': + Main() diff --git a/chromium/tools/json_schema_compiler/idl_schema_test.py b/chromium/tools/json_schema_compiler/idl_schema_test.py new file mode 100755 index 00000000000..6adbbd7e288 --- /dev/null +++ b/chromium/tools/json_schema_compiler/idl_schema_test.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python +# 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. + +import idl_schema +import unittest + +def getFunction(schema, name): + for item in schema['functions']: + if item['name'] == name: + return item + raise KeyError('Missing function %s' % name) + +def getParams(schema, name): + function = getFunction(schema, name) + return function['parameters'] + +def getType(schema, id): + for item in schema['types']: + if item['id'] == id: + return item + +class IdlSchemaTest(unittest.TestCase): + def setUp(self): + loaded = idl_schema.Load('test/idl_basics.idl') + self.assertEquals(1, len(loaded)) + self.assertEquals('idl_basics', loaded[0]['namespace']) + self.idl_basics = loaded[0] + + def testSimpleCallbacks(self): + schema = self.idl_basics + expected = [{'type':'function', 'name':'cb', 'parameters':[]}] + self.assertEquals(expected, getParams(schema, 'function4')) + + expected = [{'type':'function', 'name':'cb', + 'parameters':[{'name':'x', 'type':'integer'}]}] + self.assertEquals(expected, getParams(schema, 'function5')) + + expected = [{'type':'function', 'name':'cb', + 'parameters':[{'name':'arg', '$ref':'MyType1'}]}] + self.assertEquals(expected, getParams(schema, 'function6')) + + def testCallbackWithArrayArgument(self): + schema = self.idl_basics + expected = [{'type':'function', 'name':'cb', + 'parameters':[{'name':'arg', 'type':'array', + 'items':{'$ref':'MyType2'}}]}] + self.assertEquals(expected, getParams(schema, 'function12')) + + + def testArrayOfCallbacks(self): + schema = idl_schema.Load('test/idl_callback_arrays.idl')[0] + expected = [{'type':'array', 'name':'callbacks', + 'items':{'type':'function', 'name':'MyCallback', + 'parameters':[{'type':'integer', 'name':'x'}]}}] + self.assertEquals(expected, getParams(schema, 'whatever')) + + def testLegalValues(self): + self.assertEquals({ + 'x': {'name': 'x', 'type': 'integer', 'enum': [1,2], + 'description': 'This comment tests "double-quotes".'}, + 'y': {'name': 'y', 'type': 'string'}, + 'z': {'name': 'z', 'type': 'string'}, + 'a': {'name': 'a', 'type': 'string'}, + 'b': {'name': 'b', 'type': 'string'}, + 'c': {'name': 'c', 'type': 'string'}}, + getType(self.idl_basics, 'MyType1')['properties']) + + def testMemberOrdering(self): + self.assertEquals( + ['x', 'y', 'z', 'a', 'b', 'c'], + getType(self.idl_basics, 'MyType1')['properties'].keys()) + + def testEnum(self): + schema = self.idl_basics + expected = {'enum': ['name1', 'name2'], 'description': 'Enum description', + 'type': 'string', 'id': 'EnumType'} + self.assertEquals(expected, getType(schema, expected['id'])) + + expected = [{'name':'type', '$ref':'EnumType'}, + {'type':'function', 'name':'cb', + 'parameters':[{'name':'type', '$ref':'EnumType'}]}] + self.assertEquals(expected, getParams(schema, 'function13')) + + expected = [{'items': {'$ref': 'EnumType'}, 'name': 'types', + 'type': 'array'}] + self.assertEquals(expected, getParams(schema, 'function14')) + + def testNoCompile(self): + schema = self.idl_basics + func = getFunction(schema, 'function15') + self.assertTrue(func is not None) + self.assertTrue(func['nocompile']) + + def testNoDocOnEnum(self): + schema = self.idl_basics + enum_with_nodoc = getType(schema, 'EnumTypeWithNoDoc') + self.assertTrue(enum_with_nodoc is not None) + self.assertTrue(enum_with_nodoc['nodoc']) + + def testInternalNamespace(self): + idl_basics = self.idl_basics + self.assertEquals('idl_basics', idl_basics['namespace']) + self.assertTrue(idl_basics['internal']) + self.assertFalse(idl_basics['nodoc']) + + def testCallbackComment(self): + schema = self.idl_basics + self.assertEquals('A comment on a callback.', + getParams(schema, 'function16')[0]['description']) + self.assertEquals( + 'A parameter.', + getParams(schema, 'function16')[0]['parameters'][0]['description']) + self.assertEquals( + 'Just a parameter comment, with no comment on the callback.', + getParams(schema, 'function17')[0]['parameters'][0]['description']) + self.assertEquals( + 'Override callback comment.', + getParams(schema, 'function18')[0]['description']) + + def testFunctionComment(self): + schema = self.idl_basics + func = getFunction(schema, 'function3') + self.assertEquals(('This comment should appear in the documentation, ' + 'despite occupying multiple lines.'), + func['description']) + self.assertEquals( + [{'description': ('So should this comment about the argument. ' + '<em>HTML</em> is fine too.'), + 'name': 'arg', + '$ref': 'MyType1'}], + func['parameters']) + func = getFunction(schema, 'function4') + self.assertEquals(('This tests if "double-quotes" are escaped correctly.' + '<br/><br/> It also tests a comment with two newlines.'), + func['description']) + + def testReservedWords(self): + schema = idl_schema.Load('test/idl_reserved_words.idl')[0] + + foo_type = getType(schema, 'Foo') + self.assertEquals(['float', 'DOMString'], foo_type['enum']) + + enum_type = getType(schema, 'enum') + self.assertEquals(['callback', 'namespace'], enum_type['enum']) + + dictionary = getType(schema, 'dictionary'); + self.assertEquals('integer', dictionary['properties']['long']['type']) + + mytype = getType(schema, 'MyType') + self.assertEquals('string', mytype['properties']['interface']['type']) + + params = getParams(schema, 'static') + self.assertEquals('Foo', params[0]['$ref']) + self.assertEquals('enum', params[1]['$ref']) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/json_parse.py b/chromium/tools/json_schema_compiler/json_parse.py new file mode 100644 index 00000000000..9502e91d5c2 --- /dev/null +++ b/chromium/tools/json_schema_compiler/json_parse.py @@ -0,0 +1,61 @@ +# 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. + +import json +import logging +import os +import sys + +_FILE_PATH = os.path.dirname(os.path.realpath(__file__)) +_SYS_PATH = sys.path[:] +try: + _COMMENT_EATER_PATH = os.path.join( + _FILE_PATH, os.pardir, 'json_comment_eater') + sys.path.insert(0, _COMMENT_EATER_PATH) + import json_comment_eater +finally: + sys.path = _SYS_PATH + +try: + from collections import OrderedDict + + # Successfully imported, so we're running Python >= 2.7, and json.loads + # supports object_pairs_hook. + def Parse(json_str): + return json.loads(json_comment_eater.Nom(json_str), + object_pairs_hook=OrderedDict) + +except ImportError: + # Failed to import, so we're running Python < 2.7, and json.loads doesn't + # support object_pairs_hook. simplejson however does, but it's slow. + # + # TODO(cduvall/kalman): Refuse to start the docs server in this case, but + # let json-schema-compiler do its thing. + #logging.warning('Using simplejson to parse, this might be slow! Upgrade to ' + # 'Python 2.7.') + + _SYS_PATH = sys.path[:] + try: + _SIMPLE_JSON_PATH = os.path.join(_FILE_PATH, + os.pardir, + os.pardir, + 'third_party') + sys.path.insert(0, _SIMPLE_JSON_PATH) + # Add this path in case this is being used in the docs server. + sys.path.insert(0, os.path.join(_FILE_PATH, + os.pardir, + os.pardir, + 'third_party', + 'json_schema_compiler')) + import simplejson + from simplejson import OrderedDict + finally: + sys.path = _SYS_PATH + + def Parse(json_str): + return simplejson.loads(json_comment_eater.Nom(json_str), + object_pairs_hook=OrderedDict) + +def IsDict(item): + return isinstance(item, (dict, OrderedDict)) diff --git a/chromium/tools/json_schema_compiler/json_schema.py b/chromium/tools/json_schema_compiler/json_schema.py new file mode 100644 index 00000000000..0fa1ba57e96 --- /dev/null +++ b/chromium/tools/json_schema_compiler/json_schema.py @@ -0,0 +1,50 @@ +# 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. + +import copy +import os +import sys + +import json_parse +import schema_util + +def DeleteNodes(item, delete_key): + """Deletes the given nodes in item, recursively, that have |delete_key| as + an attribute. + """ + def HasKey(thing): + return json_parse.IsDict(thing) and thing.get(delete_key, False) + + if json_parse.IsDict(item): + toDelete = [] + for key, value in item.items(): + if HasKey(value): + toDelete.append(key) + else: + DeleteNodes(value, delete_key) + for key in toDelete: + del item[key] + elif type(item) == list: + item[:] = [DeleteNodes(thing, delete_key) + for thing in item if not HasKey(thing)] + + return item + +def Load(filename): + with open(filename, 'r') as handle: + schemas = json_parse.Parse(handle.read()) + return schemas + +# A dictionary mapping |filename| to the object resulting from loading the JSON +# at |filename|. +_cache = {} + +def CachedLoad(filename): + """Equivalent to Load(filename), but caches results for subsequent calls""" + if filename not in _cache: + _cache[filename] = Load(filename) + # Return a copy of the object so that any changes a caller makes won't affect + # the next caller. + return copy.deepcopy(_cache[filename]) + diff --git a/chromium/tools/json_schema_compiler/json_schema_test.py b/chromium/tools/json_schema_compiler/json_schema_test.py new file mode 100755 index 00000000000..11ba61e443a --- /dev/null +++ b/chromium/tools/json_schema_compiler/json_schema_test.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# 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. + +import json_schema +import json_schema_test +import unittest + +class JsonSchemaUnittest(unittest.TestCase): + def testNocompile(self): + compiled = [ + { + "namespace": "compile", + "description": "The compile API.", + "functions": [], + "types": {} + }, + + { + "namespace": "functions", + "description": "The functions API.", + "functions": [ + { + "id": "two" + }, + { + "id": "four" + } + ], + + "types": { + "one": { "key": "value" } + } + }, + + { + "namespace": "types", + "description": "The types API.", + "functions": [ + { "id": "one" } + ], + "types": { + "two": { + "key": "value" + }, + "four": { + "key": "value" + } + } + }, + + { + "namespace": "nested", + "description": "The nested API.", + "properties": { + "sync": { + "functions": [ + { + "id": "two" + }, + { + "id": "four" + } + ], + "types": { + "two": { + "key": "value" + }, + "four": { + "key": "value" + } + } + } + } + } + ] + + schema = json_schema.CachedLoad('test/json_schema_test.json') + self.assertEquals(compiled, json_schema.DeleteNodes(schema, 'nocompile')) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/memoize.py b/chromium/tools/json_schema_compiler/memoize.py new file mode 100644 index 00000000000..1402a6ecd80 --- /dev/null +++ b/chromium/tools/json_schema_compiler/memoize.py @@ -0,0 +1,13 @@ +# Copyright 2013 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. + +def memoize(fn): + '''Decorates |fn| to memoize. + ''' + memory = {} + def impl(*args): + if args not in memory: + memory[args] = fn(*args) + return memory[args] + return impl diff --git a/chromium/tools/json_schema_compiler/model.py b/chromium/tools/json_schema_compiler/model.py new file mode 100644 index 00000000000..50a9c581403 --- /dev/null +++ b/chromium/tools/json_schema_compiler/model.py @@ -0,0 +1,492 @@ +# 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. + +import os.path + +from json_parse import OrderedDict +from memoize import memoize + +class ParseException(Exception): + """Thrown when data in the model is invalid. + """ + def __init__(self, parent, message): + hierarchy = _GetModelHierarchy(parent) + hierarchy.append(message) + Exception.__init__( + self, 'Model parse exception at:\n' + '\n'.join(hierarchy)) + +class Model(object): + """Model of all namespaces that comprise an API. + + Properties: + - |namespaces| a map of a namespace name to its model.Namespace + """ + def __init__(self): + self.namespaces = {} + + def AddNamespace(self, json, source_file, include_compiler_options=False): + """Add a namespace's json to the model and returns the namespace. + """ + namespace = Namespace(json, + source_file, + include_compiler_options=include_compiler_options) + self.namespaces[namespace.name] = namespace + return namespace + +class Namespace(object): + """An API namespace. + + Properties: + - |name| the name of the namespace + - |description| the description of the namespace + - |unix_name| the unix_name of the namespace + - |source_file| the file that contained the namespace definition + - |source_file_dir| the directory component of |source_file| + - |source_file_filename| the filename component of |source_file| + - |platforms| if not None, the list of platforms that the namespace is + available to + - |types| a map of type names to their model.Type + - |functions| a map of function names to their model.Function + - |events| a map of event names to their model.Function + - |properties| a map of property names to their model.Property + - |compiler_options| the compiler_options dict, only not empty if + |include_compiler_options| is True + """ + def __init__(self, json, source_file, include_compiler_options=False): + self.name = json['namespace'] + if 'description' not in json: + # TODO(kalman): Go back to throwing an error here. + print('%s must have a "description" field. This will appear ' + 'on the API summary page.' % self.name) + json['description'] = '' + self.description = json['description'] + self.unix_name = UnixName(self.name) + self.source_file = source_file + self.source_file_dir, self.source_file_filename = os.path.split(source_file) + self.parent = None + self.platforms = _GetPlatforms(json) + toplevel_origin = Origin(from_client=True, from_json=True) + self.types = _GetTypes(self, json, self, toplevel_origin) + self.functions = _GetFunctions(self, json, self) + self.events = _GetEvents(self, json, self) + self.properties = _GetProperties(self, json, self, toplevel_origin) + self.compiler_options = (json.get('compiler_options', {}) + if include_compiler_options else {}) + +class Origin(object): + """Stores the possible origin of model object as a pair of bools. These are: + + |from_client| indicating that instances can originate from users of + generated code (for example, function results), or + |from_json| indicating that instances can originate from the JSON (for + example, function parameters) + + It is possible for model objects to originate from both the client and json, + for example Types defined in the top-level schema, in which case both + |from_client| and |from_json| would be True. + """ + def __init__(self, from_client=False, from_json=False): + if not from_client and not from_json: + raise ValueError('One of from_client or from_json must be true') + self.from_client = from_client + self.from_json = from_json + +class Type(object): + """A Type defined in the json. + + Properties: + - |name| the type name + - |namespace| the Type's namespace + - |description| the description of the type (if provided) + - |properties| a map of property unix_names to their model.Property + - |functions| a map of function names to their model.Function + - |events| a map of event names to their model.Event + - |origin| the Origin of the type + - |property_type| the PropertyType of this Type + - |item_type| if this is an array, the type of items in the array + - |simple_name| the name of this Type without a namespace + - |additional_properties| the type of the additional properties, if any is + specified + """ + def __init__(self, + parent, + name, + json, + namespace, + origin): + self.name = name + self.namespace = namespace + self.simple_name = _StripNamespace(self.name, namespace) + self.unix_name = UnixName(self.name) + self.description = json.get('description', None) + self.origin = origin + self.parent = parent + self.instance_of = json.get('isInstanceOf', None) + + # TODO(kalman): Only objects need functions/events/properties, but callers + # assume that all types have them. Fix this. + self.functions = _GetFunctions(self, json, namespace) + self.events = _GetEvents(self, json, namespace) + self.properties = _GetProperties(self, json, namespace, origin) + + json_type = json.get('type', None) + if json_type == 'array': + self.property_type = PropertyType.ARRAY + self.item_type = Type( + self, '%sType' % name, json['items'], namespace, origin) + elif '$ref' in json: + self.property_type = PropertyType.REF + self.ref_type = json['$ref'] + elif 'enum' in json and json_type == 'string': + self.property_type = PropertyType.ENUM + self.enum_values = [value for value in json['enum']] + elif json_type == 'any': + self.property_type = PropertyType.ANY + elif json_type == 'binary': + self.property_type = PropertyType.BINARY + elif json_type == 'boolean': + self.property_type = PropertyType.BOOLEAN + elif json_type == 'integer': + self.property_type = PropertyType.INTEGER + elif (json_type == 'double' or + json_type == 'number'): + self.property_type = PropertyType.DOUBLE + elif json_type == 'string': + self.property_type = PropertyType.STRING + elif 'choices' in json: + self.property_type = PropertyType.CHOICES + def generate_type_name(type_json): + if 'items' in type_json: + return '%ss' % generate_type_name(type_json['items']) + if '$ref' in type_json: + return type_json['$ref'] + if 'type' in type_json: + return type_json['type'] + return None + self.choices = [ + Type(self, + generate_type_name(choice) or 'choice%s' % i, + choice, + namespace, + origin) + for i, choice in enumerate(json['choices'])] + elif json_type == 'object': + if not ( + 'properties' in json or + 'additionalProperties' in json or + 'functions' in json or + 'events' in json): + raise ParseException(self, name + " has no properties or functions") + self.property_type = PropertyType.OBJECT + additional_properties_json = json.get('additionalProperties', None) + if additional_properties_json is not None: + self.additional_properties = Type(self, + 'additionalProperties', + additional_properties_json, + namespace, + origin) + else: + self.additional_properties = None + elif json_type == 'function': + self.property_type = PropertyType.FUNCTION + # Sometimes we might have an unnamed function, e.g. if it's a property + # of an object. Use the name of the property in that case. + function_name = json.get('name', name) + self.function = Function(self, function_name, json, namespace, origin) + else: + raise ParseException(self, 'Unsupported JSON type %s' % json_type) + +class Function(object): + """A Function defined in the API. + + Properties: + - |name| the function name + - |platforms| if not None, the list of platforms that the function is + available to + - |params| a list of parameters to the function (order matters). A separate + parameter is used for each choice of a 'choices' parameter + - |description| a description of the function (if provided) + - |callback| the callback parameter to the function. There should be exactly + one + - |optional| whether the Function is "optional"; this only makes sense to be + present when the Function is representing a callback property + - |simple_name| the name of this Function without a namespace + - |returns| the return type of the function; None if the function does not + return a value + """ + def __init__(self, + parent, + name, + json, + namespace, + origin): + self.name = name + self.simple_name = _StripNamespace(self.name, namespace) + self.platforms = _GetPlatforms(json) + self.params = [] + self.description = json.get('description') + self.callback = None + self.optional = json.get('optional', False) + self.parent = parent + self.nocompile = json.get('nocompile') + options = json.get('options', {}) + self.conditions = options.get('conditions', []) + self.actions = options.get('actions', []) + self.supports_listeners = options.get('supportsListeners', True) + self.supports_rules = options.get('supportsRules', False) + + def GeneratePropertyFromParam(p): + return Property(self, p['name'], p, namespace, origin) + + self.filters = [GeneratePropertyFromParam(filter) + for filter in json.get('filters', [])] + callback_param = None + for param in json.get('parameters', []): + if param.get('type') == 'function': + if callback_param: + # No ParseException because the webstore has this. + # Instead, pretend all intermediate callbacks are properties. + self.params.append(GeneratePropertyFromParam(callback_param)) + callback_param = param + else: + self.params.append(GeneratePropertyFromParam(param)) + + if callback_param: + self.callback = Function(self, + callback_param['name'], + callback_param, + namespace, + Origin(from_client=True)) + + self.returns = None + if 'returns' in json: + self.returns = Type(self, + '%sReturnType' % name, + json['returns'], + namespace, + origin) + +class Property(object): + """A property of a type OR a parameter to a function. + Properties: + - |name| name of the property as in the json. This shouldn't change since + it is the key used to access DictionaryValues + - |unix_name| the unix_style_name of the property. Used as variable name + - |optional| a boolean representing whether the property is optional + - |description| a description of the property (if provided) + - |type_| the model.Type of this property + - |simple_name| the name of this Property without a namespace + """ + def __init__(self, parent, name, json, namespace, origin): + """Creates a Property from JSON. + """ + self.parent = parent + self.name = name + self._unix_name = UnixName(self.name) + self._unix_name_used = False + self.origin = origin + self.simple_name = _StripNamespace(self.name, namespace) + self.description = json.get('description', None) + self.optional = json.get('optional', None) + self.instance_of = json.get('isInstanceOf', None) + + # HACK: only support very specific value types. + is_allowed_value = ( + '$ref' not in json and + ('type' not in json or json['type'] == 'integer' + or json['type'] == 'string')) + + self.value = None + if 'value' in json and is_allowed_value: + self.value = json['value'] + if 'type' not in json: + # Sometimes the type of the value is left out, and we need to figure + # it out for ourselves. + if isinstance(self.value, int): + json['type'] = 'integer' + elif isinstance(self.value, basestring): + json['type'] = 'string' + else: + # TODO(kalman): support more types as necessary. + raise ParseException( + parent, + '"%s" is not a supported type for "value"' % type(self.value)) + + self.type_ = Type(parent, name, json, namespace, origin) + + def GetUnixName(self): + """Gets the property's unix_name. Raises AttributeError if not set. + """ + if not self._unix_name: + raise AttributeError('No unix_name set on %s' % self.name) + self._unix_name_used = True + return self._unix_name + + def SetUnixName(self, unix_name): + """Set the property's unix_name. Raises AttributeError if the unix_name has + already been used (GetUnixName has been called). + """ + if unix_name == self._unix_name: + return + if self._unix_name_used: + raise AttributeError( + 'Cannot set the unix_name on %s; ' + 'it is already used elsewhere as %s' % + (self.name, self._unix_name)) + self._unix_name = unix_name + + unix_name = property(GetUnixName, SetUnixName) + +class _Enum(object): + """Superclass for enum types with a "name" field, setting up repr/eq/ne. + Enums need to do this so that equality/non-equality work over pickling. + """ + @staticmethod + def GetAll(cls): + """Yields all _Enum objects declared in |cls|. + """ + for prop_key in dir(cls): + prop_value = getattr(cls, prop_key) + if isinstance(prop_value, _Enum): + yield prop_value + + def __init__(self, name): + self.name = name + + def __eq__(self, other): + return type(other) == type(self) and other.name == self.name + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return self.name + + def __str__(self): + return repr(self) + +class _PropertyTypeInfo(_Enum): + def __init__(self, is_fundamental, name): + _Enum.__init__(self, name) + self.is_fundamental = is_fundamental + +class PropertyType(object): + """Enum of different types of properties/parameters. + """ + ANY = _PropertyTypeInfo(False, "any") + ARRAY = _PropertyTypeInfo(False, "array") + BINARY = _PropertyTypeInfo(False, "binary") + BOOLEAN = _PropertyTypeInfo(True, "boolean") + CHOICES = _PropertyTypeInfo(False, "choices") + DOUBLE = _PropertyTypeInfo(True, "double") + ENUM = _PropertyTypeInfo(False, "enum") + FUNCTION = _PropertyTypeInfo(False, "function") + INT64 = _PropertyTypeInfo(True, "int64") + INTEGER = _PropertyTypeInfo(True, "integer") + OBJECT = _PropertyTypeInfo(False, "object") + REF = _PropertyTypeInfo(False, "ref") + STRING = _PropertyTypeInfo(True, "string") + +@memoize +def UnixName(name): + '''Returns the unix_style name for a given lowerCamelCase string. + ''' + unix_name = [] + for i, c in enumerate(name): + if c.isupper() and i > 0 and name[i - 1] != '_': + # Replace lowerUpper with lower_Upper. + if name[i - 1].islower(): + unix_name.append('_') + # Replace ACMEWidgets with ACME_Widgets + elif i + 1 < len(name) and name[i + 1].islower(): + unix_name.append('_') + if c == '.': + # Replace hello.world with hello_world. + unix_name.append('_') + else: + # Everything is lowercase. + unix_name.append(c.lower()) + return ''.join(unix_name) + +def _StripNamespace(name, namespace): + if name.startswith(namespace.name + '.'): + return name[len(namespace.name + '.'):] + return name + +def _GetModelHierarchy(entity): + """Returns the hierarchy of the given model entity.""" + hierarchy = [] + while entity is not None: + hierarchy.append(getattr(entity, 'name', repr(entity))) + if isinstance(entity, Namespace): + hierarchy.insert(0, ' in %s' % entity.source_file) + entity = getattr(entity, 'parent', None) + hierarchy.reverse() + return hierarchy + +def _GetTypes(parent, json, namespace, origin): + """Creates Type objects extracted from |json|. + """ + types = OrderedDict() + for type_json in json.get('types', []): + type_ = Type(parent, type_json['id'], type_json, namespace, origin) + types[type_.name] = type_ + return types + +def _GetFunctions(parent, json, namespace): + """Creates Function objects extracted from |json|. + """ + functions = OrderedDict() + for function_json in json.get('functions', []): + function = Function(parent, + function_json['name'], + function_json, + namespace, + Origin(from_json=True)) + functions[function.name] = function + return functions + +def _GetEvents(parent, json, namespace): + """Creates Function objects generated from the events in |json|. + """ + events = OrderedDict() + for event_json in json.get('events', []): + event = Function(parent, + event_json['name'], + event_json, + namespace, + Origin(from_client=True)) + events[event.name] = event + return events + +def _GetProperties(parent, json, namespace, origin): + """Generates Property objects extracted from |json|. + """ + properties = OrderedDict() + for name, property_json in json.get('properties', {}).items(): + properties[name] = Property(parent, name, property_json, namespace, origin) + return properties + +class _PlatformInfo(_Enum): + def __init__(self, name): + _Enum.__init__(self, name) + +class Platforms(object): + """Enum of the possible platforms. + """ + CHROMEOS = _PlatformInfo("chromeos") + CHROMEOS_TOUCH = _PlatformInfo("chromeos_touch") + LINUX = _PlatformInfo("linux") + MAC = _PlatformInfo("mac") + WIN = _PlatformInfo("win") + +def _GetPlatforms(json): + if 'platforms' not in json: + return None + platforms = [] + for platform_name in json['platforms']: + for platform_enum in _Enum.GetAll(Platforms): + if platform_name == platform_enum.name: + platforms.append(platform_enum) + break + return platforms diff --git a/chromium/tools/json_schema_compiler/model_test.py b/chromium/tools/json_schema_compiler/model_test.py new file mode 100755 index 00000000000..5003af91e24 --- /dev/null +++ b/chromium/tools/json_schema_compiler/model_test.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# 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. + +from json_schema import CachedLoad +import model +import unittest + +class ModelTest(unittest.TestCase): + def setUp(self): + self.model = model.Model() + self.permissions_json = CachedLoad('test/permissions.json') + self.model.AddNamespace(self.permissions_json[0], + 'path/to/permissions.json') + self.permissions = self.model.namespaces.get('permissions') + self.windows_json = CachedLoad('test/windows.json') + self.model.AddNamespace(self.windows_json[0], + 'path/to/window.json') + self.windows = self.model.namespaces.get('windows') + self.tabs_json = CachedLoad('test/tabs.json') + self.model.AddNamespace(self.tabs_json[0], + 'path/to/tabs.json') + self.tabs = self.model.namespaces.get('tabs') + + def testNamespaces(self): + self.assertEquals(3, len(self.model.namespaces)) + self.assertTrue(self.permissions) + + def testHasFunctions(self): + self.assertEquals(["contains", "getAll", "remove", "request"], + sorted(self.permissions.functions.keys())) + + def testHasTypes(self): + self.assertEquals(['Tab'], self.tabs.types.keys()) + self.assertEquals(['Permissions'], self.permissions.types.keys()) + self.assertEquals(['Window'], self.windows.types.keys()) + + def testHasProperties(self): + self.assertEquals(["active", "favIconUrl", "highlighted", "id", + "incognito", "index", "pinned", "selected", "status", "title", "url", + "windowId"], + sorted(self.tabs.types['Tab'].properties.keys())) + + def testProperties(self): + string_prop = self.tabs.types['Tab'].properties['status'] + self.assertEquals(model.PropertyType.STRING, + string_prop.type_.property_type) + integer_prop = self.tabs.types['Tab'].properties['id'] + self.assertEquals(model.PropertyType.INTEGER, + integer_prop.type_.property_type) + array_prop = self.windows.types['Window'].properties['tabs'] + self.assertEquals(model.PropertyType.ARRAY, + array_prop.type_.property_type) + self.assertEquals(model.PropertyType.REF, + array_prop.type_.item_type.property_type) + self.assertEquals('tabs.Tab', array_prop.type_.item_type.ref_type) + object_prop = self.tabs.functions['query'].params[0] + self.assertEquals(model.PropertyType.OBJECT, + object_prop.type_.property_type) + self.assertEquals( + ["active", "highlighted", "pinned", "status", "title", "url", + "windowId", "windowType"], + sorted(object_prop.type_.properties.keys())) + + def testChoices(self): + self.assertEquals(model.PropertyType.CHOICES, + self.tabs.functions['move'].params[0].type_.property_type) + + def testPropertyNotImplemented(self): + (self.permissions_json[0]['types'][0] + ['properties']['permissions']['type']) = 'something' + self.assertRaises(model.ParseException, self.model.AddNamespace, + self.permissions_json[0], 'path/to/something.json') + + def testDescription(self): + self.assertFalse( + self.permissions.functions['contains'].params[0].description) + self.assertEquals('True if the extension has the specified permissions.', + self.permissions.functions['contains'].callback.params[0].description) + + def testPropertyUnixName(self): + param = self.tabs.functions['move'].params[0] + self.assertEquals('tab_ids', param.unix_name) + + def testUnixName(self): + expectations = { + 'foo': 'foo', + 'fooBar': 'foo_bar', + 'fooBarBaz': 'foo_bar_baz', + 'fooBARBaz': 'foo_bar_baz', + 'fooBAR': 'foo_bar', + 'FOO': 'foo', + 'FOOBar': 'foo_bar', + 'foo.bar': 'foo_bar', + 'foo.BAR': 'foo_bar', + 'foo.barBAZ': 'foo_bar_baz', + 'foo_Bar_Baz_box': 'foo_bar_baz_box', + } + for name in expectations: + self.assertEquals(expectations[name], model.UnixName(name)); + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/preview.py b/chromium/tools/json_schema_compiler/preview.py new file mode 100755 index 00000000000..22ed0f2df9f --- /dev/null +++ b/chromium/tools/json_schema_compiler/preview.py @@ -0,0 +1,366 @@ +#!/usr/bin/env python + +# 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. +"""Server for viewing the compiled C++ code from tools/json_schema_compiler. +""" + +import cc_generator +import code +import compiler +import cpp_type_generator +import cpp_util +import h_generator +import idl_schema +import json_schema +import model +import optparse +import os +import schema_loader +import sys +import urlparse +from highlighters import ( + pygments_highlighter, none_highlighter, hilite_me_highlighter) +from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer + +class CompilerHandler(BaseHTTPRequestHandler): + """A HTTPRequestHandler that outputs the result of tools/json_schema_compiler. + """ + def do_GET(self): + parsed_url = urlparse.urlparse(self.path) + request_path = self._GetRequestPath(parsed_url) + + chromium_favicon = 'http://codereview.chromium.org/static/favicon.ico' + + head = code.Code() + head.Append('<link rel="icon" href="%s">' % chromium_favicon) + head.Append('<link rel="shortcut icon" href="%s">' % chromium_favicon) + + body = code.Code() + + try: + if os.path.isdir(request_path): + self._ShowPanels(parsed_url, head, body) + else: + self._ShowCompiledFile(parsed_url, head, body) + finally: + self.wfile.write('<html><head>') + self.wfile.write(head.Render()) + self.wfile.write('</head><body>') + self.wfile.write(body.Render()) + self.wfile.write('</body></html>') + + def _GetRequestPath(self, parsed_url, strip_nav=False): + """Get the relative path from the current directory to the requested file. + """ + path = parsed_url.path + if strip_nav: + path = parsed_url.path.replace('/nav', '') + return os.path.normpath(os.curdir + path) + + def _ShowPanels(self, parsed_url, head, body): + """Show the previewer frame structure. + + Code panes are populated via XHR after links in the nav pane are clicked. + """ + (head.Append('<style>') + .Append('body {') + .Append(' margin: 0;') + .Append('}') + .Append('.pane {') + .Append(' height: 100%;') + .Append(' overflow-x: auto;') + .Append(' overflow-y: scroll;') + .Append(' display: inline-block;') + .Append('}') + .Append('#nav_pane {') + .Append(' width: 20%;') + .Append('}') + .Append('#nav_pane ul {') + .Append(' list-style-type: none;') + .Append(' padding: 0 0 0 1em;') + .Append('}') + .Append('#cc_pane {') + .Append(' width: 40%;') + .Append('}') + .Append('#h_pane {') + .Append(' width: 40%;') + .Append('}') + .Append('</style>') + ) + + body.Append( + '<div class="pane" id="nav_pane">%s</div>' + '<div class="pane" id="h_pane"></div>' + '<div class="pane" id="cc_pane"></div>' % + self._RenderNavPane(parsed_url.path[1:]) + ) + + # The Javascript that interacts with the nav pane and panes to show the + # compiled files as the URL or highlighting options change. + body.Append('''<script type="text/javascript"> +// Calls a function for each highlighter style <select> element. +function forEachHighlighterStyle(callback) { + var highlighterStyles = + document.getElementsByClassName('highlighter_styles'); + for (var i = 0; i < highlighterStyles.length; ++i) + callback(highlighterStyles[i]); +} + +// Called when anything changes, such as the highlighter or hashtag. +function updateEverything() { + var highlighters = document.getElementById('highlighters'); + var highlighterName = highlighters.value; + + // Cache in localStorage for when the page loads next. + localStorage.highlightersValue = highlighterName; + + // Show/hide the highlighter styles. + var highlighterStyleName = ''; + forEachHighlighterStyle(function(highlighterStyle) { + if (highlighterStyle.id === highlighterName + '_styles') { + highlighterStyle.removeAttribute('style') + highlighterStyleName = highlighterStyle.value; + } else { + highlighterStyle.setAttribute('style', 'display:none') + } + + // Cache in localStorage for when the page next loads. + localStorage[highlighterStyle.id + 'Value'] = highlighterStyle.value; + }); + + // Populate the code panes. + function populateViaXHR(elementId, requestPath) { + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) + return; + if (xhr.status != 200) { + alert('XHR error to ' + requestPath); + return; + } + document.getElementById(elementId).innerHTML = xhr.responseText; + }; + xhr.open('GET', requestPath, true); + xhr.send(); + } + + var targetName = window.location.hash; + targetName = targetName.substring('#'.length); + targetName = targetName.split('.', 1)[0] + + if (targetName !== '') { + var basePath = window.location.pathname; + var query = 'highlighter=' + highlighterName + '&' + + 'style=' + highlighterStyleName; + populateViaXHR('h_pane', basePath + '/' + targetName + '.h?' + query); + populateViaXHR('cc_pane', basePath + '/' + targetName + '.cc?' + query); + } +} + +// Initial load: set the values of highlighter and highlighterStyles from +// localStorage. +(function() { +var cachedValue = localStorage.highlightersValue; +if (cachedValue) + document.getElementById('highlighters').value = cachedValue; + +forEachHighlighterStyle(function(highlighterStyle) { + var cachedValue = localStorage[highlighterStyle.id + 'Value']; + if (cachedValue) + highlighterStyle.value = cachedValue; +}); +})(); + +window.addEventListener('hashchange', updateEverything, false); +updateEverything(); +</script>''') + + def _LoadModel(self, basedir, name): + """Loads and returns the model for the |name| API from either its JSON or + IDL file, e.g. + name=contextMenus will be loaded from |basedir|/context_menus.json, + name=alarms will be loaded from |basedir|/alarms.idl. + """ + loaders = { + 'json': json_schema.Load, + 'idl': idl_schema.Load + } + # APIs are referred to like "webRequest" but that's in a file + # "web_request.json" so we need to unixify the name. + unix_name = model.UnixName(name) + for loader_ext, loader_fn in loaders.items(): + file_path = '%s/%s.%s' % (basedir, unix_name, loader_ext) + if os.path.exists(file_path): + # For historical reasons these files contain a singleton list with the + # model, so just return that single object. + return (loader_fn(file_path)[0], file_path) + raise ValueError('File for model "%s" not found' % name) + + def _ShowCompiledFile(self, parsed_url, head, body): + """Show the compiled version of a json or idl file given the path to the + compiled file. + """ + api_model = model.Model() + + request_path = self._GetRequestPath(parsed_url) + (file_root, file_ext) = os.path.splitext(request_path) + (filedir, filename) = os.path.split(file_root) + + try: + # Get main file. + (api_def, file_path) = self._LoadModel(filedir, filename) + namespace = api_model.AddNamespace(api_def, file_path) + type_generator = cpp_type_generator.CppTypeGenerator( + api_model, + schema_loader.SchemaLoader(filedir), + namespace) + + # Get the model's dependencies. + for dependency in api_def.get('dependencies', []): + # Dependencies can contain : in which case they don't refer to APIs, + # rather, permissions or manifest keys. + if ':' in dependency: + continue + (api_def, file_path) = self._LoadModel(filedir, dependency) + referenced_namespace = api_model.AddNamespace(api_def, file_path) + if referenced_namespace: + type_generator.AddNamespace(referenced_namespace, + cpp_util.Classname(referenced_namespace.name).lower()) + + # Generate code + cpp_namespace = 'generated_api_schemas' + if file_ext == '.h': + cpp_code = (h_generator.HGenerator(type_generator, cpp_namespace) + .Generate(namespace).Render()) + elif file_ext == '.cc': + cpp_code = (cc_generator.CCGenerator(type_generator, cpp_namespace) + .Generate(namespace).Render()) + else: + self.send_error(404, "File not found: %s" % request_path) + return + + # Do highlighting on the generated code + (highlighter_param, style_param) = self._GetHighlighterParams(parsed_url) + head.Append('<style>' + + self.server.highlighters[highlighter_param].GetCSS(style_param) + + '</style>') + body.Append(self.server.highlighters[highlighter_param] + .GetCodeElement(cpp_code, style_param)) + except IOError: + self.send_error(404, "File not found: %s" % request_path) + return + except (TypeError, KeyError, AttributeError, + AssertionError, NotImplementedError) as error: + body.Append('<pre>') + body.Append('compiler error: %s' % error) + body.Append('Check server log for more details') + body.Append('</pre>') + raise + + def _GetHighlighterParams(self, parsed_url): + """Get the highlighting parameters from a parsed url. + """ + query_dict = urlparse.parse_qs(parsed_url.query) + return (query_dict.get('highlighter', ['pygments'])[0], + query_dict.get('style', ['colorful'])[0]) + + def _RenderNavPane(self, path): + """Renders an HTML nav pane. + + This consists of a select element to set highlight style, and a list of all + files at |path| with the appropriate onclick handlers to open either + subdirectories or JSON files. + """ + html = code.Code() + + # Highlighter chooser. + html.Append('<select id="highlighters" onChange="updateEverything()">') + for name, highlighter in self.server.highlighters.items(): + html.Append('<option value="%s">%s</option>' % + (name, highlighter.DisplayName())) + html.Append('</select>') + + html.Append('<br/>') + + # Style for each highlighter. + # The correct highlighting will be shown by Javascript. + for name, highlighter in self.server.highlighters.items(): + styles = sorted(highlighter.GetStyles()) + if not styles: + continue + + html.Append('<select class="highlighter_styles" id="%s_styles" ' + 'onChange="updateEverything()">' % name) + for style in styles: + html.Append('<option>%s</option>' % style) + html.Append('</select>') + + html.Append('<br/>') + + # The files, with appropriate handlers. + html.Append('<ul>') + + # Make path point to a non-empty directory. This can happen if a URL like + # http://localhost:8000 is navigated to. + if path == '': + path = os.curdir + + # Firstly, a .. link if this isn't the root. + if not os.path.samefile(os.curdir, path): + normpath = os.path.normpath(os.path.join(path, os.pardir)) + html.Append('<li><a href="/%s">%s/</a>' % (normpath, os.pardir)) + + # Each file under path/ + for filename in sorted(os.listdir(path)): + full_path = os.path.join(path, filename) + (file_root, file_ext) = os.path.splitext(full_path) + if os.path.isdir(full_path) and not full_path.endswith('.xcodeproj'): + html.Append('<li><a href="/%s/">%s/</a>' % (full_path, filename)) + elif file_ext in ['.json', '.idl']: + # cc/h panes will automatically update via the hash change event. + html.Append('<li><a href="#%s">%s</a>' % + (filename, filename)) + + html.Append('</ul>') + + return html.Render() + +class PreviewHTTPServer(HTTPServer, object): + def __init__(self, server_address, handler, highlighters): + super(PreviewHTTPServer, self).__init__(server_address, handler) + self.highlighters = highlighters + + +if __name__ == '__main__': + parser = optparse.OptionParser( + description='Runs a server to preview the json_schema_compiler output.', + usage='usage: %prog [option]...') + parser.add_option('-p', '--port', default='8000', + help='port to run the server on') + + (opts, argv) = parser.parse_args() + + try: + print('Starting previewserver on port %s' % opts.port) + print('The extension documentation can be found at:') + print('') + print(' http://localhost:%s/chrome/common/extensions/api' % opts.port) + print('') + + highlighters = { + 'hilite': hilite_me_highlighter.HiliteMeHighlighter(), + 'none': none_highlighter.NoneHighlighter() + } + try: + highlighters['pygments'] = pygments_highlighter.PygmentsHighlighter() + except ImportError as e: + pass + + server = PreviewHTTPServer(('', int(opts.port)), + CompilerHandler, + highlighters) + server.serve_forever() + except KeyboardInterrupt: + server.socket.close() diff --git a/chromium/tools/json_schema_compiler/schema_loader.py b/chromium/tools/json_schema_compiler/schema_loader.py new file mode 100644 index 00000000000..c434dc16746 --- /dev/null +++ b/chromium/tools/json_schema_compiler/schema_loader.py @@ -0,0 +1,53 @@ +# 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. + +import os +import sys + +import idl_schema +import json_schema +from model import Model + +class SchemaLoader(object): + '''Resolves a type name into the namespace the type belongs to. + ''' + def __init__(self, api_path): + self._api_path = api_path + + def ResolveType(self, full_name, default_namespace): + name_parts = full_name.rsplit('.', 1) + if len(name_parts) == 1: + if full_name not in default_namespace.types: + return None + return default_namespace + namespace_name, type_name = name_parts + real_name = None + for ext in ['json', 'idl']: + filename = '%s.%s' % (namespace_name, ext) + if os.path.exists(filename): + real_name = filename + break + if real_name is None: + return None + namespace = Model().AddNamespace(self.LoadSchema(real_name)[0], + os.path.join(self._api_path, real_name)) + if type_name not in namespace.types: + return None + return namespace + + def LoadSchema(self, schema): + schema_filename, schema_extension = os.path.splitext(schema) + + if schema_extension == '.json': + api_defs = json_schema.Load(schema) + elif schema_extension == '.idl': + api_defs = idl_schema.Load(schema) + else: + sys.exit('Did not recognize file extension %s for schema %s' % + (schema_extension, schema)) + if len(api_defs) != 1: + sys.exit('File %s has multiple schemas. Files are only allowed to contain' + 'a single schema.' % schema) + + return api_defs diff --git a/chromium/tools/json_schema_compiler/schema_util.py b/chromium/tools/json_schema_compiler/schema_util.py new file mode 100644 index 00000000000..7ce399e8ed3 --- /dev/null +++ b/chromium/tools/json_schema_compiler/schema_util.py @@ -0,0 +1,37 @@ +# 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. +"""Utilies for the processing of schema python structures. +""" + +import json_parse + +def CapitalizeFirstLetter(value): + return value[0].capitalize() + value[1:] + +def GetNamespace(ref): + return SplitNamespace(ref)[0] + +def StripNamespace(ref): + return SplitNamespace(ref)[1] + +def SplitNamespace(ref): + """Returns (namespace, entity) from |ref|, e.g. app.window.AppWindow -> + (app.window, AppWindow). If |ref| isn't qualified then returns (None, ref). + """ + if '.' in ref: + return tuple(ref.rsplit('.', 1)) + return (None, ref) + +def JsFunctionNameToClassName(namespace_name, function_name): + """Transform a fully qualified function name like foo.bar.baz into FooBarBaz + + Also strips any leading 'Experimental' prefix.""" + parts = [] + full_name = namespace_name + "." + function_name + for part in full_name.split("."): + parts.append(CapitalizeFirstLetter(part)) + if parts[0] == "Experimental": + del parts[0] + class_name = "".join(parts) + return class_name diff --git a/chromium/tools/json_schema_compiler/schema_util_test.py b/chromium/tools/json_schema_compiler/schema_util_test.py new file mode 100755 index 00000000000..154da0137ea --- /dev/null +++ b/chromium/tools/json_schema_compiler/schema_util_test.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +# 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. + +from schema_util import JsFunctionNameToClassName +from schema_util import StripNamespace +import unittest + +class SchemaUtilTest(unittest.TestCase): + def testStripNamespace(self): + self.assertEquals('Bar', StripNamespace('foo.Bar')) + self.assertEquals('Baz', StripNamespace('Baz')) + + def testJsFunctionNameToClassName(self): + self.assertEquals('FooBar', JsFunctionNameToClassName('foo', 'bar')) + self.assertEquals('FooBar', + JsFunctionNameToClassName('experimental.foo', 'bar')) + self.assertEquals('FooBarBaz', + JsFunctionNameToClassName('foo.bar', 'baz')) + self.assertEquals('FooBarBaz', + JsFunctionNameToClassName('experimental.foo.bar', 'baz')) + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp b/chromium/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp new file mode 100644 index 00000000000..27ec4385be8 --- /dev/null +++ b/chromium/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp @@ -0,0 +1,44 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'json_schema_compiler_tests', + 'type': 'static_library', + 'variables': { + 'chromium_code': 1, + 'schema_files': [ + 'additional_properties.json', + 'any.json', + 'arrays.json', + 'callbacks.json', + 'choices.json', + 'crossref.json', + 'enums.json', + 'functions_as_parameters.json', + 'functions_on_types.json', + 'idl_basics.idl', + 'idl_object_types.idl', + 'objects.json', + 'simple_api.json', + 'error_generation.json' + ], + 'cc_dir': 'tools/json_schema_compiler/test', + 'root_namespace': 'test::api', + }, + 'inputs': [ + '<@(schema_files)', + ], + 'sources': [ + '<@(schema_files)', + 'test_util.cc', + 'test_util.h', + ], + 'includes': ['../../../build/json_schema_compile.gypi'], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + ], +} diff --git a/chromium/tools/json_schema_compiler/util.cc b/chromium/tools/json_schema_compiler/util.cc new file mode 100644 index 00000000000..d0e77659e6a --- /dev/null +++ b/chromium/tools/json_schema_compiler/util.cc @@ -0,0 +1,97 @@ +// 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. + +#include "tools/json_schema_compiler/util.h" + +#include "base/values.h" + +namespace json_schema_compiler { +namespace util { + +bool GetItemFromList(const base::ListValue& from, int index, int* out) { + return from.GetInteger(index, out); +} + +bool GetItemFromList(const base::ListValue& from, int index, bool* out) { + return from.GetBoolean(index, out); +} + +bool GetItemFromList(const base::ListValue& from, int index, double* out) { + return from.GetDouble(index, out); +} + +bool GetItemFromList(const base::ListValue& from, int index, std::string* out) { + return from.GetString(index, out); +} + +bool GetItemFromList(const base::ListValue& from, + int index, + linked_ptr<base::Value>* out) { + const base::Value* value = NULL; + if (!from.Get(index, &value)) + return false; + *out = make_linked_ptr(value->DeepCopy()); + return true; +} + +bool GetItemFromList(const base::ListValue& from, int index, + linked_ptr<base::DictionaryValue>* out) { + const base::DictionaryValue* dict = NULL; + if (!from.GetDictionary(index, &dict)) + return false; + *out = make_linked_ptr(dict->DeepCopy()); + return true; +} + +void AddItemToList(const int from, base::ListValue* out) { + out->Append(new base::FundamentalValue(from)); +} + +void AddItemToList(const bool from, base::ListValue* out) { + out->Append(new base::FundamentalValue(from)); +} + +void AddItemToList(const double from, base::ListValue* out) { + out->Append(new base::FundamentalValue(from)); +} + +void AddItemToList(const std::string& from, base::ListValue* out) { + out->Append(new base::StringValue(from)); +} + +void AddItemToList(const linked_ptr<base::Value>& from, + base::ListValue* out) { + out->Append(from->DeepCopy()); +} + +void AddItemToList(const linked_ptr<base::DictionaryValue>& from, + base::ListValue* out) { + out->Append(static_cast<base::Value*>(from->DeepCopy())); +} + +std::string ValueTypeToString(Value::Type type) { + switch(type) { + case Value::TYPE_NULL: + return "null"; + case Value::TYPE_BOOLEAN: + return "boolean"; + case Value::TYPE_INTEGER: + return "integer"; + case Value::TYPE_DOUBLE: + return "number"; + case Value::TYPE_STRING: + return "string"; + case Value::TYPE_BINARY: + return "binary"; + case Value::TYPE_DICTIONARY: + return "dictionary"; + case Value::TYPE_LIST: + return "list"; + } + NOTREACHED(); + return ""; +} + +} // namespace api_util +} // namespace extensions diff --git a/chromium/tools/json_schema_compiler/util.h b/chromium/tools/json_schema_compiler/util.h new file mode 100644 index 00000000000..b775be7e0cc --- /dev/null +++ b/chromium/tools/json_schema_compiler/util.h @@ -0,0 +1,181 @@ +// 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. + +#ifndef TOOLS_JSON_SCHEMA_COMPILER_UTIL_H__ +#define TOOLS_JSON_SCHEMA_COMPILER_UTIL_H__ + +#include <string> +#include <vector> + +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" + +namespace json_schema_compiler { + +namespace util { + +// Creates a new item at |out| from |from|[|index|]. These are used by template +// specializations of |Get(Optional)ArrayFromList|. +bool GetItemFromList(const base::ListValue& from, int index, int* out); +bool GetItemFromList(const base::ListValue& from, int index, bool* out); +bool GetItemFromList(const base::ListValue& from, int index, double* out); +bool GetItemFromList(const base::ListValue& from, int index, std::string* out); +bool GetItemFromList(const base::ListValue& from, + int index, + linked_ptr<base::Value>* out); +bool GetItemFromList(const base::ListValue& from, + int index, + linked_ptr<base::DictionaryValue>* out); + +// This template is used for types generated by tools/json_schema_compiler. +template<class T> +bool GetItemFromList(const base::ListValue& from, + int index, + linked_ptr<T>* out) { + const base::DictionaryValue* dict; + if (!from.GetDictionary(index, &dict)) + return false; + scoped_ptr<T> obj(new T()); + if (!T::Populate(*dict, obj.get())) + return false; + *out = linked_ptr<T>(obj.release()); + return true; +} + +// Populates |out| with |list|. Returns false if there is no list at the +// specified key or if the list has anything other than |T|. +template <class T> +bool PopulateArrayFromList( + const base::ListValue& list, std::vector<T>* out) { + out->clear(); + T value; + for (size_t i = 0; i < list.GetSize(); ++i) { + if (!GetItemFromList(list, i, &value)) + return false; + out->push_back(value); + } + + return true; +} + +// Populates |out| with |from|.|name|. Returns false if there is no list at +// the specified key or if the list has anything other than |T|. +template <class T> +bool PopulateArrayFromDictionary( + const base::DictionaryValue& from, + const std::string& name, + std::vector<T>* out) { + const base::ListValue* list = NULL; + if (!from.GetListWithoutPathExpansion(name, &list)) + return false; + + return PopulateArrayFromList(*list, out); +} + +// Creates a new vector containing |list| at |out|. Returns +// true on success or if there is nothing at the specified key. Returns false +// if anything other than a list of |T| is at the specified key. +template <class T> +bool PopulateOptionalArrayFromList( + const base::ListValue& list, + scoped_ptr<std::vector<T> >* out) { + out->reset(new std::vector<T>()); + T value; + for (size_t i = 0; i < list.GetSize(); ++i) { + if (!GetItemFromList(list, i, &value)) { + out->reset(); + return false; + } + (*out)->push_back(value); + } + + return true; +} + +// Creates a new vector containing |from|.|name| at |out|. Returns +// true on success or if there is nothing at the specified key. Returns false +// if anything other than a list of |T| is at the specified key. +template <class T> +bool PopulateOptionalArrayFromDictionary( + const base::DictionaryValue& from, + const std::string& name, + scoped_ptr<std::vector<T> >* out) { + const base::ListValue* list = NULL; + { + const base::Value* maybe_list = NULL; + // Since |name| is optional, its absence is acceptable. However, anything + // other than a ListValue is not. + if (!from.GetWithoutPathExpansion(name, &maybe_list)) + return true; + if (!maybe_list->IsType(base::Value::TYPE_LIST)) + return false; + list = static_cast<const base::ListValue*>(maybe_list); + } + + return PopulateOptionalArrayFromList(*list, out); +} + +// Appends a Value newly created from |from| to |out|. These used by template +// specializations of |Set(Optional)ArrayToList|. +void AddItemToList(const int from, base::ListValue* out); +void AddItemToList(const bool from, base::ListValue* out); +void AddItemToList(const double from, base::ListValue* out); +void AddItemToList(const std::string& from, base::ListValue* out); +void AddItemToList(const linked_ptr<base::Value>& from, + base::ListValue* out); +void AddItemToList(const linked_ptr<base::DictionaryValue>& from, + base::ListValue* out); + +// This template is used for types generated by tools/json_schema_compiler. +template<class T> +void AddItemToList(const linked_ptr<T>& from, base::ListValue* out) { + out->Append(from->ToValue().release()); +} + +// Set |out| to the the contents of |from|. Requires GetItemFromList to be +// implemented for |T|. +template <class T> +void PopulateListFromArray( + const std::vector<T>& from, + base::ListValue* out) { + out->Clear(); + for (typename std::vector<T>::const_iterator it = from.begin(); + it != from.end(); ++it) { + AddItemToList(*it, out); + } +} + +// Set |out| to the the contents of |from| if |from| is non-NULL. Requires +// GetItemFromList to be implemented for |T|. +template <class T> +void PopulateListFromOptionalArray( + const scoped_ptr<std::vector<T> >& from, + base::ListValue* out) { + if (from.get()) + PopulateListFromArray(*from, out); + +} + +template <class T> +scoped_ptr<base::Value> CreateValueFromArray(const std::vector<T>& from) { + base::ListValue* list = new base::ListValue(); + PopulateListFromArray(from, list); + return scoped_ptr<base::Value>(list); +} + +template <class T> +scoped_ptr<base::Value> CreateValueFromOptionalArray( + const scoped_ptr<std::vector<T> >& from) { + if (from.get()) + return CreateValueFromArray(*from); + return scoped_ptr<base::Value>(); +} + +std::string ValueTypeToString(Value::Type type); + +} // namespace util +} // namespace json_schema_compiler + +#endif // TOOLS_JSON_SCHEMA_COMPILER_UTIL_H__ diff --git a/chromium/tools/json_schema_compiler/util_cc_helper.py b/chromium/tools/json_schema_compiler/util_cc_helper.py new file mode 100644 index 00000000000..df5b6de0f50 --- /dev/null +++ b/chromium/tools/json_schema_compiler/util_cc_helper.py @@ -0,0 +1,85 @@ +# 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. + +API_UTIL_NAMESPACE = 'json_schema_compiler::util' + +class UtilCCHelper(object): + """A util class that generates code that uses + tools/json_schema_compiler/util.cc. + """ + def __init__(self, type_manager): + self._type_manager = type_manager + + def PopulateArrayFromDictionary(self, array_prop, src, name, dst): + """Generates code to get an array from a src.name into dst. + + src: DictionaryValue* + dst: std::vector or scoped_ptr<std::vector> + """ + prop = array_prop.item_type + sub = { + 'namespace': API_UTIL_NAMESPACE, + 'name': name, + 'src': src, + 'dst': dst, + } + + sub['type'] = self._type_manager.GetCppType(prop), + if array_prop.optional: + val = ('%(namespace)s::PopulateOptionalArrayFromDictionary' + '(*%(src)s, "%(name)s", &%(dst)s)') + else: + val = ('%(namespace)s::PopulateArrayFromDictionary' + '(*%(src)s, "%(name)s", &%(dst)s)') + + return val % sub + + def PopulateArrayFromList(self, array_prop, src, dst, optional): + """Generates code to get an array from src into dst. + + src: ListValue* + dst: std::vector or scoped_ptr<std::vector> + """ + prop = array_prop.item_type + sub = { + 'namespace': API_UTIL_NAMESPACE, + 'src': src, + 'dst': dst, + 'type': self._type_manager.GetCppType(prop), + } + + if optional: + val = '%(namespace)s::PopulateOptionalArrayFromList(*%(src)s, &%(dst)s)' + else: + val = '%(namespace)s::PopulateArrayFromList(*%(src)s, &%(dst)s)' + + return val % sub + + def CreateValueFromArray(self, array_prop, src, optional): + """Generates code to create a scoped_pt<Value> from the array at src. + + src: std::vector or scoped_ptr<std::vector> + """ + prop = array_prop.item_type + sub = { + 'namespace': API_UTIL_NAMESPACE, + 'src': src, + 'type': self._type_manager.GetCppType(prop), + } + + if optional: + val = '%(namespace)s::CreateValueFromOptionalArray(%(src)s)' + else: + val = '%(namespace)s::CreateValueFromArray(%(src)s)' + + return val % sub + + def GetIncludePath(self): + return '#include "tools/json_schema_compiler/util.h"' + + def GetValueTypeString(self, value, is_ptr=False): + call = '.GetType()' + if is_ptr: + call = '->GetType()' + return 'json_schema_compiler::util::ValueTypeToString(%s%s)' % (value, call) |