wechat-formatter 1.0.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.
- wechat_formatter-1.0.0/LICENSE +21 -0
- wechat_formatter-1.0.0/PKG-INFO +174 -0
- wechat_formatter-1.0.0/README.md +149 -0
- wechat_formatter-1.0.0/pyproject.toml +40 -0
- wechat_formatter-1.0.0/setup.cfg +4 -0
- wechat_formatter-1.0.0/tests/test_basic.py +97 -0
- wechat_formatter-1.0.0/wechat_formatter/__init__.py +65 -0
- wechat_formatter-1.0.0/wechat_formatter/colors.py +150 -0
- wechat_formatter-1.0.0/wechat_formatter/plugins.py +65 -0
- wechat_formatter-1.0.0/wechat_formatter/py.typed +0 -0
- wechat_formatter-1.0.0/wechat_formatter/renderer.py +502 -0
- wechat_formatter-1.0.0/wechat_formatter/style_utils.py +67 -0
- wechat_formatter-1.0.0/wechat_formatter/templates.py +578 -0
- wechat_formatter-1.0.0/wechat_formatter/types.py +97 -0
- wechat_formatter-1.0.0/wechat_formatter.egg-info/PKG-INFO +174 -0
- wechat_formatter-1.0.0/wechat_formatter.egg-info/SOURCES.txt +17 -0
- wechat_formatter-1.0.0/wechat_formatter.egg-info/dependency_links.txt +1 -0
- wechat_formatter-1.0.0/wechat_formatter.egg-info/requires.txt +1 -0
- wechat_formatter-1.0.0/wechat_formatter.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wechat-formatter contributors
|
|
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,174 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wechat-formatter
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Render Markdown into inline-styled HTML for WeChat Official Account (公众号) articles, with 6 visual themes × 12 colors.
|
|
5
|
+
Author: wechat-formatter contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/your-org/wechat-formatter
|
|
8
|
+
Project-URL: Repository, https://github.com/your-org/wechat-formatter
|
|
9
|
+
Keywords: wechat,markdown,公众号,wechat-mp,html,formatter,mistune
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Text Processing :: Markup :: HTML
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: mistune<4.0,>=3.0
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# wechat-formatter
|
|
27
|
+
|
|
28
|
+
把 Markdown 渲染成**内联样式 HTML**,可直接复制粘贴到微信公众号编辑器。
|
|
29
|
+
|
|
30
|
+
基于 [`mistune`](https://github.com/lepture/mistune),内置 **6 种视觉风格 × 12 种配色 = 72 套模板**,所有 CSS 都写在 `style="..."` 内联属性里,不依赖任何外部 CSS 文件或 JS。
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install wechat-formatter
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
或从源码安装:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
cd wechat-formatter
|
|
42
|
+
pip install .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 快速开始
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from wechat_formatter import get_template, render_article, FormatTweaks
|
|
49
|
+
|
|
50
|
+
markdown_text = """\
|
|
51
|
+
# 你好,公众号
|
|
52
|
+
|
|
53
|
+
这是一段**普通文字**,包含 ==高亮== 内容和 ^上标^、~下标~。
|
|
54
|
+
|
|
55
|
+
## 一个小标题
|
|
56
|
+
|
|
57
|
+
- 列表项一
|
|
58
|
+
- 列表项二
|
|
59
|
+
- ~~删除线~~
|
|
60
|
+
|
|
61
|
+
> 这是一段引用
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
print("hello")
|
|
65
|
+
```
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# 按中文风格名 + 颜色名选择模板
|
|
69
|
+
template = get_template("极简风", "经典")
|
|
70
|
+
|
|
71
|
+
# 可选:覆盖字号、行高、边距等
|
|
72
|
+
tweaks = FormatTweaks(
|
|
73
|
+
fontSize=15,
|
|
74
|
+
lineHeight=1.75,
|
|
75
|
+
paragraphSpacing=16,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
html = render_article(markdown_text, template, tweaks)
|
|
79
|
+
|
|
80
|
+
# 把 html 写入文件,用浏览器打开后全选复制,粘贴到公众号编辑器
|
|
81
|
+
with open("article.html", "w", encoding="utf-8") as f:
|
|
82
|
+
f.write(html)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## 模板选择
|
|
86
|
+
|
|
87
|
+
`get_template(category, color)` 接受中文风格名或英文 id:
|
|
88
|
+
|
|
89
|
+
| 风格中文名 | 英文 id |
|
|
90
|
+
|---|---|
|
|
91
|
+
| 新粗野风 | `neo-brutalism` |
|
|
92
|
+
| 极简风 | `minimalist` |
|
|
93
|
+
| 商务风 | `business` |
|
|
94
|
+
| 文艺风 | `literary` |
|
|
95
|
+
| 科技风 | `tech` |
|
|
96
|
+
| 节庆风 | `festive` |
|
|
97
|
+
|
|
98
|
+
颜色名(12 种):`经典`、`雅致`、`先锋`、`深邃`、`晨光`、`星穹`、`暖阳`、`暮色`、`清泉`、`破晓`、`璀璨`、`幽蓝`。
|
|
99
|
+
|
|
100
|
+
也可以遍历全部模板:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from wechat_formatter import all_templates, grouped_templates
|
|
104
|
+
|
|
105
|
+
for tpl in all_templates:
|
|
106
|
+
print(tpl.category, tpl.name, tpl.themeColor)
|
|
107
|
+
|
|
108
|
+
# 按风格分组
|
|
109
|
+
for group in grouped_templates:
|
|
110
|
+
print(group["name"], len(group["templates"]))
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## FormatTweaks 参数
|
|
114
|
+
|
|
115
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
116
|
+
|---|---|---|---|
|
|
117
|
+
| `themeColor` | `str \| None` | `None` | 覆盖模板主题色(hex) |
|
|
118
|
+
| `fontSize` | `int` | `15` | 正文字号 px |
|
|
119
|
+
| `lineHeight` | `float` | `1.75` | 行高 |
|
|
120
|
+
| `paragraphSpacing` | `int` | `16` | 段间距 px |
|
|
121
|
+
| `firstLineIndent` | `bool` | `False` | 首行缩进 2em |
|
|
122
|
+
| `letterSpacing` | `float` | `0.5` | 字间距 px |
|
|
123
|
+
| `imageRadius` | `int` | `4` | 图片圆角 px |
|
|
124
|
+
| `h1Layout` | `str \| None` | `None` | 一级标题对齐:`left`/`center`/`right` |
|
|
125
|
+
| `h2Layout` | `str \| None` | `None` | 二级标题对齐 |
|
|
126
|
+
| `pagePaddingTop` | `int` | `20` | 页面上边距 px |
|
|
127
|
+
| `pagePaddingRight` | `int` | `20` | 页面右边距 px |
|
|
128
|
+
| `pagePaddingBottom` | `int` | `20` | 页面下边距 px |
|
|
129
|
+
| `pagePaddingLeft` | `int` | `20` | 页面左边距 px |
|
|
130
|
+
|
|
131
|
+
## 支持的 Markdown 语法
|
|
132
|
+
|
|
133
|
+
- 标准:标题 `#`/`##`/`###`、段落、**加粗**、*斜体*、`行内代码`、链接、图片、引用、有序/无序列表、分割线 `---`、表格
|
|
134
|
+
- 扩展:
|
|
135
|
+
- `==高亮==` → `<mark>`
|
|
136
|
+
- `^上标^` → `<sup>`
|
|
137
|
+
- `~下标~` → `<sub>`
|
|
138
|
+
- `~~删除线~~` → `<del>`
|
|
139
|
+
- 任务列表(`- [x]` / `- [ ]`)
|
|
140
|
+
- 代码块带 macOS 风格圆点头部
|
|
141
|
+
- 段落中多图自动排成一行
|
|
142
|
+
|
|
143
|
+
## API 一览
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from wechat_formatter import (
|
|
147
|
+
# 渲染
|
|
148
|
+
render_article,
|
|
149
|
+
# 模板
|
|
150
|
+
all_templates, # list[TemplateConfig]
|
|
151
|
+
grouped_templates, # list[dict],按风格分组
|
|
152
|
+
get_template, # (category, color) -> TemplateConfig
|
|
153
|
+
build_template, # 自定义构建
|
|
154
|
+
get_styles_by_category,
|
|
155
|
+
# 类型
|
|
156
|
+
TemplateConfig,
|
|
157
|
+
FormatTweaks,
|
|
158
|
+
BaseStyle,
|
|
159
|
+
# 颜色
|
|
160
|
+
color_palettes,
|
|
161
|
+
categories_list,
|
|
162
|
+
names,
|
|
163
|
+
hex_to_rgb,
|
|
164
|
+
hex_to_rgba,
|
|
165
|
+
# 工具
|
|
166
|
+
get_style_value,
|
|
167
|
+
ensure_style_value,
|
|
168
|
+
bg_fallback,
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
MIT
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# wechat-formatter
|
|
2
|
+
|
|
3
|
+
把 Markdown 渲染成**内联样式 HTML**,可直接复制粘贴到微信公众号编辑器。
|
|
4
|
+
|
|
5
|
+
基于 [`mistune`](https://github.com/lepture/mistune),内置 **6 种视觉风格 × 12 种配色 = 72 套模板**,所有 CSS 都写在 `style="..."` 内联属性里,不依赖任何外部 CSS 文件或 JS。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install wechat-formatter
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
或从源码安装:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cd wechat-formatter
|
|
17
|
+
pip install .
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 快速开始
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from wechat_formatter import get_template, render_article, FormatTweaks
|
|
24
|
+
|
|
25
|
+
markdown_text = """\
|
|
26
|
+
# 你好,公众号
|
|
27
|
+
|
|
28
|
+
这是一段**普通文字**,包含 ==高亮== 内容和 ^上标^、~下标~。
|
|
29
|
+
|
|
30
|
+
## 一个小标题
|
|
31
|
+
|
|
32
|
+
- 列表项一
|
|
33
|
+
- 列表项二
|
|
34
|
+
- ~~删除线~~
|
|
35
|
+
|
|
36
|
+
> 这是一段引用
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
print("hello")
|
|
40
|
+
```
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# 按中文风格名 + 颜色名选择模板
|
|
44
|
+
template = get_template("极简风", "经典")
|
|
45
|
+
|
|
46
|
+
# 可选:覆盖字号、行高、边距等
|
|
47
|
+
tweaks = FormatTweaks(
|
|
48
|
+
fontSize=15,
|
|
49
|
+
lineHeight=1.75,
|
|
50
|
+
paragraphSpacing=16,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
html = render_article(markdown_text, template, tweaks)
|
|
54
|
+
|
|
55
|
+
# 把 html 写入文件,用浏览器打开后全选复制,粘贴到公众号编辑器
|
|
56
|
+
with open("article.html", "w", encoding="utf-8") as f:
|
|
57
|
+
f.write(html)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 模板选择
|
|
61
|
+
|
|
62
|
+
`get_template(category, color)` 接受中文风格名或英文 id:
|
|
63
|
+
|
|
64
|
+
| 风格中文名 | 英文 id |
|
|
65
|
+
|---|---|
|
|
66
|
+
| 新粗野风 | `neo-brutalism` |
|
|
67
|
+
| 极简风 | `minimalist` |
|
|
68
|
+
| 商务风 | `business` |
|
|
69
|
+
| 文艺风 | `literary` |
|
|
70
|
+
| 科技风 | `tech` |
|
|
71
|
+
| 节庆风 | `festive` |
|
|
72
|
+
|
|
73
|
+
颜色名(12 种):`经典`、`雅致`、`先锋`、`深邃`、`晨光`、`星穹`、`暖阳`、`暮色`、`清泉`、`破晓`、`璀璨`、`幽蓝`。
|
|
74
|
+
|
|
75
|
+
也可以遍历全部模板:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
from wechat_formatter import all_templates, grouped_templates
|
|
79
|
+
|
|
80
|
+
for tpl in all_templates:
|
|
81
|
+
print(tpl.category, tpl.name, tpl.themeColor)
|
|
82
|
+
|
|
83
|
+
# 按风格分组
|
|
84
|
+
for group in grouped_templates:
|
|
85
|
+
print(group["name"], len(group["templates"]))
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## FormatTweaks 参数
|
|
89
|
+
|
|
90
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
91
|
+
|---|---|---|---|
|
|
92
|
+
| `themeColor` | `str \| None` | `None` | 覆盖模板主题色(hex) |
|
|
93
|
+
| `fontSize` | `int` | `15` | 正文字号 px |
|
|
94
|
+
| `lineHeight` | `float` | `1.75` | 行高 |
|
|
95
|
+
| `paragraphSpacing` | `int` | `16` | 段间距 px |
|
|
96
|
+
| `firstLineIndent` | `bool` | `False` | 首行缩进 2em |
|
|
97
|
+
| `letterSpacing` | `float` | `0.5` | 字间距 px |
|
|
98
|
+
| `imageRadius` | `int` | `4` | 图片圆角 px |
|
|
99
|
+
| `h1Layout` | `str \| None` | `None` | 一级标题对齐:`left`/`center`/`right` |
|
|
100
|
+
| `h2Layout` | `str \| None` | `None` | 二级标题对齐 |
|
|
101
|
+
| `pagePaddingTop` | `int` | `20` | 页面上边距 px |
|
|
102
|
+
| `pagePaddingRight` | `int` | `20` | 页面右边距 px |
|
|
103
|
+
| `pagePaddingBottom` | `int` | `20` | 页面下边距 px |
|
|
104
|
+
| `pagePaddingLeft` | `int` | `20` | 页面左边距 px |
|
|
105
|
+
|
|
106
|
+
## 支持的 Markdown 语法
|
|
107
|
+
|
|
108
|
+
- 标准:标题 `#`/`##`/`###`、段落、**加粗**、*斜体*、`行内代码`、链接、图片、引用、有序/无序列表、分割线 `---`、表格
|
|
109
|
+
- 扩展:
|
|
110
|
+
- `==高亮==` → `<mark>`
|
|
111
|
+
- `^上标^` → `<sup>`
|
|
112
|
+
- `~下标~` → `<sub>`
|
|
113
|
+
- `~~删除线~~` → `<del>`
|
|
114
|
+
- 任务列表(`- [x]` / `- [ ]`)
|
|
115
|
+
- 代码块带 macOS 风格圆点头部
|
|
116
|
+
- 段落中多图自动排成一行
|
|
117
|
+
|
|
118
|
+
## API 一览
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from wechat_formatter import (
|
|
122
|
+
# 渲染
|
|
123
|
+
render_article,
|
|
124
|
+
# 模板
|
|
125
|
+
all_templates, # list[TemplateConfig]
|
|
126
|
+
grouped_templates, # list[dict],按风格分组
|
|
127
|
+
get_template, # (category, color) -> TemplateConfig
|
|
128
|
+
build_template, # 自定义构建
|
|
129
|
+
get_styles_by_category,
|
|
130
|
+
# 类型
|
|
131
|
+
TemplateConfig,
|
|
132
|
+
FormatTweaks,
|
|
133
|
+
BaseStyle,
|
|
134
|
+
# 颜色
|
|
135
|
+
color_palettes,
|
|
136
|
+
categories_list,
|
|
137
|
+
names,
|
|
138
|
+
hex_to_rgb,
|
|
139
|
+
hex_to_rgba,
|
|
140
|
+
# 工具
|
|
141
|
+
get_style_value,
|
|
142
|
+
ensure_style_value,
|
|
143
|
+
bg_fallback,
|
|
144
|
+
)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## License
|
|
148
|
+
|
|
149
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wechat-formatter"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Render Markdown into inline-styled HTML for WeChat Official Account (公众号) articles, with 6 visual themes × 12 colors."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "wechat-formatter contributors" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["wechat", "markdown", "公众号", "wechat-mp", "html", "formatter", "mistune"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.9",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Text Processing :: Markup :: HTML",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"mistune>=3.0,<4.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/your-org/wechat-formatter"
|
|
34
|
+
Repository = "https://github.com/your-org/wechat-formatter"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools]
|
|
37
|
+
packages = ["wechat_formatter"]
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.package-data]
|
|
40
|
+
wechat_formatter = ["py.typed"]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Basic smoke tests for wechat_formatter."""
|
|
2
|
+
|
|
3
|
+
from wechat_formatter import (
|
|
4
|
+
FormatTweaks,
|
|
5
|
+
TemplateConfig,
|
|
6
|
+
all_templates,
|
|
7
|
+
categories_list,
|
|
8
|
+
get_template,
|
|
9
|
+
grouped_templates,
|
|
10
|
+
hex_to_rgba,
|
|
11
|
+
names,
|
|
12
|
+
render_article,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_template_catalogue_size():
|
|
17
|
+
# 6 categories × 12 colors = 72
|
|
18
|
+
assert len(all_templates) == 72
|
|
19
|
+
assert len(categories_list) == 6
|
|
20
|
+
assert len(names) == 12
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_grouped_templates():
|
|
24
|
+
assert len(grouped_templates) == 6
|
|
25
|
+
for group in grouped_templates:
|
|
26
|
+
assert len(group["templates"]) == 12
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_get_template_by_chinese_name():
|
|
30
|
+
tpl = get_template("极简风", "经典")
|
|
31
|
+
assert isinstance(tpl, TemplateConfig)
|
|
32
|
+
assert tpl.category == "minimalist"
|
|
33
|
+
assert tpl.name == "经典"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_get_template_by_english_id():
|
|
37
|
+
tpl = get_template("tech", "晨光")
|
|
38
|
+
assert tpl.category == "tech"
|
|
39
|
+
assert tpl.name == "晨光"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_get_template_unknown_category():
|
|
43
|
+
import pytest
|
|
44
|
+
|
|
45
|
+
with pytest.raises(ValueError):
|
|
46
|
+
get_template("不存在的风格", "经典")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_hex_to_rgba():
|
|
50
|
+
assert hex_to_rgba("#ff0000", 0.5) == "rgba(255, 0, 0, 0.5)"
|
|
51
|
+
assert hex_to_rgba("#f00", 1.0) == "rgba(255, 0, 0, 1)"
|
|
52
|
+
# alpha clamped
|
|
53
|
+
assert hex_to_rgba("#000", 5.0) == "rgba(0, 0, 0, 1)"
|
|
54
|
+
assert hex_to_rgba("#000", -1.0) == "rgba(0, 0, 0, 0)"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_render_article_basic():
|
|
58
|
+
tpl = get_template("极简风", "经典")
|
|
59
|
+
html = render_article("# Hello\n\n正文内容", tpl, FormatTweaks())
|
|
60
|
+
assert "<section" in html
|
|
61
|
+
assert "Hello" in html
|
|
62
|
+
assert "正文内容" in html
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_render_extra_syntax():
|
|
66
|
+
tpl = get_template("商务风", "经典")
|
|
67
|
+
md = "==高亮== 普通 ^上标^ ~下标~ ~~删除~~"
|
|
68
|
+
html = render_article(md, tpl, FormatTweaks())
|
|
69
|
+
assert "<mark" in html
|
|
70
|
+
assert "<sup>" in html
|
|
71
|
+
assert "<sub>" in html
|
|
72
|
+
assert "<del" in html
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_render_task_list():
|
|
76
|
+
tpl = get_template("科技风", "经典")
|
|
77
|
+
md = "- [x] 已完成\n- [ ] 未完成"
|
|
78
|
+
html = render_article(md, tpl, FormatTweaks())
|
|
79
|
+
assert "已完成" in html
|
|
80
|
+
assert "未完成" in html
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_render_code_block():
|
|
84
|
+
tpl = get_template("极简风", "经典")
|
|
85
|
+
md = '```python\nprint("hi")\n```'
|
|
86
|
+
html = render_article(md, tpl, FormatTweaks())
|
|
87
|
+
assert "print" in html
|
|
88
|
+
# code block should be wrapped in pre
|
|
89
|
+
assert "<pre" in html
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_theme_color_override():
|
|
93
|
+
tpl = get_template("极简风", "经典")
|
|
94
|
+
tweaks = FormatTweaks(themeColor="#ff0000")
|
|
95
|
+
# A heading uses themeColor in its style (color: {color}).
|
|
96
|
+
html = render_article("# 标题", tpl, tweaks)
|
|
97
|
+
assert "#ff0000" in html
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""wechat_formatter — Markdown → WeChat Official Account styled HTML.
|
|
2
|
+
|
|
3
|
+
A Markdown article template engine that renders Markdown into inline-styled
|
|
4
|
+
HTML suitable for pasting into the WeChat Official Account (公众号) editor.
|
|
5
|
+
Six visual categories × twelve color themes = 72 ready-to-use templates.
|
|
6
|
+
|
|
7
|
+
Public API::
|
|
8
|
+
|
|
9
|
+
from wechat_formatter import (
|
|
10
|
+
all_templates,
|
|
11
|
+
grouped_templates,
|
|
12
|
+
get_template,
|
|
13
|
+
render_article,
|
|
14
|
+
TemplateConfig,
|
|
15
|
+
FormatTweaks,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Pick by Chinese style name + color name:
|
|
19
|
+
tpl = get_template("极简风", "经典")
|
|
20
|
+
html = render_article("# Hello", tpl, FormatTweaks())
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from .colors import (
|
|
26
|
+
categories_list,
|
|
27
|
+
color_palettes,
|
|
28
|
+
hex_to_rgb,
|
|
29
|
+
hex_to_rgba,
|
|
30
|
+
names,
|
|
31
|
+
)
|
|
32
|
+
from .renderer import render_article
|
|
33
|
+
from .style_utils import bg_fallback, ensure_style_value, get_style_value
|
|
34
|
+
from .templates import (
|
|
35
|
+
all_templates,
|
|
36
|
+
build_template,
|
|
37
|
+
generate_templates,
|
|
38
|
+
get_styles_by_category,
|
|
39
|
+
get_template,
|
|
40
|
+
grouped_templates,
|
|
41
|
+
)
|
|
42
|
+
from .types import BaseStyle, FormatTweaks, TemplateConfig
|
|
43
|
+
|
|
44
|
+
__all__ = [
|
|
45
|
+
"BaseStyle",
|
|
46
|
+
"FormatTweaks",
|
|
47
|
+
"TemplateConfig",
|
|
48
|
+
"all_templates",
|
|
49
|
+
"build_template",
|
|
50
|
+
"categories_list",
|
|
51
|
+
"color_palettes",
|
|
52
|
+
"generate_templates",
|
|
53
|
+
"get_styles_by_category",
|
|
54
|
+
"get_template",
|
|
55
|
+
"grouped_templates",
|
|
56
|
+
"hex_to_rgb",
|
|
57
|
+
"hex_to_rgba",
|
|
58
|
+
"names",
|
|
59
|
+
"render_article",
|
|
60
|
+
"bg_fallback",
|
|
61
|
+
"ensure_style_value",
|
|
62
|
+
"get_style_value",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
__version__ = "1.0.0"
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Color palettes, template names, and hex/ rgba helpers.
|
|
2
|
+
|
|
3
|
+
Translated from the top-level constants and helpers in the TypeScript source.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Dict, List, Tuple
|
|
9
|
+
|
|
10
|
+
# ---------------------------------------------------------------------------
|
|
11
|
+
# Palettes / names / categories
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
color_palettes: Dict[str, List[str]] = {
|
|
15
|
+
"neoBrutalism": [
|
|
16
|
+
"#ff6f9f",
|
|
17
|
+
"#3b82f6",
|
|
18
|
+
"#10b981",
|
|
19
|
+
"#f59e0b",
|
|
20
|
+
"#8b5cf6",
|
|
21
|
+
"#ef4444",
|
|
22
|
+
"#06b6d4",
|
|
23
|
+
"#f43f5e",
|
|
24
|
+
"#14b8a6",
|
|
25
|
+
"#f97316",
|
|
26
|
+
"#d946ef",
|
|
27
|
+
"#84cc16",
|
|
28
|
+
],
|
|
29
|
+
"minimalist": [
|
|
30
|
+
"#3b82f6",
|
|
31
|
+
"#10b981",
|
|
32
|
+
"#6366f1",
|
|
33
|
+
"#f59e0b",
|
|
34
|
+
"#ef4444",
|
|
35
|
+
"#8b5cf6",
|
|
36
|
+
"#14b8a6",
|
|
37
|
+
"#f43f5e",
|
|
38
|
+
"#64748b",
|
|
39
|
+
"#000000",
|
|
40
|
+
"#2dd4bf",
|
|
41
|
+
"#fb923c",
|
|
42
|
+
],
|
|
43
|
+
"business": [
|
|
44
|
+
"#1e40af",
|
|
45
|
+
"#0f766e",
|
|
46
|
+
"#4338ca",
|
|
47
|
+
"#b45309",
|
|
48
|
+
"#be123c",
|
|
49
|
+
"#6d28d9",
|
|
50
|
+
"#0e7490",
|
|
51
|
+
"#9f1239",
|
|
52
|
+
"#334155",
|
|
53
|
+
"#111827",
|
|
54
|
+
"#1d4ed8",
|
|
55
|
+
"#047857",
|
|
56
|
+
],
|
|
57
|
+
"literary": [
|
|
58
|
+
"#059669",
|
|
59
|
+
"#d97706",
|
|
60
|
+
"#be185d",
|
|
61
|
+
"#7c3aed",
|
|
62
|
+
"#0284c7",
|
|
63
|
+
"#ea580c",
|
|
64
|
+
"#4d7c0f",
|
|
65
|
+
"#c026d3",
|
|
66
|
+
"#0891b2",
|
|
67
|
+
"#86198f",
|
|
68
|
+
"#db2777",
|
|
69
|
+
"#7c2d12",
|
|
70
|
+
],
|
|
71
|
+
"tech": [
|
|
72
|
+
"#2563eb",
|
|
73
|
+
"#0ea5e9",
|
|
74
|
+
"#06b6d4",
|
|
75
|
+
"#10b981",
|
|
76
|
+
"#8b5cf6",
|
|
77
|
+
"#a855f7",
|
|
78
|
+
"#d946ef",
|
|
79
|
+
"#f43f5e",
|
|
80
|
+
"#6366f1",
|
|
81
|
+
"#14b8a6",
|
|
82
|
+
"#f97316",
|
|
83
|
+
"#84cc16",
|
|
84
|
+
],
|
|
85
|
+
"festive": [
|
|
86
|
+
"#ef4444",
|
|
87
|
+
"#dc2626",
|
|
88
|
+
"#b91c1c",
|
|
89
|
+
"#f59e0b",
|
|
90
|
+
"#d97706",
|
|
91
|
+
"#ea580c",
|
|
92
|
+
"#c2410c",
|
|
93
|
+
"#be123c",
|
|
94
|
+
"#9f1239",
|
|
95
|
+
"#fbbf24",
|
|
96
|
+
"#e11d48",
|
|
97
|
+
"#ca8a04",
|
|
98
|
+
],
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
names: List[str] = [
|
|
102
|
+
"经典",
|
|
103
|
+
"雅致",
|
|
104
|
+
"先锋",
|
|
105
|
+
"深邃",
|
|
106
|
+
"晨光",
|
|
107
|
+
"星穹",
|
|
108
|
+
"暖阳",
|
|
109
|
+
"暮色",
|
|
110
|
+
"清泉",
|
|
111
|
+
"破晓",
|
|
112
|
+
"璀璨",
|
|
113
|
+
"幽蓝",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
categories_list: List[Dict[str, str]] = [
|
|
117
|
+
{"id": "neo-brutalism", "name": "新粗野风"},
|
|
118
|
+
{"id": "minimalist", "name": "极简风"},
|
|
119
|
+
{"id": "business", "name": "商务风"},
|
|
120
|
+
{"id": "literary", "name": "文艺风"},
|
|
121
|
+
{"id": "tech", "name": "科技风"},
|
|
122
|
+
{"id": "festive", "name": "节庆风"},
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# ---------------------------------------------------------------------------
|
|
127
|
+
# Color helpers
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def hex_to_rgb(hex_color: str) -> Tuple[int, int, int]:
|
|
132
|
+
"""Convert a ``#rrggbb`` or ``#rgb`` hex string to an ``(r, g, b)`` tuple."""
|
|
133
|
+
normalized = hex_color.replace("#", "").strip()
|
|
134
|
+
if len(normalized) == 3:
|
|
135
|
+
normalized = "".join(ch + ch for ch in normalized)
|
|
136
|
+
value = int(normalized, 16)
|
|
137
|
+
r = (value >> 16) & 255
|
|
138
|
+
g = (value >> 8) & 255
|
|
139
|
+
b = value & 255
|
|
140
|
+
return r, g, b
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def hex_to_rgba(hex_color: str, alpha: float) -> str:
|
|
144
|
+
"""Convert a hex color + alpha into a CSS ``rgba(...)`` string."""
|
|
145
|
+
r, g, b = hex_to_rgb(hex_color)
|
|
146
|
+
# Mirror the TS rounding: Number(alpha.toFixed(3)) then clamped to [0, 1].
|
|
147
|
+
# ``:g`` drops trailing zeros so 0.0 renders as "0" and 1.0 as "1", exactly
|
|
148
|
+
# like JS's Number-to-string coercion.
|
|
149
|
+
safe_alpha = max(0.0, min(1.0, round(float(alpha), 3)))
|
|
150
|
+
return f"rgba({r}, {g}, {b}, {safe_alpha:g})"
|