193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# Copyright (c) 2014 The Chromium Authors. All rights reserved.
293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# Use of this source code is governed by a BSD-style license that can be
393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik# found in the LICENSE file.
4b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik"""ResourceFinder is a helper class for finding resources given their name."""
693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
7b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craikimport codecs
893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikimport os
9b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
1093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikfrom tvcm import module
1193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikfrom tvcm import style_sheet as style_sheet_module
1293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikfrom tvcm import resource as resource_module
1393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikfrom tvcm import html_module
1493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikfrom tvcm import strip_js_comments
1593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
16b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik
1793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikclass ResourceLoader(object):
1893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  """Manges loading modules and their dependencies from files.
1993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
2093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  Modules handle parsing and the construction of their individual dependency
2193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  pointers. The loader deals with bookkeeping of what has been loaded, and
2293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  mapping names to file resources.
2393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  """
2493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def __init__(self, project):
2593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.project = project
2693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.stripped_js_by_filename = {}
2793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_modules = {}
2893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_raw_scripts = {}
2993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_style_sheets = {}
3093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_images = {}
3193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
3293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  @property
3393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def source_paths(self):
3493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """A list of base directories to search for modules under."""
3593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return self.project.source_paths
3693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
37f516a629824f0019315b9841dc2df76dc7862ddeChris Craik  def FindResource(self, some_path, binary=False):
3893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """Finds a Resource for the given path.
3993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
4093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    Args:
4193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      some_path: A relative or absolute path to a file.
4293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
4393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    Returns:
4493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      A Resource or None.
4593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """
4693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if os.path.isabs(some_path):
47f516a629824f0019315b9841dc2df76dc7862ddeChris Craik      return self.FindResourceGivenAbsolutePath(some_path, binary)
4893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    else:
49f516a629824f0019315b9841dc2df76dc7862ddeChris Craik      return self.FindResourceGivenRelativePath(some_path, binary)
5093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
51f516a629824f0019315b9841dc2df76dc7862ddeChris Craik  def FindResourceGivenAbsolutePath(self, absolute_path, binary=False):
5293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """Returns a Resource for the given absolute path."""
5393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    candidate_paths = []
5493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    for source_path in self.source_paths:
5593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if absolute_path.startswith(source_path):
5693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        candidate_paths.append(source_path)
5793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if len(candidate_paths) == 0:
5893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return None
5993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
6093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    # Sort by length. Longest match wins.
6193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    candidate_paths.sort(lambda x, y: len(x) - len(y))
6293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    longest_candidate = candidate_paths[-1]
63f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    return resource_module.Resource(longest_candidate, absolute_path, binary)
6493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
65f516a629824f0019315b9841dc2df76dc7862ddeChris Craik  def FindResourceGivenRelativePath(self, relative_path, binary=False):
6693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """Returns a Resource for the given relative path."""
6793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    absolute_path = None
6893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    for script_path in self.source_paths:
6993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      absolute_path = os.path.join(script_path, relative_path)
7093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if os.path.exists(absolute_path):
71f516a629824f0019315b9841dc2df76dc7862ddeChris Craik        return resource_module.Resource(script_path, absolute_path, binary)
7293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return None
7393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
74b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik  def _FindResourceGivenNameAndSuffix(
75b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      self, requested_name, extension, return_resource=False):
7693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """Searches for a file and reads its contents.
7793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
7893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    Args:
7993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      requested_name: The name of the resource that was requested.
8093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      extension: The extension for this requested resource.
8193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
8293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    Returns:
8393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      A (path, contents) pair.
8493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """
8593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    pathy_name = requested_name.replace('.', os.sep)
8693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    filename = pathy_name + extension
8793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
8893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    resource = self.FindResourceGivenRelativePath(filename)
8993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if return_resource:
9093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return resource
9193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if not resource:
9293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return None, None
9393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return _read_file(resource.absolute_path)
9493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
9593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def FindModuleResource(self, requested_module_name):
9693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    """Finds a module javascript file and returns a Resource, or none."""
97b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    js_resource = self._FindResourceGivenNameAndSuffix(
98b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        requested_module_name, '.js', return_resource=True)
99b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    html_resource = self._FindResourceGivenNameAndSuffix(
100b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        requested_module_name, '.html', return_resource=True)
10193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if js_resource and html_resource:
102b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      if html_module.IsHTMLResourceTheModuleGivenConflictingResourceNames(
103b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik          js_resource, html_resource):
10493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        return html_resource
10593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return js_resource
10693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    elif js_resource:
10793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return js_resource
10893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return html_resource
10993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
11093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def LoadModule(self, module_name=None, module_filename=None):
111b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    assert bool(module_name) ^ bool(module_filename), (
112b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        'Must provide either module_name or module_filename.')
11393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if module_filename:
11493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      resource = self.FindResource(module_filename)
11593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if not resource:
11693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        raise Exception('Could not find %s in %s' % (
11793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik            module_filename, repr(self.source_paths)))
11893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      module_name = resource.name
11993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    else:
120b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      resource = None  # Will be set if we end up needing to load.
12193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
12293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if module_name in self.loaded_modules:
12393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      assert self.loaded_modules[module_name].contents
12493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return self.loaded_modules[module_name]
12593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
126b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if not resource:  # happens when module_name was given
12793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      resource = self.FindModuleResource(module_name)
12893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if not resource:
12993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        raise module.DepsException('No resource for module "%s"' % module_name)
13093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
13193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    m = html_module.HTMLModule(self, module_name, resource)
13293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_modules[module_name] = m
13393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
134f516a629824f0019315b9841dc2df76dc7862ddeChris Craik    # Fake it, this is probably either polymer.min.js or platform.js which are
13593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    # actually .js files....
13693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if resource.absolute_path.endswith('.js'):
13793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return m
13893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
13993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    m.Parse()
14093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    m.Load()
14193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return m
14293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
14393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def LoadRawScript(self, relative_raw_script_path):
14493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    resource = None
14593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    for source_path in self.source_paths:
146b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      possible_absolute_path = os.path.join(
147b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik          source_path, os.path.normpath(relative_raw_script_path))
14893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      if os.path.exists(possible_absolute_path):
14993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        resource = resource_module.Resource(source_path, possible_absolute_path)
15093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik        break
15193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if not resource:
152b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      raise module.DepsException(
153b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik          'Could not find a file for raw script %s in %s' %
154b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik          (relative_raw_script_path, self.source_paths))
155b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    assert relative_raw_script_path == resource.unix_style_relative_path, (
156b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        'Expected %s == %s' % (relative_raw_script_path,
157b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik                               resource.unix_style_relative_path))
15893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
15993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if resource.absolute_path in self.loaded_raw_scripts:
16093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return self.loaded_raw_scripts[resource.absolute_path]
16193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
16293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    raw_script = module.RawScript(resource)
16393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_raw_scripts[resource.absolute_path] = raw_script
16493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return raw_script
16593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
16693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def LoadStyleSheet(self, name):
16793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if name in self.loaded_style_sheets:
16893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return self.loaded_style_sheets[name]
16993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
170b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    resource = self._FindResourceGivenNameAndSuffix(
171b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik        name, '.css', return_resource=True)
17293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if not resource:
173b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      raise module.DepsException(
174b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik          'Could not find a file for stylesheet %s' % name)
17593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
17693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    style_sheet = style_sheet_module.StyleSheet(self, name, resource)
17793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    style_sheet.load()
17893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_style_sheets[name] = style_sheet
17993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return style_sheet
18093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
18193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def LoadImage(self, abs_path):
18293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if abs_path in self.loaded_images:
18393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return self.loaded_images[abs_path]
18493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
18593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if not os.path.exists(abs_path):
186b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      raise module.DepsException("url('%s') did not exist" % abs_path)
18793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
188b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    res = self.FindResourceGivenAbsolutePath(abs_path, binary=True)
189b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik    if res is None:
190b2cbf1594f8d6e4ba32d384cf379f62a74ed7654Chris Craik      raise module.DepsException("url('%s') was not in search path" % abs_path)
19193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
19293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    image = style_sheet_module.Image(res)
19393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.loaded_images[abs_path] = image
19493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return image
19593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
19693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  def GetStrippedJSForFilename(self, filename, early_out_if_no_tvcm):
19793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if filename in self.stripped_js_by_filename:
19893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return self.stripped_js_by_filename[filename]
19993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
20093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    with open(filename, 'r') as f:
20193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      contents = f.read(4096)
20293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    if early_out_if_no_tvcm and ('tvcm' not in contents):
20393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik      return None
20493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
20593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    s = strip_js_comments.StripJSComments(contents)
20693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    self.stripped_js_by_filename[filename] = s
20793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    return s
20893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
20993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
21093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craikdef _read_file(absolute_path):
21193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  """Reads a file and returns a (path, contents) pair.
21293216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
21393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  Args:
21493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    absolute_path: Absolute path to a file.
21593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik
21693216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  Raises:
21793216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    Exception: The given file doesn't exist.
21893216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    IOError: There was a problem opening or reading the file.
21993216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  """
22093216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  if not os.path.exists(absolute_path):
22193216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik    raise Exception('%s not found.' % absolute_path)
222f516a629824f0019315b9841dc2df76dc7862ddeChris Craik  f = codecs.open(absolute_path, mode='r', encoding='utf-8')
22393216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  contents = f.read()
22493216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  f.close()
22593216d0b8afcc23eb8811175ca32338cd09c9dcaChris Craik  return absolute_path, contents
226