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