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 excluded_scripts=None): 113 assert bool(module_name) ^ bool(module_filename), ( 114 'Must provide either module_name or module_filename.') 115 if module_filename: 116 resource = self.FindResource(module_filename) 117 if not resource: 118 raise Exception('Could not find %s in %s' % ( 119 module_filename, repr(self.source_paths))) 120 module_name = resource.name 121 else: 122 resource = None # Will be set if we end up needing to load. 123 124 if module_name in self.loaded_modules: 125 assert self.loaded_modules[module_name].contents 126 return self.loaded_modules[module_name] 127 128 if not resource: # happens when module_name was given 129 resource = self.FindModuleResource(module_name) 130 if not resource: 131 raise module.DepsException('No resource for module "%s"' % module_name) 132 133 m = html_module.HTMLModule(self, module_name, resource) 134 self.loaded_modules[module_name] = m 135 136 # Fake it, this is probably either polymer.min.js or platform.js which are 137 # actually .js files.... 138 if resource.absolute_path.endswith('.js'): 139 return m 140 141 m.Parse(excluded_scripts) 142 m.Load(excluded_scripts) 143 return m 144 145 def LoadRawScript(self, relative_raw_script_path): 146 resource = None 147 for source_path in self.source_paths: 148 possible_absolute_path = os.path.join( 149 source_path, os.path.normpath(relative_raw_script_path)) 150 if os.path.exists(possible_absolute_path): 151 resource = resource_module.Resource( 152 source_path, possible_absolute_path) 153 break 154 if not resource: 155 raise module.DepsException( 156 'Could not find a file for raw script %s in %s' % 157 (relative_raw_script_path, self.source_paths)) 158 assert relative_raw_script_path == resource.unix_style_relative_path, ( 159 'Expected %s == %s' % (relative_raw_script_path, 160 resource.unix_style_relative_path)) 161 162 if resource.absolute_path in self.loaded_raw_scripts: 163 return self.loaded_raw_scripts[resource.absolute_path] 164 165 raw_script = module.RawScript(resource) 166 self.loaded_raw_scripts[resource.absolute_path] = raw_script 167 return raw_script 168 169 def LoadStyleSheet(self, name): 170 if name in self.loaded_style_sheets: 171 return self.loaded_style_sheets[name] 172 173 resource = self._FindResourceGivenNameAndSuffix( 174 name, '.css', return_resource=True) 175 if not resource: 176 raise module.DepsException( 177 'Could not find a file for stylesheet %s' % name) 178 179 style_sheet = style_sheet_module.StyleSheet(self, name, resource) 180 style_sheet.load() 181 self.loaded_style_sheets[name] = style_sheet 182 return style_sheet 183 184 def LoadImage(self, abs_path): 185 if abs_path in self.loaded_images: 186 return self.loaded_images[abs_path] 187 188 if not os.path.exists(abs_path): 189 raise module.DepsException("url('%s') did not exist" % abs_path) 190 191 res = self.FindResourceGivenAbsolutePath(abs_path, binary=True) 192 if res is None: 193 raise module.DepsException("url('%s') was not in search path" % abs_path) 194 195 image = style_sheet_module.Image(res) 196 self.loaded_images[abs_path] = image 197 return image 198 199 def GetStrippedJSForFilename(self, filename, early_out_if_no_py_vulcanize): 200 if filename in self.stripped_js_by_filename: 201 return self.stripped_js_by_filename[filename] 202 203 with open(filename, 'r') as f: 204 contents = f.read(4096) 205 if early_out_if_no_py_vulcanize and ('py_vulcanize' not in contents): 206 return None 207 208 s = strip_js_comments.StripJSComments(contents) 209 self.stripped_js_by_filename[filename] = s 210 return s 211 212 213def _read_file(absolute_path): 214 """Reads a file and returns a (path, contents) pair. 215 216 Args: 217 absolute_path: Absolute path to a file. 218 219 Raises: 220 Exception: The given file doesn't exist. 221 IOError: There was a problem opening or reading the file. 222 """ 223 if not os.path.exists(absolute_path): 224 raise Exception('%s not found.' % absolute_path) 225 f = codecs.open(absolute_path, mode='r', encoding='utf-8') 226 contents = f.read() 227 f.close() 228 return absolute_path, contents 229