tg-rich-render 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shali10
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: tg-rich-render
3
+ Version: 0.1.0
4
+ Summary: Lightweight Telegram rich table and format converter with zero-dependency CJK alignment.
5
+ Author-email: shali10 <99240403+shali10@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/shali10/tg-rich-render
8
+ Project-URL: Repository, https://github.com/shali10/tg-rich-render
9
+ Project-URL: Issues, https://github.com/shali10/tg-rich-render/issues
10
+ Keywords: telegram,aiogram,telegram-bot,table,markdown,cjk,rich-formatting
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Communications :: Chat
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Dynamic: license-file
25
+
26
+ # tg-rich-render 📊✨
27
+
28
+ > **Zero-dependency, CJK-aware Markdown table & rich format converter for Telegram bots.**
29
+ > 专治 Telegram 机器人表格排版错位、中英混排对不齐、手机端横向溢出变难看代码块的痛点。
30
+
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
32
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
33
+ [![CI Status](https://github.com/shali10/tg-rich-render/actions/workflows/ci.yml/badge.svg)](https://github.com/shali10/tg-rich-render/actions)
34
+
35
+ ---
36
+
37
+ ## 💡 为什么需要 tg-rich-render?
38
+
39
+ Telegram 官方的 MarkdownV2 并不支持标准的 GFM Pipe Table(管道表格)。在日常 Telegram 机器人开发(运维巡检播报、资产统计、行情早报)中,直接发送表格通常会遇到以下问题:
40
+
41
+ 1. **直接报错**:Telegram Bot API 无法解析 `| col | col |` 语法;
42
+ 2. **粗暴丢进代码块**:直接用 ```` 包裹,但在中英汉字、Emoji 混排时由于字符显示宽度(CJK 宽度为 2)导致竖线完全错位,排版歪歪扭扭;
43
+ 3. **窄屏移动端阅读体验差**:宽表格在手机竖屏下横向拉长折行。
44
+
45
+ `tg-rich-render` 提供纯 Python 标准库实现的智能 CJK 宽度对齐与多风格自适应渲染,一行代码即可集成到 **aiogram 3**、**python-telegram-bot** 或任何 HTTP 请求中。
46
+
47
+ ---
48
+
49
+ ## ✨ 核心特性
50
+
51
+ - 🚀 **零外部依赖**:纯 Python 标准库构建(基于 `unicodedata` 模块),即装即用,启动开销 0ms。
52
+ - 📐 **精准 CJK / Emoji 宽度补偿**:完美对齐汉字、日韩文、全角符号与 Emoji 表情,拒绝折线与锯齿。
53
+ - 🎨 **多风格视觉呈现**:
54
+ - `rounded`:现代优雅圆角框线(`╭───┬───╮`),视觉质感拉满。
55
+ - `classic`:经典 ASCII 风格(`+---+---+`)。
56
+ - `clean`:极简流式无竖框风格。
57
+ - `card`:移动端窄屏优先卡片流(键值对展示,杜绝横向滚动)。
58
+ - `html`:Telegram 兼容 HTML `<table>` 格式。
59
+ - 🤖 **主流框架开箱即用**:自带 aiogram 3 与 python-telegram-bot 发送适配器。
60
+ - 💻 **CLI 工具支持**:支持管道输入与终端即时预览。
61
+
62
+ ---
63
+
64
+ ## 📦 快速安装
65
+
66
+ ```bash
67
+ pip install tg-rich-render
68
+ ```
69
+
70
+ 或直接克隆使用:
71
+
72
+ ```bash
73
+ git clone https://github.com/shali10/tg-rich-render.git
74
+ cd tg-rich-render
75
+ pip install -e .
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 🚀 快速上手
81
+
82
+ ### 1. 独立使用(纯 Python)
83
+
84
+ ```python
85
+ from tg_rich_render import render_telegram
86
+
87
+ raw_markdown = """
88
+ # 节点健康巡检
89
+
90
+ | 节点 | 状态 | 延迟 |
91
+ |:---|:---:|---:|
92
+ | 香港CN2 🚀 | 正常 | 15ms |
93
+ | 美国洛杉矶 | 良好 | 135ms |
94
+ | 日本东京 ⚡ | 正常 | 48ms |
95
+
96
+ 巡检完成,无异常节点。
97
+ """
98
+
99
+ # 渲染为 Telegram 优雅圆角等宽表格
100
+ message = render_telegram(raw_markdown, style="rounded")
101
+ print(message)
102
+ ```
103
+
104
+ **输出效果:**
105
+
106
+ ```
107
+ # 节点健康巡检
108
+
109
+ ```
110
+ ╭────────────┬──────┬───────╮
111
+ │ 节点 │ 状态 │ 延迟 │
112
+ ├────────────┼──────┼───────┤
113
+ │ 香港CN2 🚀 │ 正常 │ 15ms │
114
+ │ 美国洛杉矶 │ 良好 │ 135ms │
115
+ │ 日本东京 ⚡ │ 正常 │ 48ms │
116
+ ╰────────────┴──────┴───────╯
117
+ ```
118
+
119
+ 巡检完成,无异常节点。
120
+ ```
121
+
122
+ ---
123
+
124
+ ### 2. 结合 aiogram 3
125
+
126
+ ```python
127
+ from aiogram import Bot
128
+ from tg_rich_render import send_smart_message
129
+
130
+ bot = Bot(token="YOUR_BOT_TOKEN")
131
+
132
+ # 一行代码自适应格式化并发送
133
+ await send_smart_message(
134
+ bot=bot,
135
+ chat_id=12345678,
136
+ text=raw_markdown,
137
+ table_style="rounded"
138
+ )
139
+ ```
140
+
141
+ ---
142
+
143
+ ### 3. 结合 python-telegram-bot
144
+
145
+ ```python
146
+ from telegram import Bot
147
+ from tg_rich_render import send_smart_message
148
+
149
+ bot = Bot(token="YOUR_BOT_TOKEN")
150
+
151
+ await send_smart_message(
152
+ bot=bot,
153
+ chat_id=12345678,
154
+ text=raw_markdown,
155
+ table_style="rounded"
156
+ )
157
+ ```
158
+
159
+ ---
160
+
161
+ ### 4. 命令行(CLI)使用
162
+
163
+ ```bash
164
+ # 转换 Markdown 文件
165
+ tg-rich-render report.md --style rounded
166
+
167
+ # 从终端管道流式转换
168
+ cat summary.md | tg-rich-render --style card
169
+ ```
170
+
171
+ ---
172
+
173
+ ## 🎨 渲染风格展示
174
+
175
+ | 风格名称 | 预览示意 | 适用场景 |
176
+ |:---|:---|:---|
177
+ | **rounded** (默认) | `╭─┬─╮\n│A│B│\n╰─┴─╯` | PC 端与大屏客户端,视觉质感极高 |
178
+ | **classic** | `+-+-+\n|A|B|\n+-+-+` | 通用等宽终端、标准日志输出 |
179
+ | **clean** | `A B\n─ ─\n1 2` | 极简通知、紧凑监控通知 |
180
+ | **card** | `📌 节点\n • 延迟: 15ms` | 移动端窄屏、列数较多的宽表格 |
181
+ | **html** | `<table>...</table>` | Telegram WebApp 或特定富文本容器 |
182
+
183
+ ---
184
+
185
+ ## 🧪 单元测试
186
+
187
+ 项目自带完整的自动化测试集(覆盖 CJK 宽度、对齐算法、长文本解析与适配器):
188
+
189
+ ```bash
190
+ python3 -m unittest discover -s tests
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 📄 开源许可
196
+
197
+ 本项目基于 [MIT License](LICENSE) 开源。
@@ -0,0 +1,172 @@
1
+ # tg-rich-render 📊✨
2
+
3
+ > **Zero-dependency, CJK-aware Markdown table & rich format converter for Telegram bots.**
4
+ > 专治 Telegram 机器人表格排版错位、中英混排对不齐、手机端横向溢出变难看代码块的痛点。
5
+
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
8
+ [![CI Status](https://github.com/shali10/tg-rich-render/actions/workflows/ci.yml/badge.svg)](https://github.com/shali10/tg-rich-render/actions)
9
+
10
+ ---
11
+
12
+ ## 💡 为什么需要 tg-rich-render?
13
+
14
+ Telegram 官方的 MarkdownV2 并不支持标准的 GFM Pipe Table(管道表格)。在日常 Telegram 机器人开发(运维巡检播报、资产统计、行情早报)中,直接发送表格通常会遇到以下问题:
15
+
16
+ 1. **直接报错**:Telegram Bot API 无法解析 `| col | col |` 语法;
17
+ 2. **粗暴丢进代码块**:直接用 ```` 包裹,但在中英汉字、Emoji 混排时由于字符显示宽度(CJK 宽度为 2)导致竖线完全错位,排版歪歪扭扭;
18
+ 3. **窄屏移动端阅读体验差**:宽表格在手机竖屏下横向拉长折行。
19
+
20
+ `tg-rich-render` 提供纯 Python 标准库实现的智能 CJK 宽度对齐与多风格自适应渲染,一行代码即可集成到 **aiogram 3**、**python-telegram-bot** 或任何 HTTP 请求中。
21
+
22
+ ---
23
+
24
+ ## ✨ 核心特性
25
+
26
+ - 🚀 **零外部依赖**:纯 Python 标准库构建(基于 `unicodedata` 模块),即装即用,启动开销 0ms。
27
+ - 📐 **精准 CJK / Emoji 宽度补偿**:完美对齐汉字、日韩文、全角符号与 Emoji 表情,拒绝折线与锯齿。
28
+ - 🎨 **多风格视觉呈现**:
29
+ - `rounded`:现代优雅圆角框线(`╭───┬───╮`),视觉质感拉满。
30
+ - `classic`:经典 ASCII 风格(`+---+---+`)。
31
+ - `clean`:极简流式无竖框风格。
32
+ - `card`:移动端窄屏优先卡片流(键值对展示,杜绝横向滚动)。
33
+ - `html`:Telegram 兼容 HTML `<table>` 格式。
34
+ - 🤖 **主流框架开箱即用**:自带 aiogram 3 与 python-telegram-bot 发送适配器。
35
+ - 💻 **CLI 工具支持**:支持管道输入与终端即时预览。
36
+
37
+ ---
38
+
39
+ ## 📦 快速安装
40
+
41
+ ```bash
42
+ pip install tg-rich-render
43
+ ```
44
+
45
+ 或直接克隆使用:
46
+
47
+ ```bash
48
+ git clone https://github.com/shali10/tg-rich-render.git
49
+ cd tg-rich-render
50
+ pip install -e .
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 🚀 快速上手
56
+
57
+ ### 1. 独立使用(纯 Python)
58
+
59
+ ```python
60
+ from tg_rich_render import render_telegram
61
+
62
+ raw_markdown = """
63
+ # 节点健康巡检
64
+
65
+ | 节点 | 状态 | 延迟 |
66
+ |:---|:---:|---:|
67
+ | 香港CN2 🚀 | 正常 | 15ms |
68
+ | 美国洛杉矶 | 良好 | 135ms |
69
+ | 日本东京 ⚡ | 正常 | 48ms |
70
+
71
+ 巡检完成,无异常节点。
72
+ """
73
+
74
+ # 渲染为 Telegram 优雅圆角等宽表格
75
+ message = render_telegram(raw_markdown, style="rounded")
76
+ print(message)
77
+ ```
78
+
79
+ **输出效果:**
80
+
81
+ ```
82
+ # 节点健康巡检
83
+
84
+ ```
85
+ ╭────────────┬──────┬───────╮
86
+ │ 节点 │ 状态 │ 延迟 │
87
+ ├────────────┼──────┼───────┤
88
+ │ 香港CN2 🚀 │ 正常 │ 15ms │
89
+ │ 美国洛杉矶 │ 良好 │ 135ms │
90
+ │ 日本东京 ⚡ │ 正常 │ 48ms │
91
+ ╰────────────┴──────┴───────╯
92
+ ```
93
+
94
+ 巡检完成,无异常节点。
95
+ ```
96
+
97
+ ---
98
+
99
+ ### 2. 结合 aiogram 3
100
+
101
+ ```python
102
+ from aiogram import Bot
103
+ from tg_rich_render import send_smart_message
104
+
105
+ bot = Bot(token="YOUR_BOT_TOKEN")
106
+
107
+ # 一行代码自适应格式化并发送
108
+ await send_smart_message(
109
+ bot=bot,
110
+ chat_id=12345678,
111
+ text=raw_markdown,
112
+ table_style="rounded"
113
+ )
114
+ ```
115
+
116
+ ---
117
+
118
+ ### 3. 结合 python-telegram-bot
119
+
120
+ ```python
121
+ from telegram import Bot
122
+ from tg_rich_render import send_smart_message
123
+
124
+ bot = Bot(token="YOUR_BOT_TOKEN")
125
+
126
+ await send_smart_message(
127
+ bot=bot,
128
+ chat_id=12345678,
129
+ text=raw_markdown,
130
+ table_style="rounded"
131
+ )
132
+ ```
133
+
134
+ ---
135
+
136
+ ### 4. 命令行(CLI)使用
137
+
138
+ ```bash
139
+ # 转换 Markdown 文件
140
+ tg-rich-render report.md --style rounded
141
+
142
+ # 从终端管道流式转换
143
+ cat summary.md | tg-rich-render --style card
144
+ ```
145
+
146
+ ---
147
+
148
+ ## 🎨 渲染风格展示
149
+
150
+ | 风格名称 | 预览示意 | 适用场景 |
151
+ |:---|:---|:---|
152
+ | **rounded** (默认) | `╭─┬─╮\n│A│B│\n╰─┴─╯` | PC 端与大屏客户端,视觉质感极高 |
153
+ | **classic** | `+-+-+\n|A|B|\n+-+-+` | 通用等宽终端、标准日志输出 |
154
+ | **clean** | `A B\n─ ─\n1 2` | 极简通知、紧凑监控通知 |
155
+ | **card** | `📌 节点\n • 延迟: 15ms` | 移动端窄屏、列数较多的宽表格 |
156
+ | **html** | `<table>...</table>` | Telegram WebApp 或特定富文本容器 |
157
+
158
+ ---
159
+
160
+ ## 🧪 单元测试
161
+
162
+ 项目自带完整的自动化测试集(覆盖 CJK 宽度、对齐算法、长文本解析与适配器):
163
+
164
+ ```bash
165
+ python3 -m unittest discover -s tests
166
+ ```
167
+
168
+ ---
169
+
170
+ ## 📄 开源许可
171
+
172
+ 本项目基于 [MIT License](LICENSE) 开源。
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tg-rich-render"
7
+ version = "0.1.0"
8
+ description = "Lightweight Telegram rich table and format converter with zero-dependency CJK alignment."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [
12
+ { name = "shali10", email = "99240403+shali10@users.noreply.github.com" }
13
+ ]
14
+ keywords = ["telegram", "aiogram", "telegram-bot", "table", "markdown", "cjk", "rich-formatting"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.9",
21
+ "Programming Language :: Python :: 3.10",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Topic :: Communications :: Chat",
25
+ "Topic :: Software Development :: Libraries :: Python Modules",
26
+ ]
27
+ requires-python = ">=3.8"
28
+ dependencies = []
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/shali10/tg-rich-render"
32
+ Repository = "https://github.com/shali10/tg-rich-render"
33
+ Issues = "https://github.com/shali10/tg-rich-render/issues"
34
+
35
+ [project.scripts]
36
+ tg-rich-render = "tg_rich_render.cli:main"
37
+
38
+ [tool.setuptools.packages.find]
39
+ where = ["."]
40
+ include = ["tg_rich_render*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,29 @@
1
+ import unittest
2
+ from tg_rich_render.adapter import smart_format_payload
3
+
4
+ MD_SAMPLE = """
5
+ | 维度 | 得分 |
6
+ |---|---|
7
+ | 速度 | 98 |
8
+ """
9
+
10
+ class TestAdapter(unittest.TestCase):
11
+ def test_smart_format_payload_default():
12
+ payload = smart_format_payload(MD_SAMPLE)
13
+ self.assertIsNone(payload["parse_mode"])
14
+ self.assertIn("╭", payload["text"])
15
+ self.assertIn("```", payload["text"])
16
+
17
+ def test_smart_format_payload_html(self):
18
+ payload = smart_format_payload(MD_SAMPLE, prefer_html=True)
19
+ self.assertEqual(payload["parse_mode"], "HTML")
20
+ self.assertIn("<table>", payload["text"])
21
+
22
+ def test_smart_format_payload_default(self):
23
+ payload = smart_format_payload(MD_SAMPLE)
24
+ self.assertIsNone(payload["parse_mode"])
25
+ self.assertIn("╭", payload["text"])
26
+ self.assertIn("```", payload["text"])
27
+
28
+ if __name__ == '__main__':
29
+ unittest.main()
@@ -0,0 +1,31 @@
1
+ import unittest
2
+ from tg_rich_render.cjk import get_display_width, pad_cjk, strip_ansi
3
+
4
+ class TestCJK(unittest.TestCase):
5
+ def test_strip_ansi(self):
6
+ colored = "\x1b[31mRed Text\x1b[0m"
7
+ self.assertEqual(strip_ansi(colored), "Red Text")
8
+ self.assertEqual(get_display_width(colored), 8)
9
+
10
+ def test_get_display_width(self):
11
+ self.assertEqual(get_display_width("hello"), 5)
12
+ self.assertEqual(get_display_width("你好"), 4)
13
+ self.assertEqual(get_display_width("Hello, 世界!"), 13)
14
+
15
+ def test_pad_cjk(self):
16
+ # Left alignment
17
+ padded = pad_cjk("测试", 6, align='left')
18
+ self.assertEqual(padded, "测试 ")
19
+ self.assertEqual(get_display_width(padded), 6)
20
+
21
+ # Right alignment
22
+ padded_r = pad_cjk("测试", 6, align='right')
23
+ self.assertEqual(padded_r, " 测试")
24
+ self.assertEqual(get_display_width(padded_r), 6)
25
+
26
+ # Center alignment
27
+ padded_c = pad_cjk("A", 5, align='center')
28
+ self.assertEqual(padded_c, " A ")
29
+
30
+ if __name__ == '__main__':
31
+ unittest.main()
@@ -0,0 +1,41 @@
1
+ import unittest
2
+ from tg_rich_render.parser import render_telegram, extract_tables_and_text
3
+
4
+ DOC = """# 系统巡检报告
5
+
6
+ 以下是当前节点健康度:
7
+
8
+ | 节点 | CPU | 内存 |
9
+ |---|---|---|
10
+ | US-East | 14% | 42% |
11
+ | HK-01 | 28% | 65% |
12
+
13
+ ```bash
14
+ # 这里是普通代码块,不应被误判为表格
15
+ | not | a | table |
16
+ ```
17
+
18
+ 请及时关注高负载节点。
19
+ """
20
+
21
+ class TestParser(unittest.TestCase):
22
+ def test_extract_tables_and_text(self):
23
+ blocks = extract_tables_and_text(DOC)
24
+ types = [b[0] for b in blocks]
25
+ self.assertIn('table', types)
26
+ self.assertEqual(types.count('table'), 1)
27
+
28
+ def test_render_telegram_rounded(self):
29
+ out = render_telegram(DOC, style='rounded')
30
+ self.assertIn("```", out)
31
+ self.assertIn("╭", out)
32
+ self.assertIn("系统巡检报告", out)
33
+ self.assertIn("请及时关注高负载节点", out)
34
+
35
+ def test_render_telegram_card(self):
36
+ out = render_telegram(DOC, style='card')
37
+ self.assertIn("📌 US-East", out)
38
+ self.assertIn("• CPU: 14%", out)
39
+
40
+ if __name__ == '__main__':
41
+ unittest.main()
@@ -0,0 +1,51 @@
1
+ import unittest
2
+ from tg_rich_render.table import Table
3
+
4
+ RAW_TABLE = """
5
+ | 服务 | 状态 | 延迟 |
6
+ |:---|:---:|---:|
7
+ | API 网关 | 运行中 | 12ms |
8
+ | 数据库 | 正常 | 2ms |
9
+ """
10
+
11
+ class TestTable(unittest.TestCase):
12
+ def test_table_parsing(self):
13
+ table = Table.from_markdown(RAW_TABLE)
14
+ self.assertIsNotNone(table)
15
+ self.assertEqual(table.headers, ["服务", "状态", "延迟"])
16
+ self.assertEqual(len(table.rows), 2)
17
+ self.assertEqual(table.alignments, ["left", "center", "right"])
18
+
19
+ def test_table_render_rounded(self):
20
+ table = Table.from_markdown(RAW_TABLE)
21
+ rendered = table.render_rounded()
22
+ self.assertIn("╭", rendered)
23
+ self.assertIn("╰", rendered)
24
+ self.assertIn("API 网关", rendered)
25
+
26
+ def test_table_render_classic(self):
27
+ table = Table.from_markdown(RAW_TABLE)
28
+ rendered = table.render_classic()
29
+ self.assertIn("+", rendered)
30
+ self.assertIn("|", rendered)
31
+
32
+ def test_table_render_clean(self):
33
+ table = Table.from_markdown(RAW_TABLE)
34
+ rendered = table.render_clean()
35
+ self.assertIn("─", rendered)
36
+ self.assertIn("API 网关", rendered)
37
+
38
+ def test_table_render_card(self):
39
+ table = Table.from_markdown(RAW_TABLE)
40
+ rendered = table.render_card()
41
+ self.assertIn("📌 API 网关", rendered)
42
+ self.assertIn("• 状态: 运行中", rendered)
43
+
44
+ def test_table_render_html(self):
45
+ table = Table.from_markdown(RAW_TABLE)
46
+ rendered = table.render_html()
47
+ self.assertIn("<table>", rendered)
48
+ self.assertIn("<th>服务</th>", rendered)
49
+
50
+ if __name__ == '__main__':
51
+ unittest.main()
@@ -0,0 +1,22 @@
1
+ """tg-rich-render: Lightweight Telegram rich table and format converter.
2
+
3
+ Solves the common pain point of ugly, misaligned tables in Telegram bots.
4
+ Provides zero-dependency CJK alignment and multi-style table formatting.
5
+ """
6
+
7
+ from tg_rich_render.cjk import get_display_width, pad_cjk
8
+ from tg_rich_render.table import Table
9
+ from tg_rich_render.parser import render_telegram, extract_tables_and_text, TableStyle
10
+ from tg_rich_render.adapter import smart_format_payload, send_smart_message
11
+
12
+ __version__ = "0.1.0"
13
+ __all__ = [
14
+ "Table",
15
+ "TableStyle",
16
+ "get_display_width",
17
+ "pad_cjk",
18
+ "render_telegram",
19
+ "extract_tables_and_text",
20
+ "smart_format_payload",
21
+ "send_smart_message",
22
+ ]
@@ -0,0 +1,50 @@
1
+ """Adapters and helper bindings for aiogram 3 and python-telegram-bot.
2
+ """
3
+
4
+ from typing import Any, Optional, Dict, Literal
5
+ from tg_rich_render.parser import render_telegram, TableStyle
6
+
7
+ def smart_format_payload(
8
+ text: str,
9
+ prefer_html: bool = False,
10
+ table_style: TableStyle = 'rounded'
11
+ ) -> Dict[str, Any]:
12
+ """Prepare payload dictionary with formatted text and appropriate parse_mode."""
13
+ if prefer_html:
14
+ formatted = render_telegram(text, style='html')
15
+ return {
16
+ "text": formatted,
17
+ "parse_mode": "HTML"
18
+ }
19
+ else:
20
+ formatted = render_telegram(text, style=table_style, wrap_in_code_block=True)
21
+ return {
22
+ "text": formatted,
23
+ "parse_mode": None # Plain/Markdown compatible
24
+ }
25
+
26
+ async def send_smart_message(
27
+ bot: Any,
28
+ chat_id: Any,
29
+ text: str,
30
+ table_style: TableStyle = 'rounded',
31
+ **kwargs
32
+ ) -> Any:
33
+ """Send a message through aiogram 3 or python-telegram-bot with auto-formatted rich tables.
34
+
35
+ Compatible with:
36
+ - aiogram 3.x (Bot instance)
37
+ - python-telegram-bot 20+ (Bot or ExtBot instance)
38
+ """
39
+ payload = smart_format_payload(text, table_style=table_style)
40
+ send_kwargs = {**payload, **kwargs}
41
+
42
+ # aiogram 3.x
43
+ if hasattr(bot, 'send_message') and callable(getattr(bot, 'send_message')):
44
+ return await bot.send_message(chat_id=chat_id, **send_kwargs)
45
+
46
+ # python-telegram-bot
47
+ elif hasattr(bot, 'send_message'):
48
+ return await bot.send_message(chat_id=chat_id, **send_kwargs)
49
+
50
+ raise TypeError(f"Unsupported bot instance type: {type(bot)}")
@@ -0,0 +1,51 @@
1
+ """CJK and wide-character display width utilities.
2
+
3
+ Ensures perfect column alignment for Chinese, Japanese, Korean,
4
+ full-width punctuation, and emoji in Telegram monospace contexts.
5
+ Also strips ANSI escapes so styled strings calculate accurately.
6
+ """
7
+
8
+ import re
9
+ import unicodedata
10
+ from typing import Literal
11
+
12
+ ANSI_RE = re.compile(r'\x1b\[[0-9;]*[a-zA-Z]')
13
+
14
+ def strip_ansi(text: str) -> str:
15
+ """Remove ANSI escape sequences from string."""
16
+ return ANSI_RE.sub('', text)
17
+
18
+ def get_char_width(ch: str) -> int:
19
+ """Return the display column width of a single unicode character."""
20
+ # Control characters
21
+ if unicodedata.category(ch).startswith('C'):
22
+ return 0
23
+ # East Asian Width property
24
+ # W: Wide, F: Fullwidth -> 2 columns
25
+ # A: Ambiguous -> usually 1 column in modern monospace fonts
26
+ # Na: Narrow, N: Neutral, H: Halfwidth -> 1 column
27
+ status = unicodedata.east_asian_width(ch)
28
+ if status in ('W', 'F'):
29
+ return 2
30
+ return 1
31
+
32
+ def get_display_width(text: str) -> int:
33
+ """Calculate the total display width of a string in monospace columns."""
34
+ clean_text = strip_ansi(text)
35
+ return sum(get_char_width(ch) for ch in clean_text)
36
+
37
+ def pad_cjk(text: str, width: int, align: Literal['left', 'right', 'center'] = 'left') -> str:
38
+ """Pad a string containing CJK characters to the specified display width."""
39
+ current_width = get_display_width(text)
40
+ if current_width >= width:
41
+ return text
42
+
43
+ pad_needed = width - current_width
44
+ if align == 'right':
45
+ return ' ' * pad_needed + text
46
+ elif align == 'center':
47
+ left_pad = pad_needed // 2
48
+ right_pad = pad_needed - left_pad
49
+ return ' ' * left_pad + text + ' ' * right_pad
50
+ else: # left
51
+ return text + ' ' * pad_needed
@@ -0,0 +1,44 @@
1
+ """Command Line Interface for tg-rich-render."""
2
+
3
+ import sys
4
+ import argparse
5
+ from pathlib import Path
6
+ from tg_rich_render.parser import render_telegram
7
+
8
+ def main():
9
+ parser = argparse.ArgumentParser(
10
+ description="Transform Markdown tables into Telegram-optimized rich formats."
11
+ )
12
+ parser.add_argument(
13
+ "file",
14
+ nargs="?",
15
+ help="Markdown file to read (reads stdin if omitted)"
16
+ )
17
+ parser.add_argument(
18
+ "--style",
19
+ choices=["rounded", "classic", "clean", "card", "html"],
20
+ default="rounded",
21
+ help="Table rendering style (default: rounded)"
22
+ )
23
+ parser.add_argument(
24
+ "--no-wrap",
25
+ action="store_true",
26
+ help="Do not wrap rendered tables in code blocks"
27
+ )
28
+
29
+ args = parser.parse_args()
30
+
31
+ if args.file and args.file != "-":
32
+ content = Path(args.file).read_text(encoding="utf-8")
33
+ else:
34
+ content = sys.stdin.read()
35
+
36
+ rendered = render_telegram(
37
+ content,
38
+ style=args.style,
39
+ wrap_in_code_block=not args.no_wrap
40
+ )
41
+ print(rendered)
42
+
43
+ if __name__ == "__main__":
44
+ main()
@@ -0,0 +1,115 @@
1
+ """Markdown document parser that isolates tables and applies rich Telegram formatting.
2
+ """
3
+
4
+ import re
5
+ from typing import Literal, Optional, List
6
+ from tg_rich_render.table import Table
7
+
8
+ TableStyle = Literal['rounded', 'classic', 'clean', 'card', 'html']
9
+
10
+ TABLE_LINE_RE = re.compile(r'^\s*\|.*\|\s*$')
11
+
12
+ def extract_tables_and_text(md_text: str):
13
+ """Split markdown text into ordinary text blocks and table blocks.
14
+
15
+ Protects existing fenced code blocks from being parsed as tables.
16
+ """
17
+ lines = md_text.splitlines()
18
+ blocks = []
19
+
20
+ in_code_block = False
21
+ current_text_lines: List[str] = []
22
+ current_table_lines: List[str] = []
23
+
24
+ def flush_text():
25
+ nonlocal current_text_lines
26
+ if current_text_lines:
27
+ blocks.append(('text', '\n'.join(current_text_lines)))
28
+ current_text_lines = []
29
+
30
+ def flush_table():
31
+ nonlocal current_table_lines
32
+ if current_table_lines:
33
+ raw_table = '\n'.join(current_table_lines)
34
+ t = Table.from_markdown(raw_table)
35
+ if t:
36
+ blocks.append(('table', t))
37
+ else:
38
+ blocks.append(('text', raw_table))
39
+ current_table_lines = []
40
+
41
+ for line in lines:
42
+ stripped = line.strip()
43
+
44
+ # Check code fence
45
+ if stripped.startswith('```'):
46
+ in_code_block = not in_code_block
47
+ if current_table_lines:
48
+ flush_table()
49
+ current_text_lines.append(line)
50
+ continue
51
+
52
+ if in_code_block:
53
+ current_text_lines.append(line)
54
+ continue
55
+
56
+ # Check if line looks like a markdown table row
57
+ if TABLE_LINE_RE.match(stripped):
58
+ if current_text_lines:
59
+ flush_text()
60
+ current_table_lines.append(line)
61
+ else:
62
+ if current_table_lines:
63
+ flush_table()
64
+ current_text_lines.append(line)
65
+
66
+ if current_table_lines:
67
+ flush_table()
68
+ if current_text_lines:
69
+ flush_text()
70
+
71
+ return blocks
72
+
73
+ def render_telegram(
74
+ md_text: str,
75
+ style: TableStyle = 'rounded',
76
+ wrap_in_code_block: bool = True
77
+ ) -> str:
78
+ """Transform markdown text for optimal Telegram display.
79
+
80
+ Parameters:
81
+ md_text: Input markdown text containing pipe tables.
82
+ style: Rendering style for tables ('rounded', 'classic', 'clean', 'card', 'html').
83
+ wrap_in_code_block: Wrap monospace tables in ``` for perfect alignment.
84
+ """
85
+ blocks = extract_tables_and_text(md_text)
86
+ output_parts = []
87
+
88
+ for kind, payload in blocks:
89
+ if kind == 'text':
90
+ output_parts.append(payload)
91
+ elif kind == 'table':
92
+ table: Table = payload
93
+ if style == 'rounded':
94
+ rendered = table.render_rounded()
95
+ if wrap_in_code_block:
96
+ rendered = f"```\n{rendered}\n```"
97
+ elif style == 'classic':
98
+ rendered = table.render_classic()
99
+ if wrap_in_code_block:
100
+ rendered = f"```\n{rendered}\n```"
101
+ elif style == 'clean':
102
+ rendered = table.render_clean()
103
+ if wrap_in_code_block:
104
+ rendered = f"```\n{rendered}\n```"
105
+ elif style == 'card':
106
+ rendered = table.render_card()
107
+ elif style == 'html':
108
+ rendered = table.render_html()
109
+ else:
110
+ rendered = table.render_rounded()
111
+ if wrap_in_code_block:
112
+ rendered = f"```\n{rendered}\n```"
113
+ output_parts.append(rendered)
114
+
115
+ return "\n".join(output_parts)
@@ -0,0 +1,165 @@
1
+ """Markdown Pipe Table Parser and Multi-style Telegram Renderers.
2
+
3
+ Supports:
4
+ - CJK wide character awareness
5
+ - Column alignment (:---, :---:, ---:)
6
+ - Multiple visual styles: rounded, classic, clean, card, html
7
+ """
8
+
9
+ import re
10
+ import html
11
+ from typing import List, Dict, Any, Optional, Literal
12
+ from tg_rich_render.cjk import get_display_width, pad_cjk
13
+
14
+ Alignment = Literal['left', 'right', 'center']
15
+
16
+ class Table:
17
+ """Represents a parsed Markdown table."""
18
+
19
+ def __init__(self, headers: List[str], rows: List[List[str]], alignments: Optional[List[Alignment]] = None):
20
+ self.headers = [h.strip() for h in headers]
21
+ self.rows = [[cell.strip() for cell in row] for row in rows]
22
+
23
+ col_count = len(self.headers)
24
+ if not alignments or len(alignments) < col_count:
25
+ alignments = ['left'] * col_count
26
+ self.alignments = alignments[:col_count]
27
+
28
+ # Calculate max display width per column
29
+ self.col_widths = [get_display_width(h) for h in self.headers]
30
+ for row in self.rows:
31
+ # Pad row if incomplete
32
+ while len(row) < col_count:
33
+ row.append('')
34
+ for idx in range(col_count):
35
+ cell_w = get_display_width(row[idx])
36
+ if cell_w > self.col_widths[idx]:
37
+ self.col_widths[idx] = cell_w
38
+
39
+ @classmethod
40
+ def from_markdown(cls, table_str: str) -> Optional['Table']:
41
+ """Parse a markdown pipe table string into a Table instance."""
42
+ lines = [line.strip() for line in table_str.strip().splitlines() if line.strip()]
43
+ if len(lines) < 2:
44
+ return None
45
+
46
+ # Clean pipe edges
47
+ def split_row(line: str) -> List[str]:
48
+ if line.startswith('|'):
49
+ line = line[1:]
50
+ if line.endswith('|'):
51
+ line = line[:-1]
52
+ return [cell.strip() for cell in line.split('|')]
53
+
54
+ raw_header = split_row(lines[0])
55
+ separator_line = split_row(lines[1])
56
+
57
+ # Validate separator line has dashes
58
+ alignments: List[Alignment] = []
59
+ for sep in separator_line:
60
+ s = sep.strip()
61
+ if not re.match(r'^:?-+:?$', s):
62
+ return None # Invalid markdown table separator
63
+ if s.startswith(':') and s.endswith(':'):
64
+ alignments.append('center')
65
+ elif s.endswith(':'):
66
+ alignments.append('right')
67
+ else:
68
+ alignments.append('left')
69
+
70
+ rows: List[List[str]] = []
71
+ for line in lines[2:]:
72
+ if '|' in line:
73
+ rows.append(split_row(line))
74
+
75
+ return cls(headers=raw_header, rows=rows, alignments=alignments)
76
+
77
+ def render_rounded(self) -> str:
78
+ """Render using modern Unicode rounded box characters."""
79
+ widths = self.col_widths
80
+
81
+ # ╭──────┬──────╮
82
+ top = "╭" + "┬".join("─" * (w + 2) for w in widths) + "╮"
83
+ # ├──────┼──────┤
84
+ mid = "├" + "┼".join("─" * (w + 2) for w in widths) + "┤"
85
+ # ╰──────┴──────╯
86
+ bot = "╰" + "┴".join("─" * (w + 2) for w in widths) + "╯"
87
+
88
+ def fmt_row(cells: List[str]) -> str:
89
+ padded = []
90
+ for i, cell in enumerate(cells):
91
+ w = widths[i]
92
+ align = self.alignments[i] if i < len(self.alignments) else 'left'
93
+ padded.append(" " + pad_cjk(cell, w, align=align) + " ")
94
+ return "│" + "│".join(padded) + "│"
95
+
96
+ lines = [top, fmt_row(self.headers), mid]
97
+ for row in self.rows:
98
+ lines.append(fmt_row(row))
99
+ lines.append(bot)
100
+ return "\n".join(lines)
101
+
102
+ def render_classic(self) -> str:
103
+ """Render using classic ASCII borders."""
104
+ widths = self.col_widths
105
+ sep = "+" + "+".join("-" * (w + 2) for w in widths) + "+"
106
+
107
+ def fmt_row(cells: List[str]) -> str:
108
+ padded = []
109
+ for i, cell in enumerate(cells):
110
+ w = widths[i]
111
+ align = self.alignments[i] if i < len(self.alignments) else 'left'
112
+ padded.append(" " + pad_cjk(cell, w, align=align) + " ")
113
+ return "|" + "|".join(padded) + "|"
114
+
115
+ lines = [sep, fmt_row(self.headers), sep]
116
+ for row in self.rows:
117
+ lines.append(fmt_row(row))
118
+ lines.append(sep)
119
+ return "\n".join(lines)
120
+
121
+ def render_clean(self) -> str:
122
+ """Render minimalist table without vertical border lines."""
123
+ widths = self.col_widths
124
+ header_pad = [pad_cjk(h, widths[i], align=self.alignments[i]) for i, h in enumerate(self.headers)]
125
+ sep_line = ["─" * widths[i] for i in range(len(widths))]
126
+
127
+ lines = [" ".join(header_pad), " ".join(sep_line)]
128
+ for row in self.rows:
129
+ row_pad = []
130
+ for i, cell in enumerate(row):
131
+ w = widths[i] if i < len(widths) else len(cell)
132
+ align = self.alignments[i] if i < len(self.alignments) else 'left'
133
+ row_pad.append(pad_cjk(cell, w, align=align))
134
+ lines.append(" ".join(row_pad))
135
+ return "\n".join(lines)
136
+
137
+ def render_card(self) -> str:
138
+ """Render table as mobile-friendly cards, ideal for narrow screens."""
139
+ blocks = []
140
+ for row_idx, row in enumerate(self.rows):
141
+ title = row[0] if row else f"Item {row_idx + 1}"
142
+ details = []
143
+ for col_idx in range(1, len(self.headers)):
144
+ h = self.headers[col_idx]
145
+ val = row[col_idx] if col_idx < len(row) else ""
146
+ if val:
147
+ details.append(f" • {h}: {val}")
148
+ if details:
149
+ blocks.append(f"📌 {title}\n" + "\n".join(details))
150
+ else:
151
+ blocks.append(f"📌 {title}")
152
+ return "\n\n".join(blocks)
153
+
154
+ def render_html(self) -> str:
155
+ """Render as Telegram-compatible HTML table or styled blocks."""
156
+ out = ["<table>"]
157
+ out.append(" <thead>")
158
+ out.append(" <tr>" + "".join(f"<th>{html.escape(h)}</th>" for h in self.headers) + "</tr>")
159
+ out.append(" </thead>")
160
+ out.append(" <tbody>")
161
+ for row in self.rows:
162
+ out.append(" <tr>" + "".join(f"<td>{html.escape(cell)}</td>" for cell in row) + "</tr>")
163
+ out.append(" </tbody>")
164
+ out.append("</table>")
165
+ return "\n".join(out)
@@ -0,0 +1,197 @@
1
+ Metadata-Version: 2.4
2
+ Name: tg-rich-render
3
+ Version: 0.1.0
4
+ Summary: Lightweight Telegram rich table and format converter with zero-dependency CJK alignment.
5
+ Author-email: shali10 <99240403+shali10@users.noreply.github.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/shali10/tg-rich-render
8
+ Project-URL: Repository, https://github.com/shali10/tg-rich-render
9
+ Project-URL: Issues, https://github.com/shali10/tg-rich-render/issues
10
+ Keywords: telegram,aiogram,telegram-bot,table,markdown,cjk,rich-formatting
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Communications :: Chat
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Dynamic: license-file
25
+
26
+ # tg-rich-render 📊✨
27
+
28
+ > **Zero-dependency, CJK-aware Markdown table & rich format converter for Telegram bots.**
29
+ > 专治 Telegram 机器人表格排版错位、中英混排对不齐、手机端横向溢出变难看代码块的痛点。
30
+
31
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
32
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
33
+ [![CI Status](https://github.com/shali10/tg-rich-render/actions/workflows/ci.yml/badge.svg)](https://github.com/shali10/tg-rich-render/actions)
34
+
35
+ ---
36
+
37
+ ## 💡 为什么需要 tg-rich-render?
38
+
39
+ Telegram 官方的 MarkdownV2 并不支持标准的 GFM Pipe Table(管道表格)。在日常 Telegram 机器人开发(运维巡检播报、资产统计、行情早报)中,直接发送表格通常会遇到以下问题:
40
+
41
+ 1. **直接报错**:Telegram Bot API 无法解析 `| col | col |` 语法;
42
+ 2. **粗暴丢进代码块**:直接用 ```` 包裹,但在中英汉字、Emoji 混排时由于字符显示宽度(CJK 宽度为 2)导致竖线完全错位,排版歪歪扭扭;
43
+ 3. **窄屏移动端阅读体验差**:宽表格在手机竖屏下横向拉长折行。
44
+
45
+ `tg-rich-render` 提供纯 Python 标准库实现的智能 CJK 宽度对齐与多风格自适应渲染,一行代码即可集成到 **aiogram 3**、**python-telegram-bot** 或任何 HTTP 请求中。
46
+
47
+ ---
48
+
49
+ ## ✨ 核心特性
50
+
51
+ - 🚀 **零外部依赖**:纯 Python 标准库构建(基于 `unicodedata` 模块),即装即用,启动开销 0ms。
52
+ - 📐 **精准 CJK / Emoji 宽度补偿**:完美对齐汉字、日韩文、全角符号与 Emoji 表情,拒绝折线与锯齿。
53
+ - 🎨 **多风格视觉呈现**:
54
+ - `rounded`:现代优雅圆角框线(`╭───┬───╮`),视觉质感拉满。
55
+ - `classic`:经典 ASCII 风格(`+---+---+`)。
56
+ - `clean`:极简流式无竖框风格。
57
+ - `card`:移动端窄屏优先卡片流(键值对展示,杜绝横向滚动)。
58
+ - `html`:Telegram 兼容 HTML `<table>` 格式。
59
+ - 🤖 **主流框架开箱即用**:自带 aiogram 3 与 python-telegram-bot 发送适配器。
60
+ - 💻 **CLI 工具支持**:支持管道输入与终端即时预览。
61
+
62
+ ---
63
+
64
+ ## 📦 快速安装
65
+
66
+ ```bash
67
+ pip install tg-rich-render
68
+ ```
69
+
70
+ 或直接克隆使用:
71
+
72
+ ```bash
73
+ git clone https://github.com/shali10/tg-rich-render.git
74
+ cd tg-rich-render
75
+ pip install -e .
76
+ ```
77
+
78
+ ---
79
+
80
+ ## 🚀 快速上手
81
+
82
+ ### 1. 独立使用(纯 Python)
83
+
84
+ ```python
85
+ from tg_rich_render import render_telegram
86
+
87
+ raw_markdown = """
88
+ # 节点健康巡检
89
+
90
+ | 节点 | 状态 | 延迟 |
91
+ |:---|:---:|---:|
92
+ | 香港CN2 🚀 | 正常 | 15ms |
93
+ | 美国洛杉矶 | 良好 | 135ms |
94
+ | 日本东京 ⚡ | 正常 | 48ms |
95
+
96
+ 巡检完成,无异常节点。
97
+ """
98
+
99
+ # 渲染为 Telegram 优雅圆角等宽表格
100
+ message = render_telegram(raw_markdown, style="rounded")
101
+ print(message)
102
+ ```
103
+
104
+ **输出效果:**
105
+
106
+ ```
107
+ # 节点健康巡检
108
+
109
+ ```
110
+ ╭────────────┬──────┬───────╮
111
+ │ 节点 │ 状态 │ 延迟 │
112
+ ├────────────┼──────┼───────┤
113
+ │ 香港CN2 🚀 │ 正常 │ 15ms │
114
+ │ 美国洛杉矶 │ 良好 │ 135ms │
115
+ │ 日本东京 ⚡ │ 正常 │ 48ms │
116
+ ╰────────────┴──────┴───────╯
117
+ ```
118
+
119
+ 巡检完成,无异常节点。
120
+ ```
121
+
122
+ ---
123
+
124
+ ### 2. 结合 aiogram 3
125
+
126
+ ```python
127
+ from aiogram import Bot
128
+ from tg_rich_render import send_smart_message
129
+
130
+ bot = Bot(token="YOUR_BOT_TOKEN")
131
+
132
+ # 一行代码自适应格式化并发送
133
+ await send_smart_message(
134
+ bot=bot,
135
+ chat_id=12345678,
136
+ text=raw_markdown,
137
+ table_style="rounded"
138
+ )
139
+ ```
140
+
141
+ ---
142
+
143
+ ### 3. 结合 python-telegram-bot
144
+
145
+ ```python
146
+ from telegram import Bot
147
+ from tg_rich_render import send_smart_message
148
+
149
+ bot = Bot(token="YOUR_BOT_TOKEN")
150
+
151
+ await send_smart_message(
152
+ bot=bot,
153
+ chat_id=12345678,
154
+ text=raw_markdown,
155
+ table_style="rounded"
156
+ )
157
+ ```
158
+
159
+ ---
160
+
161
+ ### 4. 命令行(CLI)使用
162
+
163
+ ```bash
164
+ # 转换 Markdown 文件
165
+ tg-rich-render report.md --style rounded
166
+
167
+ # 从终端管道流式转换
168
+ cat summary.md | tg-rich-render --style card
169
+ ```
170
+
171
+ ---
172
+
173
+ ## 🎨 渲染风格展示
174
+
175
+ | 风格名称 | 预览示意 | 适用场景 |
176
+ |:---|:---|:---|
177
+ | **rounded** (默认) | `╭─┬─╮\n│A│B│\n╰─┴─╯` | PC 端与大屏客户端,视觉质感极高 |
178
+ | **classic** | `+-+-+\n|A|B|\n+-+-+` | 通用等宽终端、标准日志输出 |
179
+ | **clean** | `A B\n─ ─\n1 2` | 极简通知、紧凑监控通知 |
180
+ | **card** | `📌 节点\n • 延迟: 15ms` | 移动端窄屏、列数较多的宽表格 |
181
+ | **html** | `<table>...</table>` | Telegram WebApp 或特定富文本容器 |
182
+
183
+ ---
184
+
185
+ ## 🧪 单元测试
186
+
187
+ 项目自带完整的自动化测试集(覆盖 CJK 宽度、对齐算法、长文本解析与适配器):
188
+
189
+ ```bash
190
+ python3 -m unittest discover -s tests
191
+ ```
192
+
193
+ ---
194
+
195
+ ## 📄 开源许可
196
+
197
+ 本项目基于 [MIT License](LICENSE) 开源。
@@ -0,0 +1,18 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ tests/test_adapter.py
5
+ tests/test_cjk.py
6
+ tests/test_parser.py
7
+ tests/test_table.py
8
+ tg_rich_render/__init__.py
9
+ tg_rich_render/adapter.py
10
+ tg_rich_render/cjk.py
11
+ tg_rich_render/cli.py
12
+ tg_rich_render/parser.py
13
+ tg_rich_render/table.py
14
+ tg_rich_render.egg-info/PKG-INFO
15
+ tg_rich_render.egg-info/SOURCES.txt
16
+ tg_rich_render.egg-info/dependency_links.txt
17
+ tg_rich_render.egg-info/entry_points.txt
18
+ tg_rich_render.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tg-rich-render = tg_rich_render.cli:main
@@ -0,0 +1 @@
1
+ tg_rich_render