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