115 lines
3.2 KiB
Python
Executable File
115 lines
3.2 KiB
Python
Executable File
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS-IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
|
|
"""Scans a source JS file for its provided and required namespaces.
|
|
|
|
Simple class to scan a JavaScript file and express its dependencies.
|
|
"""
|
|
|
|
__author__ = 'nnaze@google.com'
|
|
|
|
|
|
import re
|
|
|
|
_BASE_REGEX_STRING = '^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
|
|
_PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide')
|
|
_REQUIRES_REGEX = re.compile(_BASE_REGEX_STRING % 'require')
|
|
|
|
# This line identifies base.js and should match the line in that file.
|
|
_GOOG_BASE_LINE = (
|
|
'var goog = goog || {}; // Identifies this file as the Closure base.')
|
|
|
|
|
|
class Source(object):
|
|
"""Scans a JavaScript source for its provided and required namespaces."""
|
|
|
|
# Matches a "/* ... */" comment.
|
|
# Note: We can't definitively distinguish a "/*" in a string literal without a
|
|
# state machine tokenizer. We'll assume that a line starting with whitespace
|
|
# and "/*" is a comment.
|
|
_COMMENT_REGEX = re.compile(
|
|
r"""
|
|
^\s* # Start of a new line and whitespace
|
|
/\* # Opening "/*"
|
|
.*? # Non greedy match of any characters (including newlines)
|
|
\*/ # Closing "*/""",
|
|
re.MULTILINE | re.DOTALL | re.VERBOSE)
|
|
|
|
def __init__(self, source):
|
|
"""Initialize a source.
|
|
|
|
Args:
|
|
source: str, The JavaScript source.
|
|
"""
|
|
|
|
self.provides = set()
|
|
self.requires = set()
|
|
|
|
self._source = source
|
|
self._ScanSource()
|
|
|
|
def __str__(self):
|
|
return 'Source %s' % self._path
|
|
|
|
def GetSource(self):
|
|
"""Get the source as a string."""
|
|
return self._source
|
|
|
|
@classmethod
|
|
def _StripComments(cls, source):
|
|
return cls._COMMENT_REGEX.sub('', source)
|
|
|
|
def _ScanSource(self):
|
|
"""Fill in provides and requires by scanning the source."""
|
|
|
|
source = self._StripComments(self.GetSource())
|
|
|
|
source_lines = source.splitlines()
|
|
for line in source_lines:
|
|
match = _PROVIDE_REGEX.match(line)
|
|
if match:
|
|
self.provides.add(match.group(1))
|
|
match = _REQUIRES_REGEX.match(line)
|
|
if match:
|
|
self.requires.add(match.group(1))
|
|
|
|
# Closure's base file implicitly provides 'goog'.
|
|
for line in source_lines:
|
|
if line == _GOOG_BASE_LINE:
|
|
if len(self.provides) or len(self.requires):
|
|
raise Exception(
|
|
'Base files should not provide or require namespaces.')
|
|
self.provides.add('goog')
|
|
|
|
|
|
def GetFileContents(path):
|
|
"""Get a file's contents as a string.
|
|
|
|
Args:
|
|
path: str, Path to file.
|
|
|
|
Returns:
|
|
str, Contents of file.
|
|
|
|
Raises:
|
|
IOError: An error occurred opening or reading the file.
|
|
|
|
"""
|
|
fileobj = open(path)
|
|
try:
|
|
return fileobj.read()
|
|
finally:
|
|
fileobj.close()
|