123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- #!/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)
|