• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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