From 9b5d7cdfade3f93e96b141315665c16f6be4db79 Mon Sep 17 00:00:00 2001 From: Nanaloveyuki Date: Mon, 28 Jul 2025 21:21:21 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E4=BA=86`message?= =?UTF-8?q?`=E5=8F=82=E6=95=B0=E5=AF=B9=E4=BA=8Emd/html=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=AF=8C=E6=96=87=E6=9C=AC=E8=AF=AD=E6=B3=95=E7=9A=84=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=92=8C=E6=94=B9=E8=BF=9B=E4=BA=86HEX=E7=9D=80?= =?UTF-8?q?=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/logiliteal/levels/levels.py | 4 +- src/logiliteal/utils/fmt.py | 142 +++++++++++++++++++++++++------- src/logiliteal/utils/styles.py | 8 +- 3 files changed, 123 insertions(+), 31 deletions(-) diff --git a/src/logiliteal/levels/levels.py b/src/logiliteal/levels/levels.py index 766aa81..6271d4d 100644 --- a/src/logiliteal/levels/levels.py +++ b/src/logiliteal/levels/levels.py @@ -40,7 +40,9 @@ class Logger: with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f: f.write(fmt_file(lvn, fmt_message(msg, no_placeholder=True), pf)) if is_enable_console: - print(fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf)) + console_output = fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf) + if console_output is not None: + print(console_output) return fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf) def debug(self, message: Any, prefix: str | None = None, level: int = 0) -> Optional[str]: diff --git a/src/logiliteal/utils/fmt.py b/src/logiliteal/utils/fmt.py index e8a2597..83bb91c 100644 --- a/src/logiliteal/utils/fmt.py +++ b/src/logiliteal/utils/fmt.py @@ -12,7 +12,10 @@ from .time import get_asctime, get_time, get_weekday, get_date from .styles import set_color, set_bg_color import re -time_color = get_config("time_color") +if get_config("time_color") is None: + time_color = "#28ffb6" +else: + time_color = get_config("time_color") def fmt_level(level: str) -> int: """ @@ -89,34 +92,17 @@ def fmt_message(message: Any, no_placeholder: bool = False, no_color: bool = Fal """ def process_color_tags(msg: str) -> str: - from io import StringIO - output = StringIO() - stack = [] - last_end = 0 - pattern = re.compile(r'(<#([0-9a-fA-F]{6})>|)') - current_color = None - - for match in pattern.finditer(msg): - output.write(msg[last_end:match.start()]) - tag = match.group(1) - last_end = match.end() - - if tag.startswith('<#'): - stack.append(current_color) - current_color = match.group(2) - else: - if stack: - current_color = stack.pop() - else: - output.write(tag) - - output.write(msg[last_end:]) - result = output.getvalue() - output.close() - - if current_color: - result += ''.join(f'<#{color}>' for color in reversed(stack)) if stack else f'<#{current_color}>' - return result + # 优化处理顺序,确保链接和基础格式优先处理 + processed = _process_color_formatting( + _process_special_tags( + _process_html_styles( + _process_markdown_formats( + _process_links(msg) + ) + ) + ) + ) + return processed if no_color: processed_message = str(message) else: @@ -181,3 +167,101 @@ def fmt_file(level: int, message: Any, prefix: str | None = None) -> Optional[st prefix = fmt_message(prefix, no_placeholder=True, no_color=True), message = fmt_message(message, no_placeholder=True, no_color=True) )}\n" + +def _process_links(text: str) -> str: + from collections import deque + + link_stack = deque() + placeholder_count = 0 + placeholders = {} + + def replace_link(m): + nonlocal placeholder_count + placeholder_count += 1 + if len(m.groups()) == 2 and m.group(2) and not m.group(1).startswith('http'): + # Markdown链接 [text](url) + url = m.group(2).strip() + text = m.group(1) + else: + url = m.group(1) + text = m.group(2) + placeholder = f"__LINK_PLACEHOLDER_{placeholder_count}__" + placeholders[placeholder] = (url if url else "#", text) + link_stack.append(placeholder) + return placeholder + + text = re.sub(r'(.*?)', replace_link, text, flags=re.DOTALL) + text = re.sub(r'(.*?)', replace_link, text, flags=re.DOTALL) + text = re.sub(r'\[(.*?)\]\((.*?)\)', replace_link, text) + + for placeholder, (url, text_content) in placeholders.items(): + ansi_link = f'\033]8;;{url}\033\\{set_color("\033[4m" + text_content, "#5f93ff")}\033]8;;\033\\' + text = text.replace(placeholder, ansi_link) + + return text + +def _process_markdown_formats(text: str) -> str: + # Markdown粗体 (**text**) + text = re.sub(r'\*\*(.*?)\*\*', '\033[1m\\g<1>\033[22m', text) + # Markdown斜体 (*text*) + text = re.sub(r'\*(.*?)\*', '\033[3m\\g<1>\033[23m', text) + # Markdown下划线 (__text__) + text = re.sub(r'__(.*?)__', '\033[4m\\g<1>\033[24m', text) + # Markdown删除线 (~~text~~) + text = re.sub(r'~~(.*?)~~', '\033[9m\\g<1>\033[29m', text) + return text + +def _process_html_styles(text: str) -> str: + text = re.sub(r'([^<]*?)(|$)', + lambda m: '\033[3m' + m.group(1) + '\033[23m', text, flags=re.DOTALL) + text = re.sub(r'([^<]*?)', + lambda m: '\033[1m' + m.group(1) + '\033[22m', text) + text = re.sub(r'([^<]*?)', + lambda m: '\033[4m' + m.group(1) + '\033[24m', text) + text = re.sub(r'([^<]*?)(|$)', + lambda m: '\033[9m' + m.group(1) + '\033[29m', text, flags=re.DOTALL) + return text + +def _process_special_tags(text: str) -> str: + text = re.sub(r'
', '\n', text) + text = re.sub(r'', '\033[0m', text) + text = re.sub(r'

(.*?)

', r'\n\033[0m\g<1>\033[0m\n', text, flags=re.DOTALL) + text = re.sub(r'

(.*?)(

|$)', r'\n\033[0m\g<1>\033[0m\n', text, flags=re.DOTALL) + text = re.sub(r'

', '\033[0m\n', text) + return text + +def _process_color_formatting(text: str) -> str: + import re + + color_pattern = r'<#([0-9a-fA-F]{6})>' + close_pattern = r'' + + parts = re.split(f'({color_pattern}|{close_pattern})', text) + result = [] + color_stack = [] + + for part in parts: + if part and re.fullmatch(color_pattern, part): + color = re.match(color_pattern, part).group(1) + color_stack.append(color) + continue + elif part == '': + if color_stack: + color_stack.pop() + continue + elif part: + if color_stack: + current_color = color_stack[-1] + r = int(current_color[0:2], 16) + g = int(current_color[2:4], 16) + b = int(current_color[4:6], 16) + ansi_code = f'\033[38;2;{r};{g};{b}m' + reset_code = '\033[0m' + result.append(f'{ansi_code}{part}{reset_code}') + else: + result.append(part) + + processed_text = ''.join(result) + processed_text = re.sub(f'{color_pattern}|{close_pattern}|[0-9a-fA-F]{{6}}', '', processed_text) + + return processed_text diff --git a/src/logiliteal/utils/styles.py b/src/logiliteal/utils/styles.py index 4068344..41a98dd 100644 --- a/src/logiliteal/utils/styles.py +++ b/src/logiliteal/utils/styles.py @@ -57,13 +57,15 @@ def set_bg_color(text: str|None, color: str|None) -> str: ansi = ansi.replace("38;", "48;") return f"{ansi}{text}\033[0m" -def set_style(text: str|None, bold: bool = False, underline: bool = False, reverse: bool = False) -> str: +def set_style(text: str|None, bold: bool = False, italic: bool = False, underline: bool = False, strikethrough: bool = False, reverse: bool = False) -> str: """ 设置文本样式 Set text style :param text: 文本内容 Text content :param bold: 是否加粗 Is bold + :param italic: 是否斜体 Is italic :param underline: 是否下划线 Is underline + :param strikethrough: 是否划线 Is strikethrough :param reverse: 是否反相 Is reverse :return: 格式化后的文本 Formatted text """ @@ -72,8 +74,12 @@ def set_style(text: str|None, bold: bool = False, underline: bool = False, rever ansi = "" if bold: ansi += "\033[1m" + if italic: + ansi += "\033[3m" if underline: ansi += "\033[4m" + if strikethrough: + ansi += "\033[9m" if reverse: ansi += "\033[7m" return f"{ansi}{text}\033[0m"