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