12da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Copyright (c) 2012 The Chromium Authors. All rights reserved.
22da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# Use of this source code is governed by a BSD-style license that can be
32da489cd246702bee5938545b18a6f710ed214bcJamie Gennis# found in the LICENSE file.
42da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport sys
52da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport os
62da489cd246702bee5938545b18a6f710ed214bcJamie Gennisimport re
72da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
82da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass DepsException(Exception):
92da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  pass
102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
122da489cd246702bee5938545b18a6f710ed214bcJamie GennisThe core of this script is the calc_load_sequence function. In total, this
132da489cd246702bee5938545b18a6f710ed214bcJamie Genniswalks over the provided javascript files and figures out their dependencies
142da489cd246702bee5938545b18a6f710ed214bcJamie Gennisusing the module definitions provided in each file. This allows us to, for
152da489cd246702bee5938545b18a6f710ed214bcJamie Gennisexample, have a trio of modules:
162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
172da489cd246702bee5938545b18a6f710ed214bcJamie Gennisfoo.js:
182da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   base.require('bar');
192da489cd246702bee5938545b18a6f710ed214bcJamie Gennisand bar.js:
202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   base.require('baz');
212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2288448d9ae4dfff1805045790ef5f32495d62abccJeff Browncalc_load_sequence(['foo'], '.') will yield:
232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis   [Module('baz'), Module('bar'), Module('foo')]
242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
252da489cd246702bee5938545b18a6f710ed214bcJamie Genniswhich is, based on the dependencies, the correct sequence in which to load
262da489cd246702bee5938545b18a6f710ed214bcJamie Gennisthose modules.
272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis"""
282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
292da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass ResourceFinder(object):
302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Helper code for finding a module given a name and current module.
312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  The dependency resolution code in Module.resolve will find bits of code in the
332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  actual javascript that says things require('bar'). This
342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  code is responsible for figuring out what filename corresponds to 'bar' given
352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  a Module('foo').
362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """
372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, root_dir):
382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self._root_dir = root_dir
392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    pass
402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4188448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  @property
4288448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  def root_dir(self):
4388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    return self._root_dir
442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
4588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  def _find_and_load_filename(self, absolute_path):
462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not os.path.exists(absolute_path):
472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return None, None
482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    f = open(absolute_path, 'r')
502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    contents = f.read()
512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    f.close()
522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return absolute_path, contents
542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
5588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  def _find_and_load(self, current_module, requested_name, extension):
5688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    assert current_module.filename
5788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    pathy_name = requested_name.replace(".", os.sep)
5888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    filename = pathy_name + extension
5988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    absolute_path = os.path.join(self._root_dir, filename)
6088448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    return self._find_and_load_filename(absolute_path)
6188448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def find_and_load_module(self, current_module, requested_module_name):
632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._find_and_load(current_module, requested_module_name, ".js")
642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
6588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  def find_and_load_raw_script(self, current_module, filename):
6688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    absolute_path = os.path.join(self._root_dir, filename)
6788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    return self._find_and_load_filename(absolute_path)
6888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def find_and_load_style_sheet(self,
702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                current_module, requested_style_sheet_name):
712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return self._find_and_load(
722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      current_module, requested_style_sheet_name, ".css")
732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
752da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass StyleSheet(object):
762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Represents a stylesheet resource referenced by a module via the
772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  base.requireStylesheet(xxx) directive."""
782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, name, filename, contents):
792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.name = name
802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.filename = filename
812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.contents = contents
822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __repr__(self):
842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return "StyleSheet(%s)" % self.name
852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
8688448d9ae4dfff1805045790ef5f32495d62abccJeff Brownclass RawScript(object):
8788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  """Represents a raw script resource referenced by a module via the
8888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  base.requireRawScript(xxx) directive."""
8988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  def __init__(self, name, filename, contents):
9088448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    self.name = name
9188448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    self.filename = filename
9288448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    self.contents = contents
9388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
9488448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  def __repr__(self):
9588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    return "RawScript(%s)" % self.name
9688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
972da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _tokenize_js(text):
982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  rest = text
992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  tokens = ["//", "/*", "*/", "\n"]
1002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  while len(rest):
1012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    indices = [rest.find(token) for token in tokens]
1022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    found_indices = [index for index in indices if index >= 0]
1032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if len(found_indices) == 0:
1052da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # end of string
1062da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      yield rest
1072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      return
1082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    min_index = min(found_indices)
1102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    token_with_min = tokens[indices.index(min_index)]
1112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if min_index > 0:
1132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      yield rest[:min_index]
1142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    yield rest[min_index:min_index + len(token_with_min)]
1162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    rest = rest[min_index + len(token_with_min):]
1172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1182da489cd246702bee5938545b18a6f710ed214bcJamie Gennisdef _strip_js_comments(text):
1192da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  result_tokens = []
1202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  token_stream = _tokenize_js(text).__iter__()
1212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  while True:
1222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    try:
1232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      t = token_stream.next()
1242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    except StopIteration:
1252da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      break
1262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if t == "//":
1282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      while True:
1292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        try:
1302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          t2 = token_stream.next()
1312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if t2 == "\n":
1322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            break
1332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        except StopIteration:
1342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break
1352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    elif t == '/*':
1362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      nesting = 1
1372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      while True:
1382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        try:
1392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          t2 = token_stream.next()
1402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          if t2 == "/*":
1412da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            nesting += 1
1422da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          elif t2 == "*/":
1432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            nesting -= 1
1442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis            if nesting == 0:
1452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis              break
1462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        except StopIteration:
1472da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          break
1482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    else:
1492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      result_tokens.append(t)
1502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  return "".join(result_tokens)
1512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
15288448d9ae4dfff1805045790ef5f32495d62abccJeff Browndef _MangleRawScriptFilenameToModuleName(filename):
15388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  name = filename
15488448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  name = name.replace(os.sep, ':')
15588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  name = name.replace('..', '!!')
15688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown  return name
15788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
1582da489cd246702bee5938545b18a6f710ed214bcJamie Gennisclass Module(object):
1592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Represents a javascript module. It can either be directly requested, e.g.
1602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  passed in by name to calc_load_sequence, or created by being referenced a
1612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  module via the base.require(xxx) directive.
1622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  Interesting properties on this object are:
1642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  - filename: the file of the actual module
1662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  - contents: the actual text contents of the module
1672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  - style_sheets: StyleSheet objects that this module relies on for styling
1682da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    information.
1692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  - dependent_modules: other modules that this module needs in order to run
1702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """
1712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __init__(self, name = None):
1722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.name = name
1732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.filename = None
1742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.contents = None
1752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.dependent_module_names = []
1772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.dependent_modules = []
17888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    self.dependent_raw_script_names = []
17988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    self.dependent_raw_scripts = []
1802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.style_sheet_names = []
1812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.style_sheets = []
1822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def __repr__(self):
1842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    return "Module(%s)" % self.name
1852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
1862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def load_and_parse(self, module_filename,
1872da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                     module_contents = None,
1882da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                     decl_required = True):
1892da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not module_contents:
1902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      f = open(module_filename, 'r')
1912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self.contents = f.read()
1922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      f.close()
1932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    else:
1942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self.contents = module_contents
1952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    self.filename = module_filename
19666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    stripped_text = _strip_js_comments(self.contents)
19766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    self.validate_uses_strict_mode_(stripped_text)
19866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    self.parse_definition_(stripped_text, decl_required)
1992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def resolve(self, all_resources, resource_finder):
2012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if "scripts" not in all_resources:
2022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      all_resources["scripts"] = {}
2032da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if "style_sheets" not in all_resources:
2042da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      all_resources["style_sheets"] = {}
20588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    if "raw_scripts" not in all_resources:
20688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      all_resources["raw_scripts"] = {}
2072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    assert self.filename
2092da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2102da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for name in self.dependent_module_names:
2112da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if name in all_resources["scripts"]:
2122da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        assert all_resources["scripts"][name].contents
2132da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.dependent_modules.append(all_resources["scripts"][name])
2142da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        continue
2152da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2162da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      filename, contents = resource_finder.find_and_load_module(self, name)
2172da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not filename:
21866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        raise DepsException("No file for module %(name)s needed by %(dep)s" %
21966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis          {"name": name, "dep": self.filename})
2202da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      module = Module(name)
2222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      all_resources["scripts"][name] = module
2232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self.dependent_modules.append(module)
22466a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      try:
22566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        module.load_and_parse(filename, contents)
22666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      except Exception, e:
22766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        raise Exception('While processing ' + filename + ': ' + e.message)
2282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      module.resolve(all_resources, resource_finder)
2292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
23088448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    for name in self.dependent_raw_script_names:
23188448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      filename, contents = resource_finder.find_and_load_raw_script(self, name)
23288448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      if not filename:
23366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        raise DepsException("Could not find a file for raw script %s" % name)
23488448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
23588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      if name in all_resources["raw_scripts"]:
23688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        assert all_resources["raw_scripts"][name].contents
23788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        self.dependent_raw_scripts.append(all_resources["raw_scripts"][name])
23888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        continue
23988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
24088448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      raw_script = RawScript(name, filename, contents)
24188448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      all_resources["raw_scripts"][name] = raw_script
24288448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      self.dependent_raw_scripts.append(raw_script)
24388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
2442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for name in self.style_sheet_names:
2452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if name in all_resources["style_sheets"]:
2462da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        assert all_resources["style_sheets"][name].contents
24766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        self.style_sheets.append(all_resources["style_sheets"][name])
2482da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        continue
2492da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      filename, contents = resource_finder.find_and_load_style_sheet(self, name)
2512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if not filename:
2522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        raise DepsException("Could not find a file for stylesheet %s" % name)
2532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      style_sheet = StyleSheet(name, filename, contents)
2552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      all_resources["style_sheets"][name] = style_sheet
2562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      self.style_sheets.append(style_sheet)
2572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def compute_load_sequence_recursive(self, load_sequence, already_loaded_set):
2592da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for dependent_module in self.dependent_modules:
2602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      dependent_module.compute_load_sequence_recursive(load_sequence,
2612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                                                       already_loaded_set)
2622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if self.name not in already_loaded_set:
2632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      already_loaded_set.add(self.name)
2642da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      load_sequence.append(self)
2652da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
26666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  def validate_uses_strict_mode_(self, stripped_text):
26766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    lines = stripped_text.split('\n')
26866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    for line in lines:
26966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      line = line.strip()
27066a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      if len(line.strip()) == 0:
27166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        continue
27266a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      if line.strip() == """'use strict';""":
27366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis        break
27466a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      raise DepsException('%s must use strict mode' % self.name)
27566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis
27666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  def parse_definition_(self, stripped_text, decl_required = True):
2772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not decl_required and not self.name:
2782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      raise Exception("Module.name must be set for decl_required to be false.")
2792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    rest = stripped_text
2812da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    while True:
2822da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Things to search for.
2832da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      m_r = re.search("""base\s*\.\s*require\((["'])(.+?)\\1\)""",
2842da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                      rest, re.DOTALL)
2852da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      m_s = re.search("""base\s*\.\s*requireStylesheet\((["'])(.+?)\\1\)""",
2862da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                      rest, re.DOTALL)
28788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      m_irs = re.search("""base\s*\.\s*requireRawScript\((["'])(.+?)\\1\)""",
28888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown                      rest, re.DOTALL)
28988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      matches = [m for m in [m_r, m_s, m_irs] if m]
2902da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      # Figure out which was first.
29288448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      matches.sort(key=lambda x: x.start())
29388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      if len(matches):
29488448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        m = matches[0]
2952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      else:
2962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        break
2972da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
2982da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      if m == m_r:
2992da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        dependent_module_name = m.group(2)
3002da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        if '/' in dependent_module_name:
3012da489cd246702bee5938545b18a6f710ed214bcJamie Gennis          raise DepsException("Slashes are not allowed in module names. "
3022da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                              "Use '.' instead: %s" % dependent_module_name)
30388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        if dependent_module_name.endswith('js'):
30488448d9ae4dfff1805045790ef5f32495d62abccJeff Brown          raise DepsException("module names shouldn't end with .js"
30588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown                              "The module system will append that for you: %s" %
30688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown                              dependent_module_name)
3072da489cd246702bee5938545b18a6f710ed214bcJamie Gennis        self.dependent_module_names.append(dependent_module_name)
3082da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      elif m == m_s:
30988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        style_sheet_name = m.group(2)
31088448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        if '/' in style_sheet_name:
31188448d9ae4dfff1805045790ef5f32495d62abccJeff Brown          raise DepsException("Slashes are not allowed in style sheet names. "
31288448d9ae4dfff1805045790ef5f32495d62abccJeff Brown                              "Use '.' instead: %s" % style_sheet_name)
31388448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        if style_sheet_name.endswith('.css'):
31488448d9ae4dfff1805045790ef5f32495d62abccJeff Brown          raise DepsException("Style sheets should not end in .css. "
31588448d9ae4dfff1805045790ef5f32495d62abccJeff Brown                              "The module system will append that for you" %
31688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown                              style_sheet_name)
31788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        self.style_sheet_names.append(style_sheet_name)
31888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown      elif m == m_irs:
31988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        name = m.group(2)
32088448d9ae4dfff1805045790ef5f32495d62abccJeff Brown        self.dependent_raw_script_names.append(name)
3212da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3222da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      rest = rest[m.end():]
3232da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3242da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
32588448d9ae4dfff1805045790ef5f32495d62abccJeff Browndef calc_load_sequence(filenames, toplevel_dir):
3262da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """Given a list of starting javascript files, figure out all the Module
3272da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  objects that need to be loaded to satisfiy their dependencies.
3282da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3292da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  The javascript files shoud specify their dependencies in a format that is
3302da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  textually equivalent to base.js' require syntax, namely:
3312da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3322da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     base.require(module1);
3332da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     base.require(module2);
3342da489cd246702bee5938545b18a6f710ed214bcJamie Gennis     base.requireStylesheet(stylesheet);
3352da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3362da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  The output of this function is an array of Module objects ordered by
3372da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  dependency.
3382da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  """
3392da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  all_resources = {}
3402da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  all_resources["scripts"] = {}
34166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  resource_finder = ResourceFinder(os.path.abspath(toplevel_dir))
34266a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  initial_module_name_indices = {}
3432da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  for filename in filenames:
3442da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if not os.path.exists(filename):
3452da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      raise Exception("Could not find %s" % filename)
34688448d9ae4dfff1805045790ef5f32495d62abccJeff Brown
34788448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    rel_filename = os.path.relpath(filename, toplevel_dir)
34888448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    dirname = os.path.dirname(rel_filename)
34988448d9ae4dfff1805045790ef5f32495d62abccJeff Brown    modname  = os.path.splitext(os.path.basename(rel_filename))[0]
3502da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if len(dirname):
3512da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      name = dirname.replace('/', '.') + '.' + modname
3522da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    else:
3532da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      name = modname
3542da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3552da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    if name in all_resources["scripts"]:
3562da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      continue
3572da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3582da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    module = Module(name)
35966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    initial_module_name_indices[module.name] = len(initial_module_name_indices)
3602da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    module.load_and_parse(filename, decl_required = False)
3612da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    all_resources["scripts"][module.name] = module
3622da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    module.resolve(all_resources, resource_finder)
3632da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
36466a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  # Find the root modules: ones that have no dependencies. While doing that,
36566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  # sort the dependent module list so that the computed load order is stable.
3662da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  module_ref_counts = {}
3672da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  for module in all_resources["scripts"].values():
36866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    module.dependent_modules.sort(lambda x, y: cmp(x.name, y.name))
3692da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    module_ref_counts[module.name] = 0
3702da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3712da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  def inc_ref_count(name):
3722da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    module_ref_counts[name] = module_ref_counts[name] + 1
3732da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  for module in all_resources["scripts"].values():
3742da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    for dependent_module in module.dependent_modules:
3752da489cd246702bee5938545b18a6f710ed214bcJamie Gennis      inc_ref_count(dependent_module.name)
3762da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3772da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  root_modules = [all_resources["scripts"][name]
3782da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  for name, ref_count in module_ref_counts.items()
3792da489cd246702bee5938545b18a6f710ed214bcJamie Gennis                  if ref_count == 0]
3802da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
38166a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  # Sort root_modules by the order they were originally requested,
38266a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  # then sort everything else by name.
38366a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  def compare_root_module(x, y):
38466a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    n = len(initial_module_name_indices);
38566a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    iX = initial_module_name_indices.get(x.name, n)
38666a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    iY = initial_module_name_indices.get(y.name, n)
38766a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    if cmp(iX, iY) != 0:
38866a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis      return cmp(iX, iY)
38966a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis    return cmp(x.name, y.name)
39066a37686207944273ced825e0e8b6b6375f8c3deJamie Gennis  root_modules.sort(compare_root_module)
3912da489cd246702bee5938545b18a6f710ed214bcJamie Gennis
3922da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  already_loaded_set = set()
3932da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  load_sequence = []
3942da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  for module in root_modules:
3952da489cd246702bee5938545b18a6f710ed214bcJamie Gennis    module.compute_load_sequence_recursive(load_sequence, already_loaded_set)
3962da489cd246702bee5938545b18a6f710ed214bcJamie Gennis  return load_sequence
397