Commit 0853bcff authored by DHNishi@gmail.com's avatar DHNishi@gmail.com

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

Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=226826

Review URL: https://codereview.chromium.org/23594008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@233214 0039d316-1c4b-4281-b951-d872f2087c98
parent e06ebb97
......@@ -133,6 +133,7 @@ class Code(object):
"""
return '\n'.join([l.value for l in self._code])
class Line(object):
"""A line of code.
"""
......
......@@ -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])
# 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
#!/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
# 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
......@@ -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.
......@@ -272,6 +316,7 @@ class Function(object):
namespace,
origin)
class Property(object):
"""A property of a type OR a parameter to a function.
Properties:
......@@ -384,11 +429,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.
"""
......@@ -406,6 +453,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.
......@@ -427,11 +475,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 = []
......@@ -443,6 +493,7 @@ def _GetModelHierarchy(entity):
hierarchy.reverse()
return hierarchy
def _GetTypes(parent, json, namespace, origin):
"""Creates Type objects extracted from |json|.
"""
......@@ -452,6 +503,7 @@ def _GetTypes(parent, json, namespace, origin):
types[type_.name] = type_
return types
def _GetFunctions(parent, json, namespace):
"""Creates Function objects extracted from |json|.
"""
......@@ -465,6 +517,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|.
"""
......@@ -478,6 +531,7 @@ def _GetEvents(parent, json, namespace):
events[event.name] = event
return events
def _GetProperties(parent, json, namespace, origin):
"""Generates Property objects extracted from |json|.
"""
......@@ -486,10 +540,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.
"""
......@@ -499,6 +555,7 @@ class Platforms(object):
MAC = _PlatformInfo("mac")
WIN = _PlatformInfo("win")
def _GetPlatforms(json):
if 'platforms' not in json or json['platforms'] == None:
return None
......
......@@ -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
// 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));
}
// 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment