api_models.py revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1# Copyright 2013 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 posixpath
6
7from compiled_file_system import SingleFile, Unicode
8from extensions_paths import API_PATHS
9from file_system import FileNotFoundError
10from future import Collect, Future
11from schema_util import ProcessSchema
12from third_party.json_schema_compiler.model import Namespace, UnixName
13
14
15@SingleFile
16@Unicode
17def _CreateAPIModel(path, data):
18  schema = ProcessSchema(path, data)[0]
19  if not schema:
20    raise FileNotFoundError('No schema for %s' % path)
21  return Namespace(schema, schema['namespace'])
22
23
24class APIModels(object):
25  '''Tracks APIs and their Models.
26  '''
27
28  def __init__(self, features_bundle, compiled_fs_factory, file_system):
29    self._features_bundle = features_bundle
30    self._model_cache = compiled_fs_factory.Create(
31        file_system, _CreateAPIModel, APIModels)
32
33  def GetNames(self):
34    # API names appear alongside some of their methods/events/etc in the
35    # features file. APIs are those which either implicitly or explicitly have
36    # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are
37    # APIs; runtime.onConnectNative is not).
38    api_features = self._features_bundle.GetAPIFeatures().Get()
39    return [name for name, feature in api_features.iteritems()
40            if ('.' not in name or
41                name.rsplit('.', 1)[0] not in api_features or
42                feature.get('noparent'))]
43
44  def GetModel(self, api_name):
45    # By default |api_name| is assumed to be given without a path or extension,
46    # so combinations of known paths and extension types will be searched.
47    api_extensions = ('.json', '.idl')
48    api_paths = API_PATHS
49
50    # Callers sometimes include a file extension and/or prefix path with the
51    # |api_name| argument. We believe them and narrow the search space
52    # accordingly.
53    name, ext = posixpath.splitext(api_name)
54    if ext in api_extensions:
55      api_extensions = (ext,)
56      api_name = name
57    for api_path in api_paths:
58      if api_name.startswith(api_path):
59        api_name = api_name[len(api_path):]
60        api_paths = (api_path,)
61        break
62
63    # API names are given as declarativeContent and app.window but file names
64    # will be declarative_content and app_window.
65    file_name = UnixName(api_name).replace('.', '_')
66    # Devtools APIs are in API/devtools/ not API/, and have their
67    # "devtools" names removed from the file names.
68    basename = posixpath.basename(file_name)
69    if 'devtools_' in basename:
70      file_name = posixpath.join(
71          'devtools', file_name.replace(basename,
72                                        basename.replace('devtools_' , '')))
73
74    futures = [self._model_cache.GetFromFile(
75                   posixpath.join(path, '%s%s' % (file_name, ext)))
76               for ext in api_extensions
77               for path in api_paths]
78    def resolve():
79      for future in futures:
80        try:
81          return future.Get()
82        except FileNotFoundError: pass
83      # Propagate the first FileNotFoundError if neither were found.
84      futures[0].Get()
85    return Future(callback=resolve)
86
87  def Cron(self):
88    futures = [self.GetModel(name) for name in self.GetNames()]
89    return Collect(futures, except_pass=FileNotFoundError)
90
91  def IterModels(self):
92    future_models = [(name, self.GetModel(name)) for name in self.GetNames()]
93    for name, future_model in future_models:
94      try:
95        model = future_model.Get()
96      except FileNotFoundError:
97        continue
98      if model:
99        yield name, model
100