141 lines
8.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Общие функции для типографа 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