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