Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 1 | #!/usr/bin/python2 |
| 2 | # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| 3 | # Use of this source code is governed by a BSD-style license that can be |
| 4 | # found in the LICENSE file. |
| 5 | |
| 6 | """Authoritative source for Chromium OS region/locale configuration. |
| 7 | |
| 8 | Run this module to display all known regions (use --help to see options). |
| 9 | """ |
| 10 | |
| 11 | from __future__ import print_function |
| 12 | |
| 13 | import argparse |
| 14 | import collections |
| 15 | import json |
| 16 | import re |
| 17 | import sys |
| 18 | |
| 19 | import yaml |
| 20 | |
| 21 | |
| 22 | # The regular expression to check values in Region.keyboards and Region.locales. |
| 23 | # Keyboards should come with xkb: protocol, or the input methods (ime:, m17n:). |
| 24 | # Examples: xkb:us:intl:eng, ime:ime:zh-t:cangjie |
| 25 | KEYBOARD_PATTERN = re.compile(r'^xkb:\w+:\w*:\w+$|' |
| 26 | r'^(ime|m17n|t13n):[\w:-]+$') |
| 27 | # Locale should be a combination of language and location. |
| 28 | # Examples: en-US, ja. |
| 29 | LOCALE_PATTERN = re.compile(r'^(\w+)(-[A-Z0-9]+)?$') |
| 30 | |
| 31 | |
| 32 | class Enum(frozenset): |
| 33 | """An enumeration type. |
| 34 | |
| 35 | Usage: |
| 36 | To create a enum object: |
| 37 | dummy_enum = Enum(['A', 'B', 'C']) |
| 38 | |
| 39 | To access a enum object, use: |
| 40 | dummy_enum.A |
| 41 | dummy_enum.B |
| 42 | """ |
| 43 | |
| 44 | def __getattr__(self, name): |
| 45 | if name in self: |
| 46 | return name |
| 47 | raise AttributeError |
| 48 | |
| 49 | |
| 50 | class RegionException(Exception): |
| 51 | """Exception in Region handling.""" |
| 52 | pass |
| 53 | |
| 54 | |
| 55 | def MakeList(value): |
| 56 | """Converts the given value to a list. |
| 57 | |
| 58 | Returns: |
| 59 | A list of elements from "value" if it is iterable (except string); |
| 60 | otherwise, a list contains only one element. |
| 61 | """ |
| 62 | if (isinstance(value, collections.Iterable) and |
| 63 | not isinstance(value, basestring)): |
| 64 | return list(value) |
| 65 | return [value] |
| 66 | |
| 67 | |
| 68 | class Region(object): |
| 69 | """Comprehensive, standard locale configuration per country/region. |
| 70 | |
| 71 | See :ref:`regions-values` for detailed information on how to set these values. |
| 72 | """ |
| 73 | # pylint gets confused by some of the docstrings. |
| 74 | # pylint: disable=C0322 |
| 75 | |
| 76 | # ANSI = US-like |
| 77 | # ISO = UK-like |
| 78 | # JIS = Japanese |
| 79 | # KS = Korean |
| 80 | # ABNT2 = Brazilian (like ISO but with an extra key to the left of the |
| 81 | # right shift key) |
| 82 | KeyboardMechanicalLayout = Enum(['ANSI', 'ISO', 'JIS', 'KS', 'ABNT2']) |
| 83 | |
| 84 | region_code = None |
| 85 | """A unique identifier for the region. This may be a lower-case |
| 86 | `ISO 3166-1 alpha-2 code |
| 87 | <http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2>`_ (e.g., ``us``), |
| 88 | a variant within an alpha-2 entity (e.g., ``ca.fr``), or an |
| 89 | identifier for a collection of countries or entities (e.g., |
| 90 | ``latam-es-419`` or ``nordic``). See :ref:`region-codes`. |
| 91 | |
| 92 | Note that ``uk`` is not a valid identifier; ``gb`` is used as it is |
| 93 | the real alpha-2 code for the UK.""" |
| 94 | |
| 95 | keyboards = None |
| 96 | """A list of keyboard layout identifiers (e.g., ``xkb:us:intl:eng`` |
| 97 | or ``m17n:ar``). This field was designed to be the physical keyboard layout |
| 98 | in the beginning, and then becomes a list of OOBE keyboard selection, which |
| 99 | then includes non-physical layout elements like input methods (``ime:``). |
| 100 | To avoid confusion, physical layout is now defined by |
| 101 | :py:attr:`keyboard_mechanical_layout`, and this is reserved for logical |
| 102 | layouts. |
| 103 | |
| 104 | This is identical to the legacy VPD ``keyboard_layout`` value.""" |
| 105 | |
| 106 | time_zones = None |
| 107 | """A list of default `tz database time zone |
| 108 | <http://en.wikipedia.org/wiki/List_of_tz_database_time_zones>`_ |
| 109 | identifiers (e.g., ``America/Los_Angeles``). See |
| 110 | `timezone_settings.cc <http://goo.gl/WSVUeE>`_ for supported time |
| 111 | zones. |
| 112 | |
| 113 | This is identical to the legacy VPD ``initial_timezone`` value.""" |
| 114 | |
| 115 | locales = None |
| 116 | """A list of default locale codes (e.g., ``en-US``); see |
| 117 | `l10n_util.cc <http://goo.gl/kVkht>`_ for supported locales. |
| 118 | |
| 119 | This is identital to the legacy VPD ``initial_locale`` field.""" |
| 120 | |
| 121 | keyboard_mechanical_layout = None |
| 122 | """The keyboard's mechanical layout (``ANSI`` [US-like], ``ISO`` |
| 123 | [UK-like], ``JIS`` [Japanese], ``ABNT2`` [Brazilian] or ``KS`` [Korean]).""" |
| 124 | |
| 125 | description = None |
| 126 | """A human-readable description of the region. |
| 127 | This defaults to :py:attr:`region_code` if not set.""" |
| 128 | |
| 129 | notes = None |
| 130 | """Implementation notes about the region. This may be None.""" |
| 131 | |
| 132 | numeric_id = None |
| 133 | """An integer for mapping into Chrome OS HWID. |
| 134 | Please never change this once it is assigned.""" |
| 135 | |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 136 | regulatory_domain = None |
| 137 | """An ISO 3166-1 alpha 2 upper-cased two-letter region code for setting |
| 138 | Wireless regulatory. See crosbug.com/p/38745 for more details. |
| 139 | |
| 140 | When omitted, this will derive from region_code.""" |
| 141 | |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 142 | FIELDS = ['region_code', 'keyboards', 'time_zones', 'locales', 'description', |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 143 | 'keyboard_mechanical_layout', 'numeric_id', 'regulatory_domain'] |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 144 | """Names of fields that define the region.""" |
| 145 | |
| 146 | |
| 147 | def __init__(self, region_code, keyboards, time_zones, locales, |
| 148 | keyboard_mechanical_layout, description=None, notes=None, |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 149 | numeric_id=None, regdomain=None): |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 150 | """Constructor. |
| 151 | |
| 152 | Args: |
| 153 | region_code: See :py:attr:`region_code`. |
| 154 | keyboards: See :py:attr:`keyboards`. A single string is accepted for |
| 155 | backward compatibility. |
| 156 | time_zones: See :py:attr:`time_zones`. |
| 157 | locales: See :py:attr:`locales`. A single string is accepted |
| 158 | for backward compatibility. |
| 159 | keyboard_mechanical_layout: See :py:attr:`keyboard_mechanical_layout`. |
| 160 | description: See :py:attr:`description`. |
| 161 | notes: See :py:attr:`notes`. |
| 162 | numeric_id: See :py:attr:`numeric_id`. This must be None or a |
| 163 | non-negative integer. |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 164 | regdomain: See :py:attr:`regulatory_domain`. |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 165 | """ |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 166 | |
| 167 | def regdomain_from_region(region): |
| 168 | if region.find('.') >= 0: |
| 169 | region = region[:region.index('.')] |
| 170 | if len(region) == 2: |
| 171 | return region.upper() |
| 172 | return None |
| 173 | |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 174 | # Quick check: should be 'gb', not 'uk' |
| 175 | if region_code == 'uk': |
| 176 | raise RegionException("'uk' is not a valid region code (use 'gb')") |
| 177 | |
| 178 | self.region_code = region_code |
| 179 | self.keyboards = MakeList(keyboards) |
| 180 | self.time_zones = MakeList(time_zones) |
| 181 | self.locales = MakeList(locales) |
| 182 | self.keyboard_mechanical_layout = keyboard_mechanical_layout |
| 183 | self.description = description or region_code |
| 184 | self.notes = notes |
| 185 | self.numeric_id = numeric_id |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 186 | self.regulatory_domain = (regdomain or regdomain_from_region(region_code)) |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 187 | |
| 188 | if self.numeric_id is not None: |
| 189 | if not isinstance(self.numeric_id, int): |
| 190 | raise TypeError('Numeric ID is %r but should be an integer' % |
| 191 | (self.numeric_id,)) |
| 192 | if self.numeric_id < 0: |
| 193 | raise ValueError('Numeric ID is %r but should be non-negative' % |
| 194 | self.numeric_id) |
| 195 | |
| 196 | for f in (self.keyboards, self.locales): |
| 197 | assert all(isinstance(x, str) for x in f), ( |
| 198 | 'Expected a list of strings, not %r' % f) |
| 199 | for f in self.keyboards: |
| 200 | assert KEYBOARD_PATTERN.match(f), ( |
| 201 | 'Keyboard pattern %r does not match %r' % ( |
| 202 | f, KEYBOARD_PATTERN.pattern)) |
| 203 | for f in self.locales: |
| 204 | assert LOCALE_PATTERN.match(f), ( |
| 205 | 'Locale %r does not match %r' % ( |
| 206 | f, LOCALE_PATTERN.pattern)) |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 207 | assert (self.regulatory_domain and |
| 208 | len(self.regulatory_domain) == 2 and |
| 209 | self.regulatory_domain.upper() == self.regulatory_domain), ( |
| 210 | "Regulatory domain settings error for region %s" % region_code) |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 211 | |
| 212 | def __repr__(self): |
| 213 | return 'Region(%s)' % (', '.join([getattr(self, x) for x in self.FIELDS])) |
| 214 | |
| 215 | def GetFieldsDict(self): |
| 216 | """Returns a dict of all substantive fields. |
| 217 | |
| 218 | notes and description are excluded. |
| 219 | """ |
| 220 | return dict((k, getattr(self, k)) for k in self.FIELDS) |
| 221 | |
| 222 | _KML = Region.KeyboardMechanicalLayout |
| 223 | REGIONS_LIST = [ |
| 224 | Region('au', 'xkb:us::eng', 'Australia/Sydney', 'en-AU', _KML.ANSI, |
| 225 | 'Australia', None, 1), |
| 226 | Region('be', 'xkb:be::nld', 'Europe/Brussels', 'en-GB', _KML.ISO, 'Belgium', |
| 227 | 'Flemish (Belgian Dutch) keyboard; British English language for ' |
| 228 | 'neutrality', 2), |
| 229 | Region('br', 'xkb:br::por', 'America/Sao_Paulo', 'pt-BR', _KML.ABNT2, |
| 230 | 'Brazil (ABNT2)', |
| 231 | ('ABNT2 = ABNT NBR 10346 variant 2. This is the preferred layout ' |
| 232 | 'for Brazil. ABNT2 is mostly an ISO layout, but it 12 keys between ' |
| 233 | 'the shift keys; see http://goo.gl/twA5tq'), 3), |
| 234 | Region('br.abnt', 'xkb:br::por', 'America/Sao_Paulo', 'pt-BR', _KML.ISO, |
| 235 | 'Brazil (ABNT)', |
| 236 | ('Like ABNT2, but lacking the extra key to the left of the right ' |
| 237 | 'shift key found in that layout. ABNT2 (the "br" region) is ' |
| 238 | 'preferred to this layout'), 4), |
| 239 | Region('br.usintl', 'xkb:us:intl:eng', 'America/Sao_Paulo', 'pt-BR', |
| 240 | _KML.ANSI, 'Brazil (US Intl)', |
| 241 | 'Brazil with US International keyboard layout. ABNT2 ("br") and ' |
| 242 | 'ABNT1 ("br.abnt1 ") are both preferred to this.', 5), |
| 243 | Region('ca.ansi', 'xkb:us::eng', 'America/Toronto', 'en-CA', _KML.ANSI, |
| 244 | 'Canada (US keyboard)', |
| 245 | 'Canada with US (ANSI) keyboard. Not for en/fr hybrid ANSI ' |
| 246 | 'keyboards; for that you would want ca.hybridansi. See ' |
| 247 | 'http://goto/cros-canada', 6), |
| 248 | Region('ca.fr', 'xkb:ca::fra', 'America/Toronto', 'fr-CA', _KML.ISO, |
| 249 | 'Canada (French keyboard)', |
| 250 | ('Canadian French (ISO) keyboard. The most common configuration for ' |
| 251 | 'Canadian French SKUs. See http://goto/cros-canada'), 7), |
| 252 | Region('ca.hybrid', 'xkb:ca:eng:eng', 'America/Toronto', 'en-CA', _KML.ISO, |
| 253 | 'Canada (hybrid ISO)', |
| 254 | ('Canada with hybrid (ISO) xkb:ca:eng:eng + xkb:ca::fra keyboard, ' |
| 255 | 'defaulting to English language and keyboard. Used only if there ' |
| 256 | 'needs to be a single SKU for all of Canada. See ' |
| 257 | 'http://goto/cros-canada'), 8), |
| 258 | Region('ca.hybridansi', 'xkb:ca:eng:eng', 'America/Toronto', 'en-CA', |
| 259 | _KML.ANSI, 'Canada (hybrid ANSI)', |
| 260 | ('Canada with hybrid (ANSI) xkb:ca:eng:eng + xkb:ca::fra keyboard, ' |
| 261 | 'defaulting to English language and keyboard. Used only if there ' |
| 262 | 'needs to be a single SKU for all of Canada. See ' |
| 263 | 'http://goto/cros-canada'), 9), |
| 264 | Region('ca.multix', 'xkb:ca:multix:fra', 'America/Toronto', 'fr-CA', |
| 265 | _KML.ISO, 'Canada (multilingual)', |
| 266 | ("Canadian Multilingual keyboard; you probably don't want this. See " |
| 267 | 'http://goto/cros-canada'), 10), |
| 268 | Region('ch', 'xkb:ch::ger', 'Europe/Zurich', 'en-US', _KML.ISO, |
| 269 | 'Switzerland', |
| 270 | 'German keyboard, but US English to be language-neutral; used in ' |
| 271 | 'the common case that there is only a single Swiss SKU.', 11), |
| 272 | Region('de', 'xkb:de::ger', 'Europe/Berlin', 'de', _KML.ISO, 'Germany', |
| 273 | None, 12), |
| 274 | Region('es', 'xkb:es::spa', 'Europe/Madrid', 'es', _KML.ISO, 'Spain', |
| 275 | None, 13), |
| 276 | Region('fi', 'xkb:fi::fin', 'Europe/Helsinki', 'fi', _KML.ISO, 'Finland', |
| 277 | None, 14), |
| 278 | Region('fr', 'xkb:fr::fra', 'Europe/Paris', 'fr', _KML.ISO, 'France', |
| 279 | None, 15), |
| 280 | Region('gb', 'xkb:gb:extd:eng', 'Europe/London', 'en-GB', _KML.ISO, 'UK', |
| 281 | None, 16), |
| 282 | Region('ie', 'xkb:gb:extd:eng', 'Europe/Dublin', 'en-GB', _KML.ISO, |
| 283 | 'Ireland', None, 17), |
| 284 | Region('in', 'xkb:us::eng', 'Asia/Calcutta', 'en-US', _KML.ANSI, 'India', |
| 285 | None, 18), |
| 286 | Region('it', 'xkb:it::ita', 'Europe/Rome', 'it', _KML.ISO, 'Italy', |
| 287 | None, 19), |
| 288 | Region('latam-es-419', 'xkb:es::spa', 'America/Mexico_City', 'es-419', |
| 289 | _KML.ISO, 'Hispanophone Latin America', |
| 290 | ('Spanish-speaking countries in Latin America, using the Iberian ' |
| 291 | '(Spain) Spanish keyboard, which is increasingly dominant in ' |
| 292 | 'Latin America. Known to be correct for ' |
| 293 | 'Chile, Colombia, Mexico, Peru; ' |
| 294 | 'still unconfirmed for other es-419 countries. The old Latin ' |
| 295 | 'American layout (xkb:latam::spa) has not been approved; before ' |
| 296 | 'using that you must seek review through http://goto/vpdsettings. ' |
| 297 | 'See also http://goo.gl/Iffuqh. Note that 419 is the UN M.49 ' |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 298 | 'region code for Latin America'), 20, 'MX'), |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 299 | Region('my', 'xkb:us::eng', 'Asia/Kuala_Lumpur', 'ms', _KML.ANSI, |
| 300 | 'Malaysia', None, 21), |
| 301 | Region('nl', 'xkb:us:intl:eng', 'Europe/Amsterdam', 'nl', _KML.ANSI, |
| 302 | 'Netherlands', None, 22), |
| 303 | Region('nordic', 'xkb:se::swe', 'Europe/Stockholm', 'en-US', _KML.ISO, |
| 304 | 'Nordics', |
| 305 | ('Unified SKU for Sweden, Norway, and Denmark. This defaults ' |
| 306 | 'to Swedish keyboard layout, but starts with US English language ' |
| 307 | 'for neutrality. Use if there is a single combined SKU for Nordic ' |
Hung-Te Lin | 436b6cc | 2015-04-03 12:15:47 +0800 | [diff] [blame] | 308 | 'countries.'), 23, 'SE'), |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 309 | Region('nz', 'xkb:us::eng', 'Pacific/Auckland', 'en-NZ', _KML.ANSI, |
| 310 | 'New Zealand', None, 24), |
| 311 | Region('ph', 'xkb:us::eng', 'Asia/Manila', 'en-US', _KML.ANSI, |
| 312 | 'Philippines', None, 25), |
| 313 | Region('ru', 'xkb:ru::rus', 'Europe/Moscow', 'ru', _KML.ANSI, 'Russia', |
| 314 | 'For R31+ only; R30 and earlier must use US keyboard for login', 26), |
| 315 | Region('se', 'xkb:se::swe', 'Europe/Stockholm', 'sv', _KML.ISO, 'Sweden', |
| 316 | ('Use this if there separate SKUs for Nordic countries (Sweden, ' |
| 317 | 'Norway, and Denmark), or the device is only shipping to Sweden. ' |
| 318 | "If there is a single unified SKU, use 'nordic' instead."), 27), |
| 319 | Region('sg', 'xkb:us::eng', 'Asia/Singapore', 'en-GB', _KML.ANSI, |
| 320 | 'Singapore', None, 28), |
| 321 | Region('us', 'xkb:us::eng', 'America/Los_Angeles', 'en-US', _KML.ANSI, |
| 322 | 'United States', None, 29), |
| 323 | Region('jp', 'xkb:jp::jpn', 'Asia/Tokyo', 'ja', _KML.JIS, |
| 324 | 'Japan', None, 30), |
Hung-Te Lin | b6203bf | 2015-04-10 10:43:30 +0800 | [diff] [blame^] | 325 | Region('za', 'xkb:us:intl:eng', 'Africa/Johannesburg', 'en-ZA', _KML.ANSI, |
| 326 | 'South Africa', None, 31), |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 327 | Region('hk', |
| 328 | ['xkb:us::eng', 'ime:zh-t:cangjie', 'ime:zh-t:quick', |
| 329 | 'ime:zh-t:array', 'ime:zh-t:dayi', 'ime:zh-t:zhuyin', |
| 330 | 'ime:zh-t:pinyin'], |
| 331 | 'Asia/Hong_Kong', ['zh-TW', 'en-GB', 'zh-CN'], _KML.ANSI, |
| 332 | 'Hong Kong', None, 33), |
| 333 | Region('cz', ['xkb:cz::cze', 'xkb:cz:qwerty:cze'], 'Europe/Prague', |
| 334 | ['cs', 'en-GB'], _KML.ISO, 'Czech Republic', None, 35), |
Hung-Te Lin | b6203bf | 2015-04-10 10:43:30 +0800 | [diff] [blame^] | 335 | Region('th', ['xkb:us::eng', 'm17n:th', 'm17n:th_pattajoti', 'm17n:th_tis'], |
| 336 | 'Asia/Bangkok', ['th', 'en-GB'], _KML.ANSI, 'Thailand', None, 36), |
Hung-Te Lin | 76c55b2 | 2015-03-31 14:47:14 +0800 | [diff] [blame] | 337 | Region('tw', |
| 338 | ['xkb:us::eng', 'ime:zh-t:zhuyin', 'ime:zh-t:array', |
| 339 | 'ime:zh-t:dayi', 'ime:zh-t:cangjie', 'ime:zh-t:quick', |
| 340 | 'ime:zh-t:pinyin'], |
| 341 | 'Asia/Taipei', ['zh-TW', 'en-US'], _KML.ANSI, 'Taiwan', None, 38), |
| 342 | Region('pl', 'xkb:pl::pol', 'Europe/Warsaw', ['pl', 'en-GB'], _KML.ANSI, |
| 343 | 'Poland', None, 39), |
| 344 | ] |
| 345 | """A list of :py:class:`regions.Region` objects for |
| 346 | all **confirmed** regions. A confirmed region is a region whose |
| 347 | properties are known to be correct and may be used to launch a device.""" |
| 348 | |
| 349 | |
| 350 | UNCONFIRMED_REGIONS_LIST = [] |
| 351 | """A list of :py:class:`regions.Region` objects for |
| 352 | **unconfirmed** regions. These are believed to be correct but |
| 353 | unconfirmed, and all fields should be verified (and the row moved into |
| 354 | :py:data:`regions.Region.REGIONS_LIST`) before |
| 355 | launch. See <http://goto/vpdsettings>. |
| 356 | |
| 357 | Currently, non-Latin keyboards must use an underlying Latin keyboard |
| 358 | for VPD. (This assumption should be revisited when moving items to |
| 359 | :py:data:`regions.Region.REGIONS_LIST`.) This is |
| 360 | currently being discussed on <http://crbug.com/325389>. |
| 361 | |
| 362 | Some timezones may be missing from ``timezone_settings.cc`` (see |
| 363 | http://crosbug.com/p/23902). This must be rectified before moving |
| 364 | items to :py:data:`regions.Region.REGIONS_LIST`. |
| 365 | """ |
| 366 | |
| 367 | INCOMPLETE_REGIONS_LIST = [] |
| 368 | """A list of :py:class:`regions.Region` objects for |
| 369 | **incomplete** regions. These may contain incorrect information, and all |
| 370 | fields must be reviewed before launch. See http://goto/vpdsettings. |
| 371 | """ |
| 372 | |
| 373 | |
| 374 | def ConsolidateRegions(regions): |
| 375 | """Consolidates a list of regions into a dict. |
| 376 | |
| 377 | Args: |
| 378 | regions: A list of Region objects. All objects for any given |
| 379 | region code must be identical or we will throw an exception. |
| 380 | (We allow duplicates in case identical region objects are |
| 381 | defined in both regions.py and the overlay, e.g., when moving |
| 382 | items to the public overlay.) |
| 383 | |
| 384 | Returns: |
| 385 | A dict from region code to Region. |
| 386 | |
| 387 | Raises: |
| 388 | RegionException: If there are multiple regions defined for a given |
| 389 | region, and the values for those regions differ. |
| 390 | """ |
| 391 | # Build a dict from region_code to the first Region with that code. |
| 392 | region_dict = {} |
| 393 | for r in regions: |
| 394 | existing_region = region_dict.get(r.region_code) |
| 395 | if existing_region: |
| 396 | if existing_region.GetFieldsDict() != r.GetFieldsDict(): |
| 397 | raise RegionException( |
| 398 | 'Conflicting definitions for region %r: %r, %r' % |
| 399 | (r.region_code, existing_region.GetFieldsDict(), |
| 400 | r.GetFieldsDict())) |
| 401 | else: |
| 402 | region_dict[r.region_code] = r |
| 403 | |
| 404 | return region_dict |
| 405 | |
| 406 | |
| 407 | def BuildRegionsDict(include_all=False): |
| 408 | """Builds a dictionary mapping from code to :py:class:`regions.Region` object. |
| 409 | |
| 410 | The regions include: |
| 411 | |
| 412 | * :py:data:`regions.REGIONS_LIST` |
| 413 | * :py:data:`regions_overlay.REGIONS_LIST` |
| 414 | * Only if ``include_all`` is true: |
| 415 | |
| 416 | * :py:data:`regions.UNCONFIRMED_REGIONS_LIST` |
| 417 | * :py:data:`regions.INCOMPLETE_REGIONS_LIST` |
| 418 | |
| 419 | A region may only appear in one of the above lists, or this function |
| 420 | will (deliberately) fail. |
| 421 | """ |
| 422 | regions = list(REGIONS_LIST) |
| 423 | if include_all: |
| 424 | regions += UNCONFIRMED_REGIONS_LIST + INCOMPLETE_REGIONS_LIST |
| 425 | |
| 426 | # Build dictionary of region code to list of regions with that |
| 427 | # region code. Check manually for duplicates, since the region may |
| 428 | # be present both in the overlay and the public repo. |
| 429 | return ConsolidateRegions(regions) |
| 430 | |
| 431 | |
| 432 | REGIONS = BuildRegionsDict() |
| 433 | |
| 434 | |
| 435 | def main(args=sys.argv[1:], out=None): |
| 436 | parser = argparse.ArgumentParser(description=( |
| 437 | 'Display all known regions and their parameters. ')) |
| 438 | parser.add_argument('--format', |
| 439 | choices=('human-readable', 'csv', 'json', 'yaml'), |
| 440 | default='human-readable', |
| 441 | help='Output format (default=%(default)s)') |
| 442 | parser.add_argument('--all', action='store_true', |
| 443 | help='Include unconfirmed and incomplete regions') |
| 444 | parser.add_argument('--output', default=None, |
| 445 | help='Specify output file') |
| 446 | args = parser.parse_args(args) |
| 447 | |
| 448 | regions_dict = BuildRegionsDict(args.all) |
| 449 | if out is None: |
| 450 | if args.output is None: |
| 451 | out = sys.stdout |
| 452 | else: |
| 453 | out = open(args.output, 'w') |
| 454 | |
| 455 | # Handle YAML and JSON output. |
| 456 | if args.format == 'yaml' or args.format == 'json': |
| 457 | data = {} |
| 458 | for region in regions_dict.values(): |
| 459 | item = {} |
| 460 | for field in Region.FIELDS: |
| 461 | item[field] = getattr(region, field) |
| 462 | data[region.region_code] = item |
| 463 | if args.format == 'yaml': |
| 464 | yaml.dump(data, out) |
| 465 | else: |
| 466 | json.dump(data, out) |
| 467 | return |
| 468 | |
| 469 | # Handle CSV or plain-text output: build a list of lines to print. |
| 470 | lines = [Region.FIELDS] |
| 471 | |
| 472 | def CoerceToString(value): |
| 473 | """Returns the arguments in simple string type. |
| 474 | |
| 475 | If value is a list, concatenate its values with commas. Otherwise, just |
| 476 | return value. |
| 477 | """ |
| 478 | if isinstance(value, list): |
| 479 | return ','.join(value) |
| 480 | else: |
| 481 | return str(value) |
| 482 | for region in sorted(regions_dict.values(), key=lambda v: v.region_code): |
| 483 | lines.append([CoerceToString(getattr(region, field)) |
| 484 | for field in Region.FIELDS]) |
| 485 | |
| 486 | if args.format == 'csv': |
| 487 | # Just print the lines in CSV format. |
| 488 | for l in lines: |
| 489 | print(','.join(l)) |
| 490 | elif args.format == 'human-readable': |
| 491 | num_columns = len(lines[0]) |
| 492 | |
| 493 | # Calculate maximum length of each column. |
| 494 | max_lengths = [] |
| 495 | for column_no in xrange(num_columns): |
| 496 | max_lengths.append(max(len(line[column_no]) for line in lines)) |
| 497 | |
| 498 | # Print each line, padding as necessary to the max column length. |
| 499 | for line in lines: |
| 500 | for column_no in xrange(num_columns): |
| 501 | out.write(line[column_no].ljust(max_lengths[column_no] + 2)) |
| 502 | out.write('\n') |
| 503 | else: |
| 504 | exit('Sorry, unknown format specified: %s' % args.format) |
| 505 | |
| 506 | |
| 507 | if __name__ == '__main__': |
| 508 | main() |