1cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Copyright 2009 The Closure Library Authors. All Rights Reserved.
2cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#
3cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Licensed under the Apache License, Version 2.0 (the "License");
4cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# you may not use this file except in compliance with the License.
5cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# You may obtain a copy of the License at
6cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#
7cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#      http://www.apache.org/licenses/LICENSE-2.0
8cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)#
9cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# Unless required by applicable law or agreed to in writing, software
10cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# distributed under the License is distributed on an "AS-IS" BASIS,
11cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# See the License for the specific language governing permissions and
13cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)# limitations under the License.
14cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
15cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
16cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)"""Scans a source JS file for its provided and required namespaces.
17cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
18cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)Simple class to scan a JavaScript file and express its dependencies.
19cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)"""
20cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
21cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)__author__ = 'nnaze@google.com'
22cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
23cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
24cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)import re
25cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
26cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
27cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)_PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide')
28cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)_REQUIRES_REGEX = re.compile(_BASE_REGEX_STRING % 'require')
29cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
30cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
31cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)class Source(object):
32cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Scans a JavaScript source for its provided and required namespaces."""
33cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
34cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Matches a "/* ... */" comment.
35cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # Note: We can't definitively distinguish a "/*" in a string literal without a
36cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # state machine tokenizer. We'll assume that a line starting with whitespace
37cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  # and "/*" is a comment.
38cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  _COMMENT_REGEX = re.compile(
39cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      r"""
40cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      ^\s*   # Start of a new line and whitespace
41cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      /\*    # Opening "/*"
42cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      .*?    # Non greedy match of any characters (including newlines)
43cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      \*/    # Closing "*/""",
44cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      re.MULTILINE | re.DOTALL | re.VERBOSE)
45cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
46cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def __init__(self, source):
47cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Initialize a source.
48cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
49cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    Args:
50cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      source: str, The JavaScript source.
51cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """
52cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
53cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.provides = set()
54cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self.requires = set()
55cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
56cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self._source = source
57cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    self._ScanSource()
58cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
59cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def GetSource(self):
60cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Get the source as a string."""
61cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return self._source
62cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
63cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  @classmethod
64cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def _StripComments(cls, source):
65cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return cls._COMMENT_REGEX.sub('', source)
66cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
67cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  @classmethod
68cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def _HasProvideGoogFlag(cls, source):
69cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Determines whether the @provideGoog flag is in a comment."""
70cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for comment_content in cls._COMMENT_REGEX.findall(source):
71cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if '@provideGoog' in comment_content:
72cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        return True
73cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
74cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return False
75cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
76cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  def _ScanSource(self):
77cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    """Fill in provides and requires by scanning the source."""
78cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
79cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    stripped_source = self._StripComments(self.GetSource())
80cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
81cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    source_lines = stripped_source.splitlines()
82cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    for line in source_lines:
83cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      match = _PROVIDE_REGEX.match(line)
84cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if match:
85cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        self.provides.add(match.group(1))
86cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      match = _REQUIRES_REGEX.match(line)
87cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if match:
88cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        self.requires.add(match.group(1))
89cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
90cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # Closure's base file implicitly provides 'goog'.
91cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    # This is indicated with the @provideGoog flag.
92cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    if self._HasProvideGoogFlag(self.GetSource()):
93cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
94cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      if len(self.provides) or len(self.requires):
95cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)        raise Exception(
96cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)            'Base file should not provide or require namespaces.')
97cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
98cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)      self.provides.add('goog')
99cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
100cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
101cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)def GetFileContents(path):
102cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """Get a file's contents as a string.
103cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
104cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Args:
105cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    path: str, Path to file.
106cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
107cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Returns:
108cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    str, Contents of file.
109cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
110cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  Raises:
111cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    IOError: An error occurred opening or reading the file.
112cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)
113cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  """
114cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  fileobj = open(path)
115cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  try:
116cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    return fileobj.read()
117cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)  finally:
118cedac228d2dd51db4b79ea1e72c7f249408ee061Torne (Richard Coles)    fileobj.close()
119