wechat-formatter 1.0.0__py3-none-any.whl
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/__init__.py +65 -0
- wechat_formatter/colors.py +150 -0
- wechat_formatter/plugins.py +65 -0
- wechat_formatter/py.typed +0 -0
- wechat_formatter/renderer.py +502 -0
- wechat_formatter/style_utils.py +67 -0
- wechat_formatter/templates.py +578 -0
- wechat_formatter/types.py +97 -0
- wechat_formatter-1.0.0.dist-info/METADATA +174 -0
- wechat_formatter-1.0.0.dist-info/RECORD +13 -0
- wechat_formatter-1.0.0.dist-info/WHEEL +5 -0
- wechat_formatter-1.0.0.dist-info/licenses/LICENSE +21 -0
- wechat_formatter-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -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})"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Custom mistune inline plugins for the extra Markdown syntax.
|
|
2
|
+
|
|
3
|
+
These mirror the three marked extensions registered in the TypeScript source:
|
|
4
|
+
|
|
5
|
+
* ``==highlight==`` -> ``<mark>``
|
|
6
|
+
* ``^superscript^`` -> ``<sup>``
|
|
7
|
+
* ``~subscript~`` -> ``<sub>`` (skipping ``~~`` so strikethrough wins)
|
|
8
|
+
|
|
9
|
+
The render functions themselves are wired up in :mod:`template_engine.renderer`
|
|
10
|
+
because they depend on the active template's theme color.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Match, Optional
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # pragma: no cover
|
|
19
|
+
from mistune import Markdown
|
|
20
|
+
from mistune.core import InlineState
|
|
21
|
+
from mistune.inline_parser import InlineParser
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _parse_with_children(inline: "InlineParser", m: Match[str], state: "InlineState",
|
|
25
|
+
pattern: str, token_type: str) -> Optional[int]:
|
|
26
|
+
"""Shared helper: match ``pattern`` at the trigger position and emit a
|
|
27
|
+
token whose children are the recursively-parsed inner text."""
|
|
28
|
+
src = state.src
|
|
29
|
+
matched = re.match(pattern, src[m.start():])
|
|
30
|
+
if not matched:
|
|
31
|
+
return None
|
|
32
|
+
child = state.copy()
|
|
33
|
+
child.src = matched.group(1)
|
|
34
|
+
children = inline.render(child)
|
|
35
|
+
state.append_token({"type": token_type, "children": children})
|
|
36
|
+
return m.start() + matched.end()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_highlight(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
|
|
40
|
+
# TS: /^==([^=]+)==/
|
|
41
|
+
return _parse_with_children(inline, m, state, r"^==([^=]+)==", "highlight")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_superscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
|
|
45
|
+
# TS: /^\^([^^]+)\^/
|
|
46
|
+
return _parse_with_children(inline, m, state, r"^\^([^^]+)\^", "superscript")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def parse_subscript(inline: "InlineParser", m: Match[str], state: "InlineState") -> Optional[int]:
|
|
50
|
+
# TS: /^~([^~\s][^~]*[^~\s])~/
|
|
51
|
+
# Guard against ~~ (strikethrough) so the strikethrough plugin owns those.
|
|
52
|
+
if state.src[m.start():m.start() + 2] == "~~":
|
|
53
|
+
return None
|
|
54
|
+
return _parse_with_children(inline, m, state, r"^~([^~\s][^~]*[^~\s])~", "subscript")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def register_extra_syntax(md: "Markdown") -> None:
|
|
58
|
+
"""Register the three custom inline rules on a mistune Markdown instance.
|
|
59
|
+
|
|
60
|
+
Render methods are expected to be registered on the renderer separately
|
|
61
|
+
(they need access to the active template).
|
|
62
|
+
"""
|
|
63
|
+
md.inline.register("highlight", r"==", parse_highlight)
|
|
64
|
+
md.inline.register("superscript", r"\^", parse_superscript)
|
|
65
|
+
md.inline.register("subscript", r"~", parse_subscript)
|
|
File without changes
|