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