diff options
Diffstat (limited to 'catapult/common/py_vulcanize/py_vulcanize/module.py')
-rw-r--r-- | catapult/common/py_vulcanize/py_vulcanize/module.py | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/catapult/common/py_vulcanize/py_vulcanize/module.py b/catapult/common/py_vulcanize/py_vulcanize/module.py new file mode 100644 index 00000000..bd6a68fa --- /dev/null +++ b/catapult/common/py_vulcanize/py_vulcanize/module.py @@ -0,0 +1,262 @@ +# Copyright 2013 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +"""This module contains the Module class and other classes for resources. + +The Module class represents a module in the trace viewer system. A module has +a name, and may require a variety of other resources, such as stylesheets, +template objects, raw JavaScript, or other modules. + +Other resources include HTML templates, raw JavaScript files, and stylesheets. +""" + +import os +import inspect +import codecs + +from py_vulcanize import js_utils + + +class DepsException(Exception): + """Exceptions related to module dependency resolution.""" + + def __init__(self, fmt, *args): + from py_vulcanize import style_sheet as style_sheet_module + context = [] + frame = inspect.currentframe() + while frame: + frame_locals = frame.f_locals + + module_name = None + if 'self' in frame_locals: + s = frame_locals['self'] + if isinstance(s, Module): + module_name = s.name + if isinstance(s, style_sheet_module.StyleSheet): + module_name = s.name + '.css' + if not module_name: + if 'module' in frame_locals: + module = frame_locals['module'] + if isinstance(s, Module): + module_name = module.name + elif 'm' in frame_locals: + module = frame_locals['m'] + if isinstance(s, Module): + module_name = module.name + + if module_name: + if len(context): + if context[-1] != module_name: + context.append(module_name) + else: + context.append(module_name) + + frame = frame.f_back + + context.reverse() + self.context = context + context_str = '\n'.join(' %s' % x for x in context) + Exception.__init__( + self, 'While loading:\n%s\nGot: %s' % (context_str, (fmt % args))) + + +class ModuleDependencyMetadata(object): + + def __init__(self): + self.dependent_module_names = [] + self.dependent_raw_script_relative_paths = [] + self.style_sheet_names = [] + + def AppendMetdata(self, other): + self.dependent_module_names += other.dependent_module_names + self.dependent_raw_script_relative_paths += \ + other.dependent_raw_script_relative_paths + self.style_sheet_names += other.style_sheet_names + + +_next_module_id = 1 + + +class Module(object): + """Represents a JavaScript module. + + Interesting properties include: + name: Module name, may include a namespace, e.g. 'py_vulcanize.foo'. + filename: The filename of the actual module. + contents: The text contents of the module. + dependent_modules: Other modules that this module depends on. + + In addition to these properties, a Module also contains lists of other + resources that it depends on. + """ + + def __init__(self, loader, name, resource, load_resource=True): + assert isinstance(name, basestring), 'Got %s instead' % repr(name) + + global _next_module_id + self._id = _next_module_id + _next_module_id += 1 + + self.loader = loader + self.name = name + self.resource = resource + + if load_resource: + f = codecs.open(self.filename, mode='r', encoding='utf-8') + self.contents = f.read() + f.close() + else: + self.contents = None + + # Dependency metadata, set up during Parse(). + self.dependency_metadata = None + + # Actual dependencies, set up during load(). + self.dependent_modules = [] + self.dependent_raw_scripts = [] + self.scripts = [] + self.style_sheets = [] + + # Caches. + self._all_dependent_modules_recursive = None + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.name) + + @property + def id(self): + return self._id + + @property + def filename(self): + return self.resource.absolute_path + + def IsThirdPartyComponent(self): + """Checks whether this module is a third-party Polymer component.""" + if os.path.join('third_party', 'components') in self.filename: + return True + if os.path.join('third_party', 'polymer', 'components') in self.filename: + return True + return False + + def Parse(self, excluded_scripts): + """Parses self.contents and fills in the module's dependency metadata.""" + raise NotImplementedError() + + def GetTVCMDepsModuleType(self): + """Returns the py_vulcanize.setModuleInfo type for this module""" + raise NotImplementedError() + + def AppendJSContentsToFile(self, + f, + use_include_tags_for_scripts, + dir_for_include_tag_root): + """Appends the js for this module to the provided file.""" + for script in self.scripts: + script.AppendJSContentsToFile(f, use_include_tags_for_scripts, + dir_for_include_tag_root) + + def AppendHTMLContentsToFile(self, f, ctl, minify=False): + """Appends the HTML for this module [without links] to the provided file.""" + pass + + def Load(self, excluded_scripts=None): + """Loads the sub-resources that this module depends on from its dependency + metadata. + + Raises: + DepsException: There was a problem finding one of the dependencies. + Exception: There was a problem parsing a module that this one depends on. + """ + assert self.name, 'Module name must be set before dep resolution.' + assert self.filename, 'Module filename must be set before dep resolution.' + assert self.name in self.loader.loaded_modules, ( + 'Module must be registered in resource loader before loading.') + + metadata = self.dependency_metadata + for name in metadata.dependent_module_names: + module = self.loader.LoadModule(module_name=name, + excluded_scripts=excluded_scripts) + self.dependent_modules.append(module) + + for name in metadata.style_sheet_names: + style_sheet = self.loader.LoadStyleSheet(name) + self.style_sheets.append(style_sheet) + + @property + def all_dependent_modules_recursive(self): + if self._all_dependent_modules_recursive: + return self._all_dependent_modules_recursive + + self._all_dependent_modules_recursive = set(self.dependent_modules) + for dependent_module in self.dependent_modules: + self._all_dependent_modules_recursive.update( + dependent_module.all_dependent_modules_recursive) + return self._all_dependent_modules_recursive + + def ComputeLoadSequenceRecursive(self, load_sequence, already_loaded_set, + depth=0): + """Recursively builds up a load sequence list. + + Args: + load_sequence: A list which will be incrementally built up. + already_loaded_set: A set of modules that has already been added to the + load sequence list. + depth: The depth of recursion. If it too deep, that indicates a loop. + """ + if depth > 32: + raise Exception('Include loop detected on %s', self.name) + for dependent_module in self.dependent_modules: + if dependent_module.name in already_loaded_set: + continue + dependent_module.ComputeLoadSequenceRecursive( + load_sequence, already_loaded_set, depth + 1) + if self.name not in already_loaded_set: + already_loaded_set.add(self.name) + load_sequence.append(self) + + def GetAllDependentFilenamesRecursive(self, include_raw_scripts=True): + dependent_filenames = [] + + visited_modules = set() + + def Get(module): + module.AppendDirectlyDependentFilenamesTo( + dependent_filenames, include_raw_scripts) + visited_modules.add(module) + for m in module.dependent_modules: + if m in visited_modules: + continue + Get(m) + + Get(self) + return dependent_filenames + + def AppendDirectlyDependentFilenamesTo( + self, dependent_filenames, include_raw_scripts=True): + dependent_filenames.append(self.resource.absolute_path) + if include_raw_scripts: + for raw_script in self.dependent_raw_scripts: + dependent_filenames.append(raw_script.resource.absolute_path) + for style_sheet in self.style_sheets: + style_sheet.AppendDirectlyDependentFilenamesTo(dependent_filenames) + + +class RawScript(object): + """Represents a raw script resource referenced by a module via the + py_vulcanize.requireRawScript(xxx) directive.""" + + def __init__(self, resource): + self.resource = resource + + @property + def filename(self): + return self.resource.absolute_path + + @property + def contents(self): + return self.resource.contents + + def __repr__(self): + return 'RawScript(%s)' % self.filename |