Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1458)

Unified Diff: chrome/common/extensions/docs/server2/api_data_source.py

Issue 11315018: Extensions Docs Server: Generalize $ref's to work for any schema node (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: addressed comments Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/common/extensions/docs/server2/api_data_source.py
diff --git a/chrome/common/extensions/docs/server2/api_data_source.py b/chrome/common/extensions/docs/server2/api_data_source.py
index 49421a60f7dce381f57afb087093c8fe099cf006..61484d91339cb461e83eeddd542002e03a088308 100644
--- a/chrome/common/extensions/docs/server2/api_data_source.py
+++ b/chrome/common/extensions/docs/server2/api_data_source.py
@@ -4,11 +4,12 @@
import copy
import json
+import logging
import os
-from docs_server_utils import GetLinkToRefType
import compiled_file_system as compiled_fs
from file_system import FileNotFoundError
+from reference_resolver import ReferenceResolver
import third_party.json_schema_compiler.json_comment_eater as json_comment_eater
import third_party.json_schema_compiler.model as model
import third_party.json_schema_compiler.idl_schema as idl_schema
@@ -17,7 +18,7 @@ import third_party.json_schema_compiler.idl_parser as idl_parser
# Increment this version when there are changes to the data stored in any of
# the caches used by APIDataSource. This allows the cache to be invalidated
# without having to flush memcache on the production server.
-_VERSION = 2
+_VERSION = 3
def _RemoveNoDocs(item):
if type(item) == dict:
@@ -49,7 +50,7 @@ def _FormatValue(value):
s = str(value)
return ','.join([s[max(0, i - 3):i] for i in range(len(s), 0, -3)][::-1])
-class _JscModel(object):
+class _JSCModel(object):
"""Uses a Model from the JSON Schema Compiler and generates a dict that
a Handlebar template can use for a data source.
"""
@@ -60,8 +61,21 @@ class _JscModel(object):
else:
self._namespace = model.Namespace(clean_json, clean_json['namespace'])
- def _FormatDescription(self, description):
- if description is None or '$ref:' not in description:
+ def _GetLinkToRefType(self, ref, ref_resolver):
not at google - send to devlin 2012/11/02 17:26:48 Rename method similar to rename suggestion in Refe
cduvall 2012/11/03 01:30:05 Done.
+ ref_data = ref_resolver.GetLinkToRefType(self._namespace.name, ref)
+ if ref_data is None:
not at google - send to devlin 2012/11/02 17:26:48 nit: invert condition.
cduvall 2012/11/03 01:30:05 Done.
+ logging.error('$ref %s could not be resolved.' % ref)
+ if '.' in ref:
+ type_name = ref.rsplit('.', 1)[-1]
+ else:
+ type_name = ref
+ return { 'href': '#type-%s' % type_name, 'text': ref }
+ return ref_data
+
+ def _FormatDescription(self, description, ref_resolver):
+ if (description is None or
+ '$ref:' not in description or
+ ref_resolver is None):
return description
refs = description.split('$ref:')
formatted_description = [refs[0]]
@@ -76,43 +90,48 @@ class _JscModel(object):
if not ref[-1].isalnum():
rest = ref[-1] + rest
ref = ref[:-1]
- ref_dict = GetLinkToRefType(self._namespace.name, ref)
+ ref_dict = self._GetLinkToRefType(ref, ref_resolver)
formatted_description.append('<a href="%(href)s">%(text)s</a>%(rest)s' %
{ 'href': ref_dict['href'], 'text': ref_dict['text'], 'rest': rest })
return ''.join(formatted_description)
- def ToDict(self):
+ def ToDict(self, ref_resolver):
not at google - send to devlin 2012/11/02 17:26:48 Consider introducing a DictBuilder class that is c
cduvall 2012/11/03 01:30:05 I redid JSCModel so that it acts as a DictBuilder.
if self._namespace is None:
return {}
return {
'name': self._namespace.name,
- 'types': [self._GenerateType(t) for t in self._namespace.types.values()
+ 'types': [self._GenerateType(t, ref_resolver)
+ for t in self._namespace.types.values()
if t.type_ != model.PropertyType.ADDITIONAL_PROPERTIES],
- 'functions': self._GenerateFunctions(self._namespace.functions),
- 'events': self._GenerateEvents(self._namespace.events),
- 'properties': self._GenerateProperties(self._namespace.properties)
+ 'functions': self._GenerateFunctions(self._namespace.functions,
+ ref_resolver),
+ 'events': self._GenerateEvents(self._namespace.events,
+ ref_resolver),
+ 'properties': self._GenerateProperties(self._namespace.properties,
+ ref_resolver)
}
- def _GenerateType(self, type_):
+ def _GenerateType(self, type_, ref_resolver):
type_dict = {
'name': type_.simple_name,
- 'description': self._FormatDescription(type_.description),
- 'properties': self._GenerateProperties(type_.properties),
- 'functions': self._GenerateFunctions(type_.functions),
- 'events': self._GenerateEvents(type_.events),
+ 'description': self._FormatDescription(type_.description, ref_resolver),
+ 'properties': self._GenerateProperties(type_.properties, ref_resolver),
+ 'functions': self._GenerateFunctions(type_.functions, ref_resolver),
+ 'events': self._GenerateEvents(type_.events, ref_resolver),
'id': _CreateId(type_, 'type')
}
- self._RenderTypeInformation(type_, type_dict)
+ self._RenderTypeInformation(type_, type_dict, ref_resolver)
return type_dict
- def _GenerateFunctions(self, functions):
- return [self._GenerateFunction(f) for f in functions.values()]
+ def _GenerateFunctions(self, functions, ref_resolver):
+ return [self._GenerateFunction(f, ref_resolver) for f in functions.values()]
- def _GenerateFunction(self, function):
+ def _GenerateFunction(self, function, ref_resolver):
function_dict = {
'name': function.simple_name,
- 'description': self._FormatDescription(function.description),
- 'callback': self._GenerateCallback(function.callback),
+ 'description': self._FormatDescription(function.description,
+ ref_resolver),
+ 'callback': self._GenerateCallback(function.callback, ref_resolver),
'parameters': [],
'returns': None,
'id': _CreateId(function, 'method')
@@ -123,32 +142,39 @@ class _JscModel(object):
else:
function_dict['parent_name'] = None
if function.returns:
- function_dict['returns'] = self._GenerateProperty(function.returns)
+ function_dict['returns'] = self._GenerateProperty(function.returns,
+ ref_resolver)
for param in function.params:
- function_dict['parameters'].append(self._GenerateProperty(param))
+ function_dict['parameters'].append(self._GenerateProperty(param,
+ ref_resolver))
if function_dict['callback']:
function_dict['parameters'].append(function_dict['callback'])
if len(function_dict['parameters']) > 0:
function_dict['parameters'][-1]['last'] = True
return function_dict
- def _GenerateEvents(self, events):
- return [self._GenerateEvent(e) for e in events.values()]
+ def _GenerateEvents(self, events, ref_resolver):
+ return [self._GenerateEvent(e, ref_resolver) for e in events.values()]
- def _GenerateEvent(self, event):
+ def _GenerateEvent(self, event, ref_resolver):
event_dict = {
'name': event.simple_name,
- 'description': self._FormatDescription(event.description),
- 'parameters': map(self._GenerateProperty, event.params),
- 'callback': self._GenerateCallback(event.callback),
- 'conditions': [GetLinkToRefType(self._namespace.name, c)
- for c in event.conditions],
- 'actions': [GetLinkToRefType(self._namespace.name, a)
- for a in event.actions],
- 'filters': map(self._GenerateProperty, event.filters),
+ 'description': self._FormatDescription(event.description,
+ ref_resolver),
+ 'parameters': [self._GenerateProperty(p, ref_resolver)
+ for p in event.params],
+ 'callback': self._GenerateCallback(event.callback, ref_resolver),
+ 'filters': [self._GenerateProperty(f, ref_resolver)
+ for f in event.filters],
'supportsRules': event.supports_rules,
'id': _CreateId(event, 'event')
}
+ if ref_resolver is not None:
+ event_dict['conditions'] = [self._GetLinkToRefType(condition,
+ ref_resolver)
+ for condition in event.conditions]
+ event_dict['actions'] = [self._GetLinkToRefType(action, ref_resolver)
+ for action in event.actions]
if (event.parent is not None and
not isinstance(event.parent, model.Namespace)):
event_dict['parent_name'] = event.parent.simple_name
@@ -160,39 +186,44 @@ class _JscModel(object):
event_dict['parameters'][-1]['last'] = True
return event_dict
- def _GenerateCallback(self, callback):
+ def _GenerateCallback(self, callback, ref_resolver):
if not callback:
return None
callback_dict = {
'name': callback.simple_name,
- 'description': self._FormatDescription(callback.description),
+ 'description': self._FormatDescription(callback.description,
+ ref_resolver),
'simple_type': {'simple_type': 'function'},
'optional': callback.optional,
'parameters': []
}
for param in callback.params:
- callback_dict['parameters'].append(self._GenerateProperty(param))
+ callback_dict['parameters'].append(
+ self._GenerateProperty(param, ref_resolver))
if (len(callback_dict['parameters']) > 0):
callback_dict['parameters'][-1]['last'] = True
return callback_dict
- def _GenerateProperties(self, properties):
- return [self._GenerateProperty(v) for v in properties.values()
+ def _GenerateProperties(self, properties, ref_resolver):
+ return [self._GenerateProperty(v, ref_resolver) for v in properties.values()
if v.type_ != model.PropertyType.ADDITIONAL_PROPERTIES]
- def _GenerateProperty(self, property_):
+ def _GenerateProperty(self, property_, ref_resolver):
property_dict = {
'name': property_.simple_name,
'optional': property_.optional,
- 'description': self._FormatDescription(property_.description),
- 'properties': self._GenerateProperties(property_.properties),
+ 'description': self._FormatDescription(property_.description,
+ ref_resolver),
+ 'properties': self._GenerateProperties(property_.properties,
+ ref_resolver),
+ 'functions': self._GenerateFunctions(property_.functions, ref_resolver),
'parameters': [],
- 'functions': self._GenerateFunctions(property_.functions),
'returns': None,
'id': _CreateId(property_, 'property')
}
for param in property_.params:
- property_dict['parameters'].append(self._GenerateProperty(param))
+ property_dict['parameters'].append(self._GenerateProperty(param,
+ ref_resolver))
if property_.returns:
property_dict['returns'] = self._GenerateProperty(property_.returns)
if (property_.parent is not None and
@@ -206,22 +237,24 @@ class _JscModel(object):
else:
property_dict['value'] = property_.value
else:
- self._RenderTypeInformation(property_, property_dict)
+ self._RenderTypeInformation(property_, property_dict, ref_resolver)
return property_dict
- def _RenderTypeInformation(self, property_, dst_dict):
+ def _RenderTypeInformation(self, property_, dst_dict, ref_resolver):
if property_.type_ == model.PropertyType.CHOICES:
- dst_dict['choices'] = map(self._GenerateProperty,
- property_.choices.values())
+ dst_dict['choices'] = [self._GenerateProperty(c, ref_resolver)
+ for c in property_.choices.values()]
# We keep track of which is last for knowing when to add "or" between
# choices in templates.
if len(dst_dict['choices']) > 0:
dst_dict['choices'][-1]['last'] = True
elif property_.type_ == model.PropertyType.REF:
- dst_dict['link'] = GetLinkToRefType(self._namespace.name,
- property_.ref_type)
+ if ref_resolver is not None:
+ dst_dict['link'] = self._GetLinkToRefType(property_.ref_type,
+ ref_resolver)
elif property_.type_ == model.PropertyType.ARRAY:
- dst_dict['array'] = self._GenerateProperty(property_.item_type)
+ dst_dict['array'] = self._GenerateProperty(property_.item_type,
+ ref_resolver)
elif property_.type_ == model.PropertyType.ENUM:
dst_dict['enum_values'] = []
for enum_value in property_.enum_values:
@@ -249,7 +282,10 @@ class APIDataSource(object):
|cache_factory|, so the APIs can be plugged into templates.
"""
class Factory(object):
- def __init__(self, cache_factory, base_path, samples_factory):
+ def __init__(self,
+ cache_factory,
+ base_path,
+ api_list_data_source):
not at google - send to devlin 2012/11/02 17:26:48 As I mentioned in handler, should be passing the f
cduvall 2012/11/03 01:30:05 Done.
self._permissions_cache = cache_factory.Create(self._LoadPermissions,
compiled_fs.PERMS,
version=_VERSION)
@@ -262,44 +298,67 @@ class APIDataSource(object):
self._idl_names_cache = cache_factory.Create(self._GetIDLNames,
compiled_fs.IDL_NAMES,
version=_VERSION)
- self._samples_factory = samples_factory
+ self._names_cache = cache_factory.Create(self._GetAllNames,
+ compiled_fs.NAMES,
+ version=_VERSION)
self._base_path = base_path
-
- def Create(self, request):
+ self._ref_resolver = ReferenceResolver(
+ self.Create(None, None, resolve_refs=False),
+ api_list_data_source)
+
+ def Create(self, request, samples_data_source, resolve_refs=True):
not at google - send to devlin 2012/11/02 17:26:48 It's nice for optional arguments to be False by de
not at google - send to devlin 2012/11/02 17:26:48 Having a separate SetSamplesDataSourceFactory meth
cduvall 2012/11/03 01:30:05 Done.
cduvall 2012/11/03 01:30:05 Done.
+ """Create an APIDataSource. |resolve_refs| specifies whether $ref's in
+ APIs being processed by the |ToDict| method of _JSCModel follows $ref's
+ in the API. This prevents endless recursion in ReferenceResolver.
+ """
return APIDataSource(self._permissions_cache,
self._json_cache,
self._idl_cache,
+ self._names_cache,
self._idl_names_cache,
self._base_path,
- self._samples_factory.Create(request))
+ samples_data_source,
+ self._ref_resolver if resolve_refs else None)
not at google - send to devlin 2012/11/02 17:26:48 nit: push this logic into where ToDict is called (
cduvall 2012/11/03 01:30:05 Done.
def _LoadPermissions(self, json_str):
return json.loads(json_comment_eater.Nom(json_str))
def _LoadJsonAPI(self, api):
- return _JscModel(json.loads(json_comment_eater.Nom(api))[0])
+ return _JSCModel(json.loads(json_comment_eater.Nom(api))[0])
def _LoadIdlAPI(self, api):
idl = idl_parser.IDLParser().ParseData(api)
- return _JscModel(idl_schema.IDLSchema(idl).process()[0])
+ return _JSCModel(idl_schema.IDLSchema(idl).process()[0])
def _GetIDLNames(self, apis):
- return [model.UnixName(os.path.splitext(api.split('/')[-1])[0])
- for api in apis if api.endswith('.idl')]
+ return [
+ model.UnixName(os.path.splitext(api[len('%s/' % self._base_path):])[0])
+ for api in apis if api.endswith('.idl')
+ ]
+
+ def _GetAllNames(self, apis):
+ return [
+ model.UnixName(os.path.splitext(api[len('%s/' % self._base_path):])[0])
+ for api in apis
+ ]
def __init__(self,
permissions_cache,
json_cache,
idl_cache,
+ names_cache,
idl_names_cache,
base_path,
- samples):
+ samples,
+ ref_resolver):
self._base_path = base_path
self._permissions_cache = permissions_cache
self._json_cache = json_cache
self._idl_cache = idl_cache
+ self._names_cache = names_cache
self._idl_names_cache = idl_names_cache
self._samples = samples
+ self._ref_resolver = ref_resolver
def _GetPermsFromFile(self, filename):
try:
@@ -326,16 +385,30 @@ class APIDataSource(object):
'permissions': self._GetFeature(path),
'samples': _LazySamplesGetter(path, self._samples)
}
- return_dict.update(handlebar.ToDict())
+ return_dict.update(handlebar.ToDict(self._ref_resolver))
return return_dict
+ def _GetAsSubdirectory(self, name):
+ if name.startswith('experimental_'):
+ parts = name[len('experimental_'):].split('_', 1)
+ parts[1] = 'experimental_%s' % parts[1]
+ return '/'.join(parts)
+ return name.replace('_', '/', 1)
+
def __getitem__(self, key):
return self.get(key)
def get(self, key):
- path, ext = os.path.splitext(key)
+ if key.endswith('.html') or key.endswith('.json') or key.endswith('.idl'):
+ path, ext = os.path.splitext(key)
+ else:
+ path = key
unix_name = model.UnixName(path)
idl_names = self._idl_names_cache.GetFromFileListing(self._base_path)
+ names = self._names_cache.GetFromFileListing(self._base_path)
+ if unix_name not in names and self._GetAsSubdirectory(unix_name) in names:
+ unix_name = self._GetAsSubdirectory(unix_name)
+
cache, ext = ((self._idl_cache, '.idl') if (unix_name in idl_names) else
(self._json_cache, '.json'))
return self._GenerateHandlebarContext(

Powered by Google App Engine
This is Rietveld 408576698