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