修改为东南天坐标系
This commit is contained in:
@@ -0,0 +1,382 @@
|
||||
"""The Language class."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
from typing import NoReturn
|
||||
|
||||
from ._data import (
|
||||
_PART3_TO_CODES,
|
||||
_PART3_TO_NAME_INDEX,
|
||||
_PART3_TO_MACROLANGUAGES,
|
||||
_PART3_TO_RETIREMENTS,
|
||||
_PART2B_TO_PART3,
|
||||
_PART2T_TO_PART3,
|
||||
_PART1_TO_PART3,
|
||||
_REF_NAME_TO_PART3,
|
||||
_PRINT_NAME_TO_PART3,
|
||||
_INVERTED_NAME_TO_PART3,
|
||||
_CodesColumn,
|
||||
_NameIndexColumn,
|
||||
_RetirementsColumn,
|
||||
_MacrolanguagesColumn,
|
||||
_COLUMN_TYPE,
|
||||
)
|
||||
|
||||
|
||||
_STRING_CLEANING_FUNCS = [lambda x: x.lower(), lambda x: x.title()]
|
||||
|
||||
|
||||
class LanguageNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Name:
|
||||
"""Represents an alternative name of a language."""
|
||||
|
||||
__slots__ = ("print", "inverted")
|
||||
print: str
|
||||
inverted: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Language:
|
||||
"""Represents a language in the ISO 639-3 charts."""
|
||||
|
||||
__slots__ = (
|
||||
# From the "codes" table
|
||||
"part3",
|
||||
"part2b",
|
||||
"part2t",
|
||||
"part1",
|
||||
"scope",
|
||||
"type",
|
||||
"status",
|
||||
"name",
|
||||
"comment",
|
||||
# From the "name_index" table
|
||||
"other_names",
|
||||
# From the "macrolanguages" table
|
||||
"macrolanguage",
|
||||
# From the "retirements" table
|
||||
"retire_reason",
|
||||
"retire_change_to",
|
||||
"retire_remedy",
|
||||
"retire_date",
|
||||
)
|
||||
|
||||
# From the "codes" table
|
||||
part3: str
|
||||
part2b: str | None
|
||||
part2t: str | None
|
||||
part1: str | None
|
||||
scope: str
|
||||
type: str | None
|
||||
status: str
|
||||
name: str
|
||||
comment: str | None
|
||||
|
||||
# From the "name_index" table
|
||||
other_names: list[Name] | None
|
||||
|
||||
# From the "macrolanguages" table
|
||||
macrolanguage: str | None
|
||||
|
||||
# From the "retirements" table
|
||||
retire_reason: str | None
|
||||
retire_change_to: str | None
|
||||
retire_remedy: str | None
|
||||
retire_date: datetime.date | None
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.part3)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return isinstance(other, Language) and self.part3 == other.part3
|
||||
|
||||
@classmethod
|
||||
def match(cls, user_input: str, /, *, strict_case: bool = True) -> Language:
|
||||
"""Return a ``Language`` instance by matching on the user input.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user_input : str
|
||||
A language code or name.
|
||||
strict_case : bool, optional
|
||||
Defaults to ``True``, for enforcing strict case sensitivity.
|
||||
If ``False`` and if ``user_input`` doesn't find a match,
|
||||
further match attempts will be made with the all-lowercase version of
|
||||
``user_input`` (``"foobar"``) and the title-case version of ``user_input``
|
||||
(``"Foobar"``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
Language
|
||||
|
||||
Notes
|
||||
-----
|
||||
At a high level, `Language.match` assumes the input is more likely to be
|
||||
a language code rather than a language name.
|
||||
Beyond that, the precise order in matching is as follows:
|
||||
|
||||
* ISO 639-3 codes (among the active codes)
|
||||
* ISO 639-2 (bibliographic) codes
|
||||
* ISO 639-2 (terminological) codes
|
||||
* ISO 639-1 codes
|
||||
* ISO 639-3 codes (among the retired codes)
|
||||
* ISO 639-3 reference language names
|
||||
* ISO 639-3 alternative language names (the "print" ones)
|
||||
* ISO 639-3 alternative language names (the "inverted" ones)
|
||||
"""
|
||||
# Order of columns to query the data tables.
|
||||
# Bias towards (and therefore prioritize) the user input being
|
||||
# a language code rather than a language name.
|
||||
query_order: list[_COLUMN_TYPE] = [
|
||||
_CodesColumn.ID,
|
||||
_CodesColumn.PART2B,
|
||||
_CodesColumn.PART2T,
|
||||
_CodesColumn.PART1,
|
||||
_RetirementsColumn.ID,
|
||||
_CodesColumn.REF_NAME,
|
||||
_NameIndexColumn.PRINT_NAME,
|
||||
_NameIndexColumn.INVERTED_NAME,
|
||||
]
|
||||
return _PART3_TO_LANGUAGES[_get_part3(user_input, query_order, strict_case)]
|
||||
|
||||
@classmethod
|
||||
def from_part3(cls, user_input: str, /) -> Language:
|
||||
"""Return a ``Language`` instance from an ISO 639-3 code."""
|
||||
return _PART3_TO_LANGUAGES[
|
||||
_get_part3_exact(user_input, [_CodesColumn.ID, _RetirementsColumn.ID])
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def from_part2b(cls, user_input: str, /) -> Language:
|
||||
"""Return a ``Language`` instance from an ISO 639-2 (bibliographic) code."""
|
||||
return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART2B])]
|
||||
|
||||
@classmethod
|
||||
def from_part2t(cls, user_input: str, /) -> Language:
|
||||
"""Return a ``Language`` instance from an ISO 639-2 (terminological) code."""
|
||||
return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART2T])]
|
||||
|
||||
@classmethod
|
||||
def from_part1(cls, user_input: str, /) -> Language:
|
||||
"""Return a ``Language`` instance from an ISO 639-1 code."""
|
||||
return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, [_CodesColumn.PART1])]
|
||||
|
||||
@classmethod
|
||||
def from_name(cls, user_input: str, /) -> Language:
|
||||
"""Return a ``Language`` instance from an ISO 639-3 reference language name."""
|
||||
query_order: list[_COLUMN_TYPE] = [
|
||||
_CodesColumn.REF_NAME,
|
||||
_NameIndexColumn.PRINT_NAME,
|
||||
_NameIndexColumn.INVERTED_NAME,
|
||||
]
|
||||
return _PART3_TO_LANGUAGES[_get_part3_exact(user_input, query_order)]
|
||||
|
||||
|
||||
def _raise_language_not_found_error(user_input: str) -> NoReturn:
|
||||
raise LanguageNotFoundError(f"{user_input!r} isn't an ISO language code or name")
|
||||
|
||||
|
||||
def _get_part3(
|
||||
user_input: str, query_order: list[_COLUMN_TYPE], strict_case: bool = True
|
||||
) -> str:
|
||||
"""Get the part 3 code of a language.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user_input : str
|
||||
The user-provided language code or name.
|
||||
query_order : List[_COLUMN_TYPE]
|
||||
A list of columns to specify query order.
|
||||
strict_case : bool, optional
|
||||
Defaults to ``True``, for enforcing strict case sensitivity.
|
||||
If ``False`` and if ``user_input`` doesn't find a match,
|
||||
further match attempts will be made with the all-lowercase version of
|
||||
``user_input`` (``"foobar"``) and the title-case version of ``user_input``
|
||||
(``"Foobar"``).
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
|
||||
Raises
|
||||
------
|
||||
LanguageNotFoundError
|
||||
If `part3` isn't a language name or code
|
||||
"""
|
||||
try:
|
||||
return _get_part3_exact(user_input, query_order)
|
||||
except LanguageNotFoundError as e:
|
||||
if strict_case:
|
||||
raise e
|
||||
else:
|
||||
for func in _STRING_CLEANING_FUNCS:
|
||||
try:
|
||||
return _get_part3_exact(func(user_input), query_order, user_input)
|
||||
except LanguageNotFoundError:
|
||||
continue
|
||||
else:
|
||||
_raise_language_not_found_error(user_input)
|
||||
|
||||
|
||||
def _get_part3_exact(
|
||||
user_input: str,
|
||||
query_order: list[_COLUMN_TYPE],
|
||||
original_user_input: str | None = None,
|
||||
) -> str:
|
||||
"""Get the part 3 code of a language.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user_input : str
|
||||
The user-provided language code or name.
|
||||
query_order : list[_COLUMN_TYPE]
|
||||
A list of columns to specify query order.
|
||||
original_user_input : str, optional
|
||||
The original user input. Default is `None`.
|
||||
This argument is used when the user input has been cleaned.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
|
||||
Raises
|
||||
------
|
||||
LanguageNotFoundError
|
||||
If `part3` isn't a language name or code
|
||||
"""
|
||||
user_input = user_input.strip()
|
||||
part3: str | None = None
|
||||
for column in query_order:
|
||||
if column == _CodesColumn.ID:
|
||||
if user_input in _PART3_TO_CODES:
|
||||
return user_input
|
||||
elif column == _CodesColumn.PART2B:
|
||||
part3 = _PART2B_TO_PART3.get(user_input)
|
||||
elif column == _CodesColumn.PART2T:
|
||||
part3 = _PART2T_TO_PART3.get(user_input)
|
||||
elif column == _CodesColumn.PART1:
|
||||
part3 = _PART1_TO_PART3.get(user_input)
|
||||
elif column == _RetirementsColumn.ID:
|
||||
if user_input in _PART3_TO_RETIREMENTS:
|
||||
return user_input
|
||||
elif column == _CodesColumn.REF_NAME:
|
||||
part3 = _REF_NAME_TO_PART3.get(user_input)
|
||||
elif column == _NameIndexColumn.PRINT_NAME:
|
||||
part3 = _PRINT_NAME_TO_PART3.get(user_input)
|
||||
elif column == _NameIndexColumn.INVERTED_NAME:
|
||||
part3 = _INVERTED_NAME_TO_PART3.get(user_input)
|
||||
else:
|
||||
raise ValueError(f"Invalid column: {column}")
|
||||
if part3 is not None:
|
||||
break
|
||||
|
||||
if part3 is None:
|
||||
_raise_language_not_found_error(original_user_input or user_input)
|
||||
|
||||
return part3
|
||||
|
||||
|
||||
def _get_language(part3: str) -> Language:
|
||||
"""Create a ``Language`` instance.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
part3 : str
|
||||
Part 3 code of the language.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Language
|
||||
"""
|
||||
from_codes = _PART3_TO_CODES.get(part3)
|
||||
from_macrolanguages = _PART3_TO_MACROLANGUAGES.get(part3)
|
||||
from_retirements = _PART3_TO_RETIREMENTS.get(part3)
|
||||
|
||||
ref_name = (
|
||||
from_codes[_CodesColumn.REF_NAME]
|
||||
if from_codes
|
||||
else from_retirements[_RetirementsColumn.REF_NAME] # type: ignore
|
||||
)
|
||||
|
||||
other_names: list[Name] | None = []
|
||||
for row in _PART3_TO_NAME_INDEX.get(part3, []):
|
||||
p, i = row[_NameIndexColumn.PRINT_NAME], row[_NameIndexColumn.INVERTED_NAME]
|
||||
if not ref_name == p == i:
|
||||
other_names.append(Name(p, i)) # type: ignore
|
||||
other_names = other_names or None
|
||||
|
||||
macrolanguage = (from_macrolanguages or {}).get(_MacrolanguagesColumn.MID)
|
||||
retire_reason = (from_retirements or {}).get(_RetirementsColumn.RET_REASON)
|
||||
retire_change_to = (from_retirements or {}).get(_RetirementsColumn.CHANGE_TO)
|
||||
retire_remedy = (from_retirements or {}).get(_RetirementsColumn.REMEDY)
|
||||
|
||||
retire_date = (
|
||||
datetime.datetime.strptime(
|
||||
from_retirements[_RetirementsColumn.EFFECTIVE], "%Y-%m-%d"
|
||||
).date()
|
||||
if from_retirements
|
||||
else None
|
||||
)
|
||||
|
||||
if from_codes:
|
||||
# The ISO 639-3 code is active.
|
||||
part2b = from_codes[_CodesColumn.PART2B]
|
||||
part2t = from_codes[_CodesColumn.PART2T]
|
||||
part1 = from_codes[_CodesColumn.PART1]
|
||||
scope = from_codes[_CodesColumn.SCOPE]
|
||||
type = from_codes[_CodesColumn.TYPE]
|
||||
status = "A"
|
||||
ref_name = ref_name
|
||||
comment = from_codes[_CodesColumn.COMMENT]
|
||||
|
||||
else:
|
||||
# The ISO 639-3 code is retired.
|
||||
part2b = None
|
||||
part2t = None
|
||||
part1 = None
|
||||
scope = "I"
|
||||
type = None
|
||||
status = "R"
|
||||
ref_name = ref_name
|
||||
comment = None
|
||||
|
||||
language = Language(
|
||||
part3=part3,
|
||||
part2b=part2b or None,
|
||||
part2t=part2t or None,
|
||||
part1=part1 or None,
|
||||
scope=scope,
|
||||
type=type or None,
|
||||
status=status,
|
||||
name=ref_name,
|
||||
comment=comment or None,
|
||||
other_names=other_names or None,
|
||||
macrolanguage=macrolanguage or None,
|
||||
retire_reason=retire_reason or None,
|
||||
retire_change_to=retire_change_to or None,
|
||||
retire_remedy=retire_remedy or None,
|
||||
retire_date=retire_date or None,
|
||||
)
|
||||
|
||||
return language
|
||||
|
||||
|
||||
def _get_all_languages() -> dict[str, Language]:
|
||||
languages = {}
|
||||
for part3 in _PART3_TO_CODES:
|
||||
languages[part3] = _get_language(part3)
|
||||
for part3 in _PART3_TO_RETIREMENTS:
|
||||
languages[part3] = _get_language(part3)
|
||||
return languages
|
||||
|
||||
|
||||
_PART3_TO_LANGUAGES: dict[str, Language] = _get_all_languages()
|
||||
|
||||
ALL_LANGUAGES: set[Language] = set(_PART3_TO_LANGUAGES.values())
|
||||
Reference in New Issue
Block a user