diff options
author | DHNishi@gmail.com <DHNishi@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-03 20:10:30 +0000 |
---|---|---|
committer | DHNishi@gmail.com <DHNishi@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-03 20:10:30 +0000 |
commit | 3111f6d91219d56d2e594fc6b3d012bab1056986 (patch) | |
tree | 69c9639f0e2328d3c40ec7706b6e279aabe82cfb /tools/json_schema_compiler | |
parent | 5fad4e034b611f794326eb6298516ba5dc1631b4 (diff) | |
download | chromium_src-3111f6d91219d56d2e594fc6b3d012bab1056986.zip chromium_src-3111f6d91219d56d2e594fc6b3d012bab1056986.tar.gz chromium_src-3111f6d91219d56d2e594fc6b3d012bab1056986.tar.bz2 |
This is a preliminary patch to auto-generate the ID enum in APIPermission.
permission_features.h: https://gist.github.com/DHNishi/2014be18e7912916ea25
permission_features.cc: https://gist.github.com/DHNishi/b78bc9588b2d7a46331d
BUG=280286
Review URL: https://codereview.chromium.org/23594008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@226826 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'tools/json_schema_compiler')
-rw-r--r-- | tools/json_schema_compiler/code.py | 1 | ||||
-rw-r--r-- | tools/json_schema_compiler/cpp_util.py | 19 | ||||
-rw-r--r-- | tools/json_schema_compiler/features_cc_generator.py | 95 | ||||
-rwxr-xr-x | tools/json_schema_compiler/features_compiler.py | 76 | ||||
-rw-r--r-- | tools/json_schema_compiler/features_h_generator.py | 95 | ||||
-rw-r--r-- | tools/json_schema_compiler/model.py | 58 | ||||
-rw-r--r-- | tools/json_schema_compiler/schema_loader.py | 3 | ||||
-rw-r--r-- | tools/json_schema_compiler/test/features_unittest.cc | 22 | ||||
-rw-r--r-- | tools/json_schema_compiler/test/json_schema_compiler_tests.gyp | 19 | ||||
-rw-r--r-- | tools/json_schema_compiler/test/test_features.json | 25 |
10 files changed, 410 insertions, 3 deletions
diff --git a/tools/json_schema_compiler/code.py b/tools/json_schema_compiler/code.py index 3622237..8ce6afa 100644 --- a/tools/json_schema_compiler/code.py +++ b/tools/json_schema_compiler/code.py @@ -133,6 +133,7 @@ class Code(object): """ return '\n'.join([l.value for l in self._code]) + class Line(object): """A line of code. """ diff --git a/tools/json_schema_compiler/cpp_util.py b/tools/json_schema_compiler/cpp_util.py index 2d49343..1e7c370 100644 --- a/tools/json_schema_compiler/cpp_util.py +++ b/tools/json_schema_compiler/cpp_util.py @@ -24,6 +24,10 @@ GENERATED_BUNDLE_FILE_MESSAGE = """// GENERATED FROM THE API DEFINITIONS IN // %s // DO NOT EDIT. """ +GENERATED_FEATURE_MESSAGE = """// GENERATED FROM THE FEATURE DEFINITIONS IN +// %s +// DO NOT EDIT. +""" def Classname(s): """Translates a namespace name or function name into something more @@ -118,3 +122,18 @@ def CloseNamespace(namespace): for component in reversed(namespace.split('::')): c.Append('} // namespace %s' % component) return c + + +def ConstantName(feature_name): + """Returns a kName for a feature's name. + """ + return ('k' + ''.join(word[0].upper() + word[1:] + for word in feature_name.replace('.', ' ').split())) + + +def CamelCase(unix_name): + return ''.join(word.capitalize() for word in unix_name.split('_')) + + +def ClassName(filepath): + return CamelCase(os.path.split(filepath)[1]) diff --git a/tools/json_schema_compiler/features_cc_generator.py b/tools/json_schema_compiler/features_cc_generator.py new file mode 100644 index 0000000..d3b3717 --- /dev/null +++ b/tools/json_schema_compiler/features_cc_generator.py @@ -0,0 +1,95 @@ +# 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. + +import os.path + +from code import Code +import cpp_util + + +class CCGenerator(object): + def Generate(self, feature_defs, source_file, namespace): + return _Generator(feature_defs, source_file, namespace).Generate() + + +class _Generator(object): + """A .cc generator for features. + """ + def __init__(self, feature_defs, source_file, namespace): + self._feature_defs = feature_defs + self._source_file = source_file + self._source_file_filename, _ = os.path.splitext(source_file) + self._class_name = cpp_util.ClassName(self._source_file_filename) + self._namespace = namespace + + def Generate(self): + """Generates a Code object for features. + """ + c = Code() + (c.Append(cpp_util.CHROMIUM_LICENSE) + .Append() + .Append(cpp_util.GENERATED_FEATURE_MESSAGE % self._source_file) + .Append() + .Append('#include <string>') + .Append() + .Append('#include "%s.h"' % self._source_file_filename) + .Append() + .Append('#include "base/logging.h"') + .Append() + .Concat(cpp_util.OpenNamespace(self._namespace)) + .Append() + ) + + # Generate the constructor. + (c.Append('%s::%s() {' % (self._class_name, self._class_name)) + .Sblock() + ) + for feature in self._feature_defs: + c.Append('features_["%s"] = %s;' + % (feature.name, cpp_util.ConstantName(feature.name))) + (c.Eblock() + .Append('}') + .Append() + ) + + # Generate the ToString function. + (c.Append('const char* %s::ToString(' + '%s::ID id) const {' % (self._class_name, self._class_name)) + .Sblock() + .Append('switch (id) {') + .Sblock() + ) + for feature in self._feature_defs: + c.Append('case %s: return "%s";' % + (cpp_util.ConstantName(feature.name), feature.name)) + (c.Append('case kUnknown: break;') + .Append('case kEnumBoundary: break;') + .Eblock() + .Append('}') + .Append('NOTREACHED();') + .Append('return "";') + ) + (c.Eblock() + .Append('}') + .Append() + ) + + # Generate the FromString function. + + (c.Append('%s::ID %s::FromString(' + 'const std::string& id) const {' + % (self._class_name, self._class_name)) + .Sblock() + .Append('std::map<std::string, %s::ID>::const_iterator it' + ' = features_.find(id);' % self._class_name) + .Append('if (it == features_.end())') + .Append(' return kUnknown;') + .Append('return it->second;') + .Eblock() + .Append('}') + .Append() + .Cblock(cpp_util.CloseNamespace(self._namespace)) + ) + + return c diff --git a/tools/json_schema_compiler/features_compiler.py b/tools/json_schema_compiler/features_compiler.py new file mode 100755 index 0000000..1e4e81a --- /dev/null +++ b/tools/json_schema_compiler/features_compiler.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# 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. +"""Generator for C++ features from json files. + +Usage example: + features_compiler.py --destdir gen --root /home/Work/src _permissions.json +""" + +import optparse +import os + +from schema_loader import SchemaLoader +from features_cc_generator import CCGenerator +from features_h_generator import HGenerator +from model import CreateFeature + + +def _GenerateSchema(filename, root, destdir, namespace): + """Generates C++ features files from the json file |filename|. + """ + # Load in the feature permissions from the JSON file. + schema = os.path.normpath(filename) + schema_loader = SchemaLoader(os.path.dirname(os.path.relpath(schema, root)), + os.path.dirname(schema)) + schema_filename = os.path.splitext(schema)[0] + feature_defs = schema_loader.LoadSchema(schema) + + # Generate a list of the features defined and a list of their models. + feature_list = [] + for feature_def, feature in feature_defs.iteritems(): + feature_list.append(CreateFeature(feature_def, feature)) + + source_file_dir, _ = os.path.split(schema) + relpath = os.path.relpath(os.path.normpath(source_file_dir), root) + full_path = os.path.join(relpath, schema) + + generators = [ + ('%s.cc' % schema_filename, CCGenerator()), + ('%s.h' % schema_filename, HGenerator()) + ] + + # Generate and output the code for all features. + output_code = [] + for filename, generator in generators: + code = generator.Generate(feature_list, full_path, namespace).Render() + if destdir: + with open(os.path.join(destdir, relpath, 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++ features model 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_features', + help='C++ namespace for generated files. e.g extensions::api.') + (opts, filenames) = parser.parse_args() + + # Only one file is currently specified. + if len(filenames) != 1: + raise ValueError('One (and only one) file is required (for now).') + + result = _GenerateSchema(filenames[0], opts.root, opts.destdir, + opts.namespace) + if not opts.destdir: + print result diff --git a/tools/json_schema_compiler/features_h_generator.py b/tools/json_schema_compiler/features_h_generator.py new file mode 100644 index 0000000..025f734 --- /dev/null +++ b/tools/json_schema_compiler/features_h_generator.py @@ -0,0 +1,95 @@ +# 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. + +import os.path + +from code import Code +import cpp_util + + +class HGenerator(object): + def Generate(self, features, source_file, namespace): + return _Generator(features, source_file, namespace).Generate() + + +class _Generator(object): + """A .cc generator for features. + """ + def __init__(self, features, source_file, namespace): + self._feature_defs = features + self._source_file = source_file + self._source_file_filename, _ = os.path.splitext(source_file) + self._class_name = cpp_util.ClassName(self._source_file_filename) + self._namespace = namespace + + def Generate(self): + """Generates a Code object for features. + """ + c = Code() + (c.Append(cpp_util.CHROMIUM_LICENSE) + .Append() + .Append(cpp_util.GENERATED_FEATURE_MESSAGE % self._source_file) + .Append() + ) + ifndef_name = cpp_util.GenerateIfndefName(self._source_file_filename, + self._class_name) + (c.Append('#ifndef %s' % ifndef_name) + .Append('#define %s' % ifndef_name) + .Append() + ) + + (c.Append('#include <map>') + .Append('#include <string>') + .Append() + .Concat(cpp_util.OpenNamespace(self._namespace)) + .Append() + ) + + (c.Append('class %s {' % self._class_name) + .Append(' public:') + .Sblock() + .Concat(self._GeneratePublicBody()) + .Eblock() + .Append(' private:') + .Sblock() + .Concat(self._GeneratePrivateBody()) + .Eblock('};') + .Append() + .Cblock(cpp_util.CloseNamespace(self._namespace)) + ) + (c.Append('#endif // %s' % ifndef_name) + .Append() + ) + return c + + def _GeneratePublicBody(self): + c = Code() + + (c.Append('%s();' % self._class_name) + .Append() + .Append('enum ID {') + .Concat(self._GenerateEnumConstants()) + .Eblock('};') + .Append() + .Append('const char* ToString(ID id) const;') + .Append('ID FromString(const std::string& id) const;') + .Append() + ) + return c + + def _GeneratePrivateBody(self): + return Code().Append('std::map<std::string, ' + '%s::ID> features_;' % self._class_name) + + def _GenerateEnumConstants(self): + c = Code() + + (c.Sblock() + .Append('kUnknown,') + ) + for feature in self._feature_defs: + c.Append('%s,' % cpp_util.ConstantName(feature.name)) + c.Append('kEnumBoundary') + + return c diff --git a/tools/json_schema_compiler/model.py b/tools/json_schema_compiler/model.py index 2fcb4df..17c3cc1 100644 --- a/tools/json_schema_compiler/model.py +++ b/tools/json_schema_compiler/model.py @@ -7,6 +7,7 @@ import os.path from json_parse import OrderedDict from memoize import memoize + class ParseException(Exception): """Thrown when data in the model is invalid. """ @@ -16,6 +17,7 @@ class ParseException(Exception): Exception.__init__( self, 'Model parse exception at:\n' + '\n'.join(hierarchy)) + class Model(object): """Model of all namespaces that comprise an API. @@ -34,6 +36,45 @@ class Model(object): self.namespaces[namespace.name] = namespace return namespace + +def CreateFeature(name, model): + if isinstance(model, dict): + return SimpleFeature(name, model) + return ComplexFeature(name, [SimpleFeature(name, child) for child in model]) + + +class ComplexFeature(object): + """A complex feature which may be made of several simple features. + + Properties: + - |name| the name of the feature + - |unix_name| the unix_name of the feature + - |feature_list| a list of simple features which make up the feature + """ + def __init__(self, feature_name, features): + self.name = feature_name + self.unix_name = UnixName(self.name) + self.feature_list = features + +class SimpleFeature(object): + """A simple feature, which can make up a complex feature, as specified in + files such as chrome/common/extensions/api/_permission_features.json. + + Properties: + - |name| the name of the feature + - |unix_name| the unix_name of the feature + - |channel| the channel where the feature is released + - |extension_types| the types which can use the feature + - |whitelist| a list of extensions allowed to use the feature + """ + def __init__(self, feature_name, feature_def): + self.name = feature_name + self.unix_name = UnixName(self.name) + self.channel = feature_def['channel'] + self.extension_types = feature_def['extension_types'] + self.whitelist = feature_def.get('whitelist') + + class Namespace(object): """An API namespace. @@ -75,6 +116,7 @@ class Namespace(object): if include_compiler_options else {}) self.documentation_options = json.get('documentation_options', {}) + class Origin(object): """Stores the possible origin of model object as a pair of bools. These are: @@ -93,6 +135,7 @@ class Origin(object): self.from_client = from_client self.from_json = from_json + class Type(object): """A Type defined in the json. @@ -199,6 +242,7 @@ class Type(object): else: raise ParseException(self, 'Unsupported JSON type %s' % json_type) + class Function(object): """A Function defined in the API. @@ -270,6 +314,7 @@ class Function(object): namespace, origin) + class Property(object): """A property of a type OR a parameter to a function. Properties: @@ -341,6 +386,7 @@ class Property(object): 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. @@ -368,11 +414,13 @@ class _Enum(object): 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. """ @@ -390,6 +438,7 @@ class PropertyType(object): REF = _PropertyTypeInfo(False, "ref") STRING = _PropertyTypeInfo(True, "string") + @memoize def UnixName(name): '''Returns the unix_style name for a given lowerCamelCase string. @@ -411,11 +460,13 @@ def UnixName(name): 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 = [] @@ -427,6 +478,7 @@ def _GetModelHierarchy(entity): hierarchy.reverse() return hierarchy + def _GetTypes(parent, json, namespace, origin): """Creates Type objects extracted from |json|. """ @@ -436,6 +488,7 @@ def _GetTypes(parent, json, namespace, origin): types[type_.name] = type_ return types + def _GetFunctions(parent, json, namespace): """Creates Function objects extracted from |json|. """ @@ -449,6 +502,7 @@ def _GetFunctions(parent, json, namespace): functions[function.name] = function return functions + def _GetEvents(parent, json, namespace): """Creates Function objects generated from the events in |json|. """ @@ -462,6 +516,7 @@ def _GetEvents(parent, json, namespace): events[event.name] = event return events + def _GetProperties(parent, json, namespace, origin): """Generates Property objects extracted from |json|. """ @@ -470,10 +525,12 @@ def _GetProperties(parent, json, namespace, origin): 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. """ @@ -483,6 +540,7 @@ class Platforms(object): MAC = _PlatformInfo("mac") WIN = _PlatformInfo("win") + def _GetPlatforms(json): if 'platforms' not in json: return None diff --git a/tools/json_schema_compiler/schema_loader.py b/tools/json_schema_compiler/schema_loader.py index 80f1d3f..9fea1ab 100644 --- a/tools/json_schema_compiler/schema_loader.py +++ b/tools/json_schema_compiler/schema_loader.py @@ -59,8 +59,5 @@ class SchemaLoader(object): 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/tools/json_schema_compiler/test/features_unittest.cc b/tools/json_schema_compiler/test/features_unittest.cc new file mode 100644 index 0000000..c4f1f12 --- /dev/null +++ b/tools/json_schema_compiler/test/features_unittest.cc @@ -0,0 +1,22 @@ +// 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. + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" +#include "tools/json_schema_compiler/test/test_features.h" + +using test::features::TestFeatures; + +TEST(FeaturesGeneratorTest, FromString) { + TestFeatures test_features; + EXPECT_EQ(TestFeatures::kSimple, test_features.FromString("simple")); + EXPECT_EQ(TestFeatures::kComplex, test_features.FromString("complex")); +} + +TEST(FeaturesGeneratorTest, ToString) { + TestFeatures test_features; + EXPECT_STREQ("simple", test_features.ToString(TestFeatures::kSimple)); + EXPECT_STREQ("complex", test_features.ToString(TestFeatures::kComplex)); +} diff --git a/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp b/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp index 27ec438..29ee296 100644 --- a/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp +++ b/tools/json_schema_compiler/test/json_schema_compiler_tests.gyp @@ -40,5 +40,24 @@ # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. 'msvs_disabled_warnings': [4267, ], }, + { + 'target_name': "features_generator_tests", + 'type': 'static_library', + 'variables': { + 'chromium_code': 1, + 'schema_files': [ + 'test_features.json' + ], + 'cc_dir': 'tools/json_schema_compiler/test', + 'root_namespace': 'test::features', + }, + 'inputs': [ + '<@(schema_files)', + ], + 'sources': [ + '<@(schema_files)', + ], + 'includes': ['../../../build/features_compile.gypi'] + }, ], } diff --git a/tools/json_schema_compiler/test/test_features.json b/tools/json_schema_compiler/test/test_features.json new file mode 100644 index 0000000..26d67a1 --- /dev/null +++ b/tools/json_schema_compiler/test/test_features.json @@ -0,0 +1,25 @@ +// 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. + +{ + "simple": { + "channel": "stable", + "extension_types": ["extension", "legacy_packaged_app"], + "min_manifest_version": 2 + }, + "complex": [ + { + "channel": "dev", + "extension_types": ["platform_app"] + }, + { + "channel": "stable", + "extension_types": ["platform_app"], + "whitelist": [ + "8C3741E3AF0B93B6E8E0DDD499BB0B74839EA578", + "E703483CEF33DEC18B4B6DD84B5C776FB9182BDB" + ] + } + ] +}
\ No newline at end of file |