xref: /aosp_15_r20/external/cronet/build/locale_tool.py (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1*6777b538SAndroid Build Coastguard Worker#!/usr/bin/env vpython3
2*6777b538SAndroid Build Coastguard Worker# Copyright 2019 The Chromium Authors
3*6777b538SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*6777b538SAndroid Build Coastguard Worker# found in the LICENSE file.
5*6777b538SAndroid Build Coastguard Worker
6*6777b538SAndroid Build Coastguard Worker"""Helper script used to manage locale-related files in Chromium.
7*6777b538SAndroid Build Coastguard Worker
8*6777b538SAndroid Build Coastguard WorkerThis script is used to check, and potentially fix, many locale-related files
9*6777b538SAndroid Build Coastguard Workerin your Chromium workspace, such as:
10*6777b538SAndroid Build Coastguard Worker
11*6777b538SAndroid Build Coastguard Worker  - GRIT input files (.grd) and the corresponding translations (.xtb).
12*6777b538SAndroid Build Coastguard Worker
13*6777b538SAndroid Build Coastguard Worker  - BUILD.gn files listing Android localized resource string resource .xml
14*6777b538SAndroid Build Coastguard Worker    generated by GRIT for all supported Chrome locales. These correspond to
15*6777b538SAndroid Build Coastguard Worker    <output> elements that use the type="android" attribute.
16*6777b538SAndroid Build Coastguard Worker
17*6777b538SAndroid Build Coastguard WorkerThe --scan-dir <dir> option can be used to check for all files under a specific
18*6777b538SAndroid Build Coastguard Workerdirectory, and the --fix-inplace option can be used to try fixing any file
19*6777b538SAndroid Build Coastguard Workerthat doesn't pass the check.
20*6777b538SAndroid Build Coastguard Worker
21*6777b538SAndroid Build Coastguard WorkerThis can be very handy to avoid tedious and repetitive work when adding new
22*6777b538SAndroid Build Coastguard Workertranslations / locales to the Chrome code base, since this script can update
23*6777b538SAndroid Build Coastguard Workersaid input files for you.
24*6777b538SAndroid Build Coastguard Worker
25*6777b538SAndroid Build Coastguard WorkerImportant note: checks and fix may fail on some input files. For example
26*6777b538SAndroid Build Coastguard Workerremoting/resources/remoting_strings.grd contains an in-line comment element
27*6777b538SAndroid Build Coastguard Workerinside its <outputs> section that breaks the script. The check will fail, and
28*6777b538SAndroid Build Coastguard Workertrying to fix it too, but at least the file will not be modified.
29*6777b538SAndroid Build Coastguard Worker"""
30*6777b538SAndroid Build Coastguard Worker
31*6777b538SAndroid Build Coastguard Worker
32*6777b538SAndroid Build Coastguard Workerimport argparse
33*6777b538SAndroid Build Coastguard Workerimport json
34*6777b538SAndroid Build Coastguard Workerimport os
35*6777b538SAndroid Build Coastguard Workerimport re
36*6777b538SAndroid Build Coastguard Workerimport shutil
37*6777b538SAndroid Build Coastguard Workerimport subprocess
38*6777b538SAndroid Build Coastguard Workerimport sys
39*6777b538SAndroid Build Coastguard Workerimport unittest
40*6777b538SAndroid Build Coastguard Worker
41*6777b538SAndroid Build Coastguard Worker# Assume this script is under build/
42*6777b538SAndroid Build Coastguard Worker_SCRIPT_DIR = os.path.dirname(__file__)
43*6777b538SAndroid Build Coastguard Worker_SCRIPT_NAME = os.path.join(_SCRIPT_DIR, os.path.basename(__file__))
44*6777b538SAndroid Build Coastguard Worker_TOP_SRC_DIR = os.path.join(_SCRIPT_DIR, '..')
45*6777b538SAndroid Build Coastguard Worker
46*6777b538SAndroid Build Coastguard Worker# Need to import android/gyp/util/resource_utils.py here.
47*6777b538SAndroid Build Coastguard Workersys.path.insert(0, os.path.join(_SCRIPT_DIR, 'android/gyp'))
48*6777b538SAndroid Build Coastguard Worker
49*6777b538SAndroid Build Coastguard Workerfrom util import build_utils
50*6777b538SAndroid Build Coastguard Workerfrom util import resource_utils
51*6777b538SAndroid Build Coastguard Worker
52*6777b538SAndroid Build Coastguard Worker
53*6777b538SAndroid Build Coastguard Worker# This locale is the default and doesn't have translations.
54*6777b538SAndroid Build Coastguard Worker_DEFAULT_LOCALE = 'en-US'
55*6777b538SAndroid Build Coastguard Worker
56*6777b538SAndroid Build Coastguard Worker# Misc terminal codes to provide human friendly progress output.
57*6777b538SAndroid Build Coastguard Worker_CONSOLE_CODE_MOVE_CURSOR_TO_COLUMN_0 = '\x1b[0G'
58*6777b538SAndroid Build Coastguard Worker_CONSOLE_CODE_ERASE_LINE = '\x1b[K'
59*6777b538SAndroid Build Coastguard Worker_CONSOLE_START_LINE = (
60*6777b538SAndroid Build Coastguard Worker    _CONSOLE_CODE_MOVE_CURSOR_TO_COLUMN_0 + _CONSOLE_CODE_ERASE_LINE)
61*6777b538SAndroid Build Coastguard Worker
62*6777b538SAndroid Build Coastguard Worker##########################################################################
63*6777b538SAndroid Build Coastguard Worker##########################################################################
64*6777b538SAndroid Build Coastguard Worker#####
65*6777b538SAndroid Build Coastguard Worker#####    G E N E R I C   H E L P E R   F U N C T I O N S
66*6777b538SAndroid Build Coastguard Worker#####
67*6777b538SAndroid Build Coastguard Worker##########################################################################
68*6777b538SAndroid Build Coastguard Worker##########################################################################
69*6777b538SAndroid Build Coastguard Worker
70*6777b538SAndroid Build Coastguard Workerdef _FixChromiumLangAttribute(lang):
71*6777b538SAndroid Build Coastguard Worker  """Map XML "lang" attribute values to Chromium locale names."""
72*6777b538SAndroid Build Coastguard Worker  _CHROMIUM_LANG_FIXES = {
73*6777b538SAndroid Build Coastguard Worker      'en': 'en-US',  # For now, Chromium doesn't have an 'en' locale.
74*6777b538SAndroid Build Coastguard Worker      'iw': 'he',  # 'iw' is the obsolete form of ISO 639-1 for Hebrew
75*6777b538SAndroid Build Coastguard Worker      'no': 'nb',  # 'no' is used by the Translation Console for Norwegian (nb).
76*6777b538SAndroid Build Coastguard Worker  }
77*6777b538SAndroid Build Coastguard Worker  return _CHROMIUM_LANG_FIXES.get(lang, lang)
78*6777b538SAndroid Build Coastguard Worker
79*6777b538SAndroid Build Coastguard Worker
80*6777b538SAndroid Build Coastguard Workerdef _FixTranslationConsoleLocaleName(locale):
81*6777b538SAndroid Build Coastguard Worker  _FIXES = {
82*6777b538SAndroid Build Coastguard Worker      'nb': 'no',  # Norwegian.
83*6777b538SAndroid Build Coastguard Worker      'he': 'iw',  # Hebrew
84*6777b538SAndroid Build Coastguard Worker  }
85*6777b538SAndroid Build Coastguard Worker  return _FIXES.get(locale, locale)
86*6777b538SAndroid Build Coastguard Worker
87*6777b538SAndroid Build Coastguard Worker
88*6777b538SAndroid Build Coastguard Workerdef _CompareLocaleLists(list_a, list_expected, list_name):
89*6777b538SAndroid Build Coastguard Worker  """Compare two lists of locale names. Print errors if they differ.
90*6777b538SAndroid Build Coastguard Worker
91*6777b538SAndroid Build Coastguard Worker  Args:
92*6777b538SAndroid Build Coastguard Worker    list_a: First list of locales.
93*6777b538SAndroid Build Coastguard Worker    list_expected: Second list of locales, as expected.
94*6777b538SAndroid Build Coastguard Worker    list_name: Name of list printed in error messages.
95*6777b538SAndroid Build Coastguard Worker  Returns:
96*6777b538SAndroid Build Coastguard Worker    On success, return False. On error, print error messages and return True.
97*6777b538SAndroid Build Coastguard Worker  """
98*6777b538SAndroid Build Coastguard Worker  errors = []
99*6777b538SAndroid Build Coastguard Worker  missing_locales = sorted(set(list_a) - set(list_expected))
100*6777b538SAndroid Build Coastguard Worker  if missing_locales:
101*6777b538SAndroid Build Coastguard Worker    errors.append('Missing locales: %s' % missing_locales)
102*6777b538SAndroid Build Coastguard Worker
103*6777b538SAndroid Build Coastguard Worker  extra_locales = sorted(set(list_expected) - set(list_a))
104*6777b538SAndroid Build Coastguard Worker  if extra_locales:
105*6777b538SAndroid Build Coastguard Worker    errors.append('Unexpected locales: %s' % extra_locales)
106*6777b538SAndroid Build Coastguard Worker
107*6777b538SAndroid Build Coastguard Worker  if errors:
108*6777b538SAndroid Build Coastguard Worker    print('Errors in %s definition:' % list_name)
109*6777b538SAndroid Build Coastguard Worker    for error in errors:
110*6777b538SAndroid Build Coastguard Worker      print('  %s\n' % error)
111*6777b538SAndroid Build Coastguard Worker    return True
112*6777b538SAndroid Build Coastguard Worker
113*6777b538SAndroid Build Coastguard Worker  return False
114*6777b538SAndroid Build Coastguard Worker
115*6777b538SAndroid Build Coastguard Worker
116*6777b538SAndroid Build Coastguard Workerdef _BuildIntervalList(input_list, predicate):
117*6777b538SAndroid Build Coastguard Worker  """Find ranges of contiguous list items that pass a given predicate.
118*6777b538SAndroid Build Coastguard Worker
119*6777b538SAndroid Build Coastguard Worker  Args:
120*6777b538SAndroid Build Coastguard Worker    input_list: An input list of items of any type.
121*6777b538SAndroid Build Coastguard Worker    predicate: A function that takes a list item and return True if it
122*6777b538SAndroid Build Coastguard Worker      passes a given test.
123*6777b538SAndroid Build Coastguard Worker  Returns:
124*6777b538SAndroid Build Coastguard Worker    A list of (start_pos, end_pos) tuples, where all items in
125*6777b538SAndroid Build Coastguard Worker    [start_pos, end_pos) pass the predicate.
126*6777b538SAndroid Build Coastguard Worker  """
127*6777b538SAndroid Build Coastguard Worker  result = []
128*6777b538SAndroid Build Coastguard Worker  size = len(input_list)
129*6777b538SAndroid Build Coastguard Worker  start = 0
130*6777b538SAndroid Build Coastguard Worker  while True:
131*6777b538SAndroid Build Coastguard Worker    # Find first item in list that passes the predicate.
132*6777b538SAndroid Build Coastguard Worker    while start < size and not predicate(input_list[start]):
133*6777b538SAndroid Build Coastguard Worker      start += 1
134*6777b538SAndroid Build Coastguard Worker
135*6777b538SAndroid Build Coastguard Worker    if start >= size:
136*6777b538SAndroid Build Coastguard Worker      return result
137*6777b538SAndroid Build Coastguard Worker
138*6777b538SAndroid Build Coastguard Worker    # Find first item in the rest of the list that does not pass the
139*6777b538SAndroid Build Coastguard Worker    # predicate.
140*6777b538SAndroid Build Coastguard Worker    end = start + 1
141*6777b538SAndroid Build Coastguard Worker    while end < size and predicate(input_list[end]):
142*6777b538SAndroid Build Coastguard Worker      end += 1
143*6777b538SAndroid Build Coastguard Worker
144*6777b538SAndroid Build Coastguard Worker    result.append((start, end))
145*6777b538SAndroid Build Coastguard Worker    start = end + 1
146*6777b538SAndroid Build Coastguard Worker
147*6777b538SAndroid Build Coastguard Worker
148*6777b538SAndroid Build Coastguard Workerdef _SortListSubRange(input_list, start, end, key_func):
149*6777b538SAndroid Build Coastguard Worker  """Sort an input list's sub-range according to a specific key function.
150*6777b538SAndroid Build Coastguard Worker
151*6777b538SAndroid Build Coastguard Worker  Args:
152*6777b538SAndroid Build Coastguard Worker    input_list: An input list.
153*6777b538SAndroid Build Coastguard Worker    start: Sub-range starting position in list.
154*6777b538SAndroid Build Coastguard Worker    end: Sub-range limit position in list.
155*6777b538SAndroid Build Coastguard Worker    key_func: A function that extracts a sort key from a line.
156*6777b538SAndroid Build Coastguard Worker  Returns:
157*6777b538SAndroid Build Coastguard Worker    A copy of |input_list|, with all items in [|start|, |end|) sorted
158*6777b538SAndroid Build Coastguard Worker    according to |key_func|.
159*6777b538SAndroid Build Coastguard Worker  """
160*6777b538SAndroid Build Coastguard Worker  result = input_list[:start]
161*6777b538SAndroid Build Coastguard Worker  inputs = []
162*6777b538SAndroid Build Coastguard Worker  for pos in xrange(start, end):
163*6777b538SAndroid Build Coastguard Worker    line = input_list[pos]
164*6777b538SAndroid Build Coastguard Worker    key = key_func(line)
165*6777b538SAndroid Build Coastguard Worker    inputs.append((key, line))
166*6777b538SAndroid Build Coastguard Worker
167*6777b538SAndroid Build Coastguard Worker  for _, line in sorted(inputs):
168*6777b538SAndroid Build Coastguard Worker    result.append(line)
169*6777b538SAndroid Build Coastguard Worker
170*6777b538SAndroid Build Coastguard Worker  result += input_list[end:]
171*6777b538SAndroid Build Coastguard Worker  return result
172*6777b538SAndroid Build Coastguard Worker
173*6777b538SAndroid Build Coastguard Worker
174*6777b538SAndroid Build Coastguard Workerdef _SortElementsRanges(lines, element_predicate, element_key):
175*6777b538SAndroid Build Coastguard Worker  """Sort all elements of a given type in a list of lines by a given key.
176*6777b538SAndroid Build Coastguard Worker
177*6777b538SAndroid Build Coastguard Worker  Args:
178*6777b538SAndroid Build Coastguard Worker    lines: input lines.
179*6777b538SAndroid Build Coastguard Worker    element_predicate: predicate function to select elements to sort.
180*6777b538SAndroid Build Coastguard Worker    element_key: lambda returning a comparison key for each element that
181*6777b538SAndroid Build Coastguard Worker      passes the predicate.
182*6777b538SAndroid Build Coastguard Worker  Returns:
183*6777b538SAndroid Build Coastguard Worker    A new list of input lines, with lines [start..end) sorted.
184*6777b538SAndroid Build Coastguard Worker  """
185*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(lines, element_predicate)
186*6777b538SAndroid Build Coastguard Worker  for start, end in intervals:
187*6777b538SAndroid Build Coastguard Worker    lines = _SortListSubRange(lines, start, end, element_key)
188*6777b538SAndroid Build Coastguard Worker
189*6777b538SAndroid Build Coastguard Worker  return lines
190*6777b538SAndroid Build Coastguard Worker
191*6777b538SAndroid Build Coastguard Worker
192*6777b538SAndroid Build Coastguard Workerdef _ProcessFile(input_file, locales, check_func, fix_func):
193*6777b538SAndroid Build Coastguard Worker  """Process a given input file, potentially fixing it.
194*6777b538SAndroid Build Coastguard Worker
195*6777b538SAndroid Build Coastguard Worker  Args:
196*6777b538SAndroid Build Coastguard Worker    input_file: Input file path.
197*6777b538SAndroid Build Coastguard Worker    locales: List of Chrome locales to consider / expect.
198*6777b538SAndroid Build Coastguard Worker    check_func: A lambda called to check the input file lines with
199*6777b538SAndroid Build Coastguard Worker      (input_lines, locales) argument. It must return an list of error
200*6777b538SAndroid Build Coastguard Worker      messages, or None on success.
201*6777b538SAndroid Build Coastguard Worker    fix_func: None, or a lambda called to fix the input file lines with
202*6777b538SAndroid Build Coastguard Worker      (input_lines, locales). It must return the new list of lines for
203*6777b538SAndroid Build Coastguard Worker      the input file, and may raise an Exception in case of error.
204*6777b538SAndroid Build Coastguard Worker  Returns:
205*6777b538SAndroid Build Coastguard Worker    True at the moment.
206*6777b538SAndroid Build Coastguard Worker  """
207*6777b538SAndroid Build Coastguard Worker  print('%sProcessing %s...' % (_CONSOLE_START_LINE, input_file), end=' ')
208*6777b538SAndroid Build Coastguard Worker  sys.stdout.flush()
209*6777b538SAndroid Build Coastguard Worker  with open(input_file) as f:
210*6777b538SAndroid Build Coastguard Worker    input_lines = f.readlines()
211*6777b538SAndroid Build Coastguard Worker  errors = check_func(input_file, input_lines, locales)
212*6777b538SAndroid Build Coastguard Worker  if errors:
213*6777b538SAndroid Build Coastguard Worker    print('\n%s%s' % (_CONSOLE_START_LINE, '\n'.join(errors)))
214*6777b538SAndroid Build Coastguard Worker    if fix_func:
215*6777b538SAndroid Build Coastguard Worker      try:
216*6777b538SAndroid Build Coastguard Worker        input_lines = fix_func(input_file, input_lines, locales)
217*6777b538SAndroid Build Coastguard Worker        output = ''.join(input_lines)
218*6777b538SAndroid Build Coastguard Worker        with open(input_file, 'wt') as f:
219*6777b538SAndroid Build Coastguard Worker          f.write(output)
220*6777b538SAndroid Build Coastguard Worker        print('Fixed %s.' % input_file)
221*6777b538SAndroid Build Coastguard Worker      except Exception as e:  # pylint: disable=broad-except
222*6777b538SAndroid Build Coastguard Worker        print('Skipped %s: %s' % (input_file, e))
223*6777b538SAndroid Build Coastguard Worker
224*6777b538SAndroid Build Coastguard Worker  return True
225*6777b538SAndroid Build Coastguard Worker
226*6777b538SAndroid Build Coastguard Worker
227*6777b538SAndroid Build Coastguard Workerdef _ScanDirectoriesForFiles(scan_dirs, file_predicate):
228*6777b538SAndroid Build Coastguard Worker  """Scan a directory for files that match a given predicate.
229*6777b538SAndroid Build Coastguard Worker
230*6777b538SAndroid Build Coastguard Worker  Args:
231*6777b538SAndroid Build Coastguard Worker    scan_dir: A list of top-level directories to start scan in.
232*6777b538SAndroid Build Coastguard Worker    file_predicate: lambda function which is passed the file's base name
233*6777b538SAndroid Build Coastguard Worker      and returns True if its full path, relative to |scan_dir|, should be
234*6777b538SAndroid Build Coastguard Worker      passed in the result.
235*6777b538SAndroid Build Coastguard Worker  Returns:
236*6777b538SAndroid Build Coastguard Worker    A list of file full paths.
237*6777b538SAndroid Build Coastguard Worker  """
238*6777b538SAndroid Build Coastguard Worker  result = []
239*6777b538SAndroid Build Coastguard Worker  for src_dir in scan_dirs:
240*6777b538SAndroid Build Coastguard Worker    for root, _, files in os.walk(src_dir):
241*6777b538SAndroid Build Coastguard Worker      result.extend(os.path.join(root, f) for f in files if file_predicate(f))
242*6777b538SAndroid Build Coastguard Worker  return result
243*6777b538SAndroid Build Coastguard Worker
244*6777b538SAndroid Build Coastguard Worker
245*6777b538SAndroid Build Coastguard Workerdef _WriteFile(file_path, file_data):
246*6777b538SAndroid Build Coastguard Worker  """Write |file_data| to |file_path|."""
247*6777b538SAndroid Build Coastguard Worker  with open(file_path, 'w') as f:
248*6777b538SAndroid Build Coastguard Worker    f.write(file_data)
249*6777b538SAndroid Build Coastguard Worker
250*6777b538SAndroid Build Coastguard Worker
251*6777b538SAndroid Build Coastguard Workerdef _FindGnExecutable():
252*6777b538SAndroid Build Coastguard Worker  """Locate the real GN executable used by this Chromium checkout.
253*6777b538SAndroid Build Coastguard Worker
254*6777b538SAndroid Build Coastguard Worker  This is needed because the depot_tools 'gn' wrapper script will look
255*6777b538SAndroid Build Coastguard Worker  for .gclient and other things we really don't need here.
256*6777b538SAndroid Build Coastguard Worker
257*6777b538SAndroid Build Coastguard Worker  Returns:
258*6777b538SAndroid Build Coastguard Worker    Path of real host GN executable from current Chromium src/ checkout.
259*6777b538SAndroid Build Coastguard Worker  """
260*6777b538SAndroid Build Coastguard Worker  # Simply scan buildtools/*/gn and return the first one found so we don't
261*6777b538SAndroid Build Coastguard Worker  # have to guess the platform-specific sub-directory name (e.g. 'linux64'
262*6777b538SAndroid Build Coastguard Worker  # for 64-bit Linux machines).
263*6777b538SAndroid Build Coastguard Worker  buildtools_dir = os.path.join(_TOP_SRC_DIR, 'buildtools')
264*6777b538SAndroid Build Coastguard Worker  for subdir in os.listdir(buildtools_dir):
265*6777b538SAndroid Build Coastguard Worker    subdir_path = os.path.join(buildtools_dir, subdir)
266*6777b538SAndroid Build Coastguard Worker    if not os.path.isdir(subdir_path):
267*6777b538SAndroid Build Coastguard Worker      continue
268*6777b538SAndroid Build Coastguard Worker    gn_path = os.path.join(subdir_path, 'gn')
269*6777b538SAndroid Build Coastguard Worker    if os.path.exists(gn_path):
270*6777b538SAndroid Build Coastguard Worker      return gn_path
271*6777b538SAndroid Build Coastguard Worker  return None
272*6777b538SAndroid Build Coastguard Worker
273*6777b538SAndroid Build Coastguard Worker
274*6777b538SAndroid Build Coastguard Workerdef _PrettyPrintListAsLines(input_list, available_width, trailing_comma=False):
275*6777b538SAndroid Build Coastguard Worker  result = []
276*6777b538SAndroid Build Coastguard Worker  input_str = ', '.join(input_list)
277*6777b538SAndroid Build Coastguard Worker  while len(input_str) > available_width:
278*6777b538SAndroid Build Coastguard Worker    pos = input_str.rfind(',', 0, available_width)
279*6777b538SAndroid Build Coastguard Worker    result.append(input_str[:pos + 1])
280*6777b538SAndroid Build Coastguard Worker    input_str = input_str[pos + 1:].lstrip()
281*6777b538SAndroid Build Coastguard Worker  if trailing_comma and input_str:
282*6777b538SAndroid Build Coastguard Worker    input_str += ','
283*6777b538SAndroid Build Coastguard Worker  result.append(input_str)
284*6777b538SAndroid Build Coastguard Worker  return result
285*6777b538SAndroid Build Coastguard Worker
286*6777b538SAndroid Build Coastguard Worker
287*6777b538SAndroid Build Coastguard Workerclass _PrettyPrintListAsLinesTest(unittest.TestCase):
288*6777b538SAndroid Build Coastguard Worker
289*6777b538SAndroid Build Coastguard Worker  def test_empty_list(self):
290*6777b538SAndroid Build Coastguard Worker    self.assertListEqual([''], _PrettyPrintListAsLines([], 10))
291*6777b538SAndroid Build Coastguard Worker
292*6777b538SAndroid Build Coastguard Worker  def test_wrapping(self):
293*6777b538SAndroid Build Coastguard Worker    input_list = ['foo', 'bar', 'zoo', 'tool']
294*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
295*6777b538SAndroid Build Coastguard Worker        _PrettyPrintListAsLines(input_list, 8),
296*6777b538SAndroid Build Coastguard Worker        ['foo,', 'bar,', 'zoo,', 'tool'])
297*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
298*6777b538SAndroid Build Coastguard Worker        _PrettyPrintListAsLines(input_list, 12), ['foo, bar,', 'zoo, tool'])
299*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
300*6777b538SAndroid Build Coastguard Worker        _PrettyPrintListAsLines(input_list, 79), ['foo, bar, zoo, tool'])
301*6777b538SAndroid Build Coastguard Worker
302*6777b538SAndroid Build Coastguard Worker  def test_trailing_comma(self):
303*6777b538SAndroid Build Coastguard Worker    input_list = ['foo', 'bar', 'zoo', 'tool']
304*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
305*6777b538SAndroid Build Coastguard Worker        _PrettyPrintListAsLines(input_list, 8, trailing_comma=True),
306*6777b538SAndroid Build Coastguard Worker        ['foo,', 'bar,', 'zoo,', 'tool,'])
307*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
308*6777b538SAndroid Build Coastguard Worker        _PrettyPrintListAsLines(input_list, 12, trailing_comma=True),
309*6777b538SAndroid Build Coastguard Worker        ['foo, bar,', 'zoo, tool,'])
310*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
311*6777b538SAndroid Build Coastguard Worker        _PrettyPrintListAsLines(input_list, 79, trailing_comma=True),
312*6777b538SAndroid Build Coastguard Worker        ['foo, bar, zoo, tool,'])
313*6777b538SAndroid Build Coastguard Worker
314*6777b538SAndroid Build Coastguard Worker
315*6777b538SAndroid Build Coastguard Worker##########################################################################
316*6777b538SAndroid Build Coastguard Worker##########################################################################
317*6777b538SAndroid Build Coastguard Worker#####
318*6777b538SAndroid Build Coastguard Worker#####    L O C A L E S   L I S T S
319*6777b538SAndroid Build Coastguard Worker#####
320*6777b538SAndroid Build Coastguard Worker##########################################################################
321*6777b538SAndroid Build Coastguard Worker##########################################################################
322*6777b538SAndroid Build Coastguard Worker
323*6777b538SAndroid Build Coastguard Worker# Various list of locales that will be extracted from build/config/locales.gni
324*6777b538SAndroid Build Coastguard Worker# Do not use these directly, use ChromeLocales(), and IosUnsupportedLocales()
325*6777b538SAndroid Build Coastguard Worker# instead to access these lists.
326*6777b538SAndroid Build Coastguard Worker_INTERNAL_CHROME_LOCALES = []
327*6777b538SAndroid Build Coastguard Worker_INTERNAL_IOS_UNSUPPORTED_LOCALES = []
328*6777b538SAndroid Build Coastguard Worker
329*6777b538SAndroid Build Coastguard Worker
330*6777b538SAndroid Build Coastguard Workerdef ChromeLocales():
331*6777b538SAndroid Build Coastguard Worker  """Return the list of all locales supported by Chrome."""
332*6777b538SAndroid Build Coastguard Worker  if not _INTERNAL_CHROME_LOCALES:
333*6777b538SAndroid Build Coastguard Worker    _ExtractAllChromeLocalesLists()
334*6777b538SAndroid Build Coastguard Worker  return _INTERNAL_CHROME_LOCALES
335*6777b538SAndroid Build Coastguard Worker
336*6777b538SAndroid Build Coastguard Worker
337*6777b538SAndroid Build Coastguard Workerdef IosUnsupportedLocales():
338*6777b538SAndroid Build Coastguard Worker  """Return the list of locales that are unsupported on iOS."""
339*6777b538SAndroid Build Coastguard Worker  if not _INTERNAL_IOS_UNSUPPORTED_LOCALES:
340*6777b538SAndroid Build Coastguard Worker    _ExtractAllChromeLocalesLists()
341*6777b538SAndroid Build Coastguard Worker  return _INTERNAL_IOS_UNSUPPORTED_LOCALES
342*6777b538SAndroid Build Coastguard Worker
343*6777b538SAndroid Build Coastguard Worker
344*6777b538SAndroid Build Coastguard Workerdef _PrepareTinyGnWorkspace(work_dir, out_subdir_name='out'):
345*6777b538SAndroid Build Coastguard Worker  """Populate an empty directory with a tiny set of working GN config files.
346*6777b538SAndroid Build Coastguard Worker
347*6777b538SAndroid Build Coastguard Worker  This allows us to run 'gn gen <out> --root <work_dir>' as fast as possible
348*6777b538SAndroid Build Coastguard Worker  to generate files containing the locales list. This takes about 300ms on
349*6777b538SAndroid Build Coastguard Worker  a decent machine, instead of more than 5 seconds when running the equivalent
350*6777b538SAndroid Build Coastguard Worker  commands from a real Chromium workspace, which requires regenerating more
351*6777b538SAndroid Build Coastguard Worker  than 23k targets.
352*6777b538SAndroid Build Coastguard Worker
353*6777b538SAndroid Build Coastguard Worker  Args:
354*6777b538SAndroid Build Coastguard Worker    work_dir: target working directory.
355*6777b538SAndroid Build Coastguard Worker    out_subdir_name: Name of output sub-directory.
356*6777b538SAndroid Build Coastguard Worker  Returns:
357*6777b538SAndroid Build Coastguard Worker    Full path of output directory created inside |work_dir|.
358*6777b538SAndroid Build Coastguard Worker  """
359*6777b538SAndroid Build Coastguard Worker  # Create top-level .gn file that must point to the BUILDCONFIG.gn.
360*6777b538SAndroid Build Coastguard Worker  _WriteFile(os.path.join(work_dir, '.gn'),
361*6777b538SAndroid Build Coastguard Worker             'buildconfig = "//BUILDCONFIG.gn"\n')
362*6777b538SAndroid Build Coastguard Worker  # Create BUILDCONFIG.gn which must set a default toolchain. Also add
363*6777b538SAndroid Build Coastguard Worker  # all variables that may be used in locales.gni in a declare_args() block.
364*6777b538SAndroid Build Coastguard Worker  _WriteFile(
365*6777b538SAndroid Build Coastguard Worker      os.path.join(work_dir, 'BUILDCONFIG.gn'),
366*6777b538SAndroid Build Coastguard Worker      r'''set_default_toolchain("toolchain")
367*6777b538SAndroid Build Coastguard Workerdeclare_args () {
368*6777b538SAndroid Build Coastguard Worker  is_ios = false
369*6777b538SAndroid Build Coastguard Worker  is_android = true
370*6777b538SAndroid Build Coastguard Worker}
371*6777b538SAndroid Build Coastguard Worker''')
372*6777b538SAndroid Build Coastguard Worker
373*6777b538SAndroid Build Coastguard Worker  # Create fake toolchain required by BUILDCONFIG.gn.
374*6777b538SAndroid Build Coastguard Worker  os.mkdir(os.path.join(work_dir, 'toolchain'))
375*6777b538SAndroid Build Coastguard Worker  _WriteFile(os.path.join(work_dir, 'toolchain', 'BUILD.gn'),
376*6777b538SAndroid Build Coastguard Worker             r'''toolchain("toolchain") {
377*6777b538SAndroid Build Coastguard Worker  tool("stamp") {
378*6777b538SAndroid Build Coastguard Worker    command = "touch {{output}}"  # Required by action()
379*6777b538SAndroid Build Coastguard Worker  }
380*6777b538SAndroid Build Coastguard Worker}
381*6777b538SAndroid Build Coastguard Worker''')
382*6777b538SAndroid Build Coastguard Worker
383*6777b538SAndroid Build Coastguard Worker  # Create top-level BUILD.gn, GN requires at least one target to build so do
384*6777b538SAndroid Build Coastguard Worker  # that with a fake action which will never be invoked. Also write the locales
385*6777b538SAndroid Build Coastguard Worker  # to misc files in the output directory.
386*6777b538SAndroid Build Coastguard Worker  _WriteFile(
387*6777b538SAndroid Build Coastguard Worker      os.path.join(work_dir, 'BUILD.gn'), r'''import("//locales.gni")
388*6777b538SAndroid Build Coastguard Worker
389*6777b538SAndroid Build Coastguard Workeraction("create_foo") {   # fake action to avoid GN complaints.
390*6777b538SAndroid Build Coastguard Worker  script = "//build/create_foo.py"
391*6777b538SAndroid Build Coastguard Worker  inputs = []
392*6777b538SAndroid Build Coastguard Worker  outputs = [ "$target_out_dir/$target_name" ]
393*6777b538SAndroid Build Coastguard Worker}
394*6777b538SAndroid Build Coastguard Worker
395*6777b538SAndroid Build Coastguard Worker# Write the locales lists to files in the output directory.
396*6777b538SAndroid Build Coastguard Worker_filename = root_build_dir + "/foo"
397*6777b538SAndroid Build Coastguard Workerwrite_file(_filename + ".locales", locales, "json")
398*6777b538SAndroid Build Coastguard Workerwrite_file(_filename + ".ios_unsupported_locales",
399*6777b538SAndroid Build Coastguard Worker            ios_unsupported_locales,
400*6777b538SAndroid Build Coastguard Worker            "json")
401*6777b538SAndroid Build Coastguard Worker''')
402*6777b538SAndroid Build Coastguard Worker
403*6777b538SAndroid Build Coastguard Worker  # Copy build/config/locales.gni to the workspace, as required by BUILD.gn.
404*6777b538SAndroid Build Coastguard Worker  shutil.copyfile(os.path.join(_TOP_SRC_DIR, 'build', 'config', 'locales.gni'),
405*6777b538SAndroid Build Coastguard Worker                  os.path.join(work_dir, 'locales.gni'))
406*6777b538SAndroid Build Coastguard Worker
407*6777b538SAndroid Build Coastguard Worker  # Create output directory.
408*6777b538SAndroid Build Coastguard Worker  out_path = os.path.join(work_dir, out_subdir_name)
409*6777b538SAndroid Build Coastguard Worker  os.mkdir(out_path)
410*6777b538SAndroid Build Coastguard Worker
411*6777b538SAndroid Build Coastguard Worker  # And ... we're good.
412*6777b538SAndroid Build Coastguard Worker  return out_path
413*6777b538SAndroid Build Coastguard Worker
414*6777b538SAndroid Build Coastguard Worker
415*6777b538SAndroid Build Coastguard Worker# Set this global variable to the path of a given temporary directory
416*6777b538SAndroid Build Coastguard Worker# before calling _ExtractAllChromeLocalesLists() if you want to debug
417*6777b538SAndroid Build Coastguard Worker# the locales list extraction process.
418*6777b538SAndroid Build Coastguard Worker_DEBUG_LOCALES_WORK_DIR = None
419*6777b538SAndroid Build Coastguard Worker
420*6777b538SAndroid Build Coastguard Worker
421*6777b538SAndroid Build Coastguard Workerdef _ReadJsonList(file_path):
422*6777b538SAndroid Build Coastguard Worker  """Read a JSON file that must contain a list, and return it."""
423*6777b538SAndroid Build Coastguard Worker  with open(file_path) as f:
424*6777b538SAndroid Build Coastguard Worker    data = json.load(f)
425*6777b538SAndroid Build Coastguard Worker    assert isinstance(data, list), "JSON file %s is not a list!" % file_path
426*6777b538SAndroid Build Coastguard Worker  return [item.encode('utf8') for item in data]
427*6777b538SAndroid Build Coastguard Worker
428*6777b538SAndroid Build Coastguard Worker
429*6777b538SAndroid Build Coastguard Workerdef _ExtractAllChromeLocalesLists():
430*6777b538SAndroid Build Coastguard Worker  with build_utils.TempDir() as tmp_path:
431*6777b538SAndroid Build Coastguard Worker    if _DEBUG_LOCALES_WORK_DIR:
432*6777b538SAndroid Build Coastguard Worker      tmp_path = _DEBUG_LOCALES_WORK_DIR
433*6777b538SAndroid Build Coastguard Worker      build_utils.DeleteDirectory(tmp_path)
434*6777b538SAndroid Build Coastguard Worker      build_utils.MakeDirectory(tmp_path)
435*6777b538SAndroid Build Coastguard Worker
436*6777b538SAndroid Build Coastguard Worker    out_path = _PrepareTinyGnWorkspace(tmp_path, 'out')
437*6777b538SAndroid Build Coastguard Worker
438*6777b538SAndroid Build Coastguard Worker    # NOTE: The file suffixes used here should be kept in sync with
439*6777b538SAndroid Build Coastguard Worker    # build/config/locales.gni
440*6777b538SAndroid Build Coastguard Worker    gn_executable = _FindGnExecutable()
441*6777b538SAndroid Build Coastguard Worker    try:
442*6777b538SAndroid Build Coastguard Worker      subprocess.check_output(
443*6777b538SAndroid Build Coastguard Worker          [gn_executable, 'gen', out_path, '--root=' + tmp_path])
444*6777b538SAndroid Build Coastguard Worker    except subprocess.CalledProcessError as e:
445*6777b538SAndroid Build Coastguard Worker      print(e.output)
446*6777b538SAndroid Build Coastguard Worker      raise e
447*6777b538SAndroid Build Coastguard Worker
448*6777b538SAndroid Build Coastguard Worker    global _INTERNAL_CHROME_LOCALES
449*6777b538SAndroid Build Coastguard Worker    _INTERNAL_CHROME_LOCALES = _ReadJsonList(
450*6777b538SAndroid Build Coastguard Worker        os.path.join(out_path, 'foo.locales'))
451*6777b538SAndroid Build Coastguard Worker
452*6777b538SAndroid Build Coastguard Worker    global _INTERNAL_IOS_UNSUPPORTED_LOCALES
453*6777b538SAndroid Build Coastguard Worker    _INTERNAL_IOS_UNSUPPORTED_LOCALES = _ReadJsonList(
454*6777b538SAndroid Build Coastguard Worker        os.path.join(out_path, 'foo.ios_unsupported_locales'))
455*6777b538SAndroid Build Coastguard Worker
456*6777b538SAndroid Build Coastguard Worker
457*6777b538SAndroid Build Coastguard Worker##########################################################################
458*6777b538SAndroid Build Coastguard Worker##########################################################################
459*6777b538SAndroid Build Coastguard Worker#####
460*6777b538SAndroid Build Coastguard Worker#####    G R D   H E L P E R   F U N C T I O N S
461*6777b538SAndroid Build Coastguard Worker#####
462*6777b538SAndroid Build Coastguard Worker##########################################################################
463*6777b538SAndroid Build Coastguard Worker##########################################################################
464*6777b538SAndroid Build Coastguard Worker
465*6777b538SAndroid Build Coastguard Worker# Technical note:
466*6777b538SAndroid Build Coastguard Worker#
467*6777b538SAndroid Build Coastguard Worker# Even though .grd files are XML, an xml parser library is not used in order
468*6777b538SAndroid Build Coastguard Worker# to preserve the original file's structure after modification. ElementTree
469*6777b538SAndroid Build Coastguard Worker# tends to re-order attributes in each element when re-writing an XML
470*6777b538SAndroid Build Coastguard Worker# document tree, which is undesirable here.
471*6777b538SAndroid Build Coastguard Worker#
472*6777b538SAndroid Build Coastguard Worker# Thus simple line-based regular expression matching is used instead.
473*6777b538SAndroid Build Coastguard Worker#
474*6777b538SAndroid Build Coastguard Worker
475*6777b538SAndroid Build Coastguard Worker# Misc regular expressions used to match elements and their attributes.
476*6777b538SAndroid Build Coastguard Worker_RE_OUTPUT_ELEMENT = re.compile(r'<output (.*)\s*/>')
477*6777b538SAndroid Build Coastguard Worker_RE_TRANSLATION_ELEMENT = re.compile(r'<file( | .* )path="(.*\.xtb)".*/>')
478*6777b538SAndroid Build Coastguard Worker_RE_FILENAME_ATTRIBUTE = re.compile(r'filename="([^"]*)"')
479*6777b538SAndroid Build Coastguard Worker_RE_LANG_ATTRIBUTE = re.compile(r'lang="([^"]*)"')
480*6777b538SAndroid Build Coastguard Worker_RE_PATH_ATTRIBUTE = re.compile(r'path="([^"]*)"')
481*6777b538SAndroid Build Coastguard Worker_RE_TYPE_ANDROID_ATTRIBUTE = re.compile(r'type="android"')
482*6777b538SAndroid Build Coastguard Worker
483*6777b538SAndroid Build Coastguard Worker
484*6777b538SAndroid Build Coastguard Worker
485*6777b538SAndroid Build Coastguard Workerdef _IsGritInputFile(input_file):
486*6777b538SAndroid Build Coastguard Worker  """Returns True iff this is a GRIT input file."""
487*6777b538SAndroid Build Coastguard Worker  return input_file.endswith('.grd')
488*6777b538SAndroid Build Coastguard Worker
489*6777b538SAndroid Build Coastguard Worker
490*6777b538SAndroid Build Coastguard Workerdef _GetXmlLangAttribute(xml_line):
491*6777b538SAndroid Build Coastguard Worker  """Extract the lang attribute value from an XML input line."""
492*6777b538SAndroid Build Coastguard Worker  m = _RE_LANG_ATTRIBUTE.search(xml_line)
493*6777b538SAndroid Build Coastguard Worker  if not m:
494*6777b538SAndroid Build Coastguard Worker    return None
495*6777b538SAndroid Build Coastguard Worker  return m.group(1)
496*6777b538SAndroid Build Coastguard Worker
497*6777b538SAndroid Build Coastguard Worker
498*6777b538SAndroid Build Coastguard Workerclass _GetXmlLangAttributeTest(unittest.TestCase):
499*6777b538SAndroid Build Coastguard Worker  TEST_DATA = {
500*6777b538SAndroid Build Coastguard Worker      '': None,
501*6777b538SAndroid Build Coastguard Worker      'foo': None,
502*6777b538SAndroid Build Coastguard Worker      'lang=foo': None,
503*6777b538SAndroid Build Coastguard Worker      'lang="foo"': 'foo',
504*6777b538SAndroid Build Coastguard Worker      '<something lang="foo bar" />': 'foo bar',
505*6777b538SAndroid Build Coastguard Worker      '<file lang="fr-CA" path="path/to/strings_fr-CA.xtb" />': 'fr-CA',
506*6777b538SAndroid Build Coastguard Worker  }
507*6777b538SAndroid Build Coastguard Worker
508*6777b538SAndroid Build Coastguard Worker  def test_GetXmlLangAttribute(self):
509*6777b538SAndroid Build Coastguard Worker    for test_line, expected in self.TEST_DATA.items():
510*6777b538SAndroid Build Coastguard Worker      self.assertEquals(_GetXmlLangAttribute(test_line), expected)
511*6777b538SAndroid Build Coastguard Worker
512*6777b538SAndroid Build Coastguard Worker
513*6777b538SAndroid Build Coastguard Workerdef _SortGrdElementsRanges(grd_lines, element_predicate):
514*6777b538SAndroid Build Coastguard Worker  """Sort all .grd elements of a given type by their lang attribute."""
515*6777b538SAndroid Build Coastguard Worker  return _SortElementsRanges(grd_lines, element_predicate, _GetXmlLangAttribute)
516*6777b538SAndroid Build Coastguard Worker
517*6777b538SAndroid Build Coastguard Worker
518*6777b538SAndroid Build Coastguard Workerdef _CheckGrdElementRangeLang(grd_lines, start, end, wanted_locales):
519*6777b538SAndroid Build Coastguard Worker  """Check the element 'lang' attributes in specific .grd lines range.
520*6777b538SAndroid Build Coastguard Worker
521*6777b538SAndroid Build Coastguard Worker  This really checks the following:
522*6777b538SAndroid Build Coastguard Worker    - Each item has a correct 'lang' attribute.
523*6777b538SAndroid Build Coastguard Worker    - There are no duplicated lines for the same 'lang' attribute.
524*6777b538SAndroid Build Coastguard Worker    - That there are no extra locales that Chromium doesn't want.
525*6777b538SAndroid Build Coastguard Worker    - That no wanted locale is missing.
526*6777b538SAndroid Build Coastguard Worker
527*6777b538SAndroid Build Coastguard Worker  Args:
528*6777b538SAndroid Build Coastguard Worker    grd_lines: Input .grd lines.
529*6777b538SAndroid Build Coastguard Worker    start: Sub-range start position in input line list.
530*6777b538SAndroid Build Coastguard Worker    end: Sub-range limit position in input line list.
531*6777b538SAndroid Build Coastguard Worker    wanted_locales: Set of wanted Chromium locale names.
532*6777b538SAndroid Build Coastguard Worker  Returns:
533*6777b538SAndroid Build Coastguard Worker    List of error message strings for this input. Empty on success.
534*6777b538SAndroid Build Coastguard Worker  """
535*6777b538SAndroid Build Coastguard Worker  errors = []
536*6777b538SAndroid Build Coastguard Worker  locales = set()
537*6777b538SAndroid Build Coastguard Worker  for pos in xrange(start, end):
538*6777b538SAndroid Build Coastguard Worker    line = grd_lines[pos]
539*6777b538SAndroid Build Coastguard Worker    lang = _GetXmlLangAttribute(line)
540*6777b538SAndroid Build Coastguard Worker    if not lang:
541*6777b538SAndroid Build Coastguard Worker      errors.append('%d: Missing "lang" attribute in <output> element' % pos +
542*6777b538SAndroid Build Coastguard Worker                    1)
543*6777b538SAndroid Build Coastguard Worker      continue
544*6777b538SAndroid Build Coastguard Worker    cr_locale = _FixChromiumLangAttribute(lang)
545*6777b538SAndroid Build Coastguard Worker    if cr_locale in locales:
546*6777b538SAndroid Build Coastguard Worker      errors.append(
547*6777b538SAndroid Build Coastguard Worker          '%d: Redefinition of <output> for "%s" locale' % (pos + 1, lang))
548*6777b538SAndroid Build Coastguard Worker    locales.add(cr_locale)
549*6777b538SAndroid Build Coastguard Worker
550*6777b538SAndroid Build Coastguard Worker  extra_locales = locales.difference(wanted_locales)
551*6777b538SAndroid Build Coastguard Worker  if extra_locales:
552*6777b538SAndroid Build Coastguard Worker    errors.append('%d-%d: Extra locales found: %s' % (start + 1, end + 1,
553*6777b538SAndroid Build Coastguard Worker                                                      sorted(extra_locales)))
554*6777b538SAndroid Build Coastguard Worker
555*6777b538SAndroid Build Coastguard Worker  missing_locales = wanted_locales.difference(locales)
556*6777b538SAndroid Build Coastguard Worker  if missing_locales:
557*6777b538SAndroid Build Coastguard Worker    errors.append('%d-%d: Missing locales: %s' % (start + 1, end + 1,
558*6777b538SAndroid Build Coastguard Worker                                                  sorted(missing_locales)))
559*6777b538SAndroid Build Coastguard Worker
560*6777b538SAndroid Build Coastguard Worker  return errors
561*6777b538SAndroid Build Coastguard Worker
562*6777b538SAndroid Build Coastguard Worker
563*6777b538SAndroid Build Coastguard Worker##########################################################################
564*6777b538SAndroid Build Coastguard Worker##########################################################################
565*6777b538SAndroid Build Coastguard Worker#####
566*6777b538SAndroid Build Coastguard Worker#####    G R D   A N D R O I D   O U T P U T S
567*6777b538SAndroid Build Coastguard Worker#####
568*6777b538SAndroid Build Coastguard Worker##########################################################################
569*6777b538SAndroid Build Coastguard Worker##########################################################################
570*6777b538SAndroid Build Coastguard Worker
571*6777b538SAndroid Build Coastguard Workerdef _IsGrdAndroidOutputLine(line):
572*6777b538SAndroid Build Coastguard Worker  """Returns True iff this is an Android-specific <output> line."""
573*6777b538SAndroid Build Coastguard Worker  m = _RE_OUTPUT_ELEMENT.search(line)
574*6777b538SAndroid Build Coastguard Worker  if m:
575*6777b538SAndroid Build Coastguard Worker    return 'type="android"' in m.group(1)
576*6777b538SAndroid Build Coastguard Worker  return False
577*6777b538SAndroid Build Coastguard Worker
578*6777b538SAndroid Build Coastguard Workerassert _IsGrdAndroidOutputLine('  <output type="android"/>')
579*6777b538SAndroid Build Coastguard Worker
580*6777b538SAndroid Build Coastguard Worker# Many of the functions below have unused arguments due to genericity.
581*6777b538SAndroid Build Coastguard Worker# pylint: disable=unused-argument
582*6777b538SAndroid Build Coastguard Worker
583*6777b538SAndroid Build Coastguard Workerdef _CheckGrdElementRangeAndroidOutputFilename(grd_lines, start, end,
584*6777b538SAndroid Build Coastguard Worker                                               wanted_locales):
585*6777b538SAndroid Build Coastguard Worker  """Check all <output> elements in specific input .grd lines range.
586*6777b538SAndroid Build Coastguard Worker
587*6777b538SAndroid Build Coastguard Worker  This really checks the following:
588*6777b538SAndroid Build Coastguard Worker    - Filenames exist for each listed locale.
589*6777b538SAndroid Build Coastguard Worker    - Filenames are well-formed.
590*6777b538SAndroid Build Coastguard Worker
591*6777b538SAndroid Build Coastguard Worker  Args:
592*6777b538SAndroid Build Coastguard Worker    grd_lines: Input .grd lines.
593*6777b538SAndroid Build Coastguard Worker    start: Sub-range start position in input line list.
594*6777b538SAndroid Build Coastguard Worker    end: Sub-range limit position in input line list.
595*6777b538SAndroid Build Coastguard Worker    wanted_locales: Set of wanted Chromium locale names.
596*6777b538SAndroid Build Coastguard Worker  Returns:
597*6777b538SAndroid Build Coastguard Worker    List of error message strings for this input. Empty on success.
598*6777b538SAndroid Build Coastguard Worker  """
599*6777b538SAndroid Build Coastguard Worker  errors = []
600*6777b538SAndroid Build Coastguard Worker  for pos in xrange(start, end):
601*6777b538SAndroid Build Coastguard Worker    line = grd_lines[pos]
602*6777b538SAndroid Build Coastguard Worker    lang = _GetXmlLangAttribute(line)
603*6777b538SAndroid Build Coastguard Worker    if not lang:
604*6777b538SAndroid Build Coastguard Worker      continue
605*6777b538SAndroid Build Coastguard Worker    cr_locale = _FixChromiumLangAttribute(lang)
606*6777b538SAndroid Build Coastguard Worker
607*6777b538SAndroid Build Coastguard Worker    m = _RE_FILENAME_ATTRIBUTE.search(line)
608*6777b538SAndroid Build Coastguard Worker    if not m:
609*6777b538SAndroid Build Coastguard Worker      errors.append('%d: Missing filename attribute in <output> element' % pos +
610*6777b538SAndroid Build Coastguard Worker                    1)
611*6777b538SAndroid Build Coastguard Worker    else:
612*6777b538SAndroid Build Coastguard Worker      filename = m.group(1)
613*6777b538SAndroid Build Coastguard Worker      if not filename.endswith('.xml'):
614*6777b538SAndroid Build Coastguard Worker        errors.append(
615*6777b538SAndroid Build Coastguard Worker            '%d: Filename should end with ".xml": %s' % (pos + 1, filename))
616*6777b538SAndroid Build Coastguard Worker
617*6777b538SAndroid Build Coastguard Worker      dirname = os.path.basename(os.path.dirname(filename))
618*6777b538SAndroid Build Coastguard Worker      prefix = ('values-%s' % resource_utils.ToAndroidLocaleName(cr_locale)
619*6777b538SAndroid Build Coastguard Worker                if cr_locale != _DEFAULT_LOCALE else 'values')
620*6777b538SAndroid Build Coastguard Worker      if dirname != prefix:
621*6777b538SAndroid Build Coastguard Worker        errors.append(
622*6777b538SAndroid Build Coastguard Worker            '%s: Directory name should be %s: %s' % (pos + 1, prefix, filename))
623*6777b538SAndroid Build Coastguard Worker
624*6777b538SAndroid Build Coastguard Worker  return errors
625*6777b538SAndroid Build Coastguard Worker
626*6777b538SAndroid Build Coastguard Worker
627*6777b538SAndroid Build Coastguard Workerdef _CheckGrdAndroidOutputElements(grd_file, grd_lines, wanted_locales):
628*6777b538SAndroid Build Coastguard Worker  """Check all <output> elements related to Android.
629*6777b538SAndroid Build Coastguard Worker
630*6777b538SAndroid Build Coastguard Worker  Args:
631*6777b538SAndroid Build Coastguard Worker    grd_file: Input .grd file path.
632*6777b538SAndroid Build Coastguard Worker    grd_lines: List of input .grd lines.
633*6777b538SAndroid Build Coastguard Worker    wanted_locales: set of wanted Chromium locale names.
634*6777b538SAndroid Build Coastguard Worker  Returns:
635*6777b538SAndroid Build Coastguard Worker    List of error message strings. Empty on success.
636*6777b538SAndroid Build Coastguard Worker  """
637*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(grd_lines, _IsGrdAndroidOutputLine)
638*6777b538SAndroid Build Coastguard Worker  errors = []
639*6777b538SAndroid Build Coastguard Worker  for start, end in intervals:
640*6777b538SAndroid Build Coastguard Worker    errors += _CheckGrdElementRangeLang(grd_lines, start, end, wanted_locales)
641*6777b538SAndroid Build Coastguard Worker    errors += _CheckGrdElementRangeAndroidOutputFilename(grd_lines, start, end,
642*6777b538SAndroid Build Coastguard Worker                                                         wanted_locales)
643*6777b538SAndroid Build Coastguard Worker  return errors
644*6777b538SAndroid Build Coastguard Worker
645*6777b538SAndroid Build Coastguard Worker
646*6777b538SAndroid Build Coastguard Workerdef _AddMissingLocalesInGrdAndroidOutputs(grd_file, grd_lines, wanted_locales):
647*6777b538SAndroid Build Coastguard Worker  """Fix an input .grd line by adding missing Android outputs.
648*6777b538SAndroid Build Coastguard Worker
649*6777b538SAndroid Build Coastguard Worker  Args:
650*6777b538SAndroid Build Coastguard Worker    grd_file: Input .grd file path.
651*6777b538SAndroid Build Coastguard Worker    grd_lines: Input .grd line list.
652*6777b538SAndroid Build Coastguard Worker    wanted_locales: set of Chromium locale names.
653*6777b538SAndroid Build Coastguard Worker  Returns:
654*6777b538SAndroid Build Coastguard Worker    A new list of .grd lines, containing new <output> elements when needed
655*6777b538SAndroid Build Coastguard Worker    for locales from |wanted_locales| that were not part of the input.
656*6777b538SAndroid Build Coastguard Worker  """
657*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(grd_lines, _IsGrdAndroidOutputLine)
658*6777b538SAndroid Build Coastguard Worker  for start, end in reversed(intervals):
659*6777b538SAndroid Build Coastguard Worker    locales = set()
660*6777b538SAndroid Build Coastguard Worker    for pos in xrange(start, end):
661*6777b538SAndroid Build Coastguard Worker      lang = _GetXmlLangAttribute(grd_lines[pos])
662*6777b538SAndroid Build Coastguard Worker      locale = _FixChromiumLangAttribute(lang)
663*6777b538SAndroid Build Coastguard Worker      locales.add(locale)
664*6777b538SAndroid Build Coastguard Worker
665*6777b538SAndroid Build Coastguard Worker    missing_locales = wanted_locales.difference(locales)
666*6777b538SAndroid Build Coastguard Worker    if not missing_locales:
667*6777b538SAndroid Build Coastguard Worker      continue
668*6777b538SAndroid Build Coastguard Worker
669*6777b538SAndroid Build Coastguard Worker    src_locale = 'bg'
670*6777b538SAndroid Build Coastguard Worker    src_lang_attribute = 'lang="%s"' % src_locale
671*6777b538SAndroid Build Coastguard Worker    src_line = None
672*6777b538SAndroid Build Coastguard Worker    for pos in xrange(start, end):
673*6777b538SAndroid Build Coastguard Worker      if src_lang_attribute in grd_lines[pos]:
674*6777b538SAndroid Build Coastguard Worker        src_line = grd_lines[pos]
675*6777b538SAndroid Build Coastguard Worker        break
676*6777b538SAndroid Build Coastguard Worker
677*6777b538SAndroid Build Coastguard Worker    if not src_line:
678*6777b538SAndroid Build Coastguard Worker      raise Exception(
679*6777b538SAndroid Build Coastguard Worker          'Cannot find <output> element with "%s" lang attribute' % src_locale)
680*6777b538SAndroid Build Coastguard Worker
681*6777b538SAndroid Build Coastguard Worker    line_count = end - 1
682*6777b538SAndroid Build Coastguard Worker    for locale in missing_locales:
683*6777b538SAndroid Build Coastguard Worker      android_locale = resource_utils.ToAndroidLocaleName(locale)
684*6777b538SAndroid Build Coastguard Worker      dst_line = src_line.replace(
685*6777b538SAndroid Build Coastguard Worker          'lang="%s"' % src_locale, 'lang="%s"' % locale).replace(
686*6777b538SAndroid Build Coastguard Worker              'values-%s/' % src_locale, 'values-%s/' % android_locale)
687*6777b538SAndroid Build Coastguard Worker      grd_lines.insert(line_count, dst_line)
688*6777b538SAndroid Build Coastguard Worker      line_count += 1
689*6777b538SAndroid Build Coastguard Worker
690*6777b538SAndroid Build Coastguard Worker  # Sort the new <output> elements.
691*6777b538SAndroid Build Coastguard Worker  return _SortGrdElementsRanges(grd_lines, _IsGrdAndroidOutputLine)
692*6777b538SAndroid Build Coastguard Worker
693*6777b538SAndroid Build Coastguard Worker
694*6777b538SAndroid Build Coastguard Worker##########################################################################
695*6777b538SAndroid Build Coastguard Worker##########################################################################
696*6777b538SAndroid Build Coastguard Worker#####
697*6777b538SAndroid Build Coastguard Worker#####    G R D   T R A N S L A T I O N S
698*6777b538SAndroid Build Coastguard Worker#####
699*6777b538SAndroid Build Coastguard Worker##########################################################################
700*6777b538SAndroid Build Coastguard Worker##########################################################################
701*6777b538SAndroid Build Coastguard Worker
702*6777b538SAndroid Build Coastguard Worker
703*6777b538SAndroid Build Coastguard Workerdef _IsTranslationGrdOutputLine(line):
704*6777b538SAndroid Build Coastguard Worker  """Returns True iff this is an output .xtb <file> element."""
705*6777b538SAndroid Build Coastguard Worker  m = _RE_TRANSLATION_ELEMENT.search(line)
706*6777b538SAndroid Build Coastguard Worker  return m is not None
707*6777b538SAndroid Build Coastguard Worker
708*6777b538SAndroid Build Coastguard Worker
709*6777b538SAndroid Build Coastguard Workerclass _IsTranslationGrdOutputLineTest(unittest.TestCase):
710*6777b538SAndroid Build Coastguard Worker
711*6777b538SAndroid Build Coastguard Worker  def test_GrdTranslationOutputLines(self):
712*6777b538SAndroid Build Coastguard Worker    _VALID_INPUT_LINES = [
713*6777b538SAndroid Build Coastguard Worker        '<file path="foo/bar.xtb" />',
714*6777b538SAndroid Build Coastguard Worker        '<file path="foo/bar.xtb"/>',
715*6777b538SAndroid Build Coastguard Worker        '<file lang="fr-CA" path="translations/aw_strings_fr-CA.xtb"/>',
716*6777b538SAndroid Build Coastguard Worker        '<file lang="fr-CA" path="translations/aw_strings_fr-CA.xtb" />',
717*6777b538SAndroid Build Coastguard Worker        '  <file path="translations/aw_strings_ar.xtb" lang="ar" />',
718*6777b538SAndroid Build Coastguard Worker    ]
719*6777b538SAndroid Build Coastguard Worker    _INVALID_INPUT_LINES = ['<file path="foo/bar.xml" />']
720*6777b538SAndroid Build Coastguard Worker
721*6777b538SAndroid Build Coastguard Worker    for line in _VALID_INPUT_LINES:
722*6777b538SAndroid Build Coastguard Worker      self.assertTrue(
723*6777b538SAndroid Build Coastguard Worker          _IsTranslationGrdOutputLine(line),
724*6777b538SAndroid Build Coastguard Worker          '_IsTranslationGrdOutputLine() returned False for [%s]' % line)
725*6777b538SAndroid Build Coastguard Worker
726*6777b538SAndroid Build Coastguard Worker    for line in _INVALID_INPUT_LINES:
727*6777b538SAndroid Build Coastguard Worker      self.assertFalse(
728*6777b538SAndroid Build Coastguard Worker          _IsTranslationGrdOutputLine(line),
729*6777b538SAndroid Build Coastguard Worker          '_IsTranslationGrdOutputLine() returned True for [%s]' % line)
730*6777b538SAndroid Build Coastguard Worker
731*6777b538SAndroid Build Coastguard Worker
732*6777b538SAndroid Build Coastguard Workerdef _CheckGrdTranslationElementRange(grd_lines, start, end,
733*6777b538SAndroid Build Coastguard Worker                                     wanted_locales):
734*6777b538SAndroid Build Coastguard Worker  """Check all <translations> sub-elements in specific input .grd lines range.
735*6777b538SAndroid Build Coastguard Worker
736*6777b538SAndroid Build Coastguard Worker  This really checks the following:
737*6777b538SAndroid Build Coastguard Worker    - Each item has a 'path' attribute.
738*6777b538SAndroid Build Coastguard Worker    - Each such path value ends up with '.xtb'.
739*6777b538SAndroid Build Coastguard Worker
740*6777b538SAndroid Build Coastguard Worker  Args:
741*6777b538SAndroid Build Coastguard Worker    grd_lines: Input .grd lines.
742*6777b538SAndroid Build Coastguard Worker    start: Sub-range start position in input line list.
743*6777b538SAndroid Build Coastguard Worker    end: Sub-range limit position in input line list.
744*6777b538SAndroid Build Coastguard Worker    wanted_locales: Set of wanted Chromium locale names.
745*6777b538SAndroid Build Coastguard Worker  Returns:
746*6777b538SAndroid Build Coastguard Worker    List of error message strings for this input. Empty on success.
747*6777b538SAndroid Build Coastguard Worker  """
748*6777b538SAndroid Build Coastguard Worker  errors = []
749*6777b538SAndroid Build Coastguard Worker  for pos in xrange(start, end):
750*6777b538SAndroid Build Coastguard Worker    line = grd_lines[pos]
751*6777b538SAndroid Build Coastguard Worker    lang = _GetXmlLangAttribute(line)
752*6777b538SAndroid Build Coastguard Worker    if not lang:
753*6777b538SAndroid Build Coastguard Worker      continue
754*6777b538SAndroid Build Coastguard Worker    m = _RE_PATH_ATTRIBUTE.search(line)
755*6777b538SAndroid Build Coastguard Worker    if not m:
756*6777b538SAndroid Build Coastguard Worker      errors.append('%d: Missing path attribute in <file> element' % pos +
757*6777b538SAndroid Build Coastguard Worker                    1)
758*6777b538SAndroid Build Coastguard Worker    else:
759*6777b538SAndroid Build Coastguard Worker      filename = m.group(1)
760*6777b538SAndroid Build Coastguard Worker      if not filename.endswith('.xtb'):
761*6777b538SAndroid Build Coastguard Worker        errors.append(
762*6777b538SAndroid Build Coastguard Worker            '%d: Path should end with ".xtb": %s' % (pos + 1, filename))
763*6777b538SAndroid Build Coastguard Worker
764*6777b538SAndroid Build Coastguard Worker  return errors
765*6777b538SAndroid Build Coastguard Worker
766*6777b538SAndroid Build Coastguard Worker
767*6777b538SAndroid Build Coastguard Workerdef _CheckGrdTranslations(grd_file, grd_lines, wanted_locales):
768*6777b538SAndroid Build Coastguard Worker  """Check all <file> elements that correspond to an .xtb output file.
769*6777b538SAndroid Build Coastguard Worker
770*6777b538SAndroid Build Coastguard Worker  Args:
771*6777b538SAndroid Build Coastguard Worker    grd_file: Input .grd file path.
772*6777b538SAndroid Build Coastguard Worker    grd_lines: List of input .grd lines.
773*6777b538SAndroid Build Coastguard Worker    wanted_locales: set of wanted Chromium locale names.
774*6777b538SAndroid Build Coastguard Worker  Returns:
775*6777b538SAndroid Build Coastguard Worker    List of error message strings. Empty on success.
776*6777b538SAndroid Build Coastguard Worker  """
777*6777b538SAndroid Build Coastguard Worker  wanted_locales = wanted_locales - set([_DEFAULT_LOCALE])
778*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(grd_lines, _IsTranslationGrdOutputLine)
779*6777b538SAndroid Build Coastguard Worker  errors = []
780*6777b538SAndroid Build Coastguard Worker  for start, end in intervals:
781*6777b538SAndroid Build Coastguard Worker    errors += _CheckGrdElementRangeLang(grd_lines, start, end, wanted_locales)
782*6777b538SAndroid Build Coastguard Worker    errors += _CheckGrdTranslationElementRange(grd_lines, start, end,
783*6777b538SAndroid Build Coastguard Worker                                              wanted_locales)
784*6777b538SAndroid Build Coastguard Worker  return errors
785*6777b538SAndroid Build Coastguard Worker
786*6777b538SAndroid Build Coastguard Worker
787*6777b538SAndroid Build Coastguard Worker# Regular expression used to replace the lang attribute inside .xtb files.
788*6777b538SAndroid Build Coastguard Worker_RE_TRANSLATIONBUNDLE = re.compile('<translationbundle lang="(.*)">')
789*6777b538SAndroid Build Coastguard Worker
790*6777b538SAndroid Build Coastguard Worker
791*6777b538SAndroid Build Coastguard Workerdef _CreateFakeXtbFileFrom(src_xtb_path, dst_xtb_path, dst_locale):
792*6777b538SAndroid Build Coastguard Worker  """Create a fake .xtb file.
793*6777b538SAndroid Build Coastguard Worker
794*6777b538SAndroid Build Coastguard Worker  Args:
795*6777b538SAndroid Build Coastguard Worker    src_xtb_path: Path to source .xtb file to copy from.
796*6777b538SAndroid Build Coastguard Worker    dst_xtb_path: Path to destination .xtb file to write to.
797*6777b538SAndroid Build Coastguard Worker    dst_locale: Destination locale, the lang attribute in the source file
798*6777b538SAndroid Build Coastguard Worker      will be substituted with this value before its lines are written
799*6777b538SAndroid Build Coastguard Worker      to the destination file.
800*6777b538SAndroid Build Coastguard Worker  """
801*6777b538SAndroid Build Coastguard Worker  with open(src_xtb_path) as f:
802*6777b538SAndroid Build Coastguard Worker    src_xtb_lines = f.readlines()
803*6777b538SAndroid Build Coastguard Worker
804*6777b538SAndroid Build Coastguard Worker  def replace_xtb_lang_attribute(line):
805*6777b538SAndroid Build Coastguard Worker    m = _RE_TRANSLATIONBUNDLE.search(line)
806*6777b538SAndroid Build Coastguard Worker    if not m:
807*6777b538SAndroid Build Coastguard Worker      return line
808*6777b538SAndroid Build Coastguard Worker    return line[:m.start(1)] + dst_locale + line[m.end(1):]
809*6777b538SAndroid Build Coastguard Worker
810*6777b538SAndroid Build Coastguard Worker  dst_xtb_lines = [replace_xtb_lang_attribute(line) for line in src_xtb_lines]
811*6777b538SAndroid Build Coastguard Worker  with build_utils.AtomicOutput(dst_xtb_path) as tmp:
812*6777b538SAndroid Build Coastguard Worker    tmp.writelines(dst_xtb_lines)
813*6777b538SAndroid Build Coastguard Worker
814*6777b538SAndroid Build Coastguard Worker
815*6777b538SAndroid Build Coastguard Workerdef _AddMissingLocalesInGrdTranslations(grd_file, grd_lines, wanted_locales):
816*6777b538SAndroid Build Coastguard Worker  """Fix an input .grd line by adding missing Android outputs.
817*6777b538SAndroid Build Coastguard Worker
818*6777b538SAndroid Build Coastguard Worker  This also creates fake .xtb files from the one provided for 'en-GB'.
819*6777b538SAndroid Build Coastguard Worker
820*6777b538SAndroid Build Coastguard Worker  Args:
821*6777b538SAndroid Build Coastguard Worker    grd_file: Input .grd file path.
822*6777b538SAndroid Build Coastguard Worker    grd_lines: Input .grd line list.
823*6777b538SAndroid Build Coastguard Worker    wanted_locales: set of Chromium locale names.
824*6777b538SAndroid Build Coastguard Worker  Returns:
825*6777b538SAndroid Build Coastguard Worker    A new list of .grd lines, containing new <output> elements when needed
826*6777b538SAndroid Build Coastguard Worker    for locales from |wanted_locales| that were not part of the input.
827*6777b538SAndroid Build Coastguard Worker  """
828*6777b538SAndroid Build Coastguard Worker  wanted_locales = wanted_locales - set([_DEFAULT_LOCALE])
829*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(grd_lines, _IsTranslationGrdOutputLine)
830*6777b538SAndroid Build Coastguard Worker  for start, end in reversed(intervals):
831*6777b538SAndroid Build Coastguard Worker    locales = set()
832*6777b538SAndroid Build Coastguard Worker    for pos in xrange(start, end):
833*6777b538SAndroid Build Coastguard Worker      lang = _GetXmlLangAttribute(grd_lines[pos])
834*6777b538SAndroid Build Coastguard Worker      locale = _FixChromiumLangAttribute(lang)
835*6777b538SAndroid Build Coastguard Worker      locales.add(locale)
836*6777b538SAndroid Build Coastguard Worker
837*6777b538SAndroid Build Coastguard Worker    missing_locales = wanted_locales.difference(locales)
838*6777b538SAndroid Build Coastguard Worker    if not missing_locales:
839*6777b538SAndroid Build Coastguard Worker      continue
840*6777b538SAndroid Build Coastguard Worker
841*6777b538SAndroid Build Coastguard Worker    src_locale = 'en-GB'
842*6777b538SAndroid Build Coastguard Worker    src_lang_attribute = 'lang="%s"' % src_locale
843*6777b538SAndroid Build Coastguard Worker    src_line = None
844*6777b538SAndroid Build Coastguard Worker    for pos in xrange(start, end):
845*6777b538SAndroid Build Coastguard Worker      if src_lang_attribute in grd_lines[pos]:
846*6777b538SAndroid Build Coastguard Worker        src_line = grd_lines[pos]
847*6777b538SAndroid Build Coastguard Worker        break
848*6777b538SAndroid Build Coastguard Worker
849*6777b538SAndroid Build Coastguard Worker    if not src_line:
850*6777b538SAndroid Build Coastguard Worker      raise Exception(
851*6777b538SAndroid Build Coastguard Worker          'Cannot find <file> element with "%s" lang attribute' % src_locale)
852*6777b538SAndroid Build Coastguard Worker
853*6777b538SAndroid Build Coastguard Worker    src_path = os.path.join(
854*6777b538SAndroid Build Coastguard Worker        os.path.dirname(grd_file),
855*6777b538SAndroid Build Coastguard Worker        _RE_PATH_ATTRIBUTE.search(src_line).group(1))
856*6777b538SAndroid Build Coastguard Worker
857*6777b538SAndroid Build Coastguard Worker    line_count = end - 1
858*6777b538SAndroid Build Coastguard Worker    for locale in missing_locales:
859*6777b538SAndroid Build Coastguard Worker      dst_line = src_line.replace(
860*6777b538SAndroid Build Coastguard Worker          'lang="%s"' % src_locale, 'lang="%s"' % locale).replace(
861*6777b538SAndroid Build Coastguard Worker              '_%s.xtb' % src_locale, '_%s.xtb' % locale)
862*6777b538SAndroid Build Coastguard Worker      grd_lines.insert(line_count, dst_line)
863*6777b538SAndroid Build Coastguard Worker      line_count += 1
864*6777b538SAndroid Build Coastguard Worker
865*6777b538SAndroid Build Coastguard Worker      dst_path = src_path.replace('_%s.xtb' % src_locale, '_%s.xtb' % locale)
866*6777b538SAndroid Build Coastguard Worker      _CreateFakeXtbFileFrom(src_path, dst_path, locale)
867*6777b538SAndroid Build Coastguard Worker
868*6777b538SAndroid Build Coastguard Worker
869*6777b538SAndroid Build Coastguard Worker  # Sort the new <output> elements.
870*6777b538SAndroid Build Coastguard Worker  return _SortGrdElementsRanges(grd_lines, _IsTranslationGrdOutputLine)
871*6777b538SAndroid Build Coastguard Worker
872*6777b538SAndroid Build Coastguard Worker
873*6777b538SAndroid Build Coastguard Worker##########################################################################
874*6777b538SAndroid Build Coastguard Worker##########################################################################
875*6777b538SAndroid Build Coastguard Worker#####
876*6777b538SAndroid Build Coastguard Worker#####    G N   A N D R O I D   O U T P U T S
877*6777b538SAndroid Build Coastguard Worker#####
878*6777b538SAndroid Build Coastguard Worker##########################################################################
879*6777b538SAndroid Build Coastguard Worker##########################################################################
880*6777b538SAndroid Build Coastguard Worker
881*6777b538SAndroid Build Coastguard Worker_RE_GN_VALUES_LIST_LINE = re.compile(
882*6777b538SAndroid Build Coastguard Worker    r'^\s*".*values(\-([A-Za-z0-9-]+))?/.*\.xml",\s*$')
883*6777b538SAndroid Build Coastguard Worker
884*6777b538SAndroid Build Coastguard Workerdef _IsBuildGnInputFile(input_file):
885*6777b538SAndroid Build Coastguard Worker  """Returns True iff this is a BUILD.gn file."""
886*6777b538SAndroid Build Coastguard Worker  return os.path.basename(input_file) == 'BUILD.gn'
887*6777b538SAndroid Build Coastguard Worker
888*6777b538SAndroid Build Coastguard Worker
889*6777b538SAndroid Build Coastguard Workerdef _GetAndroidGnOutputLocale(line):
890*6777b538SAndroid Build Coastguard Worker  """Check a GN list, and return its Android locale if it is an output .xml"""
891*6777b538SAndroid Build Coastguard Worker  m = _RE_GN_VALUES_LIST_LINE.match(line)
892*6777b538SAndroid Build Coastguard Worker  if not m:
893*6777b538SAndroid Build Coastguard Worker    return None
894*6777b538SAndroid Build Coastguard Worker
895*6777b538SAndroid Build Coastguard Worker  if m.group(1):  # First group is optional and contains group 2.
896*6777b538SAndroid Build Coastguard Worker    return m.group(2)
897*6777b538SAndroid Build Coastguard Worker
898*6777b538SAndroid Build Coastguard Worker  return resource_utils.ToAndroidLocaleName(_DEFAULT_LOCALE)
899*6777b538SAndroid Build Coastguard Worker
900*6777b538SAndroid Build Coastguard Worker
901*6777b538SAndroid Build Coastguard Workerdef _IsAndroidGnOutputLine(line):
902*6777b538SAndroid Build Coastguard Worker  """Returns True iff this is an Android-specific localized .xml output."""
903*6777b538SAndroid Build Coastguard Worker  return _GetAndroidGnOutputLocale(line) != None
904*6777b538SAndroid Build Coastguard Worker
905*6777b538SAndroid Build Coastguard Worker
906*6777b538SAndroid Build Coastguard Workerdef _CheckGnOutputsRangeForLocalizedStrings(gn_lines, start, end):
907*6777b538SAndroid Build Coastguard Worker  """Check that a range of GN lines corresponds to localized strings.
908*6777b538SAndroid Build Coastguard Worker
909*6777b538SAndroid Build Coastguard Worker  Special case: Some BUILD.gn files list several non-localized .xml files
910*6777b538SAndroid Build Coastguard Worker  that should be ignored by this function, e.g. in
911*6777b538SAndroid Build Coastguard Worker  components/cronet/android/BUILD.gn, the following appears:
912*6777b538SAndroid Build Coastguard Worker
913*6777b538SAndroid Build Coastguard Worker    inputs = [
914*6777b538SAndroid Build Coastguard Worker      ...
915*6777b538SAndroid Build Coastguard Worker      "sample/res/layout/activity_main.xml",
916*6777b538SAndroid Build Coastguard Worker      "sample/res/layout/dialog_url.xml",
917*6777b538SAndroid Build Coastguard Worker      "sample/res/values/dimens.xml",
918*6777b538SAndroid Build Coastguard Worker      "sample/res/values/strings.xml",
919*6777b538SAndroid Build Coastguard Worker      ...
920*6777b538SAndroid Build Coastguard Worker    ]
921*6777b538SAndroid Build Coastguard Worker
922*6777b538SAndroid Build Coastguard Worker  These are non-localized strings, and should be ignored. This function is
923*6777b538SAndroid Build Coastguard Worker  used to detect them quickly.
924*6777b538SAndroid Build Coastguard Worker  """
925*6777b538SAndroid Build Coastguard Worker  for pos in xrange(start, end):
926*6777b538SAndroid Build Coastguard Worker    if not 'values/' in gn_lines[pos]:
927*6777b538SAndroid Build Coastguard Worker      return True
928*6777b538SAndroid Build Coastguard Worker  return False
929*6777b538SAndroid Build Coastguard Worker
930*6777b538SAndroid Build Coastguard Worker
931*6777b538SAndroid Build Coastguard Workerdef _CheckGnOutputsRange(gn_lines, start, end, wanted_locales):
932*6777b538SAndroid Build Coastguard Worker  if not _CheckGnOutputsRangeForLocalizedStrings(gn_lines, start, end):
933*6777b538SAndroid Build Coastguard Worker    return []
934*6777b538SAndroid Build Coastguard Worker
935*6777b538SAndroid Build Coastguard Worker  errors = []
936*6777b538SAndroid Build Coastguard Worker  locales = set()
937*6777b538SAndroid Build Coastguard Worker  for pos in xrange(start, end):
938*6777b538SAndroid Build Coastguard Worker    line = gn_lines[pos]
939*6777b538SAndroid Build Coastguard Worker    android_locale = _GetAndroidGnOutputLocale(line)
940*6777b538SAndroid Build Coastguard Worker    assert android_locale != None
941*6777b538SAndroid Build Coastguard Worker    cr_locale = resource_utils.ToChromiumLocaleName(android_locale)
942*6777b538SAndroid Build Coastguard Worker    if cr_locale in locales:
943*6777b538SAndroid Build Coastguard Worker      errors.append('%s: Redefinition of output for "%s" locale' %
944*6777b538SAndroid Build Coastguard Worker                    (pos + 1, android_locale))
945*6777b538SAndroid Build Coastguard Worker    locales.add(cr_locale)
946*6777b538SAndroid Build Coastguard Worker
947*6777b538SAndroid Build Coastguard Worker  extra_locales = locales.difference(wanted_locales)
948*6777b538SAndroid Build Coastguard Worker  if extra_locales:
949*6777b538SAndroid Build Coastguard Worker    errors.append('%d-%d: Extra locales: %s' % (start + 1, end + 1,
950*6777b538SAndroid Build Coastguard Worker                                                sorted(extra_locales)))
951*6777b538SAndroid Build Coastguard Worker
952*6777b538SAndroid Build Coastguard Worker  missing_locales = wanted_locales.difference(locales)
953*6777b538SAndroid Build Coastguard Worker  if missing_locales:
954*6777b538SAndroid Build Coastguard Worker    errors.append('%d-%d: Missing locales: %s' % (start + 1, end + 1,
955*6777b538SAndroid Build Coastguard Worker                                                  sorted(missing_locales)))
956*6777b538SAndroid Build Coastguard Worker
957*6777b538SAndroid Build Coastguard Worker  return errors
958*6777b538SAndroid Build Coastguard Worker
959*6777b538SAndroid Build Coastguard Worker
960*6777b538SAndroid Build Coastguard Workerdef _CheckGnAndroidOutputs(gn_file, gn_lines, wanted_locales):
961*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(gn_lines, _IsAndroidGnOutputLine)
962*6777b538SAndroid Build Coastguard Worker  errors = []
963*6777b538SAndroid Build Coastguard Worker  for start, end in intervals:
964*6777b538SAndroid Build Coastguard Worker    errors += _CheckGnOutputsRange(gn_lines, start, end, wanted_locales)
965*6777b538SAndroid Build Coastguard Worker  return errors
966*6777b538SAndroid Build Coastguard Worker
967*6777b538SAndroid Build Coastguard Worker
968*6777b538SAndroid Build Coastguard Workerdef _AddMissingLocalesInGnAndroidOutputs(gn_file, gn_lines, wanted_locales):
969*6777b538SAndroid Build Coastguard Worker  intervals = _BuildIntervalList(gn_lines, _IsAndroidGnOutputLine)
970*6777b538SAndroid Build Coastguard Worker  # NOTE: Since this may insert new lines to each interval, process the
971*6777b538SAndroid Build Coastguard Worker  # list in reverse order to maintain valid (start,end) positions during
972*6777b538SAndroid Build Coastguard Worker  # the iteration.
973*6777b538SAndroid Build Coastguard Worker  for start, end in reversed(intervals):
974*6777b538SAndroid Build Coastguard Worker    if not _CheckGnOutputsRangeForLocalizedStrings(gn_lines, start, end):
975*6777b538SAndroid Build Coastguard Worker      continue
976*6777b538SAndroid Build Coastguard Worker
977*6777b538SAndroid Build Coastguard Worker    locales = set()
978*6777b538SAndroid Build Coastguard Worker    for pos in xrange(start, end):
979*6777b538SAndroid Build Coastguard Worker      lang = _GetAndroidGnOutputLocale(gn_lines[pos])
980*6777b538SAndroid Build Coastguard Worker      locale = resource_utils.ToChromiumLocaleName(lang)
981*6777b538SAndroid Build Coastguard Worker      locales.add(locale)
982*6777b538SAndroid Build Coastguard Worker
983*6777b538SAndroid Build Coastguard Worker    missing_locales = wanted_locales.difference(locales)
984*6777b538SAndroid Build Coastguard Worker    if not missing_locales:
985*6777b538SAndroid Build Coastguard Worker      continue
986*6777b538SAndroid Build Coastguard Worker
987*6777b538SAndroid Build Coastguard Worker    src_locale = 'bg'
988*6777b538SAndroid Build Coastguard Worker    src_values = 'values-%s/' % resource_utils.ToAndroidLocaleName(src_locale)
989*6777b538SAndroid Build Coastguard Worker    src_line = None
990*6777b538SAndroid Build Coastguard Worker    for pos in xrange(start, end):
991*6777b538SAndroid Build Coastguard Worker      if src_values in gn_lines[pos]:
992*6777b538SAndroid Build Coastguard Worker        src_line = gn_lines[pos]
993*6777b538SAndroid Build Coastguard Worker        break
994*6777b538SAndroid Build Coastguard Worker
995*6777b538SAndroid Build Coastguard Worker    if not src_line:
996*6777b538SAndroid Build Coastguard Worker      raise Exception(
997*6777b538SAndroid Build Coastguard Worker          'Cannot find output list item with "%s" locale' % src_locale)
998*6777b538SAndroid Build Coastguard Worker
999*6777b538SAndroid Build Coastguard Worker    line_count = end - 1
1000*6777b538SAndroid Build Coastguard Worker    for locale in missing_locales:
1001*6777b538SAndroid Build Coastguard Worker      if locale == _DEFAULT_LOCALE:
1002*6777b538SAndroid Build Coastguard Worker        dst_line = src_line.replace('values-%s/' % src_locale, 'values/')
1003*6777b538SAndroid Build Coastguard Worker      else:
1004*6777b538SAndroid Build Coastguard Worker        dst_line = src_line.replace(
1005*6777b538SAndroid Build Coastguard Worker            'values-%s/' % src_locale,
1006*6777b538SAndroid Build Coastguard Worker            'values-%s/' % resource_utils.ToAndroidLocaleName(locale))
1007*6777b538SAndroid Build Coastguard Worker      gn_lines.insert(line_count, dst_line)
1008*6777b538SAndroid Build Coastguard Worker      line_count += 1
1009*6777b538SAndroid Build Coastguard Worker
1010*6777b538SAndroid Build Coastguard Worker    gn_lines = _SortListSubRange(
1011*6777b538SAndroid Build Coastguard Worker        gn_lines, start, line_count,
1012*6777b538SAndroid Build Coastguard Worker        lambda line: _RE_GN_VALUES_LIST_LINE.match(line).group(1))
1013*6777b538SAndroid Build Coastguard Worker
1014*6777b538SAndroid Build Coastguard Worker  return gn_lines
1015*6777b538SAndroid Build Coastguard Worker
1016*6777b538SAndroid Build Coastguard Worker
1017*6777b538SAndroid Build Coastguard Worker##########################################################################
1018*6777b538SAndroid Build Coastguard Worker##########################################################################
1019*6777b538SAndroid Build Coastguard Worker#####
1020*6777b538SAndroid Build Coastguard Worker#####    T R A N S L A T I O N   E X P E C T A T I O N S
1021*6777b538SAndroid Build Coastguard Worker#####
1022*6777b538SAndroid Build Coastguard Worker##########################################################################
1023*6777b538SAndroid Build Coastguard Worker##########################################################################
1024*6777b538SAndroid Build Coastguard Worker
1025*6777b538SAndroid Build Coastguard Worker_EXPECTATIONS_FILENAME = 'translation_expectations.pyl'
1026*6777b538SAndroid Build Coastguard Worker
1027*6777b538SAndroid Build Coastguard Worker# Technical note: the format of translation_expectations.pyl
1028*6777b538SAndroid Build Coastguard Worker# is a 'Python literal', which defines a python dictionary, so should
1029*6777b538SAndroid Build Coastguard Worker# be easy to parse. However, when modifying it, care should be taken
1030*6777b538SAndroid Build Coastguard Worker# to respect the line comments and the order of keys within the text
1031*6777b538SAndroid Build Coastguard Worker# file.
1032*6777b538SAndroid Build Coastguard Worker
1033*6777b538SAndroid Build Coastguard Worker
1034*6777b538SAndroid Build Coastguard Workerdef _ReadPythonLiteralFile(pyl_path):
1035*6777b538SAndroid Build Coastguard Worker  """Read a .pyl file into a Python data structure."""
1036*6777b538SAndroid Build Coastguard Worker  with open(pyl_path) as f:
1037*6777b538SAndroid Build Coastguard Worker    pyl_content = f.read()
1038*6777b538SAndroid Build Coastguard Worker  # Evaluate as a Python data structure, use an empty global
1039*6777b538SAndroid Build Coastguard Worker  # and local dictionary.
1040*6777b538SAndroid Build Coastguard Worker  return eval(pyl_content, dict(), dict())
1041*6777b538SAndroid Build Coastguard Worker
1042*6777b538SAndroid Build Coastguard Worker
1043*6777b538SAndroid Build Coastguard Workerdef _UpdateLocalesInExpectationLines(pyl_lines,
1044*6777b538SAndroid Build Coastguard Worker                                     wanted_locales,
1045*6777b538SAndroid Build Coastguard Worker                                     available_width=79):
1046*6777b538SAndroid Build Coastguard Worker  """Update the locales list(s) found in an expectations file.
1047*6777b538SAndroid Build Coastguard Worker
1048*6777b538SAndroid Build Coastguard Worker  Args:
1049*6777b538SAndroid Build Coastguard Worker    pyl_lines: Iterable of input lines from the file.
1050*6777b538SAndroid Build Coastguard Worker    wanted_locales: Set or list of new locale names.
1051*6777b538SAndroid Build Coastguard Worker    available_width: Optional, number of character colums used
1052*6777b538SAndroid Build Coastguard Worker      to word-wrap the new list items.
1053*6777b538SAndroid Build Coastguard Worker  Returns:
1054*6777b538SAndroid Build Coastguard Worker    New list of updated lines.
1055*6777b538SAndroid Build Coastguard Worker  """
1056*6777b538SAndroid Build Coastguard Worker  locales_list = ['"%s"' % loc for loc in sorted(wanted_locales)]
1057*6777b538SAndroid Build Coastguard Worker  result = []
1058*6777b538SAndroid Build Coastguard Worker  line_count = len(pyl_lines)
1059*6777b538SAndroid Build Coastguard Worker  line_num = 0
1060*6777b538SAndroid Build Coastguard Worker  DICT_START = '"languages": ['
1061*6777b538SAndroid Build Coastguard Worker  while line_num < line_count:
1062*6777b538SAndroid Build Coastguard Worker    line = pyl_lines[line_num]
1063*6777b538SAndroid Build Coastguard Worker    line_num += 1
1064*6777b538SAndroid Build Coastguard Worker    result.append(line)
1065*6777b538SAndroid Build Coastguard Worker    # Look for start of "languages" dictionary.
1066*6777b538SAndroid Build Coastguard Worker    pos = line.find(DICT_START)
1067*6777b538SAndroid Build Coastguard Worker    if pos < 0:
1068*6777b538SAndroid Build Coastguard Worker      continue
1069*6777b538SAndroid Build Coastguard Worker
1070*6777b538SAndroid Build Coastguard Worker    start_margin = pos
1071*6777b538SAndroid Build Coastguard Worker    start_line = line_num
1072*6777b538SAndroid Build Coastguard Worker    # Skip over all lines from the list.
1073*6777b538SAndroid Build Coastguard Worker    while (line_num < line_count and
1074*6777b538SAndroid Build Coastguard Worker           not pyl_lines[line_num].rstrip().endswith('],')):
1075*6777b538SAndroid Build Coastguard Worker      line_num += 1
1076*6777b538SAndroid Build Coastguard Worker      continue
1077*6777b538SAndroid Build Coastguard Worker
1078*6777b538SAndroid Build Coastguard Worker    if line_num == line_count:
1079*6777b538SAndroid Build Coastguard Worker      raise Exception('%d: Missing list termination!' % start_line)
1080*6777b538SAndroid Build Coastguard Worker
1081*6777b538SAndroid Build Coastguard Worker    # Format the new list according to the new margin.
1082*6777b538SAndroid Build Coastguard Worker    locale_width = available_width - (start_margin + 2)
1083*6777b538SAndroid Build Coastguard Worker    locale_lines = _PrettyPrintListAsLines(
1084*6777b538SAndroid Build Coastguard Worker        locales_list, locale_width, trailing_comma=True)
1085*6777b538SAndroid Build Coastguard Worker    for locale_line in locale_lines:
1086*6777b538SAndroid Build Coastguard Worker      result.append(' ' * (start_margin + 2) + locale_line)
1087*6777b538SAndroid Build Coastguard Worker    result.append(' ' * start_margin + '],')
1088*6777b538SAndroid Build Coastguard Worker    line_num += 1
1089*6777b538SAndroid Build Coastguard Worker
1090*6777b538SAndroid Build Coastguard Worker  return result
1091*6777b538SAndroid Build Coastguard Worker
1092*6777b538SAndroid Build Coastguard Worker
1093*6777b538SAndroid Build Coastguard Workerclass _UpdateLocalesInExpectationLinesTest(unittest.TestCase):
1094*6777b538SAndroid Build Coastguard Worker
1095*6777b538SAndroid Build Coastguard Worker  def test_simple(self):
1096*6777b538SAndroid Build Coastguard Worker    self.maxDiff = 1000
1097*6777b538SAndroid Build Coastguard Worker    input_text = r'''
1098*6777b538SAndroid Build Coastguard Worker# This comment should be preserved
1099*6777b538SAndroid Build Coastguard Worker# 23456789012345678901234567890123456789
1100*6777b538SAndroid Build Coastguard Worker{
1101*6777b538SAndroid Build Coastguard Worker  "android_grd": {
1102*6777b538SAndroid Build Coastguard Worker    "languages": [
1103*6777b538SAndroid Build Coastguard Worker      "aa", "bb", "cc", "dd", "ee",
1104*6777b538SAndroid Build Coastguard Worker      "ff", "gg", "hh", "ii", "jj",
1105*6777b538SAndroid Build Coastguard Worker      "kk"],
1106*6777b538SAndroid Build Coastguard Worker  },
1107*6777b538SAndroid Build Coastguard Worker  # Example with bad indentation in input.
1108*6777b538SAndroid Build Coastguard Worker  "another_grd": {
1109*6777b538SAndroid Build Coastguard Worker         "languages": [
1110*6777b538SAndroid Build Coastguard Worker  "aa", "bb", "cc", "dd", "ee", "ff", "gg", "hh", "ii", "jj", "kk",
1111*6777b538SAndroid Build Coastguard Worker      ],
1112*6777b538SAndroid Build Coastguard Worker  },
1113*6777b538SAndroid Build Coastguard Worker}
1114*6777b538SAndroid Build Coastguard Worker'''
1115*6777b538SAndroid Build Coastguard Worker    expected_text = r'''
1116*6777b538SAndroid Build Coastguard Worker# This comment should be preserved
1117*6777b538SAndroid Build Coastguard Worker# 23456789012345678901234567890123456789
1118*6777b538SAndroid Build Coastguard Worker{
1119*6777b538SAndroid Build Coastguard Worker  "android_grd": {
1120*6777b538SAndroid Build Coastguard Worker    "languages": [
1121*6777b538SAndroid Build Coastguard Worker      "A2", "AA", "BB", "CC", "DD",
1122*6777b538SAndroid Build Coastguard Worker      "E2", "EE", "FF", "GG", "HH",
1123*6777b538SAndroid Build Coastguard Worker      "I2", "II", "JJ", "KK",
1124*6777b538SAndroid Build Coastguard Worker    ],
1125*6777b538SAndroid Build Coastguard Worker  },
1126*6777b538SAndroid Build Coastguard Worker  # Example with bad indentation in input.
1127*6777b538SAndroid Build Coastguard Worker  "another_grd": {
1128*6777b538SAndroid Build Coastguard Worker         "languages": [
1129*6777b538SAndroid Build Coastguard Worker           "A2", "AA", "BB", "CC", "DD",
1130*6777b538SAndroid Build Coastguard Worker           "E2", "EE", "FF", "GG", "HH",
1131*6777b538SAndroid Build Coastguard Worker           "I2", "II", "JJ", "KK",
1132*6777b538SAndroid Build Coastguard Worker         ],
1133*6777b538SAndroid Build Coastguard Worker  },
1134*6777b538SAndroid Build Coastguard Worker}
1135*6777b538SAndroid Build Coastguard Worker'''
1136*6777b538SAndroid Build Coastguard Worker    input_lines = input_text.splitlines()
1137*6777b538SAndroid Build Coastguard Worker    test_locales = ([
1138*6777b538SAndroid Build Coastguard Worker        'AA', 'BB', 'CC', 'DD', 'EE', 'FF', 'GG', 'HH', 'II', 'JJ', 'KK', 'A2',
1139*6777b538SAndroid Build Coastguard Worker        'E2', 'I2'
1140*6777b538SAndroid Build Coastguard Worker    ])
1141*6777b538SAndroid Build Coastguard Worker    expected_lines = expected_text.splitlines()
1142*6777b538SAndroid Build Coastguard Worker    self.assertListEqual(
1143*6777b538SAndroid Build Coastguard Worker        _UpdateLocalesInExpectationLines(input_lines, test_locales, 40),
1144*6777b538SAndroid Build Coastguard Worker        expected_lines)
1145*6777b538SAndroid Build Coastguard Worker
1146*6777b538SAndroid Build Coastguard Worker  def test_missing_list_termination(self):
1147*6777b538SAndroid Build Coastguard Worker    input_lines = r'''
1148*6777b538SAndroid Build Coastguard Worker  "languages": ['
1149*6777b538SAndroid Build Coastguard Worker    "aa", "bb", "cc", "dd"
1150*6777b538SAndroid Build Coastguard Worker'''.splitlines()
1151*6777b538SAndroid Build Coastguard Worker    with self.assertRaises(Exception) as cm:
1152*6777b538SAndroid Build Coastguard Worker      _UpdateLocalesInExpectationLines(input_lines, ['a', 'b'], 40)
1153*6777b538SAndroid Build Coastguard Worker
1154*6777b538SAndroid Build Coastguard Worker    self.assertEqual(str(cm.exception), '2: Missing list termination!')
1155*6777b538SAndroid Build Coastguard Worker
1156*6777b538SAndroid Build Coastguard Worker
1157*6777b538SAndroid Build Coastguard Workerdef _UpdateLocalesInExpectationFile(pyl_path, wanted_locales):
1158*6777b538SAndroid Build Coastguard Worker  """Update all locales listed in a given expectations file.
1159*6777b538SAndroid Build Coastguard Worker
1160*6777b538SAndroid Build Coastguard Worker  Args:
1161*6777b538SAndroid Build Coastguard Worker    pyl_path: Path to .pyl file to update.
1162*6777b538SAndroid Build Coastguard Worker    wanted_locales: List of locales that need to be written to
1163*6777b538SAndroid Build Coastguard Worker      the file.
1164*6777b538SAndroid Build Coastguard Worker  """
1165*6777b538SAndroid Build Coastguard Worker  tc_locales = {
1166*6777b538SAndroid Build Coastguard Worker      _FixTranslationConsoleLocaleName(locale)
1167*6777b538SAndroid Build Coastguard Worker      for locale in set(wanted_locales) - set([_DEFAULT_LOCALE])
1168*6777b538SAndroid Build Coastguard Worker  }
1169*6777b538SAndroid Build Coastguard Worker
1170*6777b538SAndroid Build Coastguard Worker  with open(pyl_path) as f:
1171*6777b538SAndroid Build Coastguard Worker    input_lines = [l.rstrip() for l in f.readlines()]
1172*6777b538SAndroid Build Coastguard Worker
1173*6777b538SAndroid Build Coastguard Worker  updated_lines = _UpdateLocalesInExpectationLines(input_lines, tc_locales)
1174*6777b538SAndroid Build Coastguard Worker  with build_utils.AtomicOutput(pyl_path) as f:
1175*6777b538SAndroid Build Coastguard Worker    f.writelines('\n'.join(updated_lines) + '\n')
1176*6777b538SAndroid Build Coastguard Worker
1177*6777b538SAndroid Build Coastguard Worker
1178*6777b538SAndroid Build Coastguard Worker##########################################################################
1179*6777b538SAndroid Build Coastguard Worker##########################################################################
1180*6777b538SAndroid Build Coastguard Worker#####
1181*6777b538SAndroid Build Coastguard Worker#####    C H E C K   E V E R Y T H I N G
1182*6777b538SAndroid Build Coastguard Worker#####
1183*6777b538SAndroid Build Coastguard Worker##########################################################################
1184*6777b538SAndroid Build Coastguard Worker##########################################################################
1185*6777b538SAndroid Build Coastguard Worker
1186*6777b538SAndroid Build Coastguard Worker# pylint: enable=unused-argument
1187*6777b538SAndroid Build Coastguard Worker
1188*6777b538SAndroid Build Coastguard Worker
1189*6777b538SAndroid Build Coastguard Workerdef _IsAllInputFile(input_file):
1190*6777b538SAndroid Build Coastguard Worker  return _IsGritInputFile(input_file) or _IsBuildGnInputFile(input_file)
1191*6777b538SAndroid Build Coastguard Worker
1192*6777b538SAndroid Build Coastguard Worker
1193*6777b538SAndroid Build Coastguard Workerdef _CheckAllFiles(input_file, input_lines, wanted_locales):
1194*6777b538SAndroid Build Coastguard Worker  errors = []
1195*6777b538SAndroid Build Coastguard Worker  if _IsGritInputFile(input_file):
1196*6777b538SAndroid Build Coastguard Worker    errors += _CheckGrdTranslations(input_file, input_lines, wanted_locales)
1197*6777b538SAndroid Build Coastguard Worker    errors += _CheckGrdAndroidOutputElements(
1198*6777b538SAndroid Build Coastguard Worker        input_file, input_lines, wanted_locales)
1199*6777b538SAndroid Build Coastguard Worker  elif _IsBuildGnInputFile(input_file):
1200*6777b538SAndroid Build Coastguard Worker    errors += _CheckGnAndroidOutputs(input_file, input_lines, wanted_locales)
1201*6777b538SAndroid Build Coastguard Worker  return errors
1202*6777b538SAndroid Build Coastguard Worker
1203*6777b538SAndroid Build Coastguard Worker
1204*6777b538SAndroid Build Coastguard Workerdef _AddMissingLocalesInAllFiles(input_file, input_lines, wanted_locales):
1205*6777b538SAndroid Build Coastguard Worker  if _IsGritInputFile(input_file):
1206*6777b538SAndroid Build Coastguard Worker    lines = _AddMissingLocalesInGrdTranslations(
1207*6777b538SAndroid Build Coastguard Worker        input_file, input_lines, wanted_locales)
1208*6777b538SAndroid Build Coastguard Worker    lines = _AddMissingLocalesInGrdAndroidOutputs(
1209*6777b538SAndroid Build Coastguard Worker        input_file, lines, wanted_locales)
1210*6777b538SAndroid Build Coastguard Worker  elif _IsBuildGnInputFile(input_file):
1211*6777b538SAndroid Build Coastguard Worker    lines = _AddMissingLocalesInGnAndroidOutputs(
1212*6777b538SAndroid Build Coastguard Worker        input_file, input_lines, wanted_locales)
1213*6777b538SAndroid Build Coastguard Worker  return lines
1214*6777b538SAndroid Build Coastguard Worker
1215*6777b538SAndroid Build Coastguard Worker
1216*6777b538SAndroid Build Coastguard Worker##########################################################################
1217*6777b538SAndroid Build Coastguard Worker##########################################################################
1218*6777b538SAndroid Build Coastguard Worker#####
1219*6777b538SAndroid Build Coastguard Worker#####    C O M M A N D   H A N D L I N G
1220*6777b538SAndroid Build Coastguard Worker#####
1221*6777b538SAndroid Build Coastguard Worker##########################################################################
1222*6777b538SAndroid Build Coastguard Worker##########################################################################
1223*6777b538SAndroid Build Coastguard Worker
1224*6777b538SAndroid Build Coastguard Workerclass _Command(object):
1225*6777b538SAndroid Build Coastguard Worker  """A base class for all commands recognized by this script.
1226*6777b538SAndroid Build Coastguard Worker
1227*6777b538SAndroid Build Coastguard Worker  Usage is the following:
1228*6777b538SAndroid Build Coastguard Worker    1) Derived classes must re-define the following class-based fields:
1229*6777b538SAndroid Build Coastguard Worker       - name: Command name (e.g. 'list-locales')
1230*6777b538SAndroid Build Coastguard Worker       - description: Command short description.
1231*6777b538SAndroid Build Coastguard Worker       - long_description: Optional. Command long description.
1232*6777b538SAndroid Build Coastguard Worker         NOTE: As a convenience, if the first character is a newline,
1233*6777b538SAndroid Build Coastguard Worker         it will be omitted in the help output.
1234*6777b538SAndroid Build Coastguard Worker
1235*6777b538SAndroid Build Coastguard Worker    2) Derived classes for commands that take arguments should override
1236*6777b538SAndroid Build Coastguard Worker       RegisterExtraArgs(), which receives a corresponding argparse
1237*6777b538SAndroid Build Coastguard Worker       sub-parser as argument.
1238*6777b538SAndroid Build Coastguard Worker
1239*6777b538SAndroid Build Coastguard Worker    3) Derived classes should implement a Run() command, which can read
1240*6777b538SAndroid Build Coastguard Worker       the current arguments from self.args.
1241*6777b538SAndroid Build Coastguard Worker  """
1242*6777b538SAndroid Build Coastguard Worker  name = None
1243*6777b538SAndroid Build Coastguard Worker  description = None
1244*6777b538SAndroid Build Coastguard Worker  long_description = None
1245*6777b538SAndroid Build Coastguard Worker
1246*6777b538SAndroid Build Coastguard Worker  def __init__(self):
1247*6777b538SAndroid Build Coastguard Worker    self._parser = None
1248*6777b538SAndroid Build Coastguard Worker    self.args = None
1249*6777b538SAndroid Build Coastguard Worker
1250*6777b538SAndroid Build Coastguard Worker  def RegisterExtraArgs(self, subparser):
1251*6777b538SAndroid Build Coastguard Worker    pass
1252*6777b538SAndroid Build Coastguard Worker
1253*6777b538SAndroid Build Coastguard Worker  def RegisterArgs(self, parser):
1254*6777b538SAndroid Build Coastguard Worker    subp = parser.add_parser(
1255*6777b538SAndroid Build Coastguard Worker        self.name, help=self.description,
1256*6777b538SAndroid Build Coastguard Worker        description=self.long_description or self.description,
1257*6777b538SAndroid Build Coastguard Worker        formatter_class=argparse.RawDescriptionHelpFormatter)
1258*6777b538SAndroid Build Coastguard Worker    self._parser = subp
1259*6777b538SAndroid Build Coastguard Worker    subp.set_defaults(command=self)
1260*6777b538SAndroid Build Coastguard Worker    group = subp.add_argument_group('%s arguments' % self.name)
1261*6777b538SAndroid Build Coastguard Worker    self.RegisterExtraArgs(group)
1262*6777b538SAndroid Build Coastguard Worker
1263*6777b538SAndroid Build Coastguard Worker  def ProcessArgs(self, args):
1264*6777b538SAndroid Build Coastguard Worker    self.args = args
1265*6777b538SAndroid Build Coastguard Worker
1266*6777b538SAndroid Build Coastguard Worker
1267*6777b538SAndroid Build Coastguard Workerclass _ListLocalesCommand(_Command):
1268*6777b538SAndroid Build Coastguard Worker  """Implement the 'list-locales' command to list locale lists of interest."""
1269*6777b538SAndroid Build Coastguard Worker  name = 'list-locales'
1270*6777b538SAndroid Build Coastguard Worker  description = 'List supported Chrome locales'
1271*6777b538SAndroid Build Coastguard Worker  long_description = r'''
1272*6777b538SAndroid Build Coastguard WorkerList locales of interest, by default this prints all locales supported by
1273*6777b538SAndroid Build Coastguard WorkerChrome, but `--type=ios_unsupported` can be used for the list of locales
1274*6777b538SAndroid Build Coastguard Workerunsupported on iOS.
1275*6777b538SAndroid Build Coastguard Worker
1276*6777b538SAndroid Build Coastguard WorkerThese values are extracted directly from build/config/locales.gni.
1277*6777b538SAndroid Build Coastguard Worker
1278*6777b538SAndroid Build Coastguard WorkerAdditionally, use the --as-json argument to print the list as a JSON list,
1279*6777b538SAndroid Build Coastguard Workerinstead of the default format (which is a space-separated list of locale names).
1280*6777b538SAndroid Build Coastguard Worker'''
1281*6777b538SAndroid Build Coastguard Worker
1282*6777b538SAndroid Build Coastguard Worker  # Maps type argument to a function returning the corresponding locales list.
1283*6777b538SAndroid Build Coastguard Worker  TYPE_MAP = {
1284*6777b538SAndroid Build Coastguard Worker      'all': ChromeLocales,
1285*6777b538SAndroid Build Coastguard Worker      'ios_unsupported': IosUnsupportedLocales,
1286*6777b538SAndroid Build Coastguard Worker  }
1287*6777b538SAndroid Build Coastguard Worker
1288*6777b538SAndroid Build Coastguard Worker  def RegisterExtraArgs(self, group):
1289*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1290*6777b538SAndroid Build Coastguard Worker        '--as-json',
1291*6777b538SAndroid Build Coastguard Worker        action='store_true',
1292*6777b538SAndroid Build Coastguard Worker        help='Output as JSON list.')
1293*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1294*6777b538SAndroid Build Coastguard Worker        '--type',
1295*6777b538SAndroid Build Coastguard Worker        choices=tuple(self.TYPE_MAP.viewkeys()),
1296*6777b538SAndroid Build Coastguard Worker        default='all',
1297*6777b538SAndroid Build Coastguard Worker        help='Select type of locale list to print.')
1298*6777b538SAndroid Build Coastguard Worker
1299*6777b538SAndroid Build Coastguard Worker  def Run(self):
1300*6777b538SAndroid Build Coastguard Worker    locale_list = self.TYPE_MAP[self.args.type]()
1301*6777b538SAndroid Build Coastguard Worker    if self.args.as_json:
1302*6777b538SAndroid Build Coastguard Worker      print('[%s]' % ", ".join("'%s'" % loc for loc in locale_list))
1303*6777b538SAndroid Build Coastguard Worker    else:
1304*6777b538SAndroid Build Coastguard Worker      print(' '.join(locale_list))
1305*6777b538SAndroid Build Coastguard Worker
1306*6777b538SAndroid Build Coastguard Worker
1307*6777b538SAndroid Build Coastguard Workerclass _CheckInputFileBaseCommand(_Command):
1308*6777b538SAndroid Build Coastguard Worker  """Used as a base for other _Command subclasses that check input files.
1309*6777b538SAndroid Build Coastguard Worker
1310*6777b538SAndroid Build Coastguard Worker  Subclasses should also define the following class-level variables:
1311*6777b538SAndroid Build Coastguard Worker
1312*6777b538SAndroid Build Coastguard Worker  - select_file_func:
1313*6777b538SAndroid Build Coastguard Worker      A predicate that receives a file name (not path) and return True if it
1314*6777b538SAndroid Build Coastguard Worker      should be selected for inspection. Used when scanning directories with
1315*6777b538SAndroid Build Coastguard Worker      '--scan-dir <dir>'.
1316*6777b538SAndroid Build Coastguard Worker
1317*6777b538SAndroid Build Coastguard Worker  - check_func:
1318*6777b538SAndroid Build Coastguard Worker  - fix_func:
1319*6777b538SAndroid Build Coastguard Worker      Two functions passed as parameters to _ProcessFile(), see relevant
1320*6777b538SAndroid Build Coastguard Worker      documentation in this function's definition.
1321*6777b538SAndroid Build Coastguard Worker  """
1322*6777b538SAndroid Build Coastguard Worker  select_file_func = None
1323*6777b538SAndroid Build Coastguard Worker  check_func = None
1324*6777b538SAndroid Build Coastguard Worker  fix_func = None
1325*6777b538SAndroid Build Coastguard Worker
1326*6777b538SAndroid Build Coastguard Worker  def RegisterExtraArgs(self, group):
1327*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1328*6777b538SAndroid Build Coastguard Worker      '--scan-dir',
1329*6777b538SAndroid Build Coastguard Worker      action='append',
1330*6777b538SAndroid Build Coastguard Worker      help='Optional directory to scan for input files recursively.')
1331*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1332*6777b538SAndroid Build Coastguard Worker      'input',
1333*6777b538SAndroid Build Coastguard Worker      nargs='*',
1334*6777b538SAndroid Build Coastguard Worker      help='Input file(s) to check.')
1335*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1336*6777b538SAndroid Build Coastguard Worker      '--fix-inplace',
1337*6777b538SAndroid Build Coastguard Worker      action='store_true',
1338*6777b538SAndroid Build Coastguard Worker      help='Try to fix the files in-place too.')
1339*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1340*6777b538SAndroid Build Coastguard Worker      '--add-locales',
1341*6777b538SAndroid Build Coastguard Worker      help='Space-separated list of additional locales to use')
1342*6777b538SAndroid Build Coastguard Worker
1343*6777b538SAndroid Build Coastguard Worker  def Run(self):
1344*6777b538SAndroid Build Coastguard Worker    args = self.args
1345*6777b538SAndroid Build Coastguard Worker    input_files = []
1346*6777b538SAndroid Build Coastguard Worker    if args.input:
1347*6777b538SAndroid Build Coastguard Worker      input_files = args.input
1348*6777b538SAndroid Build Coastguard Worker    if args.scan_dir:
1349*6777b538SAndroid Build Coastguard Worker      input_files.extend(_ScanDirectoriesForFiles(
1350*6777b538SAndroid Build Coastguard Worker          args.scan_dir, self.select_file_func.__func__))
1351*6777b538SAndroid Build Coastguard Worker    locales = ChromeLocales()
1352*6777b538SAndroid Build Coastguard Worker    if args.add_locales:
1353*6777b538SAndroid Build Coastguard Worker      locales.extend(args.add_locales.split(' '))
1354*6777b538SAndroid Build Coastguard Worker
1355*6777b538SAndroid Build Coastguard Worker    locales = set(locales)
1356*6777b538SAndroid Build Coastguard Worker
1357*6777b538SAndroid Build Coastguard Worker    for input_file in input_files:
1358*6777b538SAndroid Build Coastguard Worker      _ProcessFile(input_file,
1359*6777b538SAndroid Build Coastguard Worker                   locales,
1360*6777b538SAndroid Build Coastguard Worker                   self.check_func.__func__,
1361*6777b538SAndroid Build Coastguard Worker                   self.fix_func.__func__ if args.fix_inplace else None)
1362*6777b538SAndroid Build Coastguard Worker    print('%sDone.' % (_CONSOLE_START_LINE))
1363*6777b538SAndroid Build Coastguard Worker
1364*6777b538SAndroid Build Coastguard Worker
1365*6777b538SAndroid Build Coastguard Workerclass _CheckGrdAndroidOutputsCommand(_CheckInputFileBaseCommand):
1366*6777b538SAndroid Build Coastguard Worker  name = 'check-grd-android-outputs'
1367*6777b538SAndroid Build Coastguard Worker  description = (
1368*6777b538SAndroid Build Coastguard Worker      'Check the Android resource (.xml) files outputs in GRIT input files.')
1369*6777b538SAndroid Build Coastguard Worker  long_description = r'''
1370*6777b538SAndroid Build Coastguard WorkerCheck the Android .xml files outputs in one or more input GRIT (.grd) files
1371*6777b538SAndroid Build Coastguard Workerfor the following conditions:
1372*6777b538SAndroid Build Coastguard Worker
1373*6777b538SAndroid Build Coastguard Worker    - Each item has a correct 'lang' attribute.
1374*6777b538SAndroid Build Coastguard Worker    - There are no duplicated lines for the same 'lang' attribute.
1375*6777b538SAndroid Build Coastguard Worker    - That there are no extra locales that Chromium doesn't want.
1376*6777b538SAndroid Build Coastguard Worker    - That no wanted locale is missing.
1377*6777b538SAndroid Build Coastguard Worker    - Filenames exist for each listed locale.
1378*6777b538SAndroid Build Coastguard Worker    - Filenames are well-formed.
1379*6777b538SAndroid Build Coastguard Worker'''
1380*6777b538SAndroid Build Coastguard Worker  select_file_func = _IsGritInputFile
1381*6777b538SAndroid Build Coastguard Worker  check_func = _CheckGrdAndroidOutputElements
1382*6777b538SAndroid Build Coastguard Worker  fix_func = _AddMissingLocalesInGrdAndroidOutputs
1383*6777b538SAndroid Build Coastguard Worker
1384*6777b538SAndroid Build Coastguard Worker
1385*6777b538SAndroid Build Coastguard Workerclass _CheckGrdTranslationsCommand(_CheckInputFileBaseCommand):
1386*6777b538SAndroid Build Coastguard Worker  name = 'check-grd-translations'
1387*6777b538SAndroid Build Coastguard Worker  description = (
1388*6777b538SAndroid Build Coastguard Worker      'Check the translation (.xtb) files outputted by .grd input files.')
1389*6777b538SAndroid Build Coastguard Worker  long_description = r'''
1390*6777b538SAndroid Build Coastguard WorkerCheck the translation (.xtb) file outputs in one or more input GRIT (.grd) files
1391*6777b538SAndroid Build Coastguard Workerfor the following conditions:
1392*6777b538SAndroid Build Coastguard Worker
1393*6777b538SAndroid Build Coastguard Worker    - Each item has a correct 'lang' attribute.
1394*6777b538SAndroid Build Coastguard Worker    - There are no duplicated lines for the same 'lang' attribute.
1395*6777b538SAndroid Build Coastguard Worker    - That there are no extra locales that Chromium doesn't want.
1396*6777b538SAndroid Build Coastguard Worker    - That no wanted locale is missing.
1397*6777b538SAndroid Build Coastguard Worker    - Each item has a 'path' attribute.
1398*6777b538SAndroid Build Coastguard Worker    - Each such path value ends up with '.xtb'.
1399*6777b538SAndroid Build Coastguard Worker'''
1400*6777b538SAndroid Build Coastguard Worker  select_file_func = _IsGritInputFile
1401*6777b538SAndroid Build Coastguard Worker  check_func = _CheckGrdTranslations
1402*6777b538SAndroid Build Coastguard Worker  fix_func = _AddMissingLocalesInGrdTranslations
1403*6777b538SAndroid Build Coastguard Worker
1404*6777b538SAndroid Build Coastguard Worker
1405*6777b538SAndroid Build Coastguard Workerclass _CheckGnAndroidOutputsCommand(_CheckInputFileBaseCommand):
1406*6777b538SAndroid Build Coastguard Worker  name = 'check-gn-android-outputs'
1407*6777b538SAndroid Build Coastguard Worker  description = 'Check the Android .xml file lists in GN build files.'
1408*6777b538SAndroid Build Coastguard Worker  long_description = r'''
1409*6777b538SAndroid Build Coastguard WorkerCheck one or more BUILD.gn file, looking for lists of Android resource .xml
1410*6777b538SAndroid Build Coastguard Workerfiles, and checking that:
1411*6777b538SAndroid Build Coastguard Worker
1412*6777b538SAndroid Build Coastguard Worker  - There are no duplicated output files in the list.
1413*6777b538SAndroid Build Coastguard Worker  - Each output file belongs to a wanted Chromium locale.
1414*6777b538SAndroid Build Coastguard Worker  - There are no output files for unwanted Chromium locales.
1415*6777b538SAndroid Build Coastguard Worker'''
1416*6777b538SAndroid Build Coastguard Worker  select_file_func = _IsBuildGnInputFile
1417*6777b538SAndroid Build Coastguard Worker  check_func = _CheckGnAndroidOutputs
1418*6777b538SAndroid Build Coastguard Worker  fix_func = _AddMissingLocalesInGnAndroidOutputs
1419*6777b538SAndroid Build Coastguard Worker
1420*6777b538SAndroid Build Coastguard Worker
1421*6777b538SAndroid Build Coastguard Workerclass _CheckAllCommand(_CheckInputFileBaseCommand):
1422*6777b538SAndroid Build Coastguard Worker  name = 'check-all'
1423*6777b538SAndroid Build Coastguard Worker  description = 'Check everything.'
1424*6777b538SAndroid Build Coastguard Worker  long_description = 'Equivalent to calling all other check-xxx commands.'
1425*6777b538SAndroid Build Coastguard Worker  select_file_func = _IsAllInputFile
1426*6777b538SAndroid Build Coastguard Worker  check_func = _CheckAllFiles
1427*6777b538SAndroid Build Coastguard Worker  fix_func = _AddMissingLocalesInAllFiles
1428*6777b538SAndroid Build Coastguard Worker
1429*6777b538SAndroid Build Coastguard Worker
1430*6777b538SAndroid Build Coastguard Workerclass _UpdateExpectationsCommand(_Command):
1431*6777b538SAndroid Build Coastguard Worker  name = 'update-expectations'
1432*6777b538SAndroid Build Coastguard Worker  description = 'Update translation expectations file.'
1433*6777b538SAndroid Build Coastguard Worker  long_description = r'''
1434*6777b538SAndroid Build Coastguard WorkerUpdate %s files to match the current list of locales supported by Chromium.
1435*6777b538SAndroid Build Coastguard WorkerThis is especially useful to add new locales before updating any GRIT or GN
1436*6777b538SAndroid Build Coastguard Workerinput file with the --add-locales option.
1437*6777b538SAndroid Build Coastguard Worker''' % _EXPECTATIONS_FILENAME
1438*6777b538SAndroid Build Coastguard Worker
1439*6777b538SAndroid Build Coastguard Worker  def RegisterExtraArgs(self, group):
1440*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1441*6777b538SAndroid Build Coastguard Worker        '--add-locales',
1442*6777b538SAndroid Build Coastguard Worker        help='Space-separated list of additional locales to use.')
1443*6777b538SAndroid Build Coastguard Worker
1444*6777b538SAndroid Build Coastguard Worker  def Run(self):
1445*6777b538SAndroid Build Coastguard Worker    locales = ChromeLocales()
1446*6777b538SAndroid Build Coastguard Worker    add_locales = self.args.add_locales
1447*6777b538SAndroid Build Coastguard Worker    if add_locales:
1448*6777b538SAndroid Build Coastguard Worker      locales.extend(add_locales.split(' '))
1449*6777b538SAndroid Build Coastguard Worker
1450*6777b538SAndroid Build Coastguard Worker    expectation_paths = [
1451*6777b538SAndroid Build Coastguard Worker        'tools/gritsettings/translation_expectations.pyl',
1452*6777b538SAndroid Build Coastguard Worker        'clank/tools/translation_expectations.pyl',
1453*6777b538SAndroid Build Coastguard Worker    ]
1454*6777b538SAndroid Build Coastguard Worker    missing_expectation_files = []
1455*6777b538SAndroid Build Coastguard Worker    for path in enumerate(expectation_paths):
1456*6777b538SAndroid Build Coastguard Worker      file_path = os.path.join(_TOP_SRC_DIR, path)
1457*6777b538SAndroid Build Coastguard Worker      if not os.path.exists(file_path):
1458*6777b538SAndroid Build Coastguard Worker        missing_expectation_files.append(file_path)
1459*6777b538SAndroid Build Coastguard Worker        continue
1460*6777b538SAndroid Build Coastguard Worker      _UpdateLocalesInExpectationFile(file_path, locales)
1461*6777b538SAndroid Build Coastguard Worker
1462*6777b538SAndroid Build Coastguard Worker    if missing_expectation_files:
1463*6777b538SAndroid Build Coastguard Worker      sys.stderr.write('WARNING: Missing file(s): %s\n' %
1464*6777b538SAndroid Build Coastguard Worker                       (', '.join(missing_expectation_files)))
1465*6777b538SAndroid Build Coastguard Worker
1466*6777b538SAndroid Build Coastguard Worker
1467*6777b538SAndroid Build Coastguard Workerclass _UnitTestsCommand(_Command):
1468*6777b538SAndroid Build Coastguard Worker  name = 'unit-tests'
1469*6777b538SAndroid Build Coastguard Worker  description = 'Run internal unit-tests for this script'
1470*6777b538SAndroid Build Coastguard Worker
1471*6777b538SAndroid Build Coastguard Worker  def RegisterExtraArgs(self, group):
1472*6777b538SAndroid Build Coastguard Worker    group.add_argument(
1473*6777b538SAndroid Build Coastguard Worker        '-v', '--verbose', action='count', help='Increase test verbosity.')
1474*6777b538SAndroid Build Coastguard Worker    group.add_argument('args', nargs=argparse.REMAINDER)
1475*6777b538SAndroid Build Coastguard Worker
1476*6777b538SAndroid Build Coastguard Worker  def Run(self):
1477*6777b538SAndroid Build Coastguard Worker    argv = [_SCRIPT_NAME] + self.args.args
1478*6777b538SAndroid Build Coastguard Worker    unittest.main(argv=argv, verbosity=self.args.verbose)
1479*6777b538SAndroid Build Coastguard Worker
1480*6777b538SAndroid Build Coastguard Worker
1481*6777b538SAndroid Build Coastguard Worker# List of all commands supported by this script.
1482*6777b538SAndroid Build Coastguard Worker_COMMANDS = [
1483*6777b538SAndroid Build Coastguard Worker    _ListLocalesCommand,
1484*6777b538SAndroid Build Coastguard Worker    _CheckGrdAndroidOutputsCommand,
1485*6777b538SAndroid Build Coastguard Worker    _CheckGrdTranslationsCommand,
1486*6777b538SAndroid Build Coastguard Worker    _CheckGnAndroidOutputsCommand,
1487*6777b538SAndroid Build Coastguard Worker    _CheckAllCommand,
1488*6777b538SAndroid Build Coastguard Worker    _UpdateExpectationsCommand,
1489*6777b538SAndroid Build Coastguard Worker    _UnitTestsCommand,
1490*6777b538SAndroid Build Coastguard Worker]
1491*6777b538SAndroid Build Coastguard Worker
1492*6777b538SAndroid Build Coastguard Worker
1493*6777b538SAndroid Build Coastguard Workerdef main(argv):
1494*6777b538SAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(
1495*6777b538SAndroid Build Coastguard Worker      description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
1496*6777b538SAndroid Build Coastguard Worker
1497*6777b538SAndroid Build Coastguard Worker  subparsers = parser.add_subparsers()
1498*6777b538SAndroid Build Coastguard Worker  commands = [clazz() for clazz in _COMMANDS]
1499*6777b538SAndroid Build Coastguard Worker  for command in commands:
1500*6777b538SAndroid Build Coastguard Worker    command.RegisterArgs(subparsers)
1501*6777b538SAndroid Build Coastguard Worker
1502*6777b538SAndroid Build Coastguard Worker  if not argv:
1503*6777b538SAndroid Build Coastguard Worker    argv = ['--help']
1504*6777b538SAndroid Build Coastguard Worker
1505*6777b538SAndroid Build Coastguard Worker  args = parser.parse_args(argv)
1506*6777b538SAndroid Build Coastguard Worker  args.command.ProcessArgs(args)
1507*6777b538SAndroid Build Coastguard Worker  args.command.Run()
1508*6777b538SAndroid Build Coastguard Worker
1509*6777b538SAndroid Build Coastguard Worker
1510*6777b538SAndroid Build Coastguard Workerif __name__ == "__main__":
1511*6777b538SAndroid Build Coastguard Worker  main(sys.argv[1:])
1512