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.style_sheets = [] 119 120 # Caches. 121 self._all_dependent_modules_recursive = None 122 123 def __repr__(self): 124 return '%s(%s)' % (self.__class__.__name__, self.name) 125 126 @property 127 def id(self): 128 return self._id 129 130 @property 131 def filename(self): 132 return self.resource.absolute_path 133 134 def IsThirdPartyComponent(self): 135 """Checks whether this module is a third-party Polymer component.""" 136 if os.path.join('third_party', 'components') in self.filename: 137 return True 138 if os.path.join('third_party', 'polymer', 'components') in self.filename: 139 return True 140 return False 141 142 def Parse(self): 143 """Parses self.contents and fills in the module's dependency metadata.""" 144 raise NotImplementedError() 145 146 def GetTVCMDepsModuleType(self): 147 """Returns the py_vulcanize.setModuleInfo type for this module""" 148 raise NotImplementedError() 149 150 def AppendJSContentsToFile(self, 151 f, 152 use_include_tags_for_scripts, 153 dir_for_include_tag_root): 154 """Appends the js for this module to the provided file.""" 155 for dependent_raw_script in self.dependent_raw_scripts: 156 if use_include_tags_for_scripts: 157 rel_filename = os.path.relpath(dependent_raw_script.filename, 158 dir_for_include_tag_root) 159 f.write("""<include src="%s">\n""" % rel_filename) 160 else: 161 f.write(js_utils.EscapeJSIfNeeded(dependent_raw_script.contents)) 162 f.write('\n') 163 164 def AppendHTMLContentsToFile(self, f, ctl, minify=False): 165 """Appends the HTML for this module [without links] to the provided file.""" 166 pass 167 168 def Load(self): 169 """Loads the sub-resources that this module depends on from its dependency 170 metadata. 171 172 Raises: 173 DepsException: There was a problem finding one of the dependencies. 174 Exception: There was a problem parsing a module that this one depends on. 175 """ 176 assert self.name, 'Module name must be set before dep resolution.' 177 assert self.filename, 'Module filename must be set before dep resolution.' 178 assert self.name in self.loader.loaded_modules, ( 179 'Module must be registered in resource loader before loading.') 180 181 metadata = self.dependency_metadata 182 for name in metadata.dependent_module_names: 183 module = self.loader.LoadModule(module_name=name) 184 self.dependent_modules.append(module) 185 186 for path in metadata.dependent_raw_script_relative_paths: 187 raw_script = self.loader.LoadRawScript(path) 188 self.dependent_raw_scripts.append(raw_script) 189 190 for name in metadata.style_sheet_names: 191 style_sheet = self.loader.LoadStyleSheet(name) 192 self.style_sheets.append(style_sheet) 193 194 @property 195 def all_dependent_modules_recursive(self): 196 if self._all_dependent_modules_recursive: 197 return self._all_dependent_modules_recursive 198 199 self._all_dependent_modules_recursive = set(self.dependent_modules) 200 for dependent_module in self.dependent_modules: 201 self._all_dependent_modules_recursive.update( 202 dependent_module.all_dependent_modules_recursive) 203 return self._all_dependent_modules_recursive 204 205 def ComputeLoadSequenceRecursive(self, load_sequence, already_loaded_set, 206 depth=0): 207 """Recursively builds up a load sequence list. 208 209 Args: 210 load_sequence: A list which will be incrementally built up. 211 already_loaded_set: A set of modules that has already been added to the 212 load sequence list. 213 depth: The depth of recursion. If it too deep, that indicates a loop. 214 """ 215 if depth > 32: 216 raise Exception('Include loop detected on %s', self.name) 217 for dependent_module in self.dependent_modules: 218 if dependent_module.name in already_loaded_set: 219 continue 220 dependent_module.ComputeLoadSequenceRecursive( 221 load_sequence, already_loaded_set, depth + 1) 222 if self.name not in already_loaded_set: 223 already_loaded_set.add(self.name) 224 load_sequence.append(self) 225 226 def GetAllDependentFilenamesRecursive(self, include_raw_scripts=True): 227 dependent_filenames = [] 228 229 visited_modules = set() 230 231 def Get(module): 232 module.AppendDirectlyDependentFilenamesTo( 233 dependent_filenames, include_raw_scripts) 234 visited_modules.add(module) 235 for m in module.dependent_modules: 236 if m in visited_modules: 237 continue 238 Get(m) 239 240 Get(self) 241 return dependent_filenames 242 243 def AppendDirectlyDependentFilenamesTo( 244 self, dependent_filenames, include_raw_scripts=True): 245 dependent_filenames.append(self.resource.absolute_path) 246 if include_raw_scripts: 247 for raw_script in self.dependent_raw_scripts: 248 dependent_filenames.append(raw_script.resource.absolute_path) 249 for style_sheet in self.style_sheets: 250 style_sheet.AppendDirectlyDependentFilenamesTo(dependent_filenames) 251 252 253class RawScript(object): 254 """Represents a raw script resource referenced by a module via the 255 py_vulcanize.requireRawScript(xxx) directive.""" 256 257 def __init__(self, resource): 258 self.resource = resource 259 260 @property 261 def filename(self): 262 return self.resource.absolute_path 263 264 @property 265 def contents(self): 266 return self.resource.contents 267 268 def __repr__(self): 269 return 'RawScript(%s)' % self.filename 270