2 # Copyright 2014 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Closure builder for Javascript."""
13 _BASE_REGEX_STRING
= r
'^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s
*\
)'
14 require_regex = re.compile(_BASE_REGEX_STRING % 'require
')
15 provide_regex = re.compile(_BASE_REGEX_STRING % 'provide
')
17 base = os.path.join('third_party
',
24 def process_file(filename):
25 """Extracts provided and required namespaces.
28 Scans Javascript file for provided and required namespaces.
31 filename: name of the file to process.
34 Pair of lists, where the first list contains namespaces provided by the file
35 and the second contains a list of requirements.
40 with open(filename, 'r
') as file_handle:
41 for line in file_handle:
42 if re.match(require_regex, line):
43 requires.append(re.search(require_regex, line).group(1))
44 if re.match(provide_regex, line):
45 provides.append(re.search(provide_regex, line).group(1))
46 return provides, requires
49 def extract_dependencies(filename, providers, requirements):
50 """Extracts provided and required namespaces for a file.
53 Updates maps for namespace providers and file prerequisites.
56 filename: Path of the file to process.
57 providers: Mapping of namespace to filename that provides the namespace.
58 requirements: Mapping of filename to a list of prerequisite namespaces.
61 p, r = process_file(filename)
64 providers[name] = filename
66 if filename not in requirements:
67 requirements[filename] = []
68 requirements[filename].append(name)
71 def export(target_file, source_filename, providers, requirements, processed):
72 """Writes the contents of a file.
75 Appends the contents of the source file to the target file. In order to
76 preserve proper dependencies, each file has its required namespaces
77 processed before exporting the source file itself. The set of exported files
78 is tracked to guard against multiple exports of the same file. Comments as
79 well as 'provide
' and 'require
' statements are removed during to export to
83 target_file: Handle to target file for export.
84 source_filename: Name of the file to export.
85 providers: Map of namespace to filename.
86 requirements: Map of filename to required namespaces.
87 processed: Set of processed files.
91 # Filename may have already been processed if it was a requirement of a
92 # previous exported file.
93 if source_filename in processed:
96 # Export requirements before file.
97 if source_filename in requirements:
98 for namespace in requirements[source_filename]:
99 if namespace in providers:
100 dependency = providers[namespace]
102 export(target_file, dependency, providers, requirements, processed)
104 processed.add(source_filename)
107 for name in providers:
108 if providers[name] == source_filename:
109 target_file.write('// %s%s' % (name, os.linesep))
110 source_file = open(source_filename, 'r
')
112 comment_block = False
113 for line in source_file:
114 # Skip require statements.
115 if not re.match(require_regex, line):
116 formatted = line.rstrip()
118 # Scan for trailing */ in multi-line comment.
119 index = formatted.find('*/')
121 formatted = formatted[index + 2:]
122 comment_block = False
125 # Remove full-line // style comments.
126 if formatted.lstrip().startswith('//'):
128 # Remove /* */ style comments.
129 start_comment = formatted.find('/*')
130 end_comment = formatted.find('*/')
131 while start_comment >= 0:
132 if end_comment > start_comment:
133 formatted = (formatted[:start_comment]
134 + formatted[end_comment + 2:])
135 start_comment = formatted.find('/*')
136 end_comment = formatted.find('*/')
138 formatted = formatted[:start_comment]
141 if formatted.strip():
142 target_file.write('%s%s' % (formatted, os.linesep))
145 target_file.write('\n')
148 def extract_sources(options):
149 """Extracts list of sources based on command line options.
152 options: Parsed command line options.
154 List of source files. If the path option is specified then file paths are
155 absolute. Otherwise, relative paths may be used.
159 if options.json_file:
160 # Optionally load list of source files from a json file. Useful when the
161 # list of files to process is too long for the command line.
162 with open(options.json_file, 'r
') as json_file:
164 # Strip leading comments.
165 for line in json_file:
166 if not line.startswith('#'):
168 json_object
= json
.loads(os
.linesep
.join(data
).replace('\'', '\"'))
169 path
= options
.json_sources
.split('.')
170 sources
= json_object
172 sources
= sources
[key
]
174 sources
= [os
.path
.join(options
.path
, source
) for source
in sources
]
176 sources
= options
.sources
181 """The entrypoint for this script."""
182 parser
= argparse
.ArgumentParser()
183 parser
.add_argument('--sources', nargs
='*')
184 parser
.add_argument('--target', nargs
=1)
185 parser
.add_argument('--json_file', nargs
='?')
186 parser
.add_argument('--json_sources', nargs
='?')
187 parser
.add_argument('--path', nargs
='?')
188 options
= parser
.parse_args()
190 sources
= extract_sources(options
)
191 assert sources
, 'Missing source files.'
195 for filename
in sources
:
196 extract_dependencies(filename
, providers
, requirements
)
198 with
open(options
.target
[0], 'w') as target_file
:
199 target_file
.write('var CLOSURE_NO_DEPS=true;%s' % os
.linesep
)
203 base_path
= os
.path
.join(options
.path
, base_path
)
204 export(target_file
, base_path
, providers
, requirements
, processed
)
205 for source_filename
in sources
:
206 export(target_file
, source_filename
, providers
, requirements
, processed
)
208 if __name__
== '__main__':