413 lines
18 KiB
Python
Executable File
413 lines
18 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
"""Generate NSS-compatible UNI_*.TAB translation tables from Unicode MAPPINGS.
|
|
|
|
The generated files follow the loader shape used by Novell NSS unilib.c:
|
|
|
|
256-byte ASCII header containing "Version X.Y"
|
|
LONG tabType
|
|
if tabType == 1:
|
|
unicode_t cpTab1[256]
|
|
else:
|
|
LONG cpTab1[256]
|
|
LONG minLowerByte
|
|
LONG maxLowerByte
|
|
LONG cpTab2Count
|
|
unicode_t cpTab2[cpTab2Count]
|
|
unsigned int uniTab2Count
|
|
unsigned short uniTab1[256]
|
|
unsigned short uniTab2[uniTab2Count]
|
|
|
|
No Novell unitables data is copied. Inputs are Unicode.org mapping files.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import struct
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
UNI_UNDEFINED = 0xFFFD
|
|
CP_UNDEFINED = 0xFDFF
|
|
SINGLE_LOOKUP_TAG = 0x0F000000
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class UniTableSpec:
|
|
filename: str
|
|
codepage: int
|
|
source: str
|
|
description: str
|
|
reversed_columns: bool = False
|
|
|
|
|
|
# NetWare/NSS-visible DOS/Windows and Macintosh tables that are present in the
|
|
# bundled Unicode.org MAPPINGS tree. UNI_000.TAB is generated separately from
|
|
# UnicodeData.txt because it has the NSS collation/case-table layout rather than
|
|
# the codepage translation-table layout used by the other .TAB files.
|
|
TABLES: tuple[UniTableSpec, ...] = (
|
|
UniTableSpec("UNI_437.TAB", 437, "VENDORS/MICSFT/PC/CP437.TXT", "DOS Latin US"),
|
|
UniTableSpec("UNI_737.TAB", 737, "VENDORS/MICSFT/PC/CP737.TXT", "DOS Greek"),
|
|
UniTableSpec("UNI_775.TAB", 775, "VENDORS/MICSFT/PC/CP775.TXT", "DOS Baltic"),
|
|
UniTableSpec("UNI_850.TAB", 850, "VENDORS/MICSFT/PC/CP850.TXT", "DOS Latin 1"),
|
|
UniTableSpec("UNI_852.TAB", 852, "VENDORS/MICSFT/PC/CP852.TXT", "DOS Latin 2"),
|
|
UniTableSpec("UNI_855.TAB", 855, "VENDORS/MICSFT/PC/CP855.TXT", "DOS Cyrillic"),
|
|
UniTableSpec("UNI_857.TAB", 857, "VENDORS/MICSFT/PC/CP857.TXT", "DOS Turkish"),
|
|
UniTableSpec("UNI_860.TAB", 860, "VENDORS/MICSFT/PC/CP860.TXT", "DOS Portuguese"),
|
|
UniTableSpec("UNI_861.TAB", 861, "VENDORS/MICSFT/PC/CP861.TXT", "DOS Icelandic"),
|
|
UniTableSpec("UNI_862.TAB", 862, "VENDORS/MICSFT/PC/CP862.TXT", "DOS Hebrew"),
|
|
UniTableSpec("UNI_863.TAB", 863, "VENDORS/MICSFT/PC/CP863.TXT", "DOS Canadian French"),
|
|
UniTableSpec("UNI_864.TAB", 864, "VENDORS/MICSFT/PC/CP864.TXT", "DOS Arabic"),
|
|
UniTableSpec("UNI_865.TAB", 865, "VENDORS/MICSFT/PC/CP865.TXT", "DOS Nordic"),
|
|
UniTableSpec("UNI_866.TAB", 866, "VENDORS/MICSFT/PC/CP866.TXT", "DOS Russian"),
|
|
UniTableSpec("UNI_869.TAB", 869, "VENDORS/MICSFT/PC/CP869.TXT", "DOS Greek 2"),
|
|
UniTableSpec("UNI_874.TAB", 874, "VENDORS/MICSFT/PC/CP874.TXT", "DOS Thai"),
|
|
UniTableSpec("UNI_932.TAB", 932, "VENDORS/MICSFT/WINDOWS/CP932.TXT", "Shift-JIS Japanese"),
|
|
UniTableSpec("UNI_936.TAB", 936, "VENDORS/MICSFT/WINDOWS/CP936.TXT", "GBK Chinese Simplified"),
|
|
UniTableSpec("UNI_949.TAB", 949, "VENDORS/MICSFT/WINDOWS/CP949.TXT", "Korean Unified Hangul"),
|
|
UniTableSpec("UNI_950.TAB", 950, "VENDORS/MICSFT/WINDOWS/CP950.TXT", "Big5 Chinese Traditional"),
|
|
UniTableSpec("UNI_1250.TAB", 1250, "VENDORS/MICSFT/WINDOWS/CP1250.TXT", "Windows Central European"),
|
|
UniTableSpec("UNI_1251.TAB", 1251, "VENDORS/MICSFT/WINDOWS/CP1251.TXT", "Windows Cyrillic"),
|
|
UniTableSpec("UNI_1252.TAB", 1252, "VENDORS/MICSFT/WINDOWS/CP1252.TXT", "Windows Latin 1"),
|
|
UniTableSpec("UNI_1253.TAB", 1253, "VENDORS/MICSFT/WINDOWS/CP1253.TXT", "Windows Greek"),
|
|
UniTableSpec("UNI_1254.TAB", 1254, "VENDORS/MICSFT/WINDOWS/CP1254.TXT", "Windows Turkish"),
|
|
UniTableSpec("UNI_1255.TAB", 1255, "VENDORS/MICSFT/WINDOWS/CP1255.TXT", "Windows Hebrew"),
|
|
UniTableSpec("UNI_1256.TAB", 1256, "VENDORS/MICSFT/WINDOWS/CP1256.TXT", "Windows Arabic"),
|
|
UniTableSpec("UNI_1257.TAB", 1257, "VENDORS/MICSFT/WINDOWS/CP1257.TXT", "Windows Baltic"),
|
|
UniTableSpec("UNI_1258.TAB", 1258, "VENDORS/MICSFT/WINDOWS/CP1258.TXT", "Windows Vietnamese"),
|
|
UniTableSpec("ROMAN.TAB", 1, "VENDORS/APPLE/ROMAN.TXT", "Macintosh Roman"),
|
|
UniTableSpec("CENTEURO.TAB", 2, "VENDORS/APPLE/CENTEURO.TXT", "Macintosh Central European"),
|
|
UniTableSpec("JAPANESE.TAB", 3, "VENDORS/APPLE/JAPANESE.TXT", "Macintosh Japanese"),
|
|
UniTableSpec("KOREAN.TAB", 4, "VENDORS/APPLE/KOREAN.TXT", "Macintosh Korean"),
|
|
UniTableSpec("CHINSIMP.TAB", 5, "VENDORS/APPLE/CHINSIMP.TXT", "Macintosh Chinese Simplified"),
|
|
UniTableSpec("CHINTRAD.TAB", 6, "VENDORS/APPLE/CHINTRAD.TXT", "Macintosh Chinese Traditional"),
|
|
UniTableSpec("ARABIC.TAB", 7, "VENDORS/APPLE/ARABIC.TXT", "Macintosh Arabic"),
|
|
UniTableSpec("CYRILLIC.TAB", 8, "VENDORS/APPLE/CYRILLIC.TXT", "Macintosh Cyrillic"),
|
|
UniTableSpec("CROATIAN.TAB", 9, "VENDORS/APPLE/CROATIAN.TXT", "Macintosh Croatian"),
|
|
UniTableSpec("DEVANAGA.TAB", 10, "VENDORS/APPLE/DEVANAGA.TXT", "Macintosh Devanagari"),
|
|
UniTableSpec("FARSI.TAB", 11, "VENDORS/APPLE/FARSI.TXT", "Macintosh Farsi"),
|
|
UniTableSpec("GREEK.TAB", 12, "VENDORS/APPLE/GREEK.TXT", "Macintosh Greek"),
|
|
UniTableSpec("GUJARATI.TAB", 13, "VENDORS/APPLE/GUJARATI.TXT", "Macintosh Gujarati"),
|
|
UniTableSpec("GURMUKHI.TAB", 14, "VENDORS/APPLE/GURMUKHI.TXT", "Macintosh Gurmukhi"),
|
|
UniTableSpec("HEBREW.TAB", 15, "VENDORS/APPLE/HEBREW.TXT", "Macintosh Hebrew"),
|
|
UniTableSpec("ICELAND.TAB", 16, "VENDORS/APPLE/ICELAND.TXT", "Macintosh Icelandic"),
|
|
UniTableSpec("KEYBOARD.TAB", 17, "VENDORS/APPLE/KEYBOARD.TXT", "Macintosh Keyboard Characters"),
|
|
UniTableSpec("ROMANIAN.TAB", 18, "VENDORS/APPLE/ROMANIAN.TXT", "Macintosh Romanian"),
|
|
UniTableSpec("SYMBOL.TAB", 19, "VENDORS/APPLE/SYMBOL.TXT", "Macintosh Symbol"),
|
|
UniTableSpec("THAI.TAB", 20, "VENDORS/APPLE/THAI.TXT", "Macintosh Thai"),
|
|
UniTableSpec("TURKISH.TAB", 21, "VENDORS/APPLE/TURKISH.TXT", "Macintosh Turkish"),
|
|
UniTableSpec("DINGBATS.TAB", 22, "VENDORS/APPLE/DINGBATS.TXT", "Macintosh Dingbats"),
|
|
UniTableSpec("CELTIC.TAB", 23, "VENDORS/APPLE/CELTIC.TXT", "Macintosh Celtic"),
|
|
UniTableSpec("GAELIC.TAB", 24, "VENDORS/APPLE/GAELIC.TXT", "Macintosh Gaelic"),
|
|
UniTableSpec("INUIT.TAB", 25, "VENDORS/APPLE/INUIT.TXT", "Macintosh Inuit"),
|
|
# Apple retired the separate Ukrainian map and points consumers at the
|
|
# Cyrillic table. Keep the NSS-visible UKRAINE.TAB name as a generated
|
|
# alias so libnwnss can resolve that historical Macintosh table request
|
|
# without copying Novell unitables.
|
|
UniTableSpec("UKRAINE.TAB", 8, "VENDORS/APPLE/CYRILLIC.TXT", "Macintosh Ukrainian alias to Cyrillic"),
|
|
UniTableSpec("ATARIST.TAB", 10000, "VENDORS/MISC/ATARIST.TXT", "Atari ST"),
|
|
UniTableSpec("UNI_1006.TAB", 1006, "VENDORS/MISC/CP1006.TXT", "IBM CP1006 Urdu"),
|
|
UniTableSpec("UNI_424.TAB", 424, "VENDORS/MISC/CP424.TXT", "IBM CP424 Hebrew EBCDIC"),
|
|
UniTableSpec("UNI_856.TAB", 856, "VENDORS/MISC/CP856.TXT", "IBM CP856 Hebrew PC"),
|
|
UniTableSpec("KOI8-R.TAB", 20866, "VENDORS/MISC/KOI8-R.TXT", "KOI8-R Cyrillic"),
|
|
UniTableSpec("KOI8-U.TAB", 21866, "VENDORS/MISC/KOI8-U.TXT", "KOI8-U Cyrillic/Ukrainian"),
|
|
UniTableSpec("KPS9566.TAB", 9566, "VENDORS/MISC/KPS9566.TXT", "KPS 9566 Korean"),
|
|
UniTableSpec("KZ1048.TAB", 1048, "VENDORS/MISC/KZ1048.TXT", "Kazakhstan KZ-1048"),
|
|
UniTableSpec("IBMGRAPH.TAB", 10001, "VENDORS/MISC/IBMGRAPH.TXT", "IBM graphic characters", True),
|
|
)
|
|
|
|
|
|
def parse_scalar_token(text: str) -> int | None:
|
|
# Apple Arabic/Farsi mappings mark display direction as <LR>+0xNNNN or
|
|
# <RL>+0xNNNN. NSS unitables cannot encode the directional override, but
|
|
# they can still carry the underlying scalar mapping. Keep true composite
|
|
# mappings skipped.
|
|
if text.startswith("<") and ">+" in text:
|
|
text = text.split(">+", 1)[1]
|
|
if "+" in text:
|
|
return None
|
|
try:
|
|
return int(text, 16)
|
|
except ValueError:
|
|
return None
|
|
|
|
|
|
def parse_mapping(path: Path, *, reversed_columns: bool = False) -> dict[int, int]:
|
|
pairs: dict[int, int] = {}
|
|
for line in path.read_text(encoding="utf-8", errors="replace").splitlines():
|
|
data = line.split("#", 1)[0].strip()
|
|
if not data:
|
|
continue
|
|
fields = data.split()
|
|
if len(fields) < 2:
|
|
continue
|
|
source_text, unicode_text = fields[0], fields[1]
|
|
if reversed_columns:
|
|
source_text, unicode_text = unicode_text, source_text
|
|
source = parse_scalar_token(source_text)
|
|
unicode = parse_scalar_token(unicode_text)
|
|
if source is None or unicode is None:
|
|
continue
|
|
if source > 0xFFFF or unicode > 0xFFFF:
|
|
continue
|
|
pairs.setdefault(source, unicode)
|
|
return pairs
|
|
|
|
|
|
def pack_i32(value: int) -> bytes:
|
|
return struct.pack("<i", value)
|
|
|
|
|
|
def pack_u32(value: int) -> bytes:
|
|
return struct.pack("<I", value)
|
|
|
|
|
|
def pack_u16(value: int) -> bytes:
|
|
return struct.pack("<H", value & 0xFFFF)
|
|
|
|
|
|
def make_header(spec: UniTableSpec) -> bytes:
|
|
text = (
|
|
f"MARS-NWE NSS Unicode/Codepage {spec.codepage} Translation Table, "
|
|
"Version 1.01\n"
|
|
f"Generated from Unicode.org Public/MAPPINGS/{spec.source}\n"
|
|
"No Novell unitables data copied.\n"
|
|
).encode("ascii", errors="replace")
|
|
if len(text) > 256:
|
|
raise ValueError(f"header too long for {spec.filename}")
|
|
return text + (b" " * (256 - len(text)))
|
|
|
|
|
|
def emit_mb_to_unicode(mapping: dict[int, int]) -> bytes:
|
|
max_source = max(mapping.keys(), default=0)
|
|
out = bytearray()
|
|
if max_source <= 0xFF:
|
|
out += pack_i32(1)
|
|
table = [UNI_UNDEFINED] * 256
|
|
for source, unicode in mapping.items():
|
|
table[source] = unicode
|
|
for unicode in table:
|
|
out += pack_u16(unicode)
|
|
return bytes(out)
|
|
|
|
out += pack_i32(2)
|
|
double_by_lead: dict[int, dict[int, int]] = {}
|
|
single_by_lead: dict[int, int] = {}
|
|
lows: list[int] = []
|
|
for source, unicode in mapping.items():
|
|
if source <= 0xFF:
|
|
single_by_lead[source] = unicode
|
|
continue
|
|
lead = (source >> 8) & 0xFF
|
|
low = source & 0xFF
|
|
double_by_lead.setdefault(lead, {})[low] = unicode
|
|
lows.append(low)
|
|
|
|
min_low = min(lows) if lows else 0
|
|
max_low = max(lows) if lows else 0
|
|
width = max_low - min_low + 1 if lows else 0
|
|
cp_tab1 = [SINGLE_LOOKUP_TAG | UNI_UNDEFINED] * 256
|
|
cp_tab2: list[int] = []
|
|
for lead in range(256):
|
|
if lead in double_by_lead:
|
|
# NSS unilib.c indexes type-2 second-stage tables as
|
|
# cpTab2[cpTab1[lead] + lowByte], after separately checking
|
|
# minLowerByte <= lowByte <= maxLowerByte. Store the base
|
|
# offset already biased by -minLowerByte so the loader lands at
|
|
# the first entry in this lead-byte chunk for low == minLowerByte.
|
|
cp_tab1[lead] = len(cp_tab2) - min_low
|
|
chunk = [UNI_UNDEFINED] * width
|
|
for low, unicode in double_by_lead[lead].items():
|
|
chunk[low - min_low] = unicode
|
|
cp_tab2.extend(chunk)
|
|
elif lead in single_by_lead:
|
|
cp_tab1[lead] = SINGLE_LOOKUP_TAG | single_by_lead[lead]
|
|
|
|
for value in cp_tab1:
|
|
out += pack_i32(value)
|
|
out += pack_i32(min_low)
|
|
out += pack_i32(max_low)
|
|
out += pack_i32(len(cp_tab2))
|
|
for unicode in cp_tab2:
|
|
out += pack_u16(unicode)
|
|
return bytes(out)
|
|
|
|
|
|
def encode_output_word(source: int) -> int:
|
|
if source <= 0xFF:
|
|
return source
|
|
# NSS uni2loc() writes FIRST_BYTE(copy) and then SECOND_BYTE(copy), where
|
|
# copy.word is read through a little-endian byte pair. Store multibyte
|
|
# source values byte-swapped so a logical 0x8140 mapping emits bytes
|
|
# 0x81, 0x40 instead of 0x40, 0x81.
|
|
return ((source & 0x00FF) << 8) | ((source >> 8) & 0x00FF)
|
|
|
|
|
|
def emit_unicode_to_mb(mapping: dict[int, int]) -> bytes:
|
|
# Prefer the first/lowest encoded representation for duplicate Unicode
|
|
# points. NSS stores the encoded result in a 16-bit word, with byte[0]
|
|
# consumed first on little-endian hosts.
|
|
unicode_to_code: dict[int, int] = {}
|
|
for source, unicode in sorted(mapping.items()):
|
|
if unicode == 0 or unicode > 0xFFFF:
|
|
continue
|
|
unicode_to_code.setdefault(unicode, encode_output_word(source))
|
|
|
|
default_page = [CP_UNDEFINED] * 256
|
|
pages: dict[int, list[int]] = {}
|
|
for unicode, source in unicode_to_code.items():
|
|
high = unicode >> 8
|
|
low = unicode & 0xFF
|
|
pages.setdefault(high, default_page.copy())[low] = source
|
|
|
|
uni_tab2: list[int] = default_page.copy()
|
|
uni_tab1 = [0] * 256
|
|
for high in range(256):
|
|
page = pages.get(high)
|
|
if page is None:
|
|
uni_tab1[high] = 0
|
|
continue
|
|
uni_tab1[high] = len(uni_tab2)
|
|
uni_tab2.extend(page)
|
|
|
|
out = bytearray()
|
|
out += pack_u32(len(uni_tab2))
|
|
for offset in uni_tab1:
|
|
out += pack_u16(offset)
|
|
for code in uni_tab2:
|
|
out += pack_u16(code)
|
|
return bytes(out)
|
|
|
|
|
|
def emit_table(spec: UniTableSpec, mappings_root: Path, output_dir: Path) -> tuple[int, int]:
|
|
source_path = mappings_root / spec.source
|
|
if not source_path.exists():
|
|
raise FileNotFoundError(source_path)
|
|
mapping = parse_mapping(source_path, reversed_columns=spec.reversed_columns)
|
|
if not mapping:
|
|
raise ValueError(f"no direct mappings found in {source_path}")
|
|
data = make_header(spec) + emit_mb_to_unicode(mapping) + emit_unicode_to_mb(mapping)
|
|
output_path = output_dir / spec.filename
|
|
output_path.write_bytes(data)
|
|
return len(mapping), len(data)
|
|
|
|
|
|
MAPPING_TO_UPPER_CASE_BIT = 0x08000000
|
|
MAPPING_TO_LOWER_CASE_BIT = 0x04000000
|
|
OFFSET_MASK = 0x000FFFFF
|
|
UNI_000_MASTER_COUNT = 0x10000
|
|
UNI_000_DATA_COUNT = 46507
|
|
|
|
|
|
def parse_unicode_case_data(ucd_dir: Path) -> tuple[list[int], list[int], dict[str, int]]:
|
|
unicode_data = ucd_dir / "UnicodeData.txt"
|
|
if not unicode_data.exists():
|
|
raise FileNotFoundError(unicode_data)
|
|
|
|
lower = list(range(UNI_000_MASTER_COUNT))
|
|
upper = list(range(UNI_000_MASTER_COUNT))
|
|
stats = {
|
|
"records": 0,
|
|
"lower_mappings": 0,
|
|
"upper_mappings": 0,
|
|
"non_bmp_skipped": 0,
|
|
}
|
|
|
|
with unicode_data.open("r", encoding="utf-8") as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line or line.startswith("#"):
|
|
continue
|
|
fields = line.split(";")
|
|
if len(fields) < 15:
|
|
raise ValueError(f"Malformed UnicodeData line: {line!r}")
|
|
cp = int(fields[0], 16)
|
|
stats["records"] += 1
|
|
if cp >= UNI_000_MASTER_COUNT:
|
|
stats["non_bmp_skipped"] += 1
|
|
continue
|
|
upper_field = fields[12]
|
|
lower_field = fields[13]
|
|
if upper_field:
|
|
target = int(upper_field, 16)
|
|
if target < UNI_000_MASTER_COUNT:
|
|
upper[cp] = target
|
|
stats["upper_mappings"] += 1
|
|
if lower_field:
|
|
target = int(lower_field, 16)
|
|
if target < UNI_000_MASTER_COUNT:
|
|
lower[cp] = target
|
|
stats["lower_mappings"] += 1
|
|
return lower, upper, stats
|
|
|
|
|
|
def emit_uni_000(ucd_dir: Path, output_dir: Path) -> tuple[int, int, int]:
|
|
lower, upper, stats = parse_unicode_case_data(ucd_dir)
|
|
master = [0] * UNI_000_MASTER_COUNT
|
|
data: list[int] = []
|
|
|
|
for cp in range(UNI_000_MASTER_COUNT):
|
|
flags = 0
|
|
offset = len(data)
|
|
if lower[cp] != cp:
|
|
flags |= MAPPING_TO_LOWER_CASE_BIT
|
|
data.append(lower[cp])
|
|
if upper[cp] != cp:
|
|
flags |= MAPPING_TO_UPPER_CASE_BIT
|
|
data.append(upper[cp])
|
|
if flags:
|
|
if offset > OFFSET_MASK:
|
|
raise ValueError("UNI_000.TAB case table offset exceeds NSS OFFSET_MASK")
|
|
master[cp] = flags | offset
|
|
|
|
used_data = len(data)
|
|
if used_data > UNI_000_DATA_COUNT:
|
|
raise ValueError(f"UNI_000.TAB case table needs {used_data} LONGs, NSS loader reads {UNI_000_DATA_COUNT}")
|
|
data.extend([0] * (UNI_000_DATA_COUNT - used_data))
|
|
|
|
output_path = output_dir / "UNI_000.TAB"
|
|
with output_path.open("wb") as out:
|
|
for value in master:
|
|
out.write(pack_u32(value))
|
|
for value in data:
|
|
out.write(pack_u32(value))
|
|
return used_data, output_path.stat().st_size, stats["records"]
|
|
|
|
|
|
def write_summary(output_dir: Path, rows: list[tuple[UniTableSpec, int, int]], uni000: tuple[int, int, int]) -> None:
|
|
uni000_used, uni000_size, unicode_records = uni000
|
|
with (output_dir / "README.md").open("w", encoding="utf-8") as out:
|
|
out.write("# Generated NSS-compatible unitables\n\n")
|
|
out.write("Generated by `scripts/gen_nss_unitables.py` from Unicode.org `MAPPINGS/` and `UCD/UnicodeData.txt`.\n\n")
|
|
out.write("These binary `.TAB` files follow the table layouts consumed by the NSS `unilib.c` loader. ")
|
|
out.write("They intentionally do not copy Novell `shared/sdk/unitables/*.TAB` data.\n\n")
|
|
out.write("`UNI_000.TAB` is the NSS resident collation/case table file. The generated file currently ")
|
|
out.write("populates the loader-visible simple BMP lower/upper-case mappings from UnicodeData.txt and ")
|
|
out.write("pads the original 46507-LONG data-table area expected by the loader.\n\n")
|
|
out.write("| File | Codepage | Source | Pairs/Data LONGs | Bytes |\n")
|
|
out.write("| --- | ---: | --- | ---: | ---: |\n")
|
|
out.write(f"| `UNI_000.TAB` | 0 | `UCD/UnicodeData.txt` ({unicode_records} records) | {uni000_used} | {uni000_size} |\n")
|
|
for spec, pairs, size in rows:
|
|
out.write(f"| `{spec.filename}` | {spec.codepage} | `{spec.source}` | {pairs} | {size} |\n")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--mappings-root", type=Path, default=Path("MAPPINGS"))
|
|
parser.add_argument("--ucd-dir", type=Path, default=Path("UCD"))
|
|
parser.add_argument("--output-dir", type=Path, default=Path("TAB/unitables"))
|
|
args = parser.parse_args()
|
|
|
|
output_dir = args.output_dir
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
uni000 = emit_uni_000(args.ucd_dir, output_dir)
|
|
rows = []
|
|
for spec in TABLES:
|
|
pairs, size = emit_table(spec, args.mappings_root, output_dir)
|
|
rows.append((spec, pairs, size))
|
|
write_summary(output_dir, rows, uni000)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|