From 70ddf17c9ff7f8f781745100ff84b8809674eddb Mon Sep 17 00:00:00 2001 From: erjemin Date: Tue, 13 May 2025 23:52:50 +0300 Subject: [PATCH] =?UTF-8?q?add:=20=D0=BB=D0=BE=D0=B3=D0=B3=D0=B5=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- etpgrf/__init__.py | 3 +- etpgrf/defaults.py | 10 ++++ etpgrf/hyphenation.py | 24 +++++---- etpgrf/logger.py | 121 ++++++++++++++++++++++++++++++++++++++++++ etpgrf/typograph.py | 7 ++- main.py | 7 ++- 7 files changed, 158 insertions(+), 16 deletions(-) create mode 100644 etpgrf/logger.py diff --git a/README.md b/README.md index 510ccb1..d2ce4c2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ | in progress // в процессе разработки | |--------------------------------------| -| ------5 | +| -------6 | # Типограф для Web diff --git a/etpgrf/__init__.py b/etpgrf/__init__.py index c6e4c8c..65dbc35 100644 --- a/etpgrf/__init__.py +++ b/etpgrf/__init__.py @@ -10,6 +10,7 @@ Typography - библиотека для экранной типографики """ __version__ = "0.1.0" +import etpgrf.defaults from etpgrf.typograph import Typographer from etpgrf.hyphenation import Hyphenator -import etpgrf.defaults \ No newline at end of file +import etpgrf.logger diff --git a/etpgrf/defaults.py b/etpgrf/defaults.py index 64ec46f..4673794 100644 --- a/etpgrf/defaults.py +++ b/etpgrf/defaults.py @@ -1,6 +1,15 @@ # etpgrf/defaults.py -- Настройки по умолчанию для типографа etpgrf +import logging from etpgrf.config import LANG_RU, MODE_MIXED +class LoggingDefaults: + LEVEL = logging.DEBUG + FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + # Можно добавить ещё настройки, если понадобятся: + # FORMAT: str = '%(asctime)s - %(name)s - %(levelname)s - %(module)s.%(funcName)s:%(lineno)d - %(message)s' + # FILE_PATH: str | None = None # Путь к файлу лога, если None - не пишем в файл + + class HyphenationDefaults: """ Настройки по умолчанию для Hyphenator etpgrf. @@ -16,6 +25,7 @@ class EtpgrfDefaultSettings: def __init__(self): self.LANGS: list[str] | str = LANG_RU self.MODE: str = MODE_MIXED + self.logging_settings = LoggingDefaults() self.hyphenation = HyphenationDefaults() # self.quotes = EtpgrfQuoteDefaults() diff --git a/etpgrf/hyphenation.py b/etpgrf/hyphenation.py index 5038781..4aac9ff 100755 --- a/etpgrf/hyphenation.py +++ b/etpgrf/hyphenation.py @@ -1,4 +1,5 @@ import regex +import logging from etpgrf.config import LANG_RU, LANG_RU_OLD, LANG_EN, SHY_ENTITIES, MODE_UNICODE from etpgrf.defaults import etpgrf_settings from etpgrf.comutil import parse_and_validate_mode, parse_and_validate_langs @@ -16,7 +17,11 @@ _RU_OLD_CONSONANTS_UPPER = frozenset(['Ѳ', # Фита (согласная) _EN_VOWELS_UPPER = frozenset(['A', 'E', 'I', 'O', 'U', 'Æ', 'Œ']) _EN_CONSONANTS_UPPER = frozenset(['B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z']) +# --- Настройки логирования --- +logger = logging.getLogger(__name__) + +# --- Класс Hyphenator (расстановка переносов) --- class Hyphenator: """Правила расстановки переносов для разных языков. """ @@ -41,8 +46,10 @@ class Hyphenator: self._load_language_resources_for_hyphenation() # Определяем символ переноса в зависимости от режима self._split_code: str = SHY_ENTITIES['SHY'][0] if self.mode == MODE_UNICODE else SHY_ENTITIES['SHY'][1] - print(f"========={self.max_unhyphenated_len}===========") - + # ... + logger.debug(f"Hyphenator `__init__`. Langs: {self.langs}, Mode: {self.mode}," + f" Max unhyphenated_len: {self.max_unhyphenated_len}," + f" Min chars_per_part: {self.min_chars_per_part}") def _load_language_resources_for_hyphenation(self): # Определяем наборы гласных, согласных и т.д. в зависимости языков. @@ -100,13 +107,13 @@ class Hyphenator: if len(word) <= self.max_unhyphenated_len or not any(self._is_vow(c) for c in word): # Если слово короткое или не содержит гласных, перенос не нужен return word - print("слово:", word, " // mode:", self.mode, " // langs:", self.langs) + logger.debug(f"Hyphenator: word: `{word}` // langs: {self.langs} // mode: {self.mode} // max_unhyphenated_len: {self.max_unhyphenated_len} // min_tail_len: {self.min_chars_per_part}") # 2. ОБНАРУЖЕНИЕ ЯЗЫКА И ПОДКЛЮЧЕНИЕ ЯЗЫКОВОЙ ЛОГИКИ # Поиск вхождения букв строки (слова) через `frozenset` -- O(1). Это быстрее регулярного выражения -- O(n) # 2.1. Проверяем RU и RU_OLD (правила одинаковые, но разные наборы букв) if (LANG_RU in self.langs or LANG_RU_OLD in self.langs) and frozenset(word.upper()) <= self._ru_alphabet_upper: # Пользователь подключил русскую логику, и слово содержит только русские буквы - print(f"#### Applying Russian rules to: {word}") + logger.debug(f"`{word}` -- use `{LANG_RU}` or `{LANG_RU_OLD}` rules") # Поиск допустимой позиции для переноса около заданного индекса def find_hyphen_point_ru(word_segment: str, start_idx: int) -> int: vow_indices = [i for i, char_w in enumerate(word_segment) if self._is_vow(char_w)] @@ -153,7 +160,7 @@ class Hyphenator: # 2.2. Проверяем EN elif LANG_EN in self.langs and frozenset(word.upper()) <= self._en_alphabet_upper: # Пользователь подключил английскую логику, и слово содержит только английские буквы - print(f"#### Applying English rules to: {word}") # Для отладки + logger.debug(f"`{word}` -- use `{LANG_EN}` rules") # --- Начало логики для английского языка (заглушка) --- # ПРИМЕЧАНИЕ: Это очень упрощенная заглушка. def find_hyphen_point_en(word_segment: str) -> int: @@ -175,7 +182,7 @@ class Hyphenator: return split_word_en(word) else: # кстати "слова" в которых есть пробелы или другие разделители, тоже попадают сюда - print("!!!!ФИГНЯ") + logger.debug(f"`{word}` -- use `UNDEFINE` rules") return word @@ -195,11 +202,8 @@ class Hyphenator: hyphenated_word = self.hyp_in_word(word_to_process) # ============= Для отладки (слова в которых появились переносы) ================== - print(f"hyp_in_text: '{word_to_process}'", end="") if word_to_process != hyphenated_word: - print(f" -> '{hyphenated_word}'") - else: - print(" (no change)") + logger.debug(f"hyp_in_text: '{word_to_process}' -> '{hyphenated_word}'") return hyphenated_word diff --git a/etpgrf/logger.py b/etpgrf/logger.py new file mode 100644 index 0000000..9ddaed6 --- /dev/null +++ b/etpgrf/logger.py @@ -0,0 +1,121 @@ +# etpgrf/logging_settings.py +import logging +from etpgrf.defaults import etpgrf_settings # Импортируем наш объект настроек по умолчанию + +# --- Корневой логгер для всей библиотеки etpgrf --- +# Имя логгера "etpgrf" позволит пользователям настраивать +# логирование для всех частей библиотеки. +# Например, logging.getLogger("etpgrf").setLevel(logging.DEBUG) +# или logging.getLogger("etpgrf.hyphenation").setLevel(logging.INFO) +_etpgrf_init_logger = logging.getLogger("etpgrf") + + +# --- Настройка корневого логгера --- +def setup_library_logging(): + """ + Настраивает корневой логгер для библиотеки etpgrf. + Эту функцию следует вызывать один раз (например, при импорте + основного модуля библиотеки или при первом обращении к логгеру). + """ + # Проверяем инициализацию хандлеров логера, чтобы случайно не добавлять хендлеры многократно + if not _etpgrf_init_logger.hasHandlers(): + log_level_to_set = logging.WARNING # Значение по умолчанию + log_format_to_set = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' # Формат по умолчанию + + fin_message: str | None = None + if hasattr(etpgrf_settings, 'logging_settings'): + if hasattr(etpgrf_settings.logging_settings, 'LEVEL'): + log_level_to_set = etpgrf_settings.logging_settings.LEVEL + if hasattr(etpgrf_settings.logging_settings, 'FORMAT') and etpgrf_settings.logging_settings.FORMAT: + log_format_to_set = etpgrf_settings.logging_settings.FORMAT + else: + # Этого не должно происходить, если defaults.py настроен правильно + fin_message= "ПРЕДУПРЕЖДЕНИЕ: etpgrf_settings.logging_settings не найдены при начальной настройке логгера." + + _etpgrf_init_logger.setLevel(log_level_to_set) # Устанавливаем уровень логирования + console_handler = logging.StreamHandler() # Создаем хендлер вывода в консоль + console_handler.setLevel(log_level_to_set) # Уровень для хендлера тоже + formatter = logging.Formatter(log_format_to_set) # Создаем форматтер для вывода + console_handler.setFormatter(formatter) # Устанавливаем форматтер для хендлера + _etpgrf_init_logger.addHandler(console_handler) # Добавляем хендлер в логгер + if fin_message is not None: + # Если есть сообщение об отсутствии настроек в `etpgrf_settings`, выводим его + _etpgrf_init_logger.warning(fin_message) + _etpgrf_init_logger.debug(f"Корневой логгер 'etpgrf' инициализирован." + f" Уровень: {logging.getLevelName(_etpgrf_init_logger.getEffectiveLevel())}") + + +# --- Динамическое изменение уровня логирования --- +def update_etpgrf_log_level_from_settings(): + """ + Обновляет уровень логирования для корневого логгера `etpgrf` и его + обработчиков, читая значение из `etpgrf_settings.logging_settings.LEVEL`. + """ + # Проверяем, что настройки логирования и уровень существуют в `defaults.etpgrf_settings` + if not hasattr(etpgrf_settings, 'logging_settings') or \ + not hasattr(etpgrf_settings.logging_settings, 'LEVEL'): + _etpgrf_init_logger.warning("Невозможно обновить уровень логгера: `etpgrf_settings.logging_settings.LEVEL`" + " не найден.") + return + + new_level = etpgrf_settings.logging_settings.LEVEL + _etpgrf_init_logger.setLevel(new_level) + for handler in _etpgrf_init_logger.handlers: + handler.setLevel(new_level) # Устанавливаем уровень для каждого хендлера + + _etpgrf_init_logger.info(f"Уровень логирования `etpgrf` динамически обновлен на:" + f" {logging.getLevelName(_etpgrf_init_logger.getEffectiveLevel())}") + + +# --- Динамическое изменение формата логирования --- +def update_etpgrf_log_format_from_settings(): + """ + Обновляет формат логирования для обработчиков корневого логгера etpgrf, + читая значение из etpgrf_settings.logging_settings.FORMAT. + """ + if not hasattr(etpgrf_settings, 'logging_settings') or \ + not hasattr(etpgrf_settings.logging_settings, 'FORMAT') or \ + not etpgrf_settings.logging_settings.FORMAT: + _etpgrf_init_logger.warning("Невозможно обновить формат логгера: `etpgrf_settings.logging_settings.FORMAT`" + " не найден или пуст.") + return + + new_format_string = etpgrf_settings.logging_settings.FORMAT + new_formatter = logging.Formatter(new_format_string) + + for handler in _etpgrf_init_logger.handlers: + handler.setFormatter(new_formatter) # Применяем новый форматтер к каждому хендлеру + + _etpgrf_init_logger.info(f"Формат логирования для 'etpgrf' динамически обновлен на: '{new_format_string}'") + + +# --- Инициализация логгера при первом импорте --- +setup_library_logging() + + +# --- Предоставление логгеров для модулей --- +def get_logger(name: str) -> logging.Logger: + """ + Возвращает логгер для указанного имени. + Обычно используется как logging.getLogger(__name__) в модулях. + Имя будет дочерним по отношению к "etpgrf", например, "etpgrf.hyphenation". + """ + # Убедимся, что имя логгера начинается с "etpgrf." для правильной иерархии, + # если только это не сам корневой логгер. + if not name.startswith("etpgrf") and name != "etpgrf": + # Это может быть __name__ из модуля верхнего уровня, использующего библиотеку. В этом случае мы не хотим + # делать его дочерним от "etpgrf" насильно. Просто вернем логгер с именем... + # Либо можно настроить, что все логгеры, получаемые через эту функцию, должны быть частью иерархии "etpgrf"... + # Для простоты оставим так: + pass # logging_settings = logging.getLogger(name) + # Более правильный подход для модулей ВНУТРИ библиотеки etpgrf: они должны вызывать `logging.getLogger(__name__)` + # напрямую. Тогда эта функция `get_logger()` может быть и не нужна, если модули ничего не делают кроме: + # import logging + # logging_settings = logging.getLogger(__name__) + # + # Однако, если нужно централизованно получать логгеры, можно сделать, чтобы `get_logger()` всегда возвращал + # дочерний логгер: + # if not name.startswith("etpgrf."): + # name = f"etpgrf.{name}" + return logging.getLogger(name) + diff --git a/etpgrf/typograph.py b/etpgrf/typograph.py index 89b015f..f5b38d8 100644 --- a/etpgrf/typograph.py +++ b/etpgrf/typograph.py @@ -1,6 +1,9 @@ from etpgrf.comutil import parse_and_validate_mode, parse_and_validate_langs - from etpgrf.hyphenation import Hyphenator +import logging + +# --- Настройки логирования --- +logger = logging.getLogger(__name__) # --- Основной класс Typographer --- @@ -17,7 +20,6 @@ class Typographer: self.langs: frozenset[str] = parse_and_validate_langs(langs) # B. --- Обработка и валидация параметра mode --- self.mode: str = parse_and_validate_mode(mode) - print("Typographer: langs:", self.langs, "// mode:", self.mode) # Для отладки # C. --- Инициализация правила переноса --- # Предпосылка: если вызвали типограф, значит, мы хотим обрабатывать текст и переносы тоже нужно расставлять. # А для специальных случаев, когда переносы не нужны, пусть не ленятся и делают `hyphenation=False`. @@ -35,6 +37,7 @@ class Typographer: # 4. Если hyphenation что-то неведомое, то игнорируем его и правило переноса выключено self.hyphenation = None # D. --- Конфигурация других правил--- + logger.debug(f"Typographer `__init__`: langs: {self.langs}, mode: {self.mode}, hyphenation: {self.hyphenation}") # Конвейер для обработки текста diff --git a/main.py b/main.py index a7817eb..3cda9a4 100644 --- a/main.py +++ b/main.py @@ -1,11 +1,14 @@ import etpgrf - +import logging if __name__ == '__main__': # --- Пример использования --- print("\n--- Пример использования класса---\n") - etpgrf.defaults.etpgrf_settings.hyphenation.MAX_UNHYPHENATED_LEN = 8 + etpgrf.defaults.etpgrf_settings.logging_settings.LEVEL = logging.DEBUG + etpgrf.logger.update_etpgrf_log_level_from_settings() # Обновляем уровень логирования из настроек + etpgrf.defaults.etpgrf_settings.logging_settings.FORMAT = '%(asctime)s - %(name)s = %(levelname)s - %(message)s' + etpgrf.logger.update_etpgrf_log_format_from_settings() # Обновляем формат логирования из настроек # Определяем пользовательские правила переносов hyphen_settings = etpgrf.Hyphenator(langs='ru', max_unhyphenated_len=8)