Home | History | Annotate | Download | only in py_vulcanize
      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 
      7 import codecs
      8 import os
      9 
     10 from py_vulcanize import module
     11 from py_vulcanize import style_sheet as style_sheet_module
     12 from py_vulcanize import resource as resource_module
     13 from py_vulcanize import html_module
     14 from py_vulcanize import strip_js_comments
     15 
     16 
     17 class 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 
    213 def _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