tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

scopify.py (6785B)


      1 #!/usr/bin/python
      2 #
      3 # Copyright 2010 The Closure Library Authors. All Rights Reserved.
      4 #
      5 # Licensed under the Apache License, Version 2.0 (the "License");
      6 # you may not use this file except in compliance with the License.
      7 # You may obtain a copy of the License at
      8 #
      9 #      http://www.apache.org/licenses/LICENSE-2.0
     10 #
     11 # Unless required by applicable law or agreed to in writing, software
     12 # distributed under the License is distributed on an "AS-IS" BASIS,
     13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14 # See the License for the specific language governing permissions and
     15 # limitations under the License.
     16 
     17 
     18 """Automatically converts codebases over to goog.scope.
     19 
     20 Usage:
     21 cd path/to/my/dir;
     22 ../../../../javascript/closure/bin/scopify.py
     23 
     24 Scans every file in this directory, recursively. Looks for existing
     25 goog.scope calls, and goog.require'd symbols. If it makes sense to
     26 generate a goog.scope call for the file, then we will do so, and
     27 try to auto-generate some aliases based on the goog.require'd symbols.
     28 
     29 Known Issues:
     30 
     31  When a file is goog.scope'd, the file contents will be indented +2.
     32  This may put some lines over 80 chars. These will need to be fixed manually.
     33 
     34  We will only try to create aliases for capitalized names. We do not check
     35  to see if those names will conflict with any existing locals.
     36 
     37  This creates merge conflicts for every line of every outstanding change.
     38  If you intend to run this on your codebase, make sure your team members
     39  know. Better yet, send them this script so that they can scopify their
     40  outstanding changes and "accept theirs".
     41 
     42  When an alias is "captured", it can no longer be stubbed out for testing.
     43  Run your tests.
     44 
     45 """
     46 
     47 __author__ = 'nicksantos@google.com (Nick Santos)'
     48 
     49 import os.path
     50 import re
     51 import sys
     52 
     53 REQUIRES_RE = re.compile(r"goog.require\('([^']*)'\)")
     54 
     55 # Edit this manually if you want something to "always" be aliased.
     56 # TODO(nicksantos): Add a flag for this.
     57 DEFAULT_ALIASES = {}
     58 
     59 def Transform(lines):
     60  """Converts the contents of a file into javascript that uses goog.scope.
     61 
     62  Arguments:
     63    lines: A list of strings, corresponding to each line of the file.
     64  Returns:
     65    A new list of strings, or None if the file was not modified.
     66  """
     67  requires = []
     68 
     69  # Do an initial scan to be sure that this file can be processed.
     70  for line in lines:
     71    # Skip this file if it has already been scopified.
     72    if line.find('goog.scope') != -1:
     73      return None
     74 
     75    # If there are any global vars or functions, then we also have
     76    # to skip the whole file. We might be able to deal with this
     77    # more elegantly.
     78    if line.find('var ') == 0 or line.find('function ') == 0:
     79      return None
     80 
     81    for match in REQUIRES_RE.finditer(line):
     82      requires.append(match.group(1))
     83 
     84  if len(requires) == 0:
     85    return None
     86 
     87  # Backwards-sort the requires, so that when one is a substring of another,
     88  # we match the longer one first.
     89  for val in DEFAULT_ALIASES.values():
     90    if requires.count(val) == 0:
     91      requires.append(val)
     92 
     93  requires.sort()
     94  requires.reverse()
     95 
     96  # Generate a map of requires to their aliases
     97  aliases_to_globals = DEFAULT_ALIASES.copy()
     98  for req in requires:
     99    index = req.rfind('.')
    100    if index == -1:
    101      alias = req
    102    else:
    103      alias = req[(index + 1):]
    104 
    105    # Don't scopify lowercase namespaces, because they may conflict with
    106    # local variables.
    107    if alias[0].isupper():
    108      aliases_to_globals[alias] = req
    109 
    110  aliases_to_matchers = {}
    111  globals_to_aliases = {}
    112  for alias, symbol in aliases_to_globals.items():
    113    globals_to_aliases[symbol] = alias
    114    aliases_to_matchers[alias] = re.compile('\\b%s\\b' % symbol)
    115 
    116  # Insert a goog.scope that aliases all required symbols.
    117  result = []
    118 
    119  START = 0
    120  SEEN_REQUIRES = 1
    121  IN_SCOPE = 2
    122 
    123  mode = START
    124  aliases_used = set()
    125  insertion_index = None
    126  num_blank_lines = 0
    127  for line in lines:
    128    if mode == START:
    129      result.append(line)
    130 
    131      if re.search(REQUIRES_RE, line):
    132        mode = SEEN_REQUIRES
    133 
    134    elif mode == SEEN_REQUIRES:
    135      if (line and
    136          not re.search(REQUIRES_RE, line) and
    137          not line.isspace()):
    138        # There should be two blank lines before goog.scope
    139        result += ['\n'] * 2
    140        result.append('goog.scope(function() {\n')
    141        insertion_index = len(result)
    142        result += ['\n'] * num_blank_lines
    143        mode = IN_SCOPE
    144      elif line.isspace():
    145        # Keep track of the number of blank lines before each block of code so
    146        # that we can move them after the goog.scope line if necessary.
    147        num_blank_lines += 1
    148      else:
    149        # Print the blank lines we saw before this code block
    150        result += ['\n'] * num_blank_lines
    151        num_blank_lines = 0
    152        result.append(line)
    153 
    154    if mode == IN_SCOPE:
    155      for symbol in requires:
    156        if not symbol in globals_to_aliases:
    157          continue
    158 
    159        alias = globals_to_aliases[symbol]
    160        matcher = aliases_to_matchers[alias]
    161        for match in matcher.finditer(line):
    162          # Check to make sure we're not in a string.
    163          # We do this by being as conservative as possible:
    164          # if there are any quote or double quote characters
    165          # before the symbol on this line, then bail out.
    166          before_symbol = line[:match.start(0)]
    167          if before_symbol.count('"') > 0 or before_symbol.count("'") > 0:
    168            continue
    169 
    170          line = line.replace(match.group(0), alias)
    171          aliases_used.add(alias)
    172 
    173      if line.isspace():
    174        # Truncate all-whitespace lines
    175        result.append('\n')
    176      else:
    177        result.append(line)
    178 
    179  if len(aliases_used):
    180    aliases_used = [alias for alias in aliases_used]
    181    aliases_used.sort()
    182    aliases_used.reverse()
    183    for alias in aliases_used:
    184      symbol = aliases_to_globals[alias]
    185      result.insert(insertion_index,
    186                    'var %s = %s;\n' % (alias, symbol))
    187    result.append('});  // goog.scope\n')
    188    return result
    189  else:
    190    return None
    191 
    192 def TransformFileAt(path):
    193  """Converts a file into javascript that uses goog.scope.
    194 
    195  Arguments:
    196    path: A path to a file.
    197  """
    198  f = open(path)
    199  lines = Transform(f.readlines())
    200  if lines:
    201    f = open(path, 'w')
    202    for l in lines:
    203      f.write(l)
    204    f.close()
    205 
    206 if __name__ == '__main__':
    207  args = sys.argv[1:]
    208  if not len(args):
    209    args = '.'
    210 
    211  for file_name in args:
    212    if os.path.isdir(file_name):
    213      for root, dirs, files in os.walk(file_name):
    214        for name in files:
    215          if name.endswith('.js') and \
    216              not os.path.islink(os.path.join(root, name)):
    217            TransformFileAt(os.path.join(root, name))
    218    else:
    219      if file_name.endswith('.js') and \
    220          not os.path.islink(file_name):
    221        TransformFileAt(file_name)