Add ICU message format support
[chromium-blink-merge.git] / third_party / google_input_tools / update.py
blob8d6df8e6193096b08641dcf257555c1a27fe23a3
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 """Performs pull of google-input-tools from local clone of GitHub repository."""
8 import json
9 import logging
10 import optparse
11 import os
12 import re
13 import shutil
14 import subprocess
16 _BASE_REGEX_STRING = r'^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
17 require_regex = re.compile(_BASE_REGEX_STRING % 'require')
18 provide_regex = re.compile(_BASE_REGEX_STRING % 'provide')
20 preamble = [
21 '# Copyright 2014 The Chromium Authors. All rights reserved.',
22 '# Use of this source code is governed by a BSD-style license that can be',
23 '# found in the LICENSE file.',
24 '',
25 '# This file is auto-generated using update.py.',
29 # Entry-points required to build a virtual keyboard.
30 namespaces = [
31 'i18n.input.chrome.inputview.Controller',
32 'i18n.input.chrome.inputview.content.compact.letter',
33 'i18n.input.chrome.inputview.content.compact.util',
34 'i18n.input.chrome.inputview.content.compact.symbol',
35 'i18n.input.chrome.inputview.content.compact.more',
36 'i18n.input.chrome.inputview.content.compact.numberpad',
37 'i18n.input.chrome.inputview.content.ContextlayoutUtil',
38 'i18n.input.chrome.inputview.content.util',
39 'i18n.input.chrome.inputview.EmojiType',
40 'i18n.input.chrome.inputview.layouts.CompactSpaceRow',
41 'i18n.input.chrome.inputview.layouts.RowsOf101',
42 'i18n.input.chrome.inputview.layouts.RowsOf102',
43 'i18n.input.chrome.inputview.layouts.RowsOfCompact',
44 'i18n.input.chrome.inputview.layouts.RowsOfJP',
45 'i18n.input.chrome.inputview.layouts.RowsOfNumberpad',
46 'i18n.input.chrome.inputview.layouts.SpaceRow',
47 'i18n.input.chrome.inputview.layouts.util',
48 'i18n.input.chrome.inputview.layouts.material.CompactSpaceRow',
49 'i18n.input.chrome.inputview.layouts.material.RowsOf101',
50 'i18n.input.chrome.inputview.layouts.material.RowsOf102',
51 'i18n.input.chrome.inputview.layouts.material.RowsOfCompact',
52 'i18n.input.chrome.inputview.layouts.material.RowsOfJP',
53 'i18n.input.chrome.inputview.layouts.material.RowsOfNumberpad',
54 'i18n.input.chrome.inputview.layouts.material.SpaceRow',
55 'i18n.input.chrome.inputview.layouts.material.util',
56 'i18n.input.hwt.util'
59 # Any additional required files.
60 extras = [
61 'common.css',
62 'emoji.css'
66 def process_file(filename):
67 """Extracts provided and required namespaces.
69 Description:
70 Scans Javascript file for provied and required namespaces.
72 Args:
73 filename: name of the file to process.
75 Returns:
76 Pair of lists, where the first list contains namespaces provided by the file
77 and the second contains a list of requirements.
78 """
79 provides = []
80 requires = []
81 file_handle = open(filename, 'r')
82 try:
83 for line in file_handle:
84 if re.match(require_regex, line):
85 requires.append(re.search(require_regex, line).group(1))
86 if re.match(provide_regex, line):
87 provides.append(re.search(provide_regex, line).group(1))
88 finally:
89 file_handle.close()
90 return provides, requires
93 def expand_directories(refs):
94 """Expands any directory references into inputs.
96 Description:
97 Looks for any directories in the provided references. Found directories
98 are recursively searched for .js files.
100 Args:
101 refs: a list of directories.
103 Returns:
104 Pair of maps, where the first maps each namepace to the filename that
105 provides the namespace, and the second maps a filename to prerequisite
106 namespaces.
108 providers = {}
109 requirements = {}
110 for ref in refs:
111 if os.path.isdir(ref):
112 for (root, _, files) in os.walk(ref):
113 for name in files:
114 if name.endswith('js'):
115 filename = os.path.join(root, name)
116 provides, requires = process_file(filename)
117 for p in provides:
118 providers[p] = filename
119 requirements[filename] = []
120 for r in requires:
121 requirements[filename].append(r)
122 return providers, requirements
125 def extract_dependencies(namespace, providers, requirements, dependencies):
126 """Recursively extracts all dependencies for a namespace.
128 Description:
129 Recursively extracts all dependencies for a namespace.
131 Args:
132 namespace: The namespace to process.
133 providers: Mapping of namespace to filename that provides the namespace.
134 requirements: Mapping of filename to a list of prerequisite namespaces.
135 dependencies: List of files required to build inputview.
136 Returns:
139 if namespace in providers:
140 filename = providers[namespace]
141 if filename not in dependencies:
142 for ns in requirements[filename]:
143 extract_dependencies(ns, providers, requirements, dependencies)
144 dependencies.add(filename)
147 def home_dir():
148 """Resolves the user's home directory."""
150 return os.path.expanduser('~')
153 def expand_path_relative_to_home(path):
154 """Resolves a path that is relative to the home directory.
156 Args:
157 path: Relative path.
159 Returns:
160 Resolved path.
163 return os.path.join(os.path.expanduser('~'), path)
166 def get_google_input_tools_sandbox_from_options(options):
167 """Generate the input-input-tools path from the --input flag.
169 Args:
170 options: Flags to update.py.
171 Returns:
172 Path to the google-input-tools sandbox.
175 path = options.input
176 if not path:
177 path = expand_path_relative_to_home('google-input-tools')
178 print 'Unspecified path for google-input-tools. Defaulting to %s' % path
179 return path
182 def get_closure_library_sandbox_from_options(options):
183 """Generate the closure-library path from the --input flag.
185 Args:
186 options: Flags to update.py.
187 Returns:
188 Path to the closure-library sandbox.
191 path = options.lib
192 if not path:
193 path = expand_path_relative_to_home('closure-library')
194 print 'Unspecified path for closure-library. Defaulting to %s' % path
195 return path
198 def copy_file(source, target):
199 """Copies a file from the source to the target location.
201 Args:
202 source: Path to the source file to copy.
203 target: Path to the target location to copy the file.
206 if not os.path.exists(os.path.dirname(target)):
207 os.makedirs(os.path.dirname(target))
208 shutil.copy(source, target)
209 # Ensure correct file permissions.
210 if target.endswith('py'):
211 subprocess.call(['chmod', '+x', target])
212 else:
213 subprocess.call(['chmod', '-x', target])
216 def update_file(filename, input_source, closure_source, target_files):
217 """Updates files in third_party/google_input_tools.
219 Args:
220 filename: The file to update.
221 input_source: Root of the google_input_tools sandbox.
222 closure_source: Root of the closure_library sandbox.
223 target_files: List of relative paths to target files.
226 target = ''
227 if filename.startswith(input_source):
228 target = os.path.join('src', filename[len(input_source)+1:])
229 elif filename.startswith(closure_source):
230 target = os.path.join('third_party/closure_library',
231 filename[len(closure_source)+1:])
232 if target:
233 copy_file(filename, target)
234 target_files.append(os.path.relpath(target, os.getcwd()))
237 def generate_build_file(target_files):
238 """Updates inputview.gypi.
240 Args:
241 target_files: List of files required to build inputview.js.
244 sorted_files = sorted(target_files)
245 with open('inputview.gypi', 'w') as file_handle:
246 file_handle.write(os.linesep.join(preamble))
247 json_data = {'variables': {'inputview_sources': sorted_files}}
248 json_str = json.dumps(json_data, indent=2, separators=(',', ': '))
249 file_handle.write(json_str.replace('\"', '\''))
252 def copy_dir(input_path, sub_dir):
253 """Copies all files in a subdirectory of google-input-tools.
255 Description:
256 Recursive copy of a directory under google-input-tools. Used to copy
257 localization and resource files.
259 Args:
260 input_path: Path to the google-input-tools-sandbox.
261 sub_dir: Subdirectory to copy within google-input-tools sandbox.
263 source_dir = os.path.join(input_path, 'chrome', 'os', sub_dir)
264 for (root, _, files) in os.walk(source_dir):
265 for name in files:
266 filename = os.path.join(root, name)
267 relative_path = filename[len(source_dir) + 1:]
268 target = os.path.join('src', 'chrome', 'os', sub_dir,
269 relative_path)
270 copy_file(filename, target)
273 def main():
274 """The entrypoint for this script."""
276 logging.basicConfig(format='update.py: %(message)s', level=logging.INFO)
278 usage = 'usage: %prog [options] arg'
279 parser = optparse.OptionParser(usage)
280 parser.add_option('-i',
281 '--input',
282 dest='input',
283 action='append',
284 help='Path to the google-input-tools sandbox.')
285 parser.add_option('-l',
286 '--lib',
287 dest='lib',
288 action='store',
289 help='Path to the closure-library sandbox.')
291 (options, _) = parser.parse_args()
293 input_path = get_google_input_tools_sandbox_from_options(options)
294 closure_library_path = get_closure_library_sandbox_from_options(options)
296 if not os.path.isdir(input_path):
297 print 'Could not find google-input-tools sandbox.'
298 exit(1)
299 if not os.path.isdir(closure_library_path):
300 print 'Could not find closure-library sandbox.'
301 exit(1)
303 (providers, requirements) = expand_directories([
304 os.path.join(input_path, 'chrome'),
305 closure_library_path])
307 dependencies = set()
308 for name in namespaces:
309 extract_dependencies(name, providers, requirements, dependencies)
311 target_files = []
312 for name in dependencies:
313 update_file(name, input_path, closure_library_path, target_files)
315 generate_build_file(target_files)
317 # Copy resources
318 copy_dir(input_path, 'inputview/_locales')
319 copy_dir(input_path, 'inputview/images')
320 copy_dir(input_path, 'inputview/config')
321 copy_dir(input_path, 'inputview/layouts')
322 copy_dir(input_path, 'sounds')
324 # Copy extra support files.
325 for name in extras:
326 source = os.path.join(input_path, 'chrome', 'os', 'inputview', name)
327 target = os.path.join('src', 'chrome', 'os', 'inputview', name)
328 copy_file(source, target)
331 if __name__ == '__main__':
332 main()