1from __future__ import annotations
2
3import bisect
4import os
5import sys
6
7if sys.version_info[:2] >= (3, 9):
8 from functools import cache
9else:
10 from functools import lru_cache as cache # pragma: no cover
11
12from importlib import import_module
13from typing import TYPE_CHECKING, cast
14
15from rich._unicode_data._versions import VERSIONS
16
17if TYPE_CHECKING:
18 from rich.cells import CellTable
19
20VERSION_ORDER = sorted(
21 [
22 tuple(
23 map(int, version.split(".")),
24 )
25 for version in VERSIONS
26 ]
27)
28VERSION_SET = frozenset(VERSIONS)
29
30
31def _parse_version(version: str) -> tuple[int, int, int]:
32 """Parse a version string into a tuple of 3 integers.
33
34 Args:
35 version: A version string.
36
37 Raises:
38 ValueError: If the version string is invalid.
39
40 Returns:
41 A tuple of 3 integers.
42 """
43 version_integers: tuple[int, ...]
44 try:
45 version_integers = tuple(
46 map(int, version.split(".")),
47 )
48 except ValueError:
49 raise ValueError(
50 f"unicode version string {version!r} is badly formatted"
51 ) from None
52 while len(version_integers) < 3:
53 version_integers = version_integers + (0,)
54 triple = cast("tuple[int, int, int]", version_integers[:3])
55 return triple
56
57
58@cache
59def load(unicode_version: str = "auto") -> CellTable:
60 """Load a cell table for the given unicode version.
61
62 Args:
63 unicode_version: Unicode version, or `None` to auto-detect.
64
65 """
66 if unicode_version == "auto":
67 unicode_version = os.environ.get("UNICODE_VERSION", "latest")
68 try:
69 _parse_version(unicode_version)
70 except ValueError:
71 # The environment variable is invalid
72 # Fallback to using the latest version seems reasonable
73 unicode_version = "latest"
74
75 if unicode_version == "latest":
76 version = VERSIONS[-1]
77 else:
78 try:
79 version_numbers = _parse_version(unicode_version)
80 except ValueError:
81 version_numbers = _parse_version(VERSIONS[-1])
82 major, minor, patch = version_numbers
83 version = f"{major}.{minor}.{patch}"
84 if version not in VERSION_SET:
85 insert_position = bisect.bisect_left(VERSION_ORDER, version_numbers)
86 version = VERSIONS[max(0, insert_position - 1)]
87
88 version_path_component = version.replace(".", "-")
89 module_name = f".unicode{version_path_component}"
90 module = import_module(module_name, "rich._unicode_data")
91 if TYPE_CHECKING:
92 assert isinstance(module.cell_table, CellTable)
93 return module.cell_table