141 lines
8.3 KiB
Python
141 lines
8.3 KiB
Python
# Общие функции для типографа etpgrf
|
||
from etpgrf.config import MODE_UNICODE, MODE_MNEMONIC, MODE_MIXED, SUPPORTED_LANGS
|
||
from etpgrf.defaults import etpgrf_settings
|
||
import os
|
||
import regex
|
||
import logging
|
||
|
||
# --- Настройки логирования ---
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def parse_and_validate_mode(
|
||
mode_input: str | None = None,
|
||
) -> str:
|
||
"""
|
||
Обрабатывает и валидирует входной параметр mode.
|
||
Если mode_input не предоставлен (None), используется режим по умолчанию.
|
||
|
||
:param mode_input: Режим обработки текста. Может быть 'unicode', 'mnemonic' или 'mixed'.
|
||
:return: Валидированный режим в нижнем регистре.
|
||
:raises TypeError: Если mode_input имеет неожиданный тип.
|
||
:raises ValueError: Если mode_input пуст после обработки или содержит неподдерживаемый режим.
|
||
"""
|
||
if mode_input is None:
|
||
# Если mode_input не предоставлен явно, используем режим по умолчанию
|
||
_mode_input = etpgrf_settings.MODE
|
||
else:
|
||
_mode_input = str(mode_input).lower()
|
||
|
||
if _mode_input not in {MODE_UNICODE, MODE_MNEMONIC, MODE_MIXED}:
|
||
raise ValueError(
|
||
f"etpgrf: режим '{_mode_input}' не поддерживается. Поддерживаемые режимы: {MODE_UNICODE}, {MODE_MNEMONIC}, {MODE_MIXED}"
|
||
)
|
||
|
||
return _mode_input
|
||
|
||
|
||
def parse_and_validate_langs(
|
||
langs: str | list[str] | tuple[str, ...] | frozenset[str] | None = None,
|
||
) -> frozenset[str]:
|
||
"""
|
||
Обрабатывает и валидирует входной параметр языков.
|
||
Если langs_input не предоставлен (None), используются языки по умолчанию
|
||
(сначала из переменной окружения ETPGRF_DEFAULT_LANGS, затем внутренний дефолт).
|
||
|
||
:param langs: Язык(и) для обработки. Может быть строкой (например, "ru+en"), списком, кортежем или frozenset.
|
||
:return: Frozenset валидированных кодов языков в нижнем регистре.
|
||
:raises TypeError: Если langs_input имеет неожиданный тип.
|
||
:raises ValueError: Если langs_input пуст после обработки или содержит неподдерживаемые коды.
|
||
"""
|
||
_langs = langs
|
||
|
||
if _langs is None:
|
||
# Если langs не предоставлен явно, будем выкручиваться и искать в разных местах
|
||
# 1. Попытка получить языки из переменной окружения системы
|
||
env_default_langs = os.environ.get('ETPGRF_DEFAULT_LANGS')
|
||
if env_default_langs:
|
||
# Нашли язык для библиотеки в переменных окружения
|
||
_langs = env_default_langs
|
||
# print(f"Using ETPGRF_DEFAULT_LANGS from environment: {env_default_langs}") # Для отладки
|
||
else:
|
||
# Если в переменной окружения нет, используем то что есть в конфиге `etpgrf/config.py`
|
||
_langs = etpgrf_settings.DEFAULT_LANGS
|
||
# print(f"Using library internal default langs: {DEFAULT_LANGS}") # Для отладки
|
||
|
||
if isinstance(_langs, str):
|
||
# Разделяем строку по любым небуквенным символам, приводим к нижнему регистру
|
||
# и фильтруем пустые строки
|
||
parsed_lang_codes_list = [lang.lower() for lang in regex.split(r'[^a-zA-Z]+', _langs) if lang]
|
||
elif isinstance(_langs, (list, tuple, frozenset)): # frozenset тоже итерируемый
|
||
# Приводим к строке, нижнему регистру и проверяем, что строка не пустая
|
||
parsed_lang_codes_list = [str(lang).lower() for lang in _langs if str(lang).strip()]
|
||
else:
|
||
raise TypeError(
|
||
f"etpgrf: параметр 'langs' должен быть строкой, списком, кортежем или frozenset. Получен: {type(_langs)}"
|
||
)
|
||
|
||
if not parsed_lang_codes_list:
|
||
raise ValueError(
|
||
"etpgrf: параметр 'langs' не может быть пустым или приводить к пустому списку языков после обработки."
|
||
)
|
||
|
||
validated_langs_set = set()
|
||
for code in parsed_lang_codes_list:
|
||
if code not in SUPPORTED_LANGS:
|
||
raise ValueError(
|
||
f"etpgrf: код языка '{code}' не поддерживается. Поддерживаемые языки: {list(SUPPORTED_LANGS)}"
|
||
)
|
||
validated_langs_set.add(code)
|
||
|
||
# Эта проверка на случай если parsed_lang_codes_list был не пуст, но все коды оказались невалидными
|
||
# (хотя предыдущее исключение должно было сработать раньше для каждого невалидного кода).
|
||
if not validated_langs_set:
|
||
raise ValueError("etpgrf: не предоставлено ни одного валидного кода языка.")
|
||
|
||
return frozenset(validated_langs_set)
|
||
|
||
|
||
def is_inside_unbreakable_segment(
|
||
word_segment: str,
|
||
split_index: int,
|
||
unbreakable_set: frozenset[str] | list[str] | set[str],
|
||
) -> bool:
|
||
"""
|
||
Проверяет, находится ли позиция разбиения внутри неразрывного сегмента.
|
||
|
||
:param word_segment: -- Сегмент слова, в котором мы ищем позицию разбиения.
|
||
:param split_index: -- Индекс (позиция внутри сегмента), по которому мы хотим проверить разбиение.
|
||
:param unbreakable_set: -- Набор неразрывных сегментов (например: диграфы, триграфы, акронимы...).
|
||
:return:
|
||
"""
|
||
segment_len = len(word_segment)
|
||
# Проверяем, что позиция разбиения не выходит за границы сегмента
|
||
if not (0 < split_index < segment_len):
|
||
return False
|
||
# Пер образуем все в верхний регистр, чтобы сравнения строк работали
|
||
word_segment_upper = word_segment.upper()
|
||
# unbreakable_set_upper = (unit.upper() for unit in unbreakable_set) # <-- С помощью генератора
|
||
|
||
# Отсортируем unbreakable_set по длине лексем (чем короче, тем больше шансов на "ранний выход")
|
||
# и заодно превратим в list
|
||
sorted_units = sorted(unbreakable_set, key=len)
|
||
# sorted_units = sorted(unbreakable_set_upper, key=len)
|
||
for unbreakable in sorted_units:
|
||
unit_len = len(unbreakable)
|
||
if unit_len < 2:
|
||
continue
|
||
# Спорно, что преобразование в верхний регистр эффективнее делать тут, но благодаря возможному
|
||
# "раннему выходу" это может быть быстрее с помощью генератора (см. выше комментарии)
|
||
unbreakable_upper = unbreakable.upper()
|
||
for offset in range(1, unit_len):
|
||
position_start_in_segment = split_index - offset
|
||
position_end_in_segment = position_start_in_segment + unit_len
|
||
# Убедимся, что предполагаемое положение 'unit' не выходит за границы word_segment
|
||
if position_start_in_segment >= 0 and position_end_in_segment <= segment_len and \
|
||
word_segment_upper[position_start_in_segment:position_end_in_segment] == unbreakable_upper:
|
||
# Нашли 'unbreakable', и split_index находится внутри него.
|
||
return True
|
||
return False
|
||
|