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