Compare commits

..

17 Commits

Author SHA1 Message Date
Nanaloveyuki
80e4a4d02f 🚀 发布了Beta0.1.2测试版 2025-07-29 14:20:34 +08:00
Nanaloveyuki
eed6144970 🐛 修复了时间戳格式错误的问题 2025-07-29 14:17:45 +08:00
Nanaloveyuki
796a8a82f6 🎨 改进了对占位符和正则表达式的处理 2025-07-29 14:17:22 +08:00
Nanaloveyuki
89bc381235 📝 更新了文档,使其支持占位符等 2025-07-29 14:16:38 +08:00
Nanaloveyuki
89cb26b9b3 message富文本功能基本测试完成 2025-07-28 21:21:46 +08:00
Nanaloveyuki
9b5d7cdfad 增加了message参数对于md/html部分富文本语法的支持和改进了HEX着色 2025-07-28 21:21:21 +08:00
Nanaloveyuki
d5f157bdf3 🧑‍💻 增加快速打包 2025-07-28 17:58:32 +08:00
Nanaloveyuki
5a7756b793 🐛 修复了颜色插值为空无法渲染的问题 2025-07-28 17:52:05 +08:00
Nanaloveyuki
b83984d58a 🙈 增加自动上传的自动化文件 2025-07-28 17:42:56 +08:00
Nanaloveyuki
652b6d6d2a 🚀 发布了Beta0.1.1测试版 2025-07-28 17:36:33 +08:00
Nanaloveyuki
9bc021d991 time部分测试成功 2025-07-28 17:36:07 +08:00
Nanaloveyuki
d3d9613acc ️ 提升了time类函数的效率和整洁度 2025-07-28 17:35:51 +08:00
Nanaloveyuki
388b1c2b75 🐛 修复了日志等级错位的问题 2025-07-28 17:06:26 +08:00
Nanaloveyuki
563cecac97 ️ 提升了使用fmt时的性能,优化了算法 2025-07-28 17:06:05 +08:00
Nanaloveyuki
827658beab ️ 提升了config读取写入重置的速度 2025-07-28 16:09:33 +08:00
Nanaloveyuki
9ebd08e5e2 更新tests文件夹下的导入 2025-07-28 15:41:33 +08:00
Nanaloveyuki
3291f19a25 📝 修正了文档代码部分的错误 2025-07-28 12:45:01 +08:00
19 changed files with 671 additions and 308 deletions

4
.gitignore vendored
View File

@@ -1,5 +1,7 @@
# Custom # Custom
*.backup.json *_backup.json
auto_upload.bat
auto_upload.sh
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@@ -8,14 +8,44 @@
py-logiliteal 允许嵌入到其他项目中, 并根据需要自定义日志记录器 py-logiliteal 允许嵌入到其他项目中, 并根据需要自定义日志记录器
同时也支持pip安装 同时也支持pip安装
```bash ```bash
pip install py-logiliteal pip install logiliteal
``` ```
**支持高可扩展的样式** **支持高可扩展的样式**
- 支持使用HEX十六进制颜色代码`<#ffffff>text</>`渲染颜色 - 支持使用HEX十六进制颜色代码`<#ffffff>text</>`渲染颜色
- 支持使用占位符`{placeholder}`渲染变量(可手动扩展) - 支持使用占位符`{placeholder}`渲染变量(可手动扩展)
- 支持部分Html或Markdown语法(如`<b>text</b>`)
- 支持自定义日志格式和日志颜色 - 支持自定义日志格式和日志颜色
Html语法支持:
- `<b>text</b>` 加粗
- `<i>text</i>` 斜体
- `<u>text</u>` 下划线
- `<s>text</s>` 删除线
- `<c>` 清除颜色
- `<br>` 换行
- `</>` Html万用闭合
> **注意!Html嵌套可能会有问题, 不建议过多嵌套**
Markdown语法支持:
- `**text**` 加粗
- `*text*` 斜体
- `__text__` 下划线
- `~~text~~` 删除线
- `[text](url)` 链接
> **注意!Html和Markdown语法虽然可以同时使用,但是不保证所有语法都能正常工作, 建议只使用其中一种**
> **目前语法解析属于测试阶段,欢迎反馈或者提出Pr**
- 目前支持的Html标签: `<b>`, `<i>`, `<u>`, `<s>`, `<c>`, `<br>`, `</>`
- 目前支持的Markdown语法: `**`, `*`, `__`, `~~`, `[text](url)`
- 目前支持的变量:
- `{asctime}` 对应日志完整时间(`config.asctime`)
- `{time}` 对应日志简略时间(`config.time`)
- `{weekday}` 对应日志星期(`config.weekday`)
- `{date}` 对应日志日期(`config.date`)
**支持的Python版本** **支持的Python版本**
- Python 3.13.5 - Python 3.13.5
- Python 3.13.4 - Python 3.13.4
@@ -28,7 +58,7 @@ pip install py-logiliteal
## 安装 ## 安装
暂无安装包, 请使用release发布版或直接clone代码到本地/使用pip安装 暂无安装包, 请使用release发布版或直接clone代码到本地/使用pip安装
```bash ```bash
pip install py-logiliteal pip install logiliteal
``` ```
## 文档 ## 文档
@@ -37,8 +67,8 @@ pip install py-logiliteal
## 示例 ## 示例
```python ```python
# 导入 # 导入
from py_logiliteal import Logger from logiliteal import Logger
# 或 import py_logiliteal(不推荐) # 或 import logiliteal(不推荐)
# 实例化 # 实例化
logger = Logger() logger = Logger()
@@ -51,7 +81,7 @@ logger.warn("这是一条带有前缀的警告日志", prefix="114514")
logger.critical("这是一条带有前缀并且日志等级不同的严重错误日志", prefix="114514", level=55) logger.critical("这是一条带有前缀并且日志等级不同的严重错误日志", prefix="114514", level=55)
# 自定义配置 # 自定义配置
from py_logiliteal import set_config, get_config from logiliteal import set_config, get_config
# 读取配置 # 读取配置
print(get_config("console_format")) print(get_config("console_format"))
# 默认会输出时间、日志等级、日志前缀、日志消息 # 默认会输出时间、日志等级、日志前缀、日志消息

View File

@@ -8,10 +8,13 @@
"enable_file": true, "enable_file": true,
"console_color": true, "console_color": true,
"console_level": "DEBUG", "console_level": "DEBUG",
"console_format": "{asctime} {levelname} | {prefix}{message}", "console_format": "{time} {levelname} | {prefix}{message}",
"console_prefix": "Auto", "console_prefix": "Auto",
"console_encoding": "utf-8", "console_encoding": "utf-8",
"date_format": "%Y-%m-%d %H:%M:%S", "asctime_format": "%Y-%m-%d %H:%M:%S",
"time_format": "%H:%M:%S",
"date_format": "%Y-%m-%d",
"weekday_format": "%A",
"level_name": { "level_name": {
"DEBUG": "DEBUG", "DEBUG": "DEBUG",
"INFO": "INFO", "INFO": "INFO",
@@ -21,9 +24,10 @@
}, },
"level_color": { "level_color": {
"DEBUG": "#c1d5ff", "DEBUG": "#c1d5ff",
"INFO": "#c1f8ff", "INFO": "#c1ffff",
"WARN": "#fff600", "WARN": "#fff600",
"ERRO": "#ffa000", "ERRO": "#ffa000",
"CRIT": "#ff8181" "CRIT": "#ff8181"
} },
"time_color": "#28ffb6"
} }

1
pack.bat Normal file
View File

@@ -0,0 +1 @@
python setup.py sdist bdist_wheel

View File

@@ -6,7 +6,7 @@ long_description = (here / "README.md").read_text(encoding="utf-8")
setup( setup(
name="logiliteal", name="logiliteal",
version="0.1.0", version="0.1.2",
description="简洁,高扩展性,可自定义的日志库 / Simple, high extensibility, and customizable logging library", description="简洁,高扩展性,可自定义的日志库 / Simple, high extensibility, and customizable logging library",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",

View File

@@ -5,7 +5,10 @@ py-logiliteal's config settings, used to set py-logiliteal's global config
# encoding = utf-8 # encoding = utf-8
# python 3.13.5 # python 3.13.5
from .utils import get_config, set_config, reset_config, get_asctime, get_date, get_time, get_weekday, fmt_console, fmt_placeholder, fmt_message, fmt_level_name, set_style from .utils import get_config, set_config, reset_config, create_backup
from .utils import get_asctime, get_date, get_time, get_weekday
from .utils import fmt_console, fmt_placeholder, fmt_message, fmt_level_name
from .utils import set_style, set_color, set_bg_color
from .levels import Logger from .levels import Logger
__all__ = [ __all__ = [
@@ -23,5 +26,6 @@ __all__ = [
"set_style", "set_style",
"set_color", "set_color",
"set_bg_color", "set_bg_color",
"create_backup",
"Logger" # 日志记录器非实例化 "Logger" # 日志记录器非实例化
] ]

View File

@@ -11,118 +11,116 @@ from ..utils.fmt import fmt_file, fmt_message, fmt_console
from ..utils.configs import get_config, set_config from ..utils.configs import get_config, set_config
from ..utils.time import get_asctime from ..utils.time import get_asctime
import pathlib import pathlib
from pathlib import Path
def _get_full_path(file_path, file_name): def _get_full_path(file_path, file_name):
file_path.mkdir(parents=True, exist_ok=True) file_path.mkdir(parents=True, exist_ok=True)
return file_path / file_name return file_path / file_name
file_path = pathlib.Path(get_config("file_path"))
file_name = get_config("file_name")
file_format = get_config("file_format")
file_encoding = get_config("file_encoding")
is_enable_console = get_config("enable_console")
is_enable_file = get_config("enable_file")
class Logger: class Logger:
def __init__(self): def __init__(self):
if pathlib.Path(file_path).exists(): project_root = Path(__file__).parent.parent.parent.parent
if not pathlib.Path(file_path).is_dir(): file_path = project_root / get_config("file_path")
self.warn("日志文件路径不是目录,已自动自动使用默认目录") file_path.mkdir(parents=True, exist_ok=True)
if file_path.exists():
if not file_path.is_dir():
self.warn("日志文件路径不是目录,已自动使用默认目录")
set_config("file_path", "./logs") set_config("file_path", "./logs")
pathlib.Path("./logs").mkdir(parents=True, exist_ok=True) pathlib.Path("./logs").mkdir(parents=True, exist_ok=True)
if _get_full_path(file_path, file_name).exists(): current_file = _get_full_path(file_path, get_config("file_name"))
if current_file.exists():
from os import rename from os import rename
rename(_get_full_path(file_path, file_name), _get_full_path(file_path, f"{get_asctime().replace(':', '-')}.log")) rename(current_file, _get_full_path(file_path, f"{get_asctime().replace(':', '-')}.log"))
self.debug("日志文件已存在,已自动重命名") self.debug("日志文件已存在,已自动重命名")
@staticmethod def _log(self, msg, pf, lvn, no_file: bool = False, no_console: bool = False):
def info(message: Any, prefix: str | None = None, level: int = 20) -> Optional[str]: project_root = Path(__file__).parent.parent.parent.parent
""" file_path = project_root / get_config("file_path")
信息日志 file_path.mkdir(parents=True, exist_ok=True)
Info log file_name = get_config("file_name")
:param message: 消息内容 Message content file_encoding = get_config("file_encoding")
:param prefix: 前缀 Prefix is_enable_file = get_config("enable_file")
:param level: 日志级别 Log level(11~20) is_enable_console = get_config("enable_console")
""" if not no_file and is_enable_file:
if level < 11 or level > 20: try:
return None with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
if is_enable_file: f.write(fmt_file(lvn, fmt_message(msg, no_placeholder=True, no_process=True), pf))
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f: except Exception as e:
f.write(fmt_file(level, fmt_message(message, no_placeholder=True), prefix)) self.error(f"日志写入失败: {str(e)}", no_file=True)
if is_enable_console: if not no_console and is_enable_console:
print(fmt_console(level, fmt_message(message, no_placeholder=True), prefix)) console_output = fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf)
return fmt_console(level, fmt_message(message, no_placeholder=True), prefix) if console_output is not None:
print(console_output)
return fmt_console(lvn, fmt_message(msg, no_placeholder=True), pf)
@staticmethod def debug(self, message: Any, prefix: str | None = None, level: int = 0, no_file: bool = False, no_console: bool = False) -> Optional[str]:
def debug(message: Any, prefix: str | None = None, level: int = 10) -> Optional[str]:
""" """
调试日志 调试日志
Debug log Debug log
:param message: 消息内容 Message content :param message: 消息内容 Message content
:param prefix: 前缀 Prefix :param prefix: 前缀 Prefix
:param level: 日志级别 Log level(0~10) :param level: 日志级别 Log level(0~9)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
""" """
if level < 0 or level > 10: return self._log(message, prefix, level, no_file, no_console)
return None
if is_enable_file:
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
f.write(fmt_file(level, fmt_message(message, no_placeholder=True), prefix))
if is_enable_console:
print(fmt_console(level, fmt_message(message, no_placeholder=True), prefix))
return fmt_console(level, fmt_message(message, no_placeholder=True), prefix)
@staticmethod def info(self, message: Any, prefix: str | None = None, level: int = 10, no_file: bool = False, no_console: bool = False) -> Optional[str]:
def warn(message: Any, prefix: str | None = None, level: int = 31) -> Optional[str]: """
信息日志
Info log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(10~19)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level, no_file, no_console)
def warn(self, message: Any, prefix: str | None = None, level: int = 20, no_file: bool = False, no_console: bool = False) -> Optional[str]:
""" """
警告日志 警告日志
Warn log Warn log
:param message: 消息内容 Message content :param message: 消息内容 Message content
:param prefix: 前缀 Prefix :param prefix: 前缀 Prefix
:param level: 日志级别 Log level(31~40) :param level: 日志级别 Log level(20~29)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
""" """
if level < 30 or level > 40: return self._log(message, prefix, level, no_file, no_console)
return None
if is_enable_file:
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
f.write(fmt_file(level, fmt_message(message, no_placeholder=True), prefix))
if is_enable_console:
print(fmt_console(level, fmt_message(message, no_placeholder=True), prefix))
return fmt_console(level, fmt_message(message, no_placeholder=True), prefix)
@staticmethod def error(self, message: Any, prefix: str | None = None, level: int = 30, no_file: bool = False, no_console: bool = False) -> Optional[str]:
def error(message: Any, prefix: str | None = None, level: int = 41) -> Optional[str]:
""" """
错误日志 错误日志
Error log Error log
:param message: 消息内容 Message content :param message: 消息内容 Message content
:param prefix: 前缀 Prefix :param prefix: 前缀 Prefix
:param level: 日志级别 Log level(41~50) :param level: 日志级别 Log level(30~39)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
""" """
if level < 40 or level > 50: return self._log(message, prefix, level, no_file, no_console)
return None
if is_enable_file: def critical(self, message: Any, prefix: str | None = None, level: int = 40, no_file: bool = False, no_console: bool = False) -> Optional[str]:
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
f.write(fmt_file(level, fmt_message(message, no_placeholder=True), prefix))
if is_enable_console:
print(fmt_console(level, fmt_message(message, no_placeholder=True), prefix))
return fmt_console(level, fmt_message(message, no_placeholder=True), prefix)
@staticmethod
def critical(message: Any, prefix: str | None = None, level: int = 51) -> Optional[str]:
""" """
严重错误日志 严重错误日志
Critical error log Critical error log
:param message: 消息内容 Message content :param message: 消息内容 Message content
:param prefix: 前缀 Prefix :param prefix: 前缀 Prefix
:param level: 日志级别 Log level(51~60) :param level: 日志级别 Log level(40~49)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
""" """
if level < 50 or level > 60: return self._log(message, prefix, level, no_file, no_console)
return None
if is_enable_file:
with open(_get_full_path(file_path, file_name), "a", encoding=file_encoding) as f:
f.write(fmt_file(level, fmt_message(message, no_placeholder=True), prefix))
if is_enable_console:
print(fmt_console(level, fmt_message(message, no_placeholder=True), prefix))
return fmt_console(level, fmt_message(message, no_placeholder=True), prefix)
def log(self, message: Any, prefix: str | None = None, level: int = 50, no_file: bool = False, no_console: bool = False) -> Optional[str]:
"""
自定义日志
Custom log
:param message: 消息内容 Message content
:param prefix: 前缀 Prefix
:param level: 日志级别 Log level(50~59...)
:param no_file: 不写入文件 Do not write to file
:param no_console: 不输出到控制台 Do not output to console
"""
return self._log(message, prefix, level, no_file, no_console)

View File

@@ -5,7 +5,7 @@ Utility functions
# encoding = utf-8 # encoding = utf-8
# python 3.13.5 # python 3.13.5
from .configs import get_config, set_config, reset_config from .configs import get_config, set_config, reset_config, create_backup
from .time import get_asctime, get_date, get_time, get_weekday from .time import get_asctime, get_date, get_time, get_weekday
from .fmt import fmt_console, fmt_placeholder, fmt_message, fmt_level_name from .fmt import fmt_console, fmt_placeholder, fmt_message, fmt_level_name
from .styles import set_color, set_bg_color, set_style from .styles import set_color, set_bg_color, set_style
@@ -14,6 +14,7 @@ __all__ = [
"get_config", "get_config",
"set_config", "set_config",
"reset_config", "reset_config",
"create_backup",
"get_asctime", "get_asctime",
"get_date", "get_date",
"get_time", "get_time",
@@ -24,5 +25,5 @@ __all__ = [
"fmt_level_name", "fmt_level_name",
"set_color", "set_color",
"set_bg_color", "set_bg_color",
"set_style" "set_style",
] ]

View File

@@ -6,7 +6,46 @@ py-logiliteal's config settings, used to set py-logiliteal's global config
# encoding = utf-8 # encoding = utf-8
# python 3.13.5 # python 3.13.5
DEFAULT_CONFIG_PATH = "config.json" import json
from os import remove
import shutil
import os
from pathlib import Path
from typing import Union, Optional, Tuple
from logging import error
def get_config_path():
# 检查环境变量
env_config = os.getenv('LOGILITEAL_CONFIG')
if env_config and os.path.exists(env_config):
return env_config
# 检查当前工作目录
cwd_config = os.path.join(os.getcwd(), 'logger_config.json')
if os.path.exists(cwd_config):
return cwd_config
# 检查XDG配置目录
xdg_config_dir = os.getenv('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))
xdg_config_path = os.path.join(xdg_config_dir, 'logiliteal', 'logger_config.json')
# 创建目录(如果不存在)
if not os.path.exists(os.path.dirname(xdg_config_path)):
try:
os.makedirs(os.path.dirname(xdg_config_path), exist_ok=True)
except Exception as e:
error(f"创建配置目录失败: {e}")
# 回退到项目根目录的配置文件
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))
fallback_config = os.path.join(project_root, 'logger_config.json')
if os.path.exists(fallback_config):
return fallback_config
else:
return xdg_config_path
return xdg_config_path
DEFAULT_CONFIG_PATH = get_config_path()
DEFAULT_CONFIG = { DEFAULT_CONFIG = {
"file_level": "DEBUG", "file_level": "DEBUG",
"file_name": "latest.log", "file_name": "latest.log",
@@ -16,18 +55,60 @@ DEFAULT_CONFIG = {
"enable_console": True, "enable_console": True,
"enable_file": True, "enable_file": True,
"console_color": True, "console_color": True,
"console_level": "INFO", "console_level": "DEBUG",
"console_format": "{asctime} {levelname} | {prefix}{message}", "console_format": "{time} {levelname} | {prefix}{message}",
"console_prefix": "Auto", "console_prefix": "Auto",
"console_encoding": "utf-8", "console_encoding": "utf-8",
"date_format": "%Y-%m-%d %H:%M:%S", "asctime_format": "%Y-%m-%d %H:%M:%S",
"time_format": "%H:%M:%S",
"date_format": "%Y-%m-%d",
"weekday_format": "%A",
"level_name": {"DEBUG": "DEBUG", "INFO": "INFO", "WARN": "WARN", "ERRO": "ERRO", "CRIT": "CRIT"}, "level_name": {"DEBUG": "DEBUG", "INFO": "INFO", "WARN": "WARN", "ERRO": "ERRO", "CRIT": "CRIT"},
"level_color": {"DEBUG": "#c1d5ff", "INFO": "#c1ffff", "WARN": "#fff600", "ERRO": "#ffa000", "CRIT": "#ff8181"}, "level_color": {"DEBUG": "#c1d5ff", "INFO": "#c1ffff", "WARN": "#fff600", "ERRO": "#ffa000", "CRIT": "#ff8181"},
"time_color": "#28ffb6",
} }
from typing import Union, Optional g_config_cache = None
from logging import error, info g_config_mtime = 0
import json
def create_backup(config_path: Path) -> Tuple[bool, str]:
"""
创建配置文件备份
:param config_path: 配置文件路径
:param backup_prefix: 备份文件前缀
:return: (是否成功, 备份路径或错误信息)
"""
try:
if not config_path.exists():
return False, f"配置文件不存在: {config_path}"
backup_path = config_path.parent / f"logger_config_backup.json"
if backup_path.exists():
remove(backup_path)
shutil.copy2(config_path, backup_path)
return True, str(backup_path)
except PermissionError:
return False, f"权限不足,无法创建备份: {config_path}"
except Exception as e:
return False, f"备份失败: {str(e)}"
def handle_config_exceptions(func):
"""
配置操作异常处理装饰器
"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except json.JSONDecodeError as e:
error(f"配置文件格式错误: {e}")
return False, f"配置文件格式错误: {str(e)}"
except PermissionError:
error(f"没有权限操作配置文件: {DEFAULT_CONFIG_PATH}")
return False, "没有权限操作配置文件"
except Exception as e:
error(f"配置操作失败: {e}")
return False, f"配置操作失败: {str(e)}"
return wrapper
def get_config(select: str = None) -> Union[dict, str, bool, int, None]: def get_config(select: str = None) -> Union[dict, str, bool, int, None]:
""" """
@@ -35,27 +116,26 @@ def get_config(select: str = None) -> Union[dict, str, bool, int, None]:
:param select: 配置项名称 Config item name :param select: 配置项名称 Config item name
:return: 配置项值 Config item value :return: 配置项值 Config item value
""" """
try: global g_config_cache, g_config_mtime
with open(DEFAULT_CONFIG_PATH, "r", encoding="utf-8") as f: config_path = Path(DEFAULT_CONFIG_PATH)
config = json.load(f)
if select:
return config[select]
return config
except (FileNotFoundError, json.JSONDecodeError):
with open(DEFAULT_CONFIG_PATH, "w", encoding="utf-8") as f:
json.dump(DEFAULT_CONFIG, f, indent=4)
if select:
return DEFAULT_CONFIG[select]
return DEFAULT_CONFIG
except KeyError as e:
error(f"配置项 '{select}' 不存在")
return None
except Exception as e:
error(f"读取配置文件失败: {e}")
return None
import shutil if config_path.exists():
from pathlib import Path current_mtime = config_path.stat().st_mtime
if current_mtime != g_config_mtime or g_config_cache is None:
with open(config_path, "r", encoding="utf-8") as f:
g_config_cache = json.load(f)
g_config_mtime = current_mtime
else:
# 确保目录存在
config_path.parent.mkdir(parents=True, exist_ok=True)
with open(config_path, "w", encoding="utf-8") as f:
json.dump(DEFAULT_CONFIG, f, indent=4)
g_config_cache = DEFAULT_CONFIG
g_config_mtime = config_path.stat().st_mtime
if select:
return g_config_cache.get(select)
return g_config_cache
def set_config(select: str, value: Union[dict, str, bool, int, None]) -> tuple[bool, Optional[str]]: def set_config(select: str, value: Union[dict, str, bool, int, None]) -> tuple[bool, Optional[str]]:
""" """
@@ -64,75 +144,49 @@ def set_config(select: str, value: Union[dict, str, bool, int, None]) -> tuple[b
:param value: 配置项值 Config item value :param value: 配置项值 Config item value
:return: (设置是否成功, 消息) (Set success or not, message) :return: (设置是否成功, 消息) (Set success or not, message)
""" """
try: config_path = Path(DEFAULT_CONFIG_PATH)
config_path = Path(DEFAULT_CONFIG_PATH)
backup_path = Path(__file__).parent / "configs.backup.json"
if not config_path.exists():
with open(config_path, "w", encoding="utf-8") as f:
json.dump(DEFAULT_CONFIG, f, indent=4)
shutil.copy2(config_path, backup_path)
with open(DEFAULT_CONFIG_PATH, "r+", encoding="utf-8") as f:
config = json.load(f)
config[select] = value
f.seek(0)
json.dump(config, f, indent=4)
f.truncate()
with open(DEFAULT_CONFIG_PATH, "r", encoding="utf-8") as f:
verify_config = json.load(f)
if verify_config.get(select) != value:
error(f"配置项 '{select}' 设置失败,值为 {value}")
shutil.move(backup_path, DEFAULT_CONFIG_PATH)
return False, f"配置项 '{select}' 设置失败,已恢复备份"
Path(backup_path).unlink(missing_ok=True)
return True, f"配置项 '{select}' 设置成功"
except json.JSONDecodeError as e: if not config_path.exists():
error(f"配置文件格式错误: {e}") with open(config_path, "w", encoding="utf-8") as f:
return False, f"配置文件格式错误: {str(e)}" json.dump(DEFAULT_CONFIG, f, indent=4)
except PermissionError:
error(f"没有权限操作配置文件: {DEFAULT_CONFIG_PATH}")
return False, "没有权限操作配置文件"
except Exception as e:
error(f"设置配置文件失败: {e}")
if Path(backup_path).exists(): backup_success, backup_info = create_backup(config_path)
shutil.move(backup_path, DEFAULT_CONFIG_PATH) if not backup_success:
return False, f"设置失败,已恢复备份: {str(e)}" return False, f"备份失败: {backup_info}"
return False, f"设置配置文件失败: {str(e)}"
@staticmethod with open(config_path, "r+", encoding="utf-8") as f:
config = json.load(f)
config[select] = value
f.seek(0)
json.dump(config, f, indent=4)
f.truncate()
with open(config_path, "r", encoding="utf-8") as f:
verify_config = json.load(f)
if verify_config.get(select) != value:
shutil.move(backup_info, config_path)
return False, f"配置项 '{select}' 设置失败,已恢复备份"
global g_config_cache
g_config_cache = None
return True, f"配置项 '{select}' 设置成功"
@handle_config_exceptions
def reset_config() -> tuple[bool, Optional[str]]: def reset_config() -> tuple[bool, Optional[str]]:
""" """
重置配置信息 Reset config info 重置配置信息 Reset config info
:return: (重置是否成功, 消息) (Reset success or not, message) :return: (重置是否成功, 消息) (Reset success or not, message)
""" """
try: config_path = Path(DEFAULT_CONFIG_PATH)
config_path = Path(DEFAULT_CONFIG_PATH)
if not config_path.exists():
with open(config_path, "w", encoding="utf-8") as f:
json.dump(DEFAULT_CONFIG, f, indent=4)
return True, "配置文件不存在,已创建默认配置"
from .time import get_asctime if config_path.exists():
timestamp = get_asctime() backup_success, backup_info = create_backup(config_path)
backup_path = f"{DEFAULT_CONFIG_PATH}_{timestamp}.backup.json" if not backup_success:
backup_path = backup_path.replace(":", "-") return False, f"备份失败: {backup_info}"
shutil.copy2(DEFAULT_CONFIG_PATH, backup_path)
with open(DEFAULT_CONFIG_PATH, "w", encoding="utf-8") as f: with open(config_path, "w", encoding="utf-8") as f:
json.dump(DEFAULT_CONFIG, f, indent=4) json.dump(DEFAULT_CONFIG, f, indent=4)
return True
except PermissionError: global g_config_cache
error(f"权限不足,无法操作配置文件: {DEFAULT_CONFIG_PATH}") g_config_cache = None
return False, "权限不足,无法重置配置" return True, "配置已重置为默认值"
except json.JSONDecodeError:
error("配置文件格式错误,无法解析")
return False, "配置文件格式错误,无法重置"
except Exception as e:
error(f"重置配置文件失败: {e}")
return False, f"重置配置失败: {str(e)}"

View File

@@ -7,10 +7,23 @@ py-logiliteal's formatter, used to format log output
# python 3.13.5 # python 3.13.5
from .configs import get_config from .configs import get_config
from typing import Any, Optional, Union from typing import Any, Optional
from .time import get_asctime, get_time, get_weekday, get_date from .time import get_asctime, get_time, get_weekday, get_date
from .styles import set_color, set_bg_color from .styles import set_color, set_bg_color
import re from .regex import (
process_links,
process_markdown_formats,
process_html_styles,
process_special_tags,
process_color_formatting
)
from .placeholder import process_placeholder, SafeDict
if get_config("time_color") is None:
time_color = "#28ffb6"
else:
time_color = get_config("time_color")
def fmt_level(level: str) -> int: def fmt_level(level: str) -> int:
""" """
@@ -20,15 +33,14 @@ def fmt_level(level: str) -> int:
:return: 格式化后的日志级别 Formatted log level :return: 格式化后的日志级别 Formatted log level
""" """
level_map = { level_map = {
"DEBUG": 10, "DEBUG": 0,
"INFO": 20, "INFO": 10,
"WARN": 30, "WARN": 20,
"ERRO": 40, "ERRO": 30,
"CRIT": 50, "CRIT": 40,
"UNKN": 50
} }
if level == "UNKN": return level_map.get(level.upper(), 50)
return -1
return level_map.get(level.upper(), 0)
def fmt_level_number(level: int) -> str: def fmt_level_number(level: int) -> str:
""" """
@@ -37,15 +49,15 @@ def fmt_level_number(level: int) -> str:
:param level: 日志级别数字 Log level number :param level: 日志级别数字 Log level number
:return: 格式化后的日志级别 Formatted log level :return: 格式化后的日志级别 Formatted log level
""" """
if level <= 10 and level >= 0: if level < 10:
return "DEBUG" return "DEBUG"
elif level <= 20 and level > 10: elif level < 20:
return "INFO" return "INFO"
elif level <= 30 and level > 20: elif level < 30:
return "WARN" return "WARN"
elif level <= 40 and level > 30: elif level < 40:
return "ERRO" return "ERRO"
elif level <= 50 and level > 40: elif level < 50:
return "CRIT" return "CRIT"
else: else:
return "UNKN" return "UNKN"
@@ -57,29 +69,28 @@ def fmt_placeholder(message: Any, use_date_color: bool = True) -> str:
:param message: 消息内容 Message content :param message: 消息内容 Message content
:return: 格式化后的消息 Formatted message :return: 格式化后的消息 Formatted message
""" """
class SafeDict(dict):
def __missing__(self, key):
return f'{{{key}}}'
if not isinstance(message, str): if not isinstance(message, str):
message = str(message) message = str(message)
if use_date_color:
message = message.format_map(SafeDict(
asctime = set_color(get_asctime(),"#28ffb6"),
time = set_color(get_time(),"#28ffb6"),
weekday = set_color(get_weekday(),"#28ffb6"),
date = set_color(get_date(),"#28ffb6")
))
else:
message = message.format_map(SafeDict(
asctime = get_asctime(),
time = get_time(),
weekday = get_weekday(),
date = get_date(),
))
return message
def fmt_message(message: Any, no_placeholder: bool = False, no_color: bool = False) -> str: context = {
"asctime": set_color(get_asctime(), time_color) if use_date_color else get_asctime(),
"time": set_color(get_time(), time_color) if use_date_color else get_time(),
"weekday": set_color(get_weekday(), time_color) if use_date_color else get_weekday(),
"date": set_color(get_date(), time_color) if use_date_color else get_date(),
}
return process_placeholder(message, context)
def fmt_message(
message: Any,
no_placeholder: bool = False,
no_style: bool = False,
no_process: bool = False,
no_tags: bool = False,
no_links: bool = False,
no_markdown: bool = False,
no_html: bool = False,
) -> str:
""" """
格式化消息内容 格式化消息内容
Format message content Format message content
@@ -87,55 +98,29 @@ def fmt_message(message: Any, no_placeholder: bool = False, no_color: bool = Fal
:return: 格式化后的消息 Formatted message :return: 格式化后的消息 Formatted message
""" """
def process_color_tags(msg: str) -> str: def process_color_tags(msg: str, no_process: bool = False) -> str:
stack = [] processed = process_color_formatting(
result = [] process_special_tags(
current_content = [] process_html_styles(
last_end = 0 process_markdown_formats(
pattern = re.compile(r'(<#([0-9a-fA-F]{6})>|</>)') process_links(msg, no_links),
no_markdown
for match in pattern.finditer(msg): ),
text_between = msg[last_end:match.start()] no_html
if stack: ),
current_content.append(text_between) no_tags
else: ),
result.append(text_between) no_process
)
last_end = match.end() return processed
tag = match.group(1) processed_message = str(message)
if not no_placeholder:
if tag.startswith('<#'): processed_message = fmt_placeholder(processed_message)
color_code = match.group(2) if not no_style:
stack.append(color_code) processed_message = process_color_tags(processed_message)
else: if no_process:
if stack: return message
color = stack.pop() return processed_message
colored_content = set_color(''.join(current_content), f'#{color}')
result.append(colored_content)
current_content = []
else:
result.append(tag)
remaining_text = msg[last_end:]
if stack:
current_content.append(remaining_text)
else:
result.append(remaining_text)
for color in reversed(stack):
result.append(f'<#{color}>')
result.append(''.join(current_content))
current_content = []
return ''.join(result)
if no_color:
processed_message = str(message)
else:
processed_message = process_color_tags(str(message))
if no_placeholder:
return processed_message
else:
return process_color_tags(fmt_placeholder(processed_message)) if not no_color else fmt_placeholder(processed_message)
def fmt_level_name(level_name: str) -> str: def fmt_level_name(level_name: str) -> str:
if get_config("console_color") != True: if get_config("console_color") != True:
@@ -161,14 +146,12 @@ def fmt_console(level: int, message: Any, prefix: str | None = None) -> Optional
:param message: 消息内容 Message content :param message: 消息内容 Message content
:return: 格式化后的消息 Formatted message :return: 格式化后的消息 Formatted message
""" """
cl = get_config("console_level") console_level = get_config("console_level")
fmt = get_config("console_format") if level != -1 and level < fmt_level(console_level):
if fmt_level(cl) > level:
return None return None
if prefix is None: fmt = get_config("console_format")
prefix = "" prefix = prefix or ""
fmt = fmt_placeholder(fmt) return fmt_placeholder(fmt).format(
return fmt.format(
levelname = fmt_level_name(fmt_level_number(level)), levelname = fmt_level_name(fmt_level_number(level)),
prefix = fmt_message(prefix, no_placeholder=True), prefix = fmt_message(prefix, no_placeholder=True),
message = fmt_message(message, no_placeholder=True) message = fmt_message(message, no_placeholder=True)
@@ -184,13 +167,14 @@ def fmt_file(level: int, message: Any, prefix: str | None = None) -> Optional[st
""" """
fl = get_config("file_level") fl = get_config("file_level")
fmt = get_config("file_format") fmt = get_config("file_format")
if fmt_level(fl) > level: if level != -1 and level < fmt_level(fl):
return None return None
if prefix is None: if prefix is None:
prefix = "" prefix = ""
fmt = fmt_placeholder(fmt, use_date_color=False) fmt = fmt_placeholder(fmt, use_date_color=False)
return f"{fmt.format( return f"{fmt.format(
levelname = fmt_level_number(level), levelname = fmt_level_number(level),
prefix = fmt_message(prefix, no_placeholder=True, no_color=True), prefix = fmt_message(prefix, no_placeholder=True, no_style=True),
message = fmt_message(message, no_placeholder=True, no_color=True) message = fmt_message(message, no_placeholder=True, no_style=True)
)}\n" )}\n"

View File

@@ -0,0 +1,62 @@
"""
占位符处理工具
"""
from typing import Dict, Any, Optional
import re
class SafeDict(Dict[str, Any]):
"""安全的字典类,用于处理缺失键的占位符替换"""
def __missing__(self, key: str) -> str:
return f"{{{key}}}"
def process_placeholder(text: str, context: Optional[Dict[str, Any]] = None) -> str:
"""处理文本中的占位符替换
Args:
text: 包含占位符的原始文本
context: 用于替换占位符的上下文字典
Returns:
替换后的文本
"""
if not context:
return text
# 处理简单占位符 {key}
safe_context = SafeDict(**context)
text = text.format_map(safe_context)
# 处理条件占位符 {{if condition}}content{{endif}}
def replace_condition(match):
condition = match.group(1).strip()
content = match.group(2).strip()
# 简单条件解析仅支持key存在性检查
if condition.startswith('!'):
key = condition[1:].strip()
return '' if key in context else content
return content if condition in context else ''
text = re.sub(r'\{\{if\s+(.*?)\}\}([\s\S]*?)\{\{endif\}\}', replace_condition, text, flags=re.IGNORECASE)
# 处理循环占位符 {{for item in list}}content{{endfor}}
def replace_loop(match):
items = match.group(1).strip().split(' in ')
if len(items) != 2:
return match.group(0)
item_name, list_name = items
content = match.group(2).strip()
if list_name not in context or not isinstance(context[list_name], list):
return ''
result = []
for item in context[list_name]:
item_context = {item_name: item}
result.append(process_placeholder(content, item_context))
return ''.join(result)
text = re.sub(r'\{\{for\s+(.*?)\}\}([\s\S]*?)\{\{endfor\}\}', replace_loop, text, flags=re.IGNORECASE)
return text

View File

@@ -0,0 +1,128 @@
"""
正则表达式处理工具
"""
import re
from collections import deque
from .styles import set_color, set_bg_color
def process_links(text: str, no_process: bool = False) -> str:
"""处理链接标签(HTML和Markdown格式)"""
if no_process:
return text
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'<a\s+href="([^"]+)">(.*?)</a>', replace_link, text, flags=re.DOTALL)
text = re.sub(r'<link\s+href="([^"]+)">(.*?)</link>', 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, no_process: bool = False) -> str:
"""处理Markdown格式"""
if no_process:
return text
# 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, no_process: bool = False) -> str:
"""处理HTML样式标签"""
if no_process:
return text
# HTML斜体 <i></i>
text = re.sub(r'<i>([^<]*?)(</i>|$)',
lambda m: '\033[3m' + m.group(1) + '\033[23m', text, flags=re.DOTALL)
# HTML粗体 <b></b>
text = re.sub(r'<b>([^<]*?)</b>',
lambda m: '\033[1m' + m.group(1) + '\033[22m', text)
# HTML下划线 <u></u>
text = re.sub(r'<u>([^<]*?)</u>',
lambda m: '\033[4m' + m.group(1) + '\033[24m', text)
# HTML删除线 <s></s>
text = re.sub(r'<s>([^<]*?)(</s>|$)',
lambda m: '\033[9m' + m.group(1) + '\033[29m', text, flags=re.DOTALL)
return text
def process_special_tags(text: str, no_process: bool = False) -> str:
"""处理特殊标签(换行、重置、段落)"""
if no_process:
return text
text = re.sub(r'<br>', '\n', text)
text = re.sub(r'<c>', '\033[0m', text)
# 处理段落标签
text = re.sub(r'<p>(.*?)</p>', r'\n\033[0m\\g<1>\033[0m\n', text, flags=re.DOTALL)
text = re.sub(r'<p>(.*?)(</p>|$)', r'\n\033[0m\\g<1>\033[0m\n', text, flags=re.DOTALL)
text = re.sub(r'</p>', '\033[0m\n', text)
return text
def process_color_formatting(text: str, no_process: bool = False) -> str:
"""处理颜色标签"""
if no_process:
return text
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:
processed_text = part
processed_text = re.sub(r'<#([0-9a-fA-F]{6})>', lambda m: f'<{set_color(f"#{m.group(1)}")}>', processed_text)
result.append(processed_text)
processed_text = ''.join(result)
processed_text = re.sub(f'{color_pattern}|{close_pattern}', '', processed_text)
processed_text = re.sub(r'[0-9a-fA-F]{6}', '', processed_text)
return processed_text

View File

@@ -8,19 +8,21 @@ py-logiliteal's style tools, used to format log output
from typing import Union, Optional from typing import Union, Optional
def _get_hex_to_ansi(hex_color: str) -> Union[Optional[str], None]: def _get_hex_to_ansi(hex_color: str|None) -> Union[Optional[str], None]:
""" """
将16进制颜色值转换为ANSI转义序列 将16进制颜色值转换为ANSI转义序列
Convert hex color value to ANSI escape sequence Convert hex color value to ANSI escape sequence
:param hex_color: 16进制颜色值 Hex color value :param hex_color: 16进制颜色值 Hex color value
:return: ANSI转义序列 ANSI escape sequence :return: ANSI转义序列 ANSI escape sequence
""" """
if not hex_color:
return ""
if not hex_color.startswith("#"): if not hex_color.startswith("#"):
return None return None
r, g, b = int(hex_color[1:3], 16), int(hex_color[3:5], 16), int(hex_color[5:7], 16) r, g, b = int(hex_color[1:3], 16), int(hex_color[3:5], 16), int(hex_color[5:7], 16)
return f"\033[38;2;{r};{g};{b}m" return f"\033[38;2;{r};{g};{b}m"
def set_color(text: str, color: str) -> str: def set_color(text: str|None, color: str|None) -> str:
""" """
设置文本颜色 设置文本颜色
Set text color Set text color
@@ -28,12 +30,16 @@ def set_color(text: str, color: str) -> str:
:param color: 颜色值 Color value :param color: 颜色值 Color value
:return: 格式化后的文本 Formatted text :return: 格式化后的文本 Formatted text
""" """
if not text:
return ""
if not color:
return text
ansi = _get_hex_to_ansi(color) ansi = _get_hex_to_ansi(color)
if not ansi: if not ansi:
return text return text
return f"{ansi}{text}\033[0m" return f"{ansi}{text}\033[0m"
def set_bg_color(text: str, color: str) -> str: def set_bg_color(text: str|None, color: str|None) -> str:
""" """
设置文本背景颜色 设置文本背景颜色
Set text background color Set text background color
@@ -41,28 +47,39 @@ def set_bg_color(text: str, color: str) -> str:
:param color: 颜色值 Color value :param color: 颜色值 Color value
:return: 格式化后的文本 Formatted text :return: 格式化后的文本 Formatted text
""" """
if not text:
return ""
if not color:
return text
ansi = _get_hex_to_ansi(color) ansi = _get_hex_to_ansi(color)
if not ansi: if not ansi:
return text return text
# 将前景色ANSI代码转换为背景色代码 (38→48)
ansi = ansi.replace("38;", "48;") ansi = ansi.replace("38;", "48;")
return f"{ansi}{text}\033[0m" return f"{ansi}{text}\033[0m"
def set_style(text: str, 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 Set text style
:param text: 文本内容 Text content :param text: 文本内容 Text content
:param bold: 是否加粗 Is bold :param bold: 是否加粗 Is bold
:param italic: 是否斜体 Is italic
:param underline: 是否下划线 Is underline :param underline: 是否下划线 Is underline
:param strikethrough: 是否划线 Is strikethrough
:param reverse: 是否反相 Is reverse :param reverse: 是否反相 Is reverse
:return: 格式化后的文本 Formatted text :return: 格式化后的文本 Formatted text
""" """
if not text:
return ""
ansi = "" ansi = ""
if bold: if bold:
ansi += "\033[1m" ansi += "\033[1m"
if italic:
ansi += "\033[3m"
if underline: if underline:
ansi += "\033[4m" ansi += "\033[4m"
if strikethrough:
ansi += "\033[9m"
if reverse: if reverse:
ansi += "\033[7m" ansi += "\033[7m"
return f"{ansi}{text}\033[0m" return f"{ansi}{text}\033[0m"

View File

@@ -9,22 +9,13 @@ Time utility module, used for time formatting and output, caching time formattin
from datetime import datetime from datetime import datetime
from .configs import get_config from .configs import get_config
cache_time: str = ""
cache_asctime: str = ""
cache_date: str = ""
cache_weekday: str = ""
def get_asctime() -> str: def get_asctime() -> str:
""" """
获取当前时间(YYYY-MM-DD HH:MM:SS),并缓存格式化结果 获取当前时间(YYYY-MM-DD HH:MM:SS),并缓存格式化结果
Get current time(YYYY-MM-DD HH:MM:SS) and cache formatted result Get current time(YYYY-MM-DD HH:MM:SS) and cache formatted result
:return: 格式化后的时间 Formatted time :return: 格式化后的时间 Formatted time
""" """
global cache_asctime return _get_time(get_config("asctime_format"))
if cache_asctime:
return cache_asctime
cache_asctime = datetime.now().strftime(get_config("date_format"))
return cache_asctime
def get_time() -> str: def get_time() -> str:
""" """
@@ -32,24 +23,15 @@ def get_time() -> str:
Get current time(HH:MM:SS) and cache formatted result Get current time(HH:MM:SS) and cache formatted result
:return: 格式化后的时间 Formatted time :return: 格式化后的时间 Formatted time
""" """
global cache_time return _get_time(get_config("time_format"))
if cache_time:
return cache_time
cache_time = datetime.now().strftime("%H:%M:%S")
return cache_time
def get_date() -> str: def get_date() -> str:
""" """
获取当前日期(YYYY-MM-DD),并缓存格式化结果 获取当前日期(YYYY-MM-DD),并缓存格式化结果
获取当前日期(星期几),并缓存格式化结果
Get current date(YYYY-MM-DD) and cache formatted result Get current date(YYYY-MM-DD) and cache formatted result
:return: 格式化后的日期 Formatted date :return: 格式化后的日期 Formatted date
""" """
global cache_date return _get_time(get_config("date_format"))
if cache_date:
return cache_date
cache_date = datetime.now().strftime("%Y-%m-%d")
return cache_date
def get_weekday() -> str: def get_weekday() -> str:
""" """
@@ -57,8 +39,14 @@ def get_weekday() -> str:
Get current date(weekday) and cache formatted result Get current date(weekday) and cache formatted result
:return: 格式化后的星期几 Formatted weekday :return: 格式化后的星期几 Formatted weekday
""" """
global cache_weekday return _get_time(get_config("weekday_format"))
if cache_weekday:
return cache_weekday def _get_time(fmt: str) -> str:
cache_weekday = datetime.now().strftime("%A") """
return cache_weekday 获取当前时间(根据指定格式),并缓存格式化结果
Get current time(based on specified format) and cache formatted result
:param fmt: 时间格式 Time format
:return: 格式化后的时间 Formatted time
"""
cache_time = datetime.now().strftime(fmt)
return cache_time

53
tests/t-color-priority.py Normal file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
测试颜色优先级处理功能
"""
import sys
import os
from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.append(str(project_root))
# 添加项目根目录到Python路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from src.logiliteal.utils.fmt import fmt_message
def test_color_priority():
"""测试颜色优先级"""
print("=== 颜色优先级测试 ===")
# 测试1: 基本颜色标签
test1 = "<#ff0000>红色文本</>"
print(f"基本颜色: {test1}")
print(f"结果: {fmt_message(test1)}")
print()
# 测试2: 嵌套颜色(内层优先级高)
test2 = "<#ff0000>外层<#00ff00>内层</>外层</>"
print(f"嵌套颜色: {test2}")
print(f"结果: {fmt_message(test2)}")
print()
# 测试3: 颜色重叠测试(最后边颜色为主)
test3 = "<#ff0000>颜色重叠<#0000ff>测<#ff00c2>试</></></>"
print(f"颜色重叠: {test3}")
print(f"结果: {fmt_message(test3)}")
print()
# 测试4: 多层嵌套
test4 = "<#ff0000>1<#00ff00>2<#0000ff>3<#ffff00>4</>3</>2</>1</>"
print(f"多层嵌套: {test4}")
print(f"结果: {fmt_message(test4)}")
print()
# 测试5: 未闭合标签(忽略)
test5 = "<#ff0000>未闭合标签"
print(f"未闭合标签: {test5}")
print(f"结果: {fmt_message(test5)}")
print()
if __name__ == "__main__":
test_color_priority()

View File

@@ -4,7 +4,7 @@ from pathlib import Path
project_root = Path(__file__).parent.parent project_root = Path(__file__).parent.parent
sys.path.append(str(project_root)) sys.path.append(str(project_root))
from src.utils.configs import get_config, set_config, reset_config from src.logiliteal.utils.configs import get_config, set_config, reset_config, create_backup
print(f"\n配置更换测试") print(f"\n配置更换测试")
set_config("file_level", "DEBUG") set_config("file_level", "DEBUG")

34
tests/t-fmt-text.py Normal file
View File

@@ -0,0 +1,34 @@
import sys
from pathlib import Path
project_root = Path(__file__).parent.parent
sys.path.append(str(project_root))
from src.logiliteal.levels import Logger
log = Logger()
log.info("<i>html斜体</i>")
log.info("<b>html加粗</b>")
log.info("<u>html下划线</u>")
log.info("<s>html删除线</s>")
log.info("<p>html段落</p>")
log.info("<a href=\"https://www.baidu.com\">html超链接</a>")
log.info("**Markdown加粗**")
log.info("*Markdown斜体*")
log.info("`Markdown代码块`")
log.info("~~Markdown删除线~~\n")
log.info("[md超链接](https://www.baidu.com)")
log.info("--测试<i>重复--")
log.info("<#ff0000>颜色重叠<#0000ff>测<#ff00c2>试</></></>")
log.info("<a href=\"https://www.baidu.com\">超<link href=\"https://www.bing.cn\">链接[重叠](https://www.360.com)</link></a>")
"""
while True:
try:
log.info(get_asctime())
time.sleep(1)
except KeyboardInterrupt:
log.info("测试结束")
break
"""

View File

@@ -4,7 +4,7 @@ from pathlib import Path
project_root = Path(__file__).parent.parent project_root = Path(__file__).parent.parent
sys.path.append(str(project_root)) sys.path.append(str(project_root))
from src.utils.fmt import fmt_level, fmt_level_number, fmt_console, fmt_message, fmt_placeholder from src.logiliteal.utils.fmt import fmt_level, fmt_level_number, fmt_console, fmt_message, fmt_placeholder
print("日志级别测试") print("日志级别测试")
print(fmt_level("DEBUG")) print(fmt_level("DEBUG"))

View File

@@ -4,9 +4,12 @@ from pathlib import Path
project_root = Path(__file__).parent.parent project_root = Path(__file__).parent.parent
sys.path.append(str(project_root)) sys.path.append(str(project_root))
from src import Logger from src.logiliteal import Logger
log = Logger() log = Logger()
log.debug("测试调试日志")
log.info("测试信息日志") log.info("测试信息日志")
log.debug("测试调试日志") log.warn("测试警告日志")
log.error("测试错误日志")
log.critical("测试严重错误日志")