222 lines
6.6 KiB
Python
222 lines
6.6 KiB
Python
#!/usr/bin/python
|
|
#
|
|
# Copyright 2010 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.
|
|
|
|
|
|
"""Automatically converts codebases over to goog.scope.
|
|
|
|
Usage:
|
|
cd path/to/my/dir;
|
|
../../../../javascript/closure/bin/scopify.py
|
|
|
|
Scans every file in this directory, recursively. Looks for existing
|
|
goog.scope calls, and goog.require'd symbols. If it makes sense to
|
|
generate a goog.scope call for the file, then we will do so, and
|
|
try to auto-generate some aliases based on the goog.require'd symbols.
|
|
|
|
Known Issues:
|
|
|
|
When a file is goog.scope'd, the file contents will be indented +2.
|
|
This may put some lines over 80 chars. These will need to be fixed manually.
|
|
|
|
We will only try to create aliases for capitalized names. We do not check
|
|
to see if those names will conflict with any existing locals.
|
|
|
|
This creates merge conflicts for every line of every outstanding change.
|
|
If you intend to run this on your codebase, make sure your team members
|
|
know. Better yet, send them this script so that they can scopify their
|
|
outstanding changes and "accept theirs".
|
|
|
|
When an alias is "captured", it can no longer be stubbed out for testing.
|
|
Run your tests.
|
|
|
|
"""
|
|
|
|
__author__ = 'nicksantos@google.com (Nick Santos)'
|
|
|
|
import os.path
|
|
import re
|
|
import sys
|
|
|
|
REQUIRES_RE = re.compile(r"goog.require\('([^']*)'\)")
|
|
|
|
# Edit this manually if you want something to "always" be aliased.
|
|
# TODO(nicksantos): Add a flag for this.
|
|
DEFAULT_ALIASES = {}
|
|
|
|
def Transform(lines):
|
|
"""Converts the contents of a file into javascript that uses goog.scope.
|
|
|
|
Arguments:
|
|
lines: A list of strings, corresponding to each line of the file.
|
|
Returns:
|
|
A new list of strings, or None if the file was not modified.
|
|
"""
|
|
requires = []
|
|
|
|
# Do an initial scan to be sure that this file can be processed.
|
|
for line in lines:
|
|
# Skip this file if it has already been scopified.
|
|
if line.find('goog.scope') != -1:
|
|
return None
|
|
|
|
# If there are any global vars or functions, then we also have
|
|
# to skip the whole file. We might be able to deal with this
|
|
# more elegantly.
|
|
if line.find('var ') == 0 or line.find('function ') == 0:
|
|
return None
|
|
|
|
for match in REQUIRES_RE.finditer(line):
|
|
requires.append(match.group(1))
|
|
|
|
if len(requires) == 0:
|
|
return None
|
|
|
|
# Backwards-sort the requires, so that when one is a substring of another,
|
|
# we match the longer one first.
|
|
for val in DEFAULT_ALIASES.values():
|
|
if requires.count(val) == 0:
|
|
requires.append(val)
|
|
|
|
requires.sort()
|
|
requires.reverse()
|
|
|
|
# Generate a map of requires to their aliases
|
|
aliases_to_globals = DEFAULT_ALIASES.copy()
|
|
for req in requires:
|
|
index = req.rfind('.')
|
|
if index == -1:
|
|
alias = req
|
|
else:
|
|
alias = req[(index + 1):]
|
|
|
|
# Don't scopify lowercase namespaces, because they may conflict with
|
|
# local variables.
|
|
if alias[0].isupper():
|
|
aliases_to_globals[alias] = req
|
|
|
|
aliases_to_matchers = {}
|
|
globals_to_aliases = {}
|
|
for alias, symbol in aliases_to_globals.items():
|
|
globals_to_aliases[symbol] = alias
|
|
aliases_to_matchers[alias] = re.compile('\\b%s\\b' % symbol)
|
|
|
|
# Insert a goog.scope that aliases all required symbols.
|
|
result = []
|
|
|
|
START = 0
|
|
SEEN_REQUIRES = 1
|
|
IN_SCOPE = 2
|
|
|
|
mode = START
|
|
aliases_used = set()
|
|
insertion_index = None
|
|
num_blank_lines = 0
|
|
for line in lines:
|
|
if mode == START:
|
|
result.append(line)
|
|
|
|
if re.search(REQUIRES_RE, line):
|
|
mode = SEEN_REQUIRES
|
|
|
|
elif mode == SEEN_REQUIRES:
|
|
if (line and
|
|
not re.search(REQUIRES_RE, line) and
|
|
not line.isspace()):
|
|
# There should be two blank lines before goog.scope
|
|
result += ['\n'] * 2
|
|
result.append('goog.scope(function() {\n')
|
|
insertion_index = len(result)
|
|
result += ['\n'] * num_blank_lines
|
|
mode = IN_SCOPE
|
|
elif line.isspace():
|
|
# Keep track of the number of blank lines before each block of code so
|
|
# that we can move them after the goog.scope line if necessary.
|
|
num_blank_lines += 1
|
|
else:
|
|
# Print the blank lines we saw before this code block
|
|
result += ['\n'] * num_blank_lines
|
|
num_blank_lines = 0
|
|
result.append(line)
|
|
|
|
if mode == IN_SCOPE:
|
|
for symbol in requires:
|
|
if not symbol in globals_to_aliases:
|
|
continue
|
|
|
|
alias = globals_to_aliases[symbol]
|
|
matcher = aliases_to_matchers[alias]
|
|
for match in matcher.finditer(line):
|
|
# Check to make sure we're not in a string.
|
|
# We do this by being as conservative as possible:
|
|
# if there are any quote or double quote characters
|
|
# before the symbol on this line, then bail out.
|
|
before_symbol = line[:match.start(0)]
|
|
if before_symbol.count('"') > 0 or before_symbol.count("'") > 0:
|
|
continue
|
|
|
|
line = line.replace(match.group(0), alias)
|
|
aliases_used.add(alias)
|
|
|
|
if line.isspace():
|
|
# Truncate all-whitespace lines
|
|
result.append('\n')
|
|
else:
|
|
result.append(line)
|
|
|
|
if len(aliases_used):
|
|
aliases_used = [alias for alias in aliases_used]
|
|
aliases_used.sort()
|
|
aliases_used.reverse()
|
|
for alias in aliases_used:
|
|
symbol = aliases_to_globals[alias]
|
|
result.insert(insertion_index,
|
|
'var %s = %s;\n' % (alias, symbol))
|
|
result.append('}); // goog.scope\n')
|
|
return result
|
|
else:
|
|
return None
|
|
|
|
def TransformFileAt(path):
|
|
"""Converts a file into javascript that uses goog.scope.
|
|
|
|
Arguments:
|
|
path: A path to a file.
|
|
"""
|
|
f = open(path)
|
|
lines = Transform(f.readlines())
|
|
if lines:
|
|
f = open(path, 'w')
|
|
for l in lines:
|
|
f.write(l)
|
|
f.close()
|
|
|
|
if __name__ == '__main__':
|
|
args = sys.argv[1:]
|
|
if not len(args):
|
|
args = '.'
|
|
|
|
for file_name in args:
|
|
if os.path.isdir(file_name):
|
|
for root, dirs, files in os.walk(file_name):
|
|
for name in files:
|
|
if name.endswith('.js') and \
|
|
not os.path.islink(os.path.join(root, name)):
|
|
TransformFileAt(os.path.join(root, name))
|
|
else:
|
|
if file_name.endswith('.js') and \
|
|
not os.path.islink(file_name):
|
|
TransformFileAt(file_name)
|