Chromium Code Reviews| 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..56d515c9492effbeb734e9f856600f932e26b401 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,144 @@ 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 or is 'callback' if the |
| + # lookup_path describes an event callback. However, a node may be named |
| + # 'callback'; if this is the case, then self._lookup_path[-2] will be a |
| + # node category. If it's not, then we are dealing with an event callback. |
| + if self._lookup_path[-2] not in _NODE_CATEGORIES: |
| + assert 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 a node category. |
|
not at google - send to devlin
2014/07/01 20:58:50
maybe I'm misunderstanding, but if this is an even
ahernandez
2014/07/01 21:04:34
Yes it should. Will change.
|
| + self._AssertIsValidCategory(self._lookup_path[-3]) |
| + return self._lookup_path[:-1] |
| + # This is not an event callback, 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 |
| + try: |
| + callback_index = self._lookup_path.index('callback') |
|
not at google - send to devlin
2014/07/01 20:58:50
nit: do this outside of the try block
|
| + self._lookup_path.pop(callback_index) |
| + node_availability = self._LookupNodeAvailability() |
| + finally: |
| + self._lookup_path.insert(callback_index, 'callback') |
| + return node_availability |
| + return None |
| + |
| + def GetAvailability(self): |
| + '''Returns availability information for this node. |
| + ''' |
| + for lookup in (self._LookupNodeAvailability, |
| + self._CheckEventCallback, |
| + self._CheckNamespacePrefix): |
| + node_availability = lookup() |
| + if node_availability is not None: |
| + break |
| + |
| + if node_availability is None: |
| + logging.warning('No availability found for: %s > %s' % |
| + (self._namespace_name, ' > '.join(self._lookup_path))) |
| + return None |
| + |
| + # Only render this node's availability if it differs from the parent |
| + # node's availability. |
| + if node_availability == self._LookupParentNodeAvailability(): |
| + 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 +220,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 +270,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 +408,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 +487,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 +527,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): |