summaryrefslogtreecommitdiffstats
path: root/tools/json_schema_compiler
diff options
context:
space:
mode:
authorDHNishi@gmail.com <DHNishi@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-03 20:10:30 +0000
committerDHNishi@gmail.com <DHNishi@gmail.com@0039d316-1c4b-4281-b951-d872f2087c98>2013-10-03 20:10:30 +0000
commit3111f6d91219d56d2e594fc6b3d012bab1056986 (patch)
tree69c9639f0e2328d3c40ec7706b6e279aabe82cfb /tools/json_schema_compiler
parent5fad4e034b611f794326eb6298516ba5dc1631b4 (diff)
downloadchromium_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.py1
-rw-r--r--tools/json_schema_compiler/cpp_util.py19
-rw-r--r--tools/json_schema_compiler/features_cc_generator.py95
-rwxr-xr-xtools/json_schema_compiler/features_compiler.py76
-rw-r--r--tools/json_schema_compiler/features_h_generator.py95
-rw-r--r--tools/json_schema_compiler/model.py58
-rw-r--r--tools/json_schema_compiler/schema_loader.py3
-rw-r--r--tools/json_schema_compiler/test/features_unittest.cc22
-rw-r--r--tools/json_schema_compiler/test/json_schema_compiler_tests.gyp19
-rw-r--r--tools/json_schema_compiler/test/test_features.json25
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