Add type annotations to CldrReader

Add some type annotatons to cldr2qlocalexml.py as well. Based on the
default arguments the constructor of CldrReader was expecting callables
that return None, but in reality we are passing in functions that
return integers.

Task-number: QTBUG-129613
Pick-to: 6.8
Change-Id: I06832240956ea635ca0cc0ec45c466a3b2539ff7
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Mate Barany 2024-10-14 17:01:29 +02:00
parent 96b9c4c317
commit 812f79e75f
2 changed files with 37 additions and 24 deletions

View File

@ -21,7 +21,8 @@ from localetools import names_clash
from qlocalexml import Locale from qlocalexml import Locale
class CldrReader (object): class CldrReader (object):
def __init__(self, root: Path, grumble = lambda msg: None, whitter = lambda msg: None): def __init__(self, root: Path, grumble: Callable[[str], int] = lambda msg: 0,
whitter: Callable[[str], int] = lambda msg: 0) -> None:
"""Set up a reader object for reading CLDR data. """Set up a reader object for reading CLDR data.
Single parameter, root, is the file-system path to the root of Single parameter, root, is the file-system path to the root of
@ -38,9 +39,10 @@ class CldrReader (object):
self.whitter, self.grumble = whitter, grumble self.whitter, self.grumble = whitter, grumble
self.root.checkEnumData(grumble) self.root.checkEnumData(grumble)
# TODO: can we do anything but ignore with the namings here ? # TODO: can we do anything but ignore with the namings here ?
self.__bcp47Alias, ignore = self.root.bcp47Aliases() self.__bcp47Alias, _ = self.root.bcp47Aliases()
def likelySubTags(self): def likelySubTags(self) -> Iterator[tuple[tuple[int, int, int, int],
tuple[int, int, int, int]]]:
"""Generator for likely subtag information. """Generator for likely subtag information.
Yields pairs (have, give) of 4-tuples; if what you have Yields pairs (have, give) of 4-tuples; if what you have
@ -51,8 +53,8 @@ class CldrReader (object):
skips = [] skips = []
for got, use in self.root.likelySubTags(): for got, use in self.root.likelySubTags():
try: try:
have = self.__parseTags(got) have: tuple[int, int, int, int] = self.__parseTags(got)
give = self.__parseTags(use) give: tuple[int, int, int, int] = self.__parseTags(use)
except Error as e: except Error as e:
if ((use.startswith(got) or got.startswith('und_')) if ((use.startswith(got) or got.startswith('und_'))
and e.message.startswith('Unknown ') and ' code ' in e.message): and e.message.startswith('Unknown ') and ' code ' in e.message):
@ -77,7 +79,12 @@ class CldrReader (object):
# more out. # more out.
pass # self.__wrapped(self.whitter, 'Skipping likelySubtags (for unknown codes): ', skips) pass # self.__wrapped(self.whitter, 'Skipping likelySubtags (for unknown codes): ', skips)
def zoneData(self): def zoneData(self) -> tuple[dict[str, str],
dict[str, str],
dict[tuple[str, str], str],
dict[str, dict[str, str]],
dict[str, tuple[tuple[int, int, str], ...]],
dict[str, str]]:
"""Locale-independent timezone data. """Locale-independent timezone data.
Returns a tuple (alias, defaults, winIds, metamap, zones, Returns a tuple (alias, defaults, winIds, metamap, zones,
@ -107,12 +114,15 @@ class CldrReader (object):
that are not mentioned in enumdata.territory_map, on any that are not mentioned in enumdata.territory_map, on any
Windows IDs given in zonedata.windowsIdList that are no longer Windows IDs given in zonedata.windowsIdList that are no longer
covered by the CLDR data.""" covered by the CLDR data."""
alias = self.__bcp47Alias alias: dict[str, str] = self.__bcp47Alias
# defaults is a dict[str, str] and winIds is a list[tuple[str, str, str]]
defaults, winIds = self.root.readWindowsTimeZones(alias) defaults, winIds = self.root.readWindowsTimeZones(alias)
# metamap is a dict[str, dict[str, str]],
# zones is dict[str, tuple[tuple[int, int, str], ...]], territorial is a dict[str, str]
metamap, zones, territorial = self.root.readMetaZoneMap(alias) metamap, zones, territorial = self.root.readMetaZoneMap(alias)
from zonedata import windowsIdList from zonedata import windowsIdList
winUnused = set(n for n, o in windowsIdList).difference( winUnused: set[str] = set(n for n, o in windowsIdList).difference(
set(defaults).union(w for w, t, ids in winIds)) set(defaults).union(w for w, t, ids in winIds))
if winUnused: if winUnused:
joined = "\n\t".join(winUnused) joined = "\n\t".join(winUnused)
@ -122,7 +132,7 @@ class CldrReader (object):
# Check for duplicate entries in winIds: # Check for duplicate entries in winIds:
last: tuple[str, str, str] = ('', '', '') last: tuple[str, str, str] = ('', '', '')
winDup = {} winDup: dict[tuple[str, str], list[str]] = {}
for triple in sorted(winIds): for triple in sorted(winIds):
if triple[:2] == last[:2]: if triple[:2] == last[:2]:
winDup.setdefault(triple[:2], []).append(triple[-1]) winDup.setdefault(triple[:2], []).append(triple[-1])
@ -142,7 +152,7 @@ class CldrReader (object):
winIds.append((w, t, ' '.join(ianaList))) winIds.append((w, t, ' '.join(ianaList)))
from enumdata import territory_map from enumdata import territory_map
unLand = set(t for w, t, ids in winIds).union(territorial) unLand: set[str] = set(t for w, t, ids in winIds).union(territorial)
for bok in metamap.values(): for bok in metamap.values():
unLand = unLand.union(bok) unLand = unLand.union(bok)
unLand = unLand.difference(v[1] for k, v in territory_map.items()) unLand = unLand.difference(v[1] for k, v in territory_map.items())
@ -154,16 +164,17 @@ class CldrReader (object):
winIds = [(w, t, ids) for w, t, ids in winIds if t not in unLand] winIds = [(w, t, ids) for w, t, ids in winIds if t not in unLand]
# Convert list of triples to mapping: # Convert list of triples to mapping:
winIds = {(w, t): ids for w, t, ids in winIds} winIds: dict[tuple[str, str], str] = {(w, t): ids for w, t, ids in winIds}
return alias, defaults, winIds, metamap, zones, territorial return alias, defaults, winIds, metamap, zones, territorial
def readLocales(self, calendars = ('gregorian',)): def readLocales(self, calendars: Iterable[str] = ('gregorian',)
) -> dict[tuple[int, int, int, int], Locale]:
return {(k.language_id, k.script_id, k.territory_id, k.variant_id): k return {(k.language_id, k.script_id, k.territory_id, k.variant_id): k
for k in self.__allLocales(calendars)} for k in self.__allLocales(calendars)}
def __allLocales(self, calendars): def __allLocales(self, calendars: list[str]) -> Iterator[Locale]:
def skip(locale, reason): def skip(locale: str, reason: str) -> str:
return f'Skipping defaultContent locale "{locale}" ({reason})\n' return f'Skipping defaultContent locale "{locale}" ({reason})\n'
for locale in self.root.defaultContentLocales: for locale in self.root.defaultContentLocales:
@ -205,14 +216,14 @@ class CldrReader (object):
import textwrap import textwrap
@staticmethod @staticmethod
def __wrapped(writer, prefix, tokens, wrap = textwrap.wrap): def __wrapped(writer, prefix, tokens, wrap = textwrap.wrap) -> None:
writer('\n'.join(wrap(prefix + ', '.join(tokens), writer('\n'.join(wrap(prefix + ', '.join(tokens),
subsequent_indent=' ', width=80)) + '\n') subsequent_indent=' ', width=80)) + '\n')
del textwrap del textwrap
def __parseTags(self, locale): def __parseTags(self, locale: str) -> tuple[int, int, int, int]:
tags = self.__splitLocale(locale) tags: Iterator[str] = self.__splitLocale(locale)
language = next(tags) language: str = next(tags)
script = territory = variant = '' script = territory = variant = ''
try: try:
script, territory, variant = tags script, territory, variant = tags
@ -220,7 +231,7 @@ class CldrReader (object):
pass pass
return tuple(p[0] for p in self.root.codesToIdName(language, script, territory, variant)) return tuple(p[0] for p in self.root.codesToIdName(language, script, territory, variant))
def __splitLocale(self, name): def __splitLocale(self, name: str) -> Iterator[str]:
"""Generate (language, script, territory, variant) from a locale name """Generate (language, script, territory, variant) from a locale name
Ignores any trailing fields (with a warning), leaves script (a Ignores any trailing fields (with a warning), leaves script (a
@ -229,11 +240,11 @@ class CldrReader (object):
empty if unspecified. Only generates one entry if name is a empty if unspecified. Only generates one entry if name is a
single tag (i.e. contains no underscores). Always yields 1 or single tag (i.e. contains no underscores). Always yields 1 or
4 values, never 2 or 3.""" 4 values, never 2 or 3."""
tags = iter(name.split('_')) tags: Iterator[str] = iter(name.split('_'))
yield next(tags) # Language yield next(tags) # Language
try: try:
tag = next(tags) tag: str = next(tags)
except StopIteration: except StopIteration:
return return
@ -270,7 +281,8 @@ class CldrReader (object):
if rest: if rest:
self.grumble(f'Ignoring unparsed cruft {"_".join(rest)} in {name}\n') self.grumble(f'Ignoring unparsed cruft {"_".join(rest)} in {name}\n')
def __getLocaleData(self, scan, calendars, language, script, territory, variant): def __getLocaleData(self, scan: LocaleScanner, calendars: list[str], language: str,
script: str, territory: str, variant: str) -> Locale:
ids, names = zip(*self.root.codesToIdName(language, script, territory, variant)) ids, names = zip(*self.root.codesToIdName(language, script, territory, variant))
assert ids[0] > 0 and ids[2] > 0, (language, script, territory, variant) assert ids[0] > 0 and ids[2] > 0, (language, script, territory, variant)
locale = Locale( locale = Locale(

View File

@ -38,10 +38,11 @@ from pathlib import Path
import argparse import argparse
from cldr import CldrReader from cldr import CldrReader
from typing import TextIO
from qlocalexml import QLocaleXmlWriter from qlocalexml import QLocaleXmlWriter
def main(argv, out, err): def main(argv: list[str], out: TextIO, err: TextIO) -> int:
"""Generate a QLocaleXML file from CLDR data. """Generate a QLocaleXML file from CLDR data.
Takes sys.argv, sys.stdout, sys.stderr (or equivalents) as Takes sys.argv, sys.stdout, sys.stderr (or equivalents) as
@ -87,7 +88,7 @@ def main(argv, out, err):
parser.error(f'Failed to open "{xml}" to write output to it') parser.error(f'Failed to open "{xml}" to write output to it')
reader = CldrReader(root, reader = CldrReader(root,
(lambda *x: None) if args.verbose < 0 else (lambda *x: 0) if args.verbose < 0 else
# Use stderr for logging if stdout is where our XML is going: # Use stderr for logging if stdout is where our XML is going:
err.write if out is emit else out.write, err.write if out is emit else out.write,
err.write) err.write)