3 # Copyright 2010 The Closure Library Authors. All Rights Reserved.
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 """Automatically converts codebases over to goog.scope.
22 ../../../../javascript/closure/bin/scopify.py
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.
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.
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.
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".
42 When an alias is "captured", it can no longer be stubbed out for testing.
47 __author__
= 'nicksantos@google.com (Nick Santos)'
53 REQUIRES_RE
= re
.compile(r
"goog.require\('([^']*)'\)")
55 # Edit this manually if you want something to "always" be aliased.
56 # TODO(nicksantos): Add a flag for this.
60 """Converts the contents of a file into javascript that uses goog.scope.
63 lines: A list of strings, corresponding to each line of the file.
65 A new list of strings, or None if the file was not modified.
69 # Do an initial scan to be sure that this file can be processed.
71 # Skip this file if it has already been scopified.
72 if line
.find('goog.scope') != -1:
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
78 if line
.find('var ') == 0 or line
.find('function ') == 0:
81 for match
in REQUIRES_RE
.finditer(line
):
82 requires
.append(match
.group(1))
84 if len(requires
) == 0:
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:
96 # Generate a map of requires to their aliases
97 aliases_to_globals
= DEFAULT_ALIASES
.copy()
99 index
= req
.rfind('.')
103 alias
= req
[(index
+ 1):]
105 # Don't scopify lowercase namespaces, because they may conflict with
107 if alias
[0].isupper():
108 aliases_to_globals
[alias
] = req
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
)
116 # Insert a goog.scope that aliases all required symbols.
125 insertion_index
= None
131 if re
.search(REQUIRES_RE
, line
):
134 elif mode
== SEEN_REQUIRES
:
136 not re
.search(REQUIRES_RE
, line
) and
138 # There should be two blank lines before goog.scope
140 result
.append('goog.scope(function() {\n')
141 insertion_index
= len(result
)
142 result
+= ['\n'] * num_blank_lines
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.
149 # Print the blank lines we saw before this code block
150 result
+= ['\n'] * num_blank_lines
155 for symbol
in requires
:
156 if not symbol
in globals_to_aliases
:
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:
170 line
= line
.replace(match
.group(0), alias
)
171 aliases_used
.add(alias
)
174 # Truncate all-whitespace lines
179 if len(aliases_used
):
180 aliases_used
= [alias
for alias
in aliases_used
]
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')
192 def TransformFileAt(path
):
193 """Converts a file into javascript that uses goog.scope.
196 path: A path to a file.
199 lines
= Transform(f
.readlines())
206 if __name__
== '__main__':
211 for file_name
in args
:
212 if os
.path
.isdir(file_name
):
213 for root
, dirs
, files
in os
.walk(file_name
):
215 if name
.endswith('.js') and \
216 not os
.path
.islink(os
.path
.join(root
, name
)):
217 TransformFileAt(os
.path
.join(root
, name
))
219 if file_name
.endswith('.js') and \
220 not os
.path
.islink(file_name
):
221 TransformFileAt(file_name
)