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

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

Issue 354073004: Docserver: Add template support for object level availability (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 6 months 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
« no previous file with comments | « no previous file | chrome/common/extensions/docs/server2/api_data_source_test.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 882c0a66b51072fe9914da7954293c1f902d04a9..6fdc6f6abd6d575845b47cd4a2e6e8c866077d3c 100644
--- a/chrome/common/extensions/docs/server2/api_data_source.py
+++ b/chrome/common/extensions/docs/server2/api_data_source.py
@@ -20,6 +20,10 @@ from environment import IsPreviewServer
from third_party.json_schema_compiler.memoize import memoize
+# The set of possible categories a node may belong to.
+_NODE_CATEGORIES = ('types', 'functions', 'events', 'properties')
+
+
def _CreateId(node, prefix):
if node.parent is not None and not isinstance(node.parent, model.Namespace):
return '-'.join([prefix, node.parent.simple_name, node.simple_name])
@@ -43,7 +47,7 @@ def _GetByNameDict(namespace):
namespace['events'], and namespace['properties'].
'''
by_name = {}
- for item_type in ('types', 'functions', 'events', 'properties'):
+ for item_type in _NODE_CATEGORIES:
if item_type in namespace:
old_size = len(by_name)
by_name.update(
@@ -65,6 +69,162 @@ def _GetEventByNameFromEvents(events):
return _GetByNameDict(event_list[0])
+class _APINodeCursor(object):
+ '''An abstract representation of a node in an APISchemaGraph.
+ The current position in the graph is represented by a path into the
+ underlying dictionary. So if the APISchemaGraph is:
+
+ {
+ 'tabs': {
+ 'types': {
+ 'Tab': {
+ 'properties': {
+ 'url': {
+ ...
+ }
+ }
+ }
+ }
+ }
+ }
+
+ then the 'url' property would be represented by:
+
+ ['tabs', 'types', 'Tab', 'properties', 'url']
+ '''
+
+ def __init__(self, availability_finder, namespace_name):
+ self._lookup_path = []
+ self._node_availabilities = availability_finder.GetAPINodeAvailability(
+ namespace_name)
+ self._namespace_name = namespace_name
+
+ def _AssertIsValidCategory(self, category):
+ assert category in _NODE_CATEGORIES, \
+ '%s is not a valid category. Full path: %s' % (category,
+ self._lookup_path)
+
+ def _GetParentPath(self):
+ '''Returns the path pointing to this node's parent.
+ '''
+ assert len(self._lookup_path) > 1, \
+ 'Tried to look up parent for the top-level node.'
+
+ # lookup_path[-1] is the name of the current node. If this lookup_path
+ # describes a regular node, then lookup_path[-2] will be a node category.
+ # Otherwise, it's an event callback or a function parameter.
+ if self._lookup_path[-2] not in _NODE_CATEGORIES:
+ if self._lookup_path[-1] == 'callback':
+ # This is an event callback, so lookup_path[-2] is the event
+ # node name, thus lookup_path[-3] must be 'events'.
+ assert self._lookup_path[-3] == 'events'
+ return self._lookup_path[:-1]
+ # This is a function parameter.
+ assert self._lookup_path[-2] == 'parameters'
+ return self._lookup_path[:-2]
+ # This is a regular node, so lookup_path[-2] should
+ # be a node category.
+ self._AssertIsValidCategory(self._lookup_path[-2])
+ return self._lookup_path[:-2]
+
+ def _LookupNodeAvailability(self):
+ '''Returns the ChannelInfo object for this node.
+ '''
+ return self._node_availabilities.Lookup(self._namespace_name,
+ *self._lookup_path).annotation
+
+ def _LookupParentNodeAvailability(self):
+ '''Returns the ChannelInfo object for this node's parent.
+ '''
+ return self._node_availabilities.Lookup(self._namespace_name,
+ *self._GetParentPath()).annotation
+
+ def _CheckNamespacePrefix(self):
+ '''API schemas may prepend the namespace name to top-level types
+ (e.g. declarativeWebRequest > types > declarativeWebRequest.IgnoreRules),
+ but just the base name (here, 'IgnoreRules') will be in the |lookup_path|.
+ Try creating an alternate |lookup_path| by adding the namespace name.
+ '''
+ # lookup_path[0] is always the node category (e.g. types, functions, etc.).
+ # Thus, lookup_path[1] is always the top-level node name.
+ self._AssertIsValidCategory(self._lookup_path[0])
+ base_name = self._lookup_path[1]
+ self._lookup_path[1] = '%s.%s' % (self._namespace_name, base_name)
+ try:
+ node_availability = self._LookupNodeAvailability()
+ if node_availability is not None:
+ return node_availability
+ finally:
+ # Restore lookup_path.
+ self._lookup_path[1] = base_name
+ return None
+
+ def _CheckEventCallback(self):
+ '''Within API schemas, an event has a list of 'properties' that the event's
+ callback expects. The callback itself is not explicitly represented in the
+ schema. However, when creating an event node in _JSCModel, a callback node
+ is generated and acts as the parent for the event's properties.
+ Modify |lookup_path| to check the original schema format.
+ '''
+ if 'events' in self._lookup_path:
+ assert 'callback' in self._lookup_path
+ callback_index = self._lookup_path.index('callback')
+ try:
+ self._lookup_path.pop(callback_index)
+ node_availability = self._LookupNodeAvailability()
+ finally:
+ self._lookup_path.insert(callback_index, 'callback')
+ return node_availability
+ return None
+
+ def _LookupAvailability(self):
+ '''Runs all the lookup checks on self._lookup_path and
+ returns the node availability if found, None otherwise.
+ '''
+ for lookup in (self._LookupNodeAvailability,
+ self._CheckEventCallback,
+ self._CheckNamespacePrefix):
+ node_availability = lookup()
+ if node_availability is not None:
+ return node_availability
+ return None
+
+ def GetAvailability(self):
+ '''Returns availability information for this node.
+ '''
+ node_availability = self._LookupAvailability()
+ if node_availability is None:
+ logging.warning('No availability found for: %s > %s' %
+ (self._namespace_name, ' > '.join(self._lookup_path)))
+ return None
+
+ try:
+ current_path = self._lookup_path
+ self._lookup_path = self._GetParentPath()
+ parent_node_availability = self._LookupAvailability()
+ finally:
+ self._lookup_path = current_path
+ # If the parent node availability couldn't be found, something
+ # is very wrong.
+ assert parent_node_availability is not None
+
+ # Only render this node's availability if it differs from the parent
+ # node's availability.
+ if node_availability == parent_node_availability:
+ return None
+ return node_availability
+
+ def Descend(self, *path):
+ '''Moves down the APISchemaGraph, following |path|.
+ '''
+ class scope(object):
+ def __enter__(self2):
+ self._lookup_path.extend(path)
+ def __exit__(self2, _, __, ___):
+ self._lookup_path[:] = self._lookup_path[:-len(path)]
+ return scope()
+
+
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.
@@ -78,6 +238,7 @@ class _JSCModel(object):
features_bundle,
event_byname_future):
self._availability = availability_finder.GetAPIAvailability(namespace.name)
+ self._current_node = _APINodeCursor(availability_finder, namespace.name)
self._api_availabilities = json_cache.GetFromFile(
posixpath.join(JSON_TEMPLATES, 'api_availabilities.json'))
self._intro_tables = json_cache.GetFromFile(
@@ -127,95 +288,103 @@ class _JSCModel(object):
return self._namespace.name.startswith('experimental')
def _GenerateTypes(self, types):
- return [self._GenerateType(t) for t in types]
+ with self._current_node.Descend('types'):
+ return [self._GenerateType(t) for t in types]
def _GenerateType(self, type_):
- type_dict = {
- 'name': type_.simple_name,
- 'description': type_.description,
- 'properties': self._GenerateProperties(type_.properties),
- 'functions': self._GenerateFunctions(type_.functions),
- 'events': self._GenerateEvents(type_.events),
- 'id': _CreateId(type_, 'type')
- }
- self._RenderTypeInformation(type_, type_dict)
- return type_dict
+ with self._current_node.Descend(type_.simple_name):
+ type_dict = {
+ 'name': type_.simple_name,
+ 'description': type_.description,
+ 'properties': self._GenerateProperties(type_.properties),
+ 'functions': self._GenerateFunctions(type_.functions),
+ 'events': self._GenerateEvents(type_.events),
+ 'id': _CreateId(type_, 'type'),
+ }
+ self._RenderTypeInformation(type_, type_dict)
+ return type_dict
def _GenerateFunctions(self, functions):
- return [self._GenerateFunction(f) for f in functions.values()]
+ with self._current_node.Descend('functions'):
+ return [self._GenerateFunction(f) for f in functions.values()]
def _GenerateFunction(self, function):
- function_dict = {
- 'name': function.simple_name,
- 'description': function.description,
- 'callback': self._GenerateCallback(function.callback),
- 'parameters': [],
- 'returns': None,
- 'id': _CreateId(function, 'method')
- }
- self._AddCommonProperties(function_dict, function)
- if function.returns:
- function_dict['returns'] = self._GenerateType(function.returns)
- for param in function.params:
- function_dict['parameters'].append(self._GenerateProperty(param))
- if function.callback is not None:
- # Show the callback as an extra parameter.
- function_dict['parameters'].append(
- self._GenerateCallbackProperty(function.callback))
- if len(function_dict['parameters']) > 0:
- function_dict['parameters'][-1]['last'] = True
- return function_dict
+ with self._current_node.Descend(function.simple_name):
+ function_dict = {
+ 'name': function.simple_name,
+ 'description': function.description,
+ 'callback': self._GenerateCallback(function.callback),
+ 'parameters': [],
+ 'returns': None,
+ 'id': _CreateId(function, 'method'),
+ 'availability': self._GetAvailabilityTemplate()
+ }
+ self._AddCommonProperties(function_dict, function)
+ if function.returns:
+ function_dict['returns'] = self._GenerateType(function.returns)
+ for param in function.params:
+ function_dict['parameters'].append(self._GenerateProperty(param))
+ if function.callback is not None:
+ # Show the callback as an extra parameter.
+ function_dict['parameters'].append(
+ self._GenerateCallbackProperty(function.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()
- if not e.supports_dom]
+ with self._current_node.Descend('events'):
+ return [self._GenerateEvent(e) for e in events.values()
+ if not e.supports_dom]
def _GenerateDomEvents(self, events):
- return [self._GenerateEvent(e) for e in events.values()
- if e.supports_dom]
+ with self._current_node.Descend('events'):
+ return [self._GenerateEvent(e) for e in events.values()
+ if e.supports_dom]
def _GenerateEvent(self, event):
- event_dict = {
- 'name': event.simple_name,
- 'description': event.description,
- 'filters': [self._GenerateProperty(f) for f in event.filters],
- 'conditions': [self._GetLink(condition)
- for condition in event.conditions],
- 'actions': [self._GetLink(action) for action in event.actions],
- 'supportsRules': event.supports_rules,
- 'supportsListeners': event.supports_listeners,
- 'properties': [],
- 'id': _CreateId(event, 'event'),
- 'byName': {},
- }
- self._AddCommonProperties(event_dict, event)
- # Add the Event members to each event in this object.
- if self._event_byname_future:
- event_dict['byName'].update(self._event_byname_future.Get())
- # We need to create the method description for addListener based on the
- # information stored in |event|.
- if event.supports_listeners:
- callback_object = model.Function(parent=event,
- name='callback',
- json={},
- namespace=event.parent,
- origin='')
- callback_object.params = event.params
- if event.callback:
- callback_object.callback = event.callback
- callback_parameters = self._GenerateCallbackProperty(callback_object)
- callback_parameters['last'] = True
- event_dict['byName']['addListener'] = {
- 'name': 'addListener',
- 'callback': self._GenerateFunction(callback_object),
- 'parameters': [callback_parameters]
+ with self._current_node.Descend(event.simple_name):
+ event_dict = {
+ 'name': event.simple_name,
+ 'description': event.description,
+ 'filters': [self._GenerateProperty(f) for f in event.filters],
+ 'conditions': [self._GetLink(condition)
+ for condition in event.conditions],
+ 'actions': [self._GetLink(action) for action in event.actions],
+ 'supportsRules': event.supports_rules,
+ 'supportsListeners': event.supports_listeners,
+ 'properties': [],
+ 'id': _CreateId(event, 'event'),
+ 'byName': {},
}
- if event.supports_dom:
- # Treat params as properties of the custom Event object associated with
- # this DOM Event.
- event_dict['properties'] += [self._GenerateProperty(param)
- for param in event.params]
- return event_dict
+ self._AddCommonProperties(event_dict, event)
+ # Add the Event members to each event in this object.
+ if self._event_byname_future:
+ event_dict['byName'].update(self._event_byname_future.Get())
+ # We need to create the method description for addListener based on the
+ # information stored in |event|.
+ if event.supports_listeners:
+ callback_object = model.Function(parent=event,
+ name='callback',
+ json={},
+ namespace=event.parent,
+ origin='')
+ callback_object.params = event.params
+ if event.callback:
+ callback_object.callback = event.callback
+ callback_parameters = self._GenerateCallbackProperty(callback_object)
+ callback_parameters['last'] = True
+ event_dict['byName']['addListener'] = {
+ 'name': 'addListener',
+ 'callback': self._GenerateFunction(callback_object),
+ 'parameters': [callback_parameters]
+ }
+ if event.supports_dom:
+ # Treat params as properties of the custom Event object associated with
+ # this DOM Event.
+ event_dict['properties'] += [self._GenerateProperty(param)
+ for param in event.params]
+ return event_dict
def _GenerateCallback(self, callback):
if not callback:
@@ -257,7 +426,7 @@ class _JSCModel(object):
'functions': self._GenerateFunctions(type_.functions),
'parameters': [],
'returns': None,
- 'id': _CreateId(property_, 'property')
+ 'id': _CreateId(property_, 'property'),
}
self._AddCommonProperties(property_dict, property_)
@@ -336,6 +505,23 @@ class _JSCModel(object):
return intro_rows
+ def _GetAvailabilityTemplate(self, status=None, version=None, scheduled=None):
+ '''Returns an object that the templates use to display availability
+ information.
+ '''
+ if status is None:
+ availability_info = self._current_node.GetAvailability()
+ if availability_info is None:
+ return None
+ status = availability_info.channel
+ version = availability_info.version
+ return {
+ 'partial': self._template_cache.GetFromFile(
+ '%sintro_tables/%s_message.html' % (PRIVATE_TEMPLATES, status)).Get(),
+ 'scheduled': scheduled,
+ 'version': version
+ }
+
def _GetIntroDescriptionRow(self):
''' Generates the 'Description' row data for an API intro table.
'''
@@ -359,14 +545,11 @@ class _JSCModel(object):
scheduled = self._availability.scheduled
return {
'title': 'Availability',
- 'content': [{
- 'partial': self._template_cache.GetFromFile(
- posixpath.join(PRIVATE_TEMPLATES,
- 'intro_tables',
- '%s_message.html' % status)).Get(),
- 'version': version,
- 'scheduled': scheduled
- }]
+ 'content': [
+ self._GetAvailabilityTemplate(status=status,
+ version=version,
+ scheduled=scheduled)
+ ]
}
def _GetIntroDependencyRows(self):
« no previous file with comments | « no previous file | chrome/common/extensions/docs/server2/api_data_source_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698