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..df9c0f0b41dce9755fb0259a3976a79187d6b021 100644 |
| --- a/chrome/common/extensions/docs/server2/api_data_source.py |
| +++ b/chrome/common/extensions/docs/server2/api_data_source.py |
| @@ -65,6 +65,123 @@ 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'] |
| + ''' |
|
not at google - send to devlin
2014/07/01 16:40:57
nit: blank line here
|
| + def __init__(self, availability_finder, namespace_name): |
| + # The cursor begins life at the root. |
| + self._lookup_path = [namespace_name] |
|
not at google - send to devlin
2014/07/01 16:40:57
rather than doing this could you include |namespac
|
| + self._node_availabilities = availability_finder.GetAPINodeAvailability( |
| + namespace_name) |
| + self._namespace_name = namespace_name |
| + |
| + def _GetParentPath(self): |
| + '''Returns the path pointing to this node's parent. |
| + ''' |
| + assert len(self._lookup_path) > 2, \ |
| + 'Tried to look up parent for the top-level node.' |
| + |
| + # lookup_path[-1] is the name of the current node. |
| + # lookup_path[-2] is this node's category (e.g. types, events, etc.). |
|
not at google - send to devlin
2014/07/01 16:40:57
could you define the set of acceptable categories
|
| + # Thus, the parent node is described by lookup_path[:-2]. |
| + return self._lookup_path[:-2] |
| + |
| + def _LookupNodeAvailability(self): |
| + '''Returns the ChannelInfo object for this node. |
| + ''' |
| + return self._node_availabilities.Lookup(*self._lookup_path).annotation |
| + |
| + def _LookupParentNodeAvailability(self): |
| + '''Returns the ChannelInfo object for this node's parent. |
| + ''' |
| + return self._node_availabilities.Lookup(*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 API namespace, and |
| + # lookup_path[1] is always the node category (e.g. types, functions, etc.). |
|
not at google - send to devlin
2014/07/01 16:40:57
ditto
|
| + # Thus, lookup_path[2] is always the top-level node name. |
| + base_name = self._lookup_path[2] |
| + self._lookup_path[2] = '%s.%s' % (self._namespace_name, base_name) |
| + node_availability = self._LookupNodeAvailability() |
| + if node_availability is not None: |
| + return node_availability |
| + # We want to maintain a working lookup_path, so only restore it |
|
not at google - send to devlin
2014/07/01 16:40:58
I'd rather you always restored this, and in a try.
|
| + # if modifying the lookup_path did not work. |
| + self._lookup_path[2] = 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 and 'callback' in self._lookup_path: |
|
not at google - send to devlin
2014/07/01 16:40:57
so if there's an 'events' there *must* be a 'callb
|
| + lookup_path_copy = copy(self._lookup_path) |
| + self._lookup_path.remove('callback') |
| + node_availability = self._LookupNodeAvailability() |
| + self._lookup_path = lookup_path_copy |
|
not at google - send to devlin
2014/07/01 16:40:57
you should be able to do this without copy'ing; us
|
| + 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' % ' > '.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) |
|
not at google - send to devlin
2014/07/01 16:40:58
actually this is fine
|
| + def __exit__(self2, _, __, ___): |
| + self._lookup_path = self._lookup_path[:-len(path)] |
|
not at google - send to devlin
2014/07/01 16:40:57
nit: slightly better to be consistent and mutate |
|
| + 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 +195,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 +245,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'): |
|
not at google - send to devlin
2014/07/01 16:40:58
nice!
|
| + 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 +383,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 +462,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( |
|
not at google - send to devlin
2014/07/01 16:40:57
nit: indent -=2 on these
|
| + '%sintro_tables/%s_message.html' % (PRIVATE_TEMPLATES, status)).Get(), |
|
not at google - send to devlin
2014/07/01 16:40:58
nit: indent += 2 (relative to the previous line) h
|
| + 'version': version, |
| + 'scheduled': scheduled |
|
not at google - send to devlin
2014/07/01 16:40:58
nit: I'm going to try making these objects alphabe
|
| + } |
| + |
| def _GetIntroDescriptionRow(self): |
| ''' Generates the 'Description' row data for an API intro table. |
| ''' |
| @@ -359,14 +502,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): |