Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 1 | # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import copy |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 6 | import logging |
| 7 | import os |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 8 | from collections import defaultdict, Mapping |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 9 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 10 | from branch_utility import BranchUtility |
| 11 | import svn_constants |
| 12 | from third_party.handlebar import Handlebar |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 13 | import third_party.json_schema_compiler.json_parse as json_parse |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 14 | import third_party.json_schema_compiler.model as model |
| 15 | import third_party.json_schema_compiler.idl_schema as idl_schema |
| 16 | import third_party.json_schema_compiler.idl_parser as idl_parser |
| 17 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 18 | def _RemoveNoDocs(item): |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 19 | if json_parse.IsDict(item): |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 20 | if item.get('nodoc', False): |
| 21 | return True |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 22 | for key, value in item.items(): |
| 23 | if _RemoveNoDocs(value): |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 24 | del item[key] |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 25 | elif type(item) == list: |
| 26 | to_remove = [] |
| 27 | for i in item: |
| 28 | if _RemoveNoDocs(i): |
| 29 | to_remove.append(i) |
| 30 | for i in to_remove: |
| 31 | item.remove(i) |
| 32 | return False |
| 33 | |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 34 | def _DetectInlineableTypes(schema): |
| 35 | """Look for documents that are only referenced once and mark them as inline. |
| 36 | Actual inlining is done by _InlineDocs. |
| 37 | """ |
| 38 | if not schema.get('types'): |
| 39 | return |
| 40 | |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 41 | ignore = frozenset(('value', 'choices')) |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 42 | refcounts = defaultdict(int) |
| 43 | # Use an explicit stack instead of recursion. |
| 44 | stack = [schema] |
| 45 | |
| 46 | while stack: |
| 47 | node = stack.pop() |
| 48 | if isinstance(node, list): |
| 49 | stack.extend(node) |
| 50 | elif isinstance(node, Mapping): |
| 51 | if '$ref' in node: |
| 52 | refcounts[node['$ref']] += 1 |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 53 | stack.extend(v for k, v in node.iteritems() if k not in ignore) |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 54 | |
| 55 | for type_ in schema['types']: |
Torne (Richard Coles) | 868fa2f | 2013-06-11 10:57:03 +0100 | [diff] [blame] | 56 | if not 'noinline_doc' in type_: |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 57 | if refcounts[type_['id']] == 1: |
| 58 | type_['inline_doc'] = True |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 59 | |
| 60 | def _InlineDocs(schema): |
| 61 | """Replace '$ref's that refer to inline_docs with the json for those docs. |
| 62 | """ |
| 63 | types = schema.get('types') |
| 64 | if types is None: |
| 65 | return |
| 66 | |
| 67 | inline_docs = {} |
| 68 | types_without_inline_doc = [] |
| 69 | |
| 70 | # Gather the types with inline_doc. |
| 71 | for type_ in types: |
| 72 | if type_.get('inline_doc'): |
| 73 | inline_docs[type_['id']] = type_ |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 74 | for k in ('description', 'id', 'inline_doc'): |
| 75 | type_.pop(k, None) |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 76 | else: |
| 77 | types_without_inline_doc.append(type_) |
| 78 | schema['types'] = types_without_inline_doc |
| 79 | |
| 80 | def apply_inline(node): |
| 81 | if isinstance(node, list): |
| 82 | for i in node: |
| 83 | apply_inline(i) |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 84 | elif isinstance(node, Mapping): |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 85 | ref = node.get('$ref') |
| 86 | if ref and ref in inline_docs: |
| 87 | node.update(inline_docs[ref]) |
| 88 | del node['$ref'] |
| 89 | for k, v in node.iteritems(): |
| 90 | apply_inline(v) |
| 91 | |
| 92 | apply_inline(schema) |
| 93 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 94 | def _CreateId(node, prefix): |
| 95 | if node.parent is not None and not isinstance(node.parent, model.Namespace): |
| 96 | return '-'.join([prefix, node.parent.simple_name, node.simple_name]) |
| 97 | return '-'.join([prefix, node.simple_name]) |
| 98 | |
| 99 | def _FormatValue(value): |
| 100 | """Inserts commas every three digits for integer values. It is magic. |
| 101 | """ |
| 102 | s = str(value) |
| 103 | return ','.join([s[max(0, i - 3):i] for i in range(len(s), 0, -3)][::-1]) |
| 104 | |
| 105 | class _JSCModel(object): |
| 106 | """Uses a Model from the JSON Schema Compiler and generates a dict that |
| 107 | a Handlebar template can use for a data source. |
| 108 | """ |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 109 | def __init__(self, |
| 110 | json, |
| 111 | ref_resolver, |
| 112 | disable_refs, |
| 113 | availability_finder, |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 114 | parse_cache, |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 115 | template_data_source, |
| 116 | idl=False): |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 117 | self._ref_resolver = ref_resolver |
| 118 | self._disable_refs = disable_refs |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 119 | self._availability_finder = availability_finder |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 120 | self._intro_tables = parse_cache.GetFromFile( |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 121 | '%s/intro_tables.json' % svn_constants.JSON_PATH) |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 122 | self._api_features = parse_cache.GetFromFile( |
| 123 | '%s/_api_features.json' % svn_constants.API_PATH) |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 124 | self._template_data_source = template_data_source |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 125 | clean_json = copy.deepcopy(json) |
| 126 | if _RemoveNoDocs(clean_json): |
| 127 | self._namespace = None |
| 128 | else: |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 129 | if idl: |
| 130 | _DetectInlineableTypes(clean_json) |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 131 | _InlineDocs(clean_json) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 132 | self._namespace = model.Namespace(clean_json, clean_json['namespace']) |
| 133 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 134 | def _FormatDescription(self, description): |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 135 | if self._disable_refs: |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 136 | return description |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 137 | return self._ref_resolver.ResolveAllLinks(description, |
| 138 | namespace=self._namespace.name) |
| 139 | |
| 140 | def _GetLink(self, link): |
| 141 | if self._disable_refs: |
| 142 | type_name = link.split('.', 1)[-1] |
| 143 | return { 'href': '#type-%s' % type_name, 'text': link, 'name': link } |
| 144 | return self._ref_resolver.SafeGetLink(link, namespace=self._namespace.name) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 145 | |
| 146 | def ToDict(self): |
| 147 | if self._namespace is None: |
| 148 | return {} |
| 149 | return { |
| 150 | 'name': self._namespace.name, |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 151 | 'types': self._GenerateTypes(self._namespace.types.values()), |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 152 | 'functions': self._GenerateFunctions(self._namespace.functions), |
| 153 | 'events': self._GenerateEvents(self._namespace.events), |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 154 | 'properties': self._GenerateProperties(self._namespace.properties), |
| 155 | 'intro_list': self._GetIntroTableList(), |
| 156 | 'channel_warning': self._GetChannelWarning() |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 157 | } |
| 158 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 159 | def _GetIntroTableList(self): |
| 160 | """Create a generic data structure that can be traversed by the templates |
| 161 | to create an API intro table. |
| 162 | """ |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 163 | intro_rows = [ |
| 164 | self._GetIntroDescriptionRow(), |
| 165 | self._GetIntroAvailabilityRow() |
| 166 | ] + self._GetIntroDependencyRows() |
| 167 | |
| 168 | # Add rows using data from intro_tables.json, overriding any existing rows |
| 169 | # if they share the same 'title' attribute. |
| 170 | row_titles = [row['title'] for row in intro_rows] |
| 171 | for misc_row in self._GetMiscIntroRows(): |
| 172 | if misc_row['title'] in row_titles: |
| 173 | intro_rows[row_titles.index(misc_row['title'])] = misc_row |
| 174 | else: |
| 175 | intro_rows.append(misc_row) |
| 176 | |
| 177 | return intro_rows |
| 178 | |
| 179 | def _GetIntroDescriptionRow(self): |
| 180 | """ Generates the 'Description' row data for an API intro table. |
| 181 | """ |
| 182 | return { |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 183 | 'title': 'Description', |
| 184 | 'content': [ |
| 185 | { 'text': self._FormatDescription(self._namespace.description) } |
| 186 | ] |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 187 | } |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 188 | |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 189 | def _GetIntroAvailabilityRow(self): |
| 190 | """ Generates the 'Availability' row data for an API intro table. |
| 191 | """ |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 192 | if self._IsExperimental(): |
| 193 | status = 'experimental' |
| 194 | version = None |
| 195 | else: |
| 196 | availability = self._GetApiAvailability() |
| 197 | status = availability.channel |
| 198 | version = availability.version |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 199 | return { |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 200 | 'title': 'Availability', |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 201 | 'content': [{ |
| 202 | 'partial': self._template_data_source.get( |
| 203 | 'intro_tables/%s_message.html' % status), |
| 204 | 'version': version |
| 205 | }] |
| 206 | } |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 207 | |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 208 | def _GetIntroDependencyRows(self): |
| 209 | # Devtools aren't in _api_features. If we're dealing with devtools, bail. |
| 210 | if 'devtools' in self._namespace.name: |
| 211 | return [] |
| 212 | feature = self._api_features.get(self._namespace.name) |
| 213 | assert feature, ('"%s" not found in _api_features.json.' |
| 214 | % self._namespace.name) |
| 215 | |
| 216 | dependencies = feature.get('dependencies') |
| 217 | if dependencies is None: |
| 218 | return [] |
| 219 | |
| 220 | def make_code_node(text): |
| 221 | return { 'class': 'code', 'text': text } |
| 222 | |
| 223 | permissions_content = [] |
| 224 | manifest_content = [] |
| 225 | |
Ben Murdoch | 3240926 | 2013-08-07 11:04:47 +0100 | [diff] [blame] | 226 | def categorize_dependency(dependency): |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 227 | context, name = dependency.split(':', 1) |
| 228 | if context == 'permission': |
| 229 | permissions_content.append(make_code_node('"%s"' % name)) |
| 230 | elif context == 'manifest': |
| 231 | manifest_content.append(make_code_node('"%s": {...}' % name)) |
Ben Murdoch | 3240926 | 2013-08-07 11:04:47 +0100 | [diff] [blame] | 232 | elif context == 'api': |
| 233 | transitive_dependencies = ( |
| 234 | self._api_features.get(context, {}).get('dependencies', [])) |
| 235 | for transitive_dependency in transitive_dependencies: |
| 236 | categorize_dependency(transitive_dependency) |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 237 | else: |
Ben Murdoch | 3240926 | 2013-08-07 11:04:47 +0100 | [diff] [blame] | 238 | raise ValueError('Unrecognized dependency for %s: %s' % ( |
| 239 | self._namespace.name, context)) |
| 240 | |
| 241 | for dependency in dependencies: |
| 242 | categorize_dependency(dependency) |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 243 | |
| 244 | dependency_rows = [] |
| 245 | if permissions_content: |
| 246 | dependency_rows.append({ |
| 247 | 'title': 'Permissions', |
| 248 | 'content': permissions_content |
| 249 | }) |
| 250 | if manifest_content: |
| 251 | dependency_rows.append({ |
| 252 | 'title': 'Manifest', |
| 253 | 'content': manifest_content |
| 254 | }) |
| 255 | return dependency_rows |
| 256 | |
| 257 | def _GetMiscIntroRows(self): |
| 258 | """ Generates miscellaneous intro table row data, such as 'Permissions', |
| 259 | 'Samples', and 'Learn More', using intro_tables.json. |
| 260 | """ |
| 261 | misc_rows = [] |
| 262 | # Look up the API name in intro_tables.json, which is structured |
| 263 | # similarly to the data structure being created. If the name is found, loop |
| 264 | # through the attributes and add them to this structure. |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 265 | table_info = self._intro_tables.get(self._namespace.name) |
| 266 | if table_info is None: |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 267 | return misc_rows |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 268 | |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 269 | for category in table_info.keys(): |
| 270 | content = copy.deepcopy(table_info[category]) |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 271 | for node in content: |
| 272 | # If there is a 'partial' argument and it hasn't already been |
| 273 | # converted to a Handlebar object, transform it to a template. |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 274 | if 'partial' in node: |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 275 | node['partial'] = self._template_data_source.get(node['partial']) |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 276 | misc_rows.append({ 'title': category, 'content': content }) |
| 277 | return misc_rows |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 278 | |
| 279 | def _GetApiAvailability(self): |
| 280 | return self._availability_finder.GetApiAvailability(self._namespace.name) |
| 281 | |
| 282 | def _GetChannelWarning(self): |
| 283 | if not self._IsExperimental(): |
| 284 | return { self._GetApiAvailability().channel: True } |
| 285 | return None |
| 286 | |
| 287 | def _IsExperimental(self): |
| 288 | return self._namespace.name.startswith('experimental') |
| 289 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 290 | def _GenerateTypes(self, types): |
| 291 | return [self._GenerateType(t) for t in types] |
| 292 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 293 | def _GenerateType(self, type_): |
| 294 | type_dict = { |
| 295 | 'name': type_.simple_name, |
| 296 | 'description': self._FormatDescription(type_.description), |
| 297 | 'properties': self._GenerateProperties(type_.properties), |
| 298 | 'functions': self._GenerateFunctions(type_.functions), |
| 299 | 'events': self._GenerateEvents(type_.events), |
| 300 | 'id': _CreateId(type_, 'type') |
| 301 | } |
| 302 | self._RenderTypeInformation(type_, type_dict) |
| 303 | return type_dict |
| 304 | |
| 305 | def _GenerateFunctions(self, functions): |
| 306 | return [self._GenerateFunction(f) for f in functions.values()] |
| 307 | |
| 308 | def _GenerateFunction(self, function): |
| 309 | function_dict = { |
| 310 | 'name': function.simple_name, |
| 311 | 'description': self._FormatDescription(function.description), |
| 312 | 'callback': self._GenerateCallback(function.callback), |
| 313 | 'parameters': [], |
| 314 | 'returns': None, |
| 315 | 'id': _CreateId(function, 'method') |
| 316 | } |
| 317 | if (function.parent is not None and |
| 318 | not isinstance(function.parent, model.Namespace)): |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 319 | function_dict['parentName'] = function.parent.simple_name |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 320 | if function.returns: |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 321 | function_dict['returns'] = self._GenerateType(function.returns) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 322 | for param in function.params: |
| 323 | function_dict['parameters'].append(self._GenerateProperty(param)) |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 324 | if function.callback is not None: |
| 325 | # Show the callback as an extra parameter. |
| 326 | function_dict['parameters'].append( |
| 327 | self._GenerateCallbackProperty(function.callback)) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 328 | if len(function_dict['parameters']) > 0: |
| 329 | function_dict['parameters'][-1]['last'] = True |
| 330 | return function_dict |
| 331 | |
| 332 | def _GenerateEvents(self, events): |
| 333 | return [self._GenerateEvent(e) for e in events.values()] |
| 334 | |
| 335 | def _GenerateEvent(self, event): |
| 336 | event_dict = { |
| 337 | 'name': event.simple_name, |
| 338 | 'description': self._FormatDescription(event.description), |
| 339 | 'parameters': [self._GenerateProperty(p) for p in event.params], |
| 340 | 'callback': self._GenerateCallback(event.callback), |
| 341 | 'filters': [self._GenerateProperty(f) for f in event.filters], |
| 342 | 'conditions': [self._GetLink(condition) |
| 343 | for condition in event.conditions], |
| 344 | 'actions': [self._GetLink(action) for action in event.actions], |
| 345 | 'supportsRules': event.supports_rules, |
| 346 | 'id': _CreateId(event, 'event') |
| 347 | } |
| 348 | if (event.parent is not None and |
| 349 | not isinstance(event.parent, model.Namespace)): |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 350 | event_dict['parentName'] = event.parent.simple_name |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 351 | if event.callback is not None: |
| 352 | # Show the callback as an extra parameter. |
| 353 | event_dict['parameters'].append( |
| 354 | self._GenerateCallbackProperty(event.callback)) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 355 | if len(event_dict['parameters']) > 0: |
| 356 | event_dict['parameters'][-1]['last'] = True |
| 357 | return event_dict |
| 358 | |
| 359 | def _GenerateCallback(self, callback): |
| 360 | if not callback: |
| 361 | return None |
| 362 | callback_dict = { |
| 363 | 'name': callback.simple_name, |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 364 | 'simple_type': {'simple_type': 'function'}, |
| 365 | 'optional': callback.optional, |
| 366 | 'parameters': [] |
| 367 | } |
| 368 | for param in callback.params: |
| 369 | callback_dict['parameters'].append(self._GenerateProperty(param)) |
| 370 | if (len(callback_dict['parameters']) > 0): |
| 371 | callback_dict['parameters'][-1]['last'] = True |
| 372 | return callback_dict |
| 373 | |
| 374 | def _GenerateProperties(self, properties): |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 375 | return [self._GenerateProperty(v) for v in properties.values()] |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 376 | |
| 377 | def _GenerateProperty(self, property_): |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 378 | if not hasattr(property_, 'type_'): |
| 379 | for d in dir(property_): |
| 380 | if not d.startswith('_'): |
| 381 | print ('%s -> %s' % (d, getattr(property_, d))) |
| 382 | type_ = property_.type_ |
| 383 | |
| 384 | # Make sure we generate property info for arrays, too. |
| 385 | # TODO(kalman): what about choices? |
| 386 | if type_.property_type == model.PropertyType.ARRAY: |
| 387 | properties = type_.item_type.properties |
| 388 | else: |
| 389 | properties = type_.properties |
| 390 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 391 | property_dict = { |
| 392 | 'name': property_.simple_name, |
| 393 | 'optional': property_.optional, |
| 394 | 'description': self._FormatDescription(property_.description), |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 395 | 'properties': self._GenerateProperties(type_.properties), |
| 396 | 'functions': self._GenerateFunctions(type_.functions), |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 397 | 'parameters': [], |
| 398 | 'returns': None, |
| 399 | 'id': _CreateId(property_, 'property') |
| 400 | } |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 401 | |
| 402 | if type_.property_type == model.PropertyType.FUNCTION: |
| 403 | function = type_.function |
| 404 | for param in function.params: |
| 405 | property_dict['parameters'].append(self._GenerateProperty(param)) |
| 406 | if function.returns: |
| 407 | property_dict['returns'] = self._GenerateType(function.returns) |
| 408 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 409 | if (property_.parent is not None and |
| 410 | not isinstance(property_.parent, model.Namespace)): |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 411 | property_dict['parentName'] = property_.parent.simple_name |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 412 | |
| 413 | value = property_.value |
| 414 | if value is not None: |
| 415 | if isinstance(value, int): |
| 416 | property_dict['value'] = _FormatValue(value) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 417 | else: |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 418 | property_dict['value'] = value |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 419 | else: |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 420 | self._RenderTypeInformation(type_, property_dict) |
| 421 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 422 | return property_dict |
| 423 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 424 | def _GenerateCallbackProperty(self, callback): |
| 425 | property_dict = { |
| 426 | 'name': callback.simple_name, |
| 427 | 'description': self._FormatDescription(callback.description), |
| 428 | 'optional': callback.optional, |
| 429 | 'id': _CreateId(callback, 'property'), |
| 430 | 'simple_type': 'function', |
| 431 | } |
| 432 | if (callback.parent is not None and |
| 433 | not isinstance(callback.parent, model.Namespace)): |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 434 | property_dict['parentName'] = callback.parent.simple_name |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 435 | return property_dict |
| 436 | |
| 437 | def _RenderTypeInformation(self, type_, dst_dict): |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 438 | dst_dict['is_object'] = type_.property_type == model.PropertyType.OBJECT |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 439 | if type_.property_type == model.PropertyType.CHOICES: |
| 440 | dst_dict['choices'] = self._GenerateTypes(type_.choices) |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 441 | # We keep track of which == last for knowing when to add "or" between |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 442 | # choices in templates. |
| 443 | if len(dst_dict['choices']) > 0: |
| 444 | dst_dict['choices'][-1]['last'] = True |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 445 | elif type_.property_type == model.PropertyType.REF: |
| 446 | dst_dict['link'] = self._GetLink(type_.ref_type) |
| 447 | elif type_.property_type == model.PropertyType.ARRAY: |
| 448 | dst_dict['array'] = self._GenerateType(type_.item_type) |
| 449 | elif type_.property_type == model.PropertyType.ENUM: |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 450 | dst_dict['enum_values'] = [] |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 451 | for enum_value in type_.enum_values: |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 452 | dst_dict['enum_values'].append({'name': enum_value}) |
| 453 | if len(dst_dict['enum_values']) > 0: |
| 454 | dst_dict['enum_values'][-1]['last'] = True |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 455 | elif type_.instance_of is not None: |
| 456 | dst_dict['simple_type'] = type_.instance_of.lower() |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 457 | else: |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 458 | dst_dict['simple_type'] = type_.property_type.name.lower() |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 459 | |
| 460 | class _LazySamplesGetter(object): |
| 461 | """This class is needed so that an extensions API page does not have to fetch |
| 462 | the apps samples page and vice versa. |
| 463 | """ |
| 464 | def __init__(self, api_name, samples): |
| 465 | self._api_name = api_name |
| 466 | self._samples = samples |
| 467 | |
| 468 | def get(self, key): |
| 469 | return self._samples.FilterSamples(key, self._api_name) |
| 470 | |
| 471 | class APIDataSource(object): |
| 472 | """This class fetches and loads JSON APIs from the FileSystem passed in with |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 473 | |compiled_fs_factory|, so the APIs can be plugged into templates. |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 474 | """ |
| 475 | class Factory(object): |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 476 | def __init__(self, |
| 477 | compiled_fs_factory, |
| 478 | base_path, |
| 479 | availability_finder_factory): |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 480 | def create_compiled_fs(fn, category): |
| 481 | return compiled_fs_factory.Create(fn, APIDataSource, category=category) |
| 482 | |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 483 | self._json_cache = create_compiled_fs( |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 484 | lambda api_name, api: self._LoadJsonAPI(api, False), |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 485 | 'json') |
| 486 | self._idl_cache = create_compiled_fs( |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 487 | lambda api_name, api: self._LoadIdlAPI(api, False), |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 488 | 'idl') |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 489 | |
| 490 | # These caches are used if an APIDataSource does not want to resolve the |
| 491 | # $refs in an API. This is needed to prevent infinite recursion in |
| 492 | # ReferenceResolver. |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 493 | self._json_cache_no_refs = create_compiled_fs( |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 494 | lambda api_name, api: self._LoadJsonAPI(api, True), |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 495 | 'json-no-refs') |
| 496 | self._idl_cache_no_refs = create_compiled_fs( |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 497 | lambda api_name, api: self._LoadIdlAPI(api, True), |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 498 | 'idl-no-refs') |
| 499 | |
| 500 | self._idl_names_cache = create_compiled_fs(self._GetIDLNames, 'idl-names') |
| 501 | self._names_cache = create_compiled_fs(self._GetAllNames, 'names') |
| 502 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 503 | self._base_path = base_path |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 504 | self._availability_finder = availability_finder_factory.Create() |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 505 | self._parse_cache = create_compiled_fs( |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 506 | lambda _, json: json_parse.Parse(json), |
| 507 | 'intro-cache') |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 508 | # These must be set later via the SetFooDataSourceFactory methods. |
| 509 | self._ref_resolver_factory = None |
| 510 | self._samples_data_source_factory = None |
| 511 | |
| 512 | def SetSamplesDataSourceFactory(self, samples_data_source_factory): |
| 513 | self._samples_data_source_factory = samples_data_source_factory |
| 514 | |
| 515 | def SetReferenceResolverFactory(self, ref_resolver_factory): |
| 516 | self._ref_resolver_factory = ref_resolver_factory |
| 517 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 518 | def SetTemplateDataSource(self, template_data_source_factory): |
| 519 | # This TemplateDataSource is only being used for fetching template data. |
| 520 | self._template_data_source = template_data_source_factory.Create(None, '') |
| 521 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 522 | def Create(self, request, disable_refs=False): |
| 523 | """Create an APIDataSource. |disable_refs| specifies whether $ref's in |
| 524 | APIs being processed by the |ToDict| method of _JSCModel follows $ref's |
| 525 | in the API. This prevents endless recursion in ReferenceResolver. |
| 526 | """ |
| 527 | if self._samples_data_source_factory is None: |
| 528 | # Only error if there is a request, which means this APIDataSource is |
| 529 | # actually being used to render a page. |
| 530 | if request is not None: |
| 531 | logging.error('SamplesDataSource.Factory was never set in ' |
| 532 | 'APIDataSource.Factory.') |
| 533 | samples = None |
| 534 | else: |
| 535 | samples = self._samples_data_source_factory.Create(request) |
| 536 | if not disable_refs and self._ref_resolver_factory is None: |
| 537 | logging.error('ReferenceResolver.Factory was never set in ' |
| 538 | 'APIDataSource.Factory.') |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 539 | return APIDataSource(self._json_cache, |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 540 | self._idl_cache, |
| 541 | self._json_cache_no_refs, |
| 542 | self._idl_cache_no_refs, |
| 543 | self._names_cache, |
| 544 | self._idl_names_cache, |
| 545 | self._base_path, |
| 546 | samples, |
| 547 | disable_refs) |
| 548 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 549 | def _LoadJsonAPI(self, api, disable_refs): |
| 550 | return _JSCModel( |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 551 | json_parse.Parse(api)[0], |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 552 | self._ref_resolver_factory.Create() if not disable_refs else None, |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 553 | disable_refs, |
| 554 | self._availability_finder, |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 555 | self._parse_cache, |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 556 | self._template_data_source).ToDict() |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 557 | |
| 558 | def _LoadIdlAPI(self, api, disable_refs): |
| 559 | idl = idl_parser.IDLParser().ParseData(api) |
| 560 | return _JSCModel( |
| 561 | idl_schema.IDLSchema(idl).process()[0], |
| 562 | self._ref_resolver_factory.Create() if not disable_refs else None, |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 563 | disable_refs, |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 564 | self._availability_finder, |
Ben Murdoch | 558790d | 2013-07-30 15:19:42 +0100 | [diff] [blame] | 565 | self._parse_cache, |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 566 | self._template_data_source, |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 567 | idl=True).ToDict() |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 568 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 569 | def _GetIDLNames(self, base_dir, apis): |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 570 | return self._GetExtNames(apis, ['idl']) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 571 | |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 572 | def _GetAllNames(self, base_dir, apis): |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 573 | return self._GetExtNames(apis, ['json', 'idl']) |
| 574 | |
| 575 | def _GetExtNames(self, apis, exts): |
| 576 | return [model.UnixName(os.path.splitext(api)[0]) for api in apis |
| 577 | if os.path.splitext(api)[1][1:] in exts] |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 578 | |
| 579 | def __init__(self, |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 580 | json_cache, |
| 581 | idl_cache, |
| 582 | json_cache_no_refs, |
| 583 | idl_cache_no_refs, |
| 584 | names_cache, |
| 585 | idl_names_cache, |
| 586 | base_path, |
| 587 | samples, |
| 588 | disable_refs): |
| 589 | self._base_path = base_path |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 590 | self._json_cache = json_cache |
| 591 | self._idl_cache = idl_cache |
| 592 | self._json_cache_no_refs = json_cache_no_refs |
| 593 | self._idl_cache_no_refs = idl_cache_no_refs |
| 594 | self._names_cache = names_cache |
| 595 | self._idl_names_cache = idl_names_cache |
| 596 | self._samples = samples |
| 597 | self._disable_refs = disable_refs |
| 598 | |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 599 | def _GenerateHandlebarContext(self, handlebar_dict, path): |
Torne (Richard Coles) | 2a99a7e | 2013-03-28 15:31:22 +0000 | [diff] [blame] | 600 | handlebar_dict['samples'] = _LazySamplesGetter(path, self._samples) |
| 601 | return handlebar_dict |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 602 | |
| 603 | def _GetAsSubdirectory(self, name): |
| 604 | if name.startswith('experimental_'): |
| 605 | parts = name[len('experimental_'):].split('_', 1) |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 606 | if len(parts) > 1: |
| 607 | parts[1] = 'experimental_%s' % parts[1] |
| 608 | return '/'.join(parts) |
| 609 | return '%s/%s' % (parts[0], name) |
Torne (Richard Coles) | 5821806 | 2012-11-14 11:43:16 +0000 | [diff] [blame] | 610 | return name.replace('_', '/', 1) |
| 611 | |
| 612 | def get(self, key): |
| 613 | if key.endswith('.html') or key.endswith('.json') or key.endswith('.idl'): |
| 614 | path, ext = os.path.splitext(key) |
| 615 | else: |
| 616 | path = key |
| 617 | unix_name = model.UnixName(path) |
| 618 | idl_names = self._idl_names_cache.GetFromFileListing(self._base_path) |
| 619 | names = self._names_cache.GetFromFileListing(self._base_path) |
| 620 | if unix_name not in names and self._GetAsSubdirectory(unix_name) in names: |
| 621 | unix_name = self._GetAsSubdirectory(unix_name) |
| 622 | |
| 623 | if self._disable_refs: |
| 624 | cache, ext = ( |
| 625 | (self._idl_cache_no_refs, '.idl') if (unix_name in idl_names) else |
| 626 | (self._json_cache_no_refs, '.json')) |
| 627 | else: |
| 628 | cache, ext = ((self._idl_cache, '.idl') if (unix_name in idl_names) else |
| 629 | (self._json_cache, '.json')) |
| 630 | return self._GenerateHandlebarContext( |
| 631 | cache.GetFromFile('%s/%s%s' % (self._base_path, unix_name, ext)), |
| 632 | path) |