• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2014 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"""ResourceFinder is a helper class for finding resources given their name."""
6
7import codecs
8import os
9
10from py_vulcanize import module
11from py_vulcanize import style_sheet as style_sheet_module
12from py_vulcanize import resource as resource_module
13from py_vulcanize import html_module
14from py_vulcanize import strip_js_comments
15
16
17class ResourceLoader(object):
18  """Manges loading modules and their dependencies from files.
19
20  Modules handle parsing and the construction of their individual dependency
21  pointers. The loader deals with bookkeeping of what has been loaded, and
22  mapping names to file resources.
23  """
24
25  def __init__(self, project):
26    self.project = project
27    self.stripped_js_by_filename = {}
28    self.loaded_modules = {}
29    self.loaded_raw_scripts = {}
30    self.loaded_style_sheets = {}
31    self.loaded_images = {}
32
33  @property
34  def source_paths(self):
35    """A list of base directories to search for modules under."""
36    return self.project.source_paths
37
38  def FindResource(self, some_path, binary=False):
39    """Finds a Resource for the given path.
40
41    Args:
42      some_path: A relative or absolute path to a file.
43
44    Returns:
45      A Resource or None.
46    """
47    if os.path.isabs(some_path):
48      return self.FindResourceGivenAbsolutePath(some_path, binary)
49    else:
50      return self.FindResourceGivenRelativePath(some_path, binary)
51
52  def FindResourceGivenAbsolutePath(self, absolute_path, binary=False):
53    """Returns a Resource for the given absolute path."""
54    candidate_paths = []
55    for source_path in self.source_paths:
56      if absolute_path.startswith(source_path):
57        candidate_paths.append(source_path)
58    if len(candidate_paths) == 0:
59      return None
60
61    # Sort by length. Longest match wins.
62    candidate_paths.sort(lambda x, y: len(x) - len(y))
63    longest_candidate = candidate_paths[-1]
64    return resource_module.Resource(longest_candidate, absolute_path, binary)
65
66  def FindResourceGivenRelativePath(self, relative_path, binary=False):
67    """Returns a Resource for the given relative path."""
68    absolute_path = None
69    for script_path in self.source_paths:
70      absolute_path = os.path.join(script_path, relative_path)
71      if os.path.exists(absolute_path):
72        return resource_module.Resource(script_path, absolute_path, binary)
73    return None
74
75  def _FindResourceGivenNameAndSuffix(
76        self, requested_name, extension, return_resource=False):
77    """Searches for a file and reads its contents.
78
79    Args:
80      requested_name: The name of the resource that was requested.
81      extension: The extension for this requested resource.
82
83    Returns:
84      A (path, contents) pair.
85    """
86    pathy_name = requested_name.replace('.', os.sep)
87    filename = pathy_name + extension
88
89    resource = self.FindResourceGivenRelativePath(filename)
90    if return_resource:
91      return resource
92    if not resource:
93      return None, None
94    return _read_file(resource.absolute_path)
95
96  def FindModuleResource(self, requested_module_name):
97    """Finds a module javascript file and returns a Resource, or none."""
98    js_resource = self._FindResourceGivenNameAndSuffix(
99        requested_module_name, '.js', return_resource=True)
100    html_resource = self._FindResourceGivenNameAndSuffix(
101        requested_module_name, '.html', return_resource=True)
102    if js_resource and html_resource:
103      if html_module.IsHTMLResourceTheModuleGivenConflictingResourceNames(
104          js_resource, html_resource):
105        return html_resource
106      return js_resource
107    elif js_resource:
108      return js_resource
109    return html_resource
110
111  def LoadModule(self, module_name=None, module_filename=None,
112                 excluded_scripts=None):
113    assert bool(module_name) ^ bool(module_filename), (
114        'Must provide either module_name or module_filename.')
115    if module_filename:
116      resource = self.FindResource(module_filename)
117      if not resource:
118        raise Exception('Could not find %s in %s' % (
119            module_filename, repr(self.source_paths)))
120      module_name = resource.name
121    else:
122      resource = None  # Will be set if we end up needing to load.
123
124    if module_name in self.loaded_modules:
125      assert self.loaded_modules[module_name].contents
126      return self.loaded_modules[module_name]
127
128    if not resource:  # happens when module_name was given
129      resource = self.FindModuleResource(module_name)
130      if not resource:
131        raise module.DepsException('No resource for module "%s"' % module_name)
132
133    m = html_module.HTMLModule(self, module_name, resource)
134    self.loaded_modules[module_name] = m
135
136    # Fake it, this is probably either polymer.min.js or platform.js which are
137    # actually .js files....
138    if resource.absolute_path.endswith('.js'):
139      return m
140
141    m.Parse(excluded_scripts)
142    m.Load(excluded_scripts)
143    return m
144
145  def LoadRawScript(self, relative_raw_script_path):
146    resource = None
147    for source_path in self.source_paths:
148      possible_absolute_path = os.path.join(
149          source_path, os.path.normpath(relative_raw_script_path))
150      if os.path.exists(possible_absolute_path):
151        resource = resource_module.Resource(
152            source_path, possible_absolute_path)
153        break
154    if not resource:
155      raise module.DepsException(
156          'Could not find a file for raw script %s in %s' %
157          (relative_raw_script_path, self.source_paths))
158    assert relative_raw_script_path == resource.unix_style_relative_path, (
159        'Expected %s == %s' % (relative_raw_script_path,
160                               resource.unix_style_relative_path))
161
162    if resource.absolute_path in self.loaded_raw_scripts:
163      return self.loaded_raw_scripts[resource.absolute_path]
164
165    raw_script = module.RawScript(resource)
166    self.loaded_raw_scripts[resource.absolute_path] = raw_script
167    return raw_script
168
169  def LoadStyleSheet(self, name):
170    if name in self.loaded_style_sheets:
171      return self.loaded_style_sheets[name]
172
173    resource = self._FindResourceGivenNameAndSuffix(
174        name, '.css', return_resource=True)
175    if not resource:
176      raise module.DepsException(
177          'Could not find a file for stylesheet %s' % name)
178
179    style_sheet = style_sheet_module.StyleSheet(self, name, resource)
180    style_sheet.load()
181    self.loaded_style_sheets[name] = style_sheet
182    return style_sheet
183
184  def LoadImage(self, abs_path):
185    if abs_path in self.loaded_images:
186      return self.loaded_images[abs_path]
187
188    if not os.path.exists(abs_path):
189      raise module.DepsException("url('%s') did not exist" % abs_path)
190
191    res = self.FindResourceGivenAbsolutePath(abs_path, binary=True)
192    if res is None:
193      raise module.DepsException("url('%s') was not in search path" % abs_path)
194
195    image = style_sheet_module.Image(res)
196    self.loaded_images[abs_path] = image
197    return image
198
199  def GetStrippedJSForFilename(self, filename, early_out_if_no_py_vulcanize):
200    if filename in self.stripped_js_by_filename:
201      return self.stripped_js_by_filename[filename]
202
203    with open(filename, 'r') as f:
204      contents = f.read(4096)
205    if early_out_if_no_py_vulcanize and ('py_vulcanize' not in contents):
206      return None
207
208    s = strip_js_comments.StripJSComments(contents)
209    self.stripped_js_by_filename[filename] = s
210    return s
211
212
213def _read_file(absolute_path):
214  """Reads a file and returns a (path, contents) pair.
215
216  Args:
217    absolute_path: Absolute path to a file.
218
219  Raises:
220    Exception: The given file doesn't exist.
221    IOError: There was a problem opening or reading the file.
222  """
223  if not os.path.exists(absolute_path):
224    raise Exception('%s not found.' % absolute_path)
225  f = codecs.open(absolute_path, mode='r', encoding='utf-8')
226  contents = f.read()
227  f.close()
228  return absolute_path, contents
229