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