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 5"""This module contains the Module class and other classes for resources. 6 7The Module class represents a module in the trace viewer system. A module has 8a name, and may require a variety of other resources, such as stylesheets, 9template objects, raw JavaScript, or other modules. 10 11Other resources include HTML templates, raw JavaScript files, and stylesheets. 12""" 13 14import os 15import inspect 16import codecs 17 18from py_vulcanize import js_utils 19 20 21class DepsException(Exception): 22 """Exceptions related to module dependency resolution.""" 23 24 def __init__(self, fmt, *args): 25 from py_vulcanize import style_sheet as style_sheet_module 26 context = [] 27 frame = inspect.currentframe() 28 while frame: 29 frame_locals = frame.f_locals 30 31 module_name = None 32 if 'self' in frame_locals: 33 s = frame_locals['self'] 34 if isinstance(s, Module): 35 module_name = s.name 36 if isinstance(s, style_sheet_module.StyleSheet): 37 module_name = s.name + '.css' 38 if not module_name: 39 if 'module' in frame_locals: 40 module = frame_locals['module'] 41 if isinstance(s, Module): 42 module_name = module.name 43 elif 'm' in frame_locals: 44 module = frame_locals['m'] 45 if isinstance(s, Module): 46 module_name = module.name 47 48 if module_name: 49 if len(context): 50 if context[-1] != module_name: 51 context.append(module_name) 52 else: 53 context.append(module_name) 54 55 frame = frame.f_back 56 57 context.reverse() 58 self.context = context 59 context_str = '\n'.join(' %s' % x for x in context) 60 Exception.__init__( 61 self, 'While loading:\n%s\nGot: %s' % (context_str, (fmt % args))) 62 63 64class ModuleDependencyMetadata(object): 65 66 def __init__(self): 67 self.dependent_module_names = [] 68 self.dependent_raw_script_relative_paths = [] 69 self.style_sheet_names = [] 70 71 def AppendMetdata(self, other): 72 self.dependent_module_names += other.dependent_module_names 73 self.dependent_raw_script_relative_paths += \ 74 other.dependent_raw_script_relative_paths 75 self.style_sheet_names += other.style_sheet_names 76 77 78_next_module_id = 1 79 80 81class Module(object): 82 """Represents a JavaScript module. 83 84 Interesting properties include: 85 name: Module name, may include a namespace, e.g. 'py_vulcanize.foo'. 86 filename: The filename of the actual module. 87 contents: The text contents of the module. 88 dependent_modules: Other modules that this module depends on. 89 90 In addition to these properties, a Module also contains lists of other 91 resources that it depends on. 92 """ 93 94 def __init__(self, loader, name, resource, load_resource=True): 95 assert isinstance(name, basestring), 'Got %s instead' % repr(name) 96 97 global _next_module_id 98 self._id = _next_module_id 99 _next_module_id += 1 100 101 self.loader = loader 102 self.name = name 103 self.resource = resource 104 105 if load_resource: 106 f = codecs.open(self.filename, mode='r', encoding='utf-8') 107 self.contents = f.read() 108 f.close() 109 else: 110 self.contents = None 111 112 # Dependency metadata, set up during Parse(). 113 self.dependency_metadata = None 114 115 # Actual dependencies, set up during load(). 116 self.dependent_modules = [] 117 self.dependent_raw_scripts = [] 118 self.scripts = [] 119 self.style_sheets = [] 120 121 # Caches. 122 self._all_dependent_modules_recursive = None 123 124 def __repr__(self): 125 return '%s(%s)' % (self.__class__.__name__, self.name) 126 127 @property 128 def id(self): 129 return self._id 130 131 @property 132 def filename(self): 133 return self.resource.absolute_path 134 135 def IsThirdPartyComponent(self): 136 """Checks whether this module is a third-party Polymer component.""" 137 if os.path.join('third_party', 'components') in self.filename: 138 return True 139 if os.path.join('third_party', 'polymer', 'components') in self.filename: 140 return True 141 return False 142 143 def Parse(self, excluded_scripts): 144 """Parses self.contents and fills in the module's dependency metadata.""" 145 raise NotImplementedError() 146 147 def GetTVCMDepsModuleType(self): 148 """Returns the py_vulcanize.setModuleInfo type for this module""" 149 raise NotImplementedError() 150 151 def AppendJSContentsToFile(self, 152 f, 153 use_include_tags_for_scripts, 154 dir_for_include_tag_root): 155 """Appends the js for this module to the provided file.""" 156 for script in self.scripts: 157 script.AppendJSContentsToFile(f, use_include_tags_for_scripts, 158 dir_for_include_tag_root) 159 160 def AppendHTMLContentsToFile(self, f, ctl, minify=False): 161 """Appends the HTML for this module [without links] to the provided file.""" 162 pass 163 164 def Load(self, excluded_scripts=None): 165 """Loads the sub-resources that this module depends on from its dependency 166 metadata. 167 168 Raises: 169 DepsException: There was a problem finding one of the dependencies. 170 Exception: There was a problem parsing a module that this one depends on. 171 """ 172 assert self.name, 'Module name must be set before dep resolution.' 173 assert self.filename, 'Module filename must be set before dep resolution.' 174 assert self.name in self.loader.loaded_modules, ( 175 'Module must be registered in resource loader before loading.') 176 177 metadata = self.dependency_metadata 178 for name in metadata.dependent_module_names: 179 module = self.loader.LoadModule(module_name=name, 180 excluded_scripts=excluded_scripts) 181 self.dependent_modules.append(module) 182 183 for name in metadata.style_sheet_names: 184 style_sheet = self.loader.LoadStyleSheet(name) 185 self.style_sheets.append(style_sheet) 186 187 @property 188 def all_dependent_modules_recursive(self): 189 if self._all_dependent_modules_recursive: 190 return self._all_dependent_modules_recursive 191 192 self._all_dependent_modules_recursive = set(self.dependent_modules) 193 for dependent_module in self.dependent_modules: 194 self._all_dependent_modules_recursive.update( 195 dependent_module.all_dependent_modules_recursive) 196 return self._all_dependent_modules_recursive 197 198 def ComputeLoadSequenceRecursive(self, load_sequence, already_loaded_set, 199 depth=0): 200 """Recursively builds up a load sequence list. 201 202 Args: 203 load_sequence: A list which will be incrementally built up. 204 already_loaded_set: A set of modules that has already been added to the 205 load sequence list. 206 depth: The depth of recursion. If it too deep, that indicates a loop. 207 """ 208 if depth > 32: 209 raise Exception('Include loop detected on %s', self.name) 210 for dependent_module in self.dependent_modules: 211 if dependent_module.name in already_loaded_set: 212 continue 213 dependent_module.ComputeLoadSequenceRecursive( 214 load_sequence, already_loaded_set, depth + 1) 215 if self.name not in already_loaded_set: 216 already_loaded_set.add(self.name) 217 load_sequence.append(self) 218 219 def GetAllDependentFilenamesRecursive(self, include_raw_scripts=True): 220 dependent_filenames = [] 221 222 visited_modules = set() 223 224 def Get(module): 225 module.AppendDirectlyDependentFilenamesTo( 226 dependent_filenames, include_raw_scripts) 227 visited_modules.add(module) 228 for m in module.dependent_modules: 229 if m in visited_modules: 230 continue 231 Get(m) 232 233 Get(self) 234 return dependent_filenames 235 236 def AppendDirectlyDependentFilenamesTo( 237 self, dependent_filenames, include_raw_scripts=True): 238 dependent_filenames.append(self.resource.absolute_path) 239 if include_raw_scripts: 240 for raw_script in self.dependent_raw_scripts: 241 dependent_filenames.append(raw_script.resource.absolute_path) 242 for style_sheet in self.style_sheets: 243 style_sheet.AppendDirectlyDependentFilenamesTo(dependent_filenames) 244 245 246class RawScript(object): 247 """Represents a raw script resource referenced by a module via the 248 py_vulcanize.requireRawScript(xxx) directive.""" 249 250 def __init__(self, resource): 251 self.resource = resource 252 253 @property 254 def filename(self): 255 return self.resource.absolute_path 256 257 @property 258 def contents(self): 259 return self.resource.contents 260 261 def __repr__(self): 262 return 'RawScript(%s)' % self.filename 263