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.
@@ -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