Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / google_input_tools / builder.py
blob4e86ec44cf9d580bb1b648b804f94b1f5a61c83c
1 #!/usr/bin/python
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."""
8 import argparse
9 import json
10 import os
11 import re
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',
18 'closure_library',
19 'closure',
20 'goog',
21 'base.js')
24 def process_file(filename):
25 """Extracts provided and required namespaces.
27 Description:
28 Scans Javascript file for provided and required namespaces.
30 Args:
31 filename: name of the file to process.
33 Returns:
34 Pair of lists, where the first list contains namespaces provided by the file
35 and the second contains a list of requirements.
36 """
38 provides = []
39 requires = []
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.
52 Description:
53 Updates maps for namespace providers and file prerequisites.
55 Args:
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.
59 """
61 p, r = process_file(filename)
63 for name in p:
64 providers[name] = filename
65 for name in r:
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.
74 Description:
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
80 reduce file size.
82 Args:
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.
88 Returns:
89 """
91 # Filename may have already been processed if it was a requirement of a
92 # previous exported file.
93 if source_filename in processed:
94 return
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]
101 if dependency:
102 export(target_file, dependency, providers, requirements, processed)
104 processed.add(source_filename)
106 # Export file
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')
111 try:
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()
117 if comment_block:
118 # Scan for trailing */ in multi-line comment.
119 index = formatted.find('*/')
120 if index >= 0:
121 formatted = formatted[index + 2:]
122 comment_block = False
123 else:
124 formatted = ''
125 # Remove full-line // style comments.
126 if formatted.lstrip().startswith('//'):
127 formatted = ''
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('*/')
137 else:
138 formatted = formatted[:start_comment]
139 comment_block = True
140 start_comment = -1
141 if formatted.strip():
142 target_file.write('%s%s' % (formatted, os.linesep))
143 finally:
144 source_file.close()
145 target_file.write('\n')
148 def extract_sources(options):
149 """Extracts list of sources based on command line options.
151 Args:
152 options: Parsed command line options.
153 Returns:
154 List of source files. If the path option is specified then file paths are
155 absolute. Otherwise, relative paths may be used.
158 sources = None
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:
163 data = []
164 # Strip leading comments.
165 for line in json_file:
166 if not line.startswith('#'):
167 data.append(line)
168 json_object = json.loads(os.linesep.join(data).replace('\'', '\"'))
169 path = options.json_sources.split('.')
170 sources = json_object
171 for key in path:
172 sources = sources[key]
173 if options.path:
174 sources = [os.path.join(options.path, source) for source in sources]
175 else:
176 sources = options.sources
177 return sources
180 def main():
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.'
193 providers = {}
194 requirements = {}
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)
200 processed = set()
201 base_path = base
202 if options.path:
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__':
209 main()