1# Copyright (c) 2014 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"""ResourceFinder is a helper class for finding resources given their name.""" 6 7import codecs 8import os 9 10from tvcm import module 11from tvcm import style_sheet as style_sheet_module 12from tvcm import resource as resource_module 13from tvcm import html_module 14from tvcm import strip_js_comments 15 16 17class ResourceLoader(object): 18 """Manges loading modules and their dependencies from files. 19 20 Modules handle parsing and the construction of their individual dependency 21 pointers. The loader deals with bookkeeping of what has been loaded, and 22 mapping names to file resources. 23 """ 24 def __init__(self, project): 25 self.project = project 26 self.stripped_js_by_filename = {} 27 self.loaded_modules = {} 28 self.loaded_raw_scripts = {} 29 self.loaded_style_sheets = {} 30 self.loaded_images = {} 31 32 @property 33 def source_paths(self): 34 """A list of base directories to search for modules under.""" 35 return self.project.source_paths 36 37 def FindResource(self, some_path, binary=False): 38 """Finds a Resource for the given path. 39 40 Args: 41 some_path: A relative or absolute path to a file. 42 43 Returns: 44 A Resource or None. 45 """ 46 if os.path.isabs(some_path): 47 return self.FindResourceGivenAbsolutePath(some_path, binary) 48 else: 49 return self.FindResourceGivenRelativePath(some_path, binary) 50 51 def FindResourceGivenAbsolutePath(self, absolute_path, binary=False): 52 """Returns a Resource for the given absolute path.""" 53 candidate_paths = [] 54 for source_path in self.source_paths: 55 if absolute_path.startswith(source_path): 56 candidate_paths.append(source_path) 57 if len(candidate_paths) == 0: 58 return None 59 60 # Sort by length. Longest match wins. 61 candidate_paths.sort(lambda x, y: len(x) - len(y)) 62 longest_candidate = candidate_paths[-1] 63 return resource_module.Resource(longest_candidate, absolute_path, binary) 64 65 def FindResourceGivenRelativePath(self, relative_path, binary=False): 66 """Returns a Resource for the given relative path.""" 67 absolute_path = None 68 for script_path in self.source_paths: 69 absolute_path = os.path.join(script_path, relative_path) 70 if os.path.exists(absolute_path): 71 return resource_module.Resource(script_path, absolute_path, binary) 72 return None 73 74 def _FindResourceGivenNameAndSuffix( 75 self, requested_name, extension, return_resource=False): 76 """Searches for a file and reads its contents. 77 78 Args: 79 requested_name: The name of the resource that was requested. 80 extension: The extension for this requested resource. 81 82 Returns: 83 A (path, contents) pair. 84 """ 85 pathy_name = requested_name.replace('.', os.sep) 86 filename = pathy_name + extension 87 88 resource = self.FindResourceGivenRelativePath(filename) 89 if return_resource: 90 return resource 91 if not resource: 92 return None, None 93 return _read_file(resource.absolute_path) 94 95 def FindModuleResource(self, requested_module_name): 96 """Finds a module javascript file and returns a Resource, or none.""" 97 js_resource = self._FindResourceGivenNameAndSuffix( 98 requested_module_name, '.js', return_resource=True) 99 html_resource = self._FindResourceGivenNameAndSuffix( 100 requested_module_name, '.html', return_resource=True) 101 if js_resource and html_resource: 102 if html_module.IsHTMLResourceTheModuleGivenConflictingResourceNames( 103 js_resource, html_resource): 104 return html_resource 105 return js_resource 106 elif js_resource: 107 return js_resource 108 return html_resource 109 110 def LoadModule(self, module_name=None, module_filename=None): 111 assert bool(module_name) ^ bool(module_filename), ( 112 'Must provide either module_name or module_filename.') 113 if module_filename: 114 resource = self.FindResource(module_filename) 115 if not resource: 116 raise Exception('Could not find %s in %s' % ( 117 module_filename, repr(self.source_paths))) 118 module_name = resource.name 119 else: 120 resource = None # Will be set if we end up needing to load. 121 122 if module_name in self.loaded_modules: 123 assert self.loaded_modules[module_name].contents 124 return self.loaded_modules[module_name] 125 126 if not resource: # happens when module_name was given 127 resource = self.FindModuleResource(module_name) 128 if not resource: 129 raise module.DepsException('No resource for module "%s"' % module_name) 130 131 m = html_module.HTMLModule(self, module_name, resource) 132 self.loaded_modules[module_name] = m 133 134 # Fake it, this is probably either polymer.min.js or platform.js which are 135 # actually .js files.... 136 if resource.absolute_path.endswith('.js'): 137 return m 138 139 m.Parse() 140 m.Load() 141 return m 142 143 def LoadRawScript(self, relative_raw_script_path): 144 resource = None 145 for source_path in self.source_paths: 146 possible_absolute_path = os.path.join( 147 source_path, os.path.normpath(relative_raw_script_path)) 148 if os.path.exists(possible_absolute_path): 149 resource = resource_module.Resource(source_path, possible_absolute_path) 150 break 151 if not resource: 152 raise module.DepsException( 153 'Could not find a file for raw script %s in %s' % 154 (relative_raw_script_path, self.source_paths)) 155 assert relative_raw_script_path == resource.unix_style_relative_path, ( 156 'Expected %s == %s' % (relative_raw_script_path, 157 resource.unix_style_relative_path)) 158 159 if resource.absolute_path in self.loaded_raw_scripts: 160 return self.loaded_raw_scripts[resource.absolute_path] 161 162 raw_script = module.RawScript(resource) 163 self.loaded_raw_scripts[resource.absolute_path] = raw_script 164 return raw_script 165 166 def LoadStyleSheet(self, name): 167 if name in self.loaded_style_sheets: 168 return self.loaded_style_sheets[name] 169 170 resource = self._FindResourceGivenNameAndSuffix( 171 name, '.css', return_resource=True) 172 if not resource: 173 raise module.DepsException( 174 'Could not find a file for stylesheet %s' % name) 175 176 style_sheet = style_sheet_module.StyleSheet(self, name, resource) 177 style_sheet.load() 178 self.loaded_style_sheets[name] = style_sheet 179 return style_sheet 180 181 def LoadImage(self, abs_path): 182 if abs_path in self.loaded_images: 183 return self.loaded_images[abs_path] 184 185 if not os.path.exists(abs_path): 186 raise module.DepsException("url('%s') did not exist" % abs_path) 187 188 res = self.FindResourceGivenAbsolutePath(abs_path, binary=True) 189 if res is None: 190 raise module.DepsException("url('%s') was not in search path" % abs_path) 191 192 image = style_sheet_module.Image(res) 193 self.loaded_images[abs_path] = image 194 return image 195 196 def GetStrippedJSForFilename(self, filename, early_out_if_no_tvcm): 197 if filename in self.stripped_js_by_filename: 198 return self.stripped_js_by_filename[filename] 199 200 with open(filename, 'r') as f: 201 contents = f.read(4096) 202 if early_out_if_no_tvcm and ('tvcm' not in contents): 203 return None 204 205 s = strip_js_comments.StripJSComments(contents) 206 self.stripped_js_by_filename[filename] = s 207 return s 208 209 210def _read_file(absolute_path): 211 """Reads a file and returns a (path, contents) pair. 212 213 Args: 214 absolute_path: Absolute path to a file. 215 216 Raises: 217 Exception: The given file doesn't exist. 218 IOError: There was a problem opening or reading the file. 219 """ 220 if not os.path.exists(absolute_path): 221 raise Exception('%s not found.' % absolute_path) 222 f = codecs.open(absolute_path, mode='r', encoding='utf-8') 223 contents = f.read() 224 f.close() 225 return absolute_path, contents 226