forked from Ikalus1988/MisakaNet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnew_lesson.py
More file actions
206 lines (160 loc) · 5.95 KB
/
Copy pathnew_lesson.py
File metadata and controls
206 lines (160 loc) · 5.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#!/usr/bin/env python3
"""
交互式贡献模板 — 引导填写 Lesson 并自动生成标准 Markdown 文件。
用法:
python3 scripts/new_lesson.py ← 交互式向导
python3 scripts/new_lesson.py --batch ← 一行参数快速创建
"""
import argparse
import json
import os
import sys
import re
from datetime import datetime, timezone
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
LESSONS_DIR = REPO / "lessons"
DOMAINS = sorted([
"rag", "feishu", "development", "devops", "general",
"python", "wsl", "git", "docker", "network",
"security", "frontend", "testing", "ci-cd",
])
def _slugify(title: str) -> str:
import unicodedata
# 1. Normalize unicode (NFKD) to decompose accents/diacritics
normalized = unicodedata.normalize('NFKD', title)
# 2. Lowercase and replace slashes and backslashes with hyphens explicitly
slug = normalized.lower().strip()
slug = slug.replace('/', '-').replace('\\', '-')
# 3. Replace non-alphanumeric (except Chinese characters \u4e00-\u9fff) with hyphens
slug = re.sub(r"[^a-z0-9\u4e00-\u9fff]+", "-", slug)
# 4. Collapse consecutive hyphens and strip leading/trailing
slug = re.sub(r"-+", "-", slug).strip("-")
# 5. Cap length at 60 characters, being careful not to end with a trailing hyphen
slug = slug[:60].strip("-")
# 6. Fallback if the slug is empty (e.g. purely emojis or special characters)
if not slug:
now = datetime.now(timezone.utc)
slug = f"lesson-{now.strftime('%Y%m%d-%H%M%S')}"
# 7. Protect against Windows reserved filenames (CON, PRN, AUX, NUL, COM1-COM9, LPT1-LPT9)
reserved_names = {"con", "prn", "aux", "nul"}
for i in range(1, 10):
reserved_names.add(f"com{i}")
reserved_names.add(f"lpt{i}")
if slug.lower() in reserved_names:
slug = f"safe-{slug}"
return slug
def _input_or_default(prompt: str, default: str = "") -> str:
if default:
val = input(f"{prompt} [{default}]: ").strip()
else:
val = input(f"{prompt}: ").strip()
return val if val else default
def _choose_domain() -> str:
print("\n 可选领域:")
for i, d in enumerate(DOMAINS, 1):
print(f" {i}. {d}")
while True:
choice = input(" 选择编号或直接输入领域名: ").strip()
if choice.isdigit():
idx = int(choice) - 1
if 0 <= idx < len(DOMAINS):
return DOMAINS[idx]
elif choice:
return choice
print(" 无效选择,请重试")
def interactive():
print("\n" + "=" * 50)
print(" MisakaNet — 贡献新 Lesson")
print("=" * 50)
title = _input_or_default("问题/踩坑标题")
if not title:
print(" ❌ 标题不能为空")
return False
domain = _choose_domain()
tags_raw = _input_or_default("标签(逗号分隔)")
tags = [t.strip() for t in tags_raw.split(",") if t.strip()] if tags_raw else []
print("\n --- 以下是模板引导,逐项填写 ---")
problem = _input_or_default(" 1. 遇到什么问题")
root_cause = _input_or_default(" 2. 根因是什么")
fix = _input_or_default(" 3. 怎么修复的")
verify = _input_or_default(" 4. 怎么验证修复结果")
source = os.environ.get("MISAKANET_NODE_ID", "manual")
now = datetime.now(timezone.utc)
frontmatter = {
"title": title,
"domain": domain,
"source": source,
"status": "published",
"tags": tags,
"created": now.strftime("%Y-%m-%d %H:%M:%S UTC"),
"updated": now.strftime("%Y-%m-%d %H:%M:%S UTC"),
}
body = f"""---
{json.dumps(frontmatter, ensure_ascii=False)}
---
## 问题
{problem}
## 根因
{root_cause}
## 修复方案
{fix}
## 验证
{verify}
---
*自动生成于 {now.strftime('%Y-%m-%d %H:%M:%S')} UTC | 来源: {source}*
"""
filename = f"{_slugify(title)}.md"
filepath = LESSONS_DIR / filename
print(f"\n 即将写入: {filepath}")
print(f" 标题: {title}")
print(f" 领域: {domain}")
print(f" 标签: {tags}")
confirm = input(" 确认写入? (Y/n): ").strip().lower()
if confirm == "n":
print(" ❌ 已取消")
return False
LESSONS_DIR.mkdir(parents=True, exist_ok=True)
filepath.write_text(body, encoding="utf-8")
from misakanet.profile import increment_lesson
increment_lesson()
print(f" ✅ lesson 已创建: {filepath}")
print(f"\n 查看: cat {filepath}")
print(f" 同步: python3 scripts/queue_lesson.py --file {filepath}")
return True
def batch(title: str, domain: str, content: str, tags: list[str] = None):
"""非交互式快速创建,适合 agent 调用。"""
source = os.environ.get("MISAKANET_NODE_ID", "agent")
now = datetime.now(timezone.utc)
frontmatter = {
"title": title,
"domain": domain or "general",
"source": source,
"status": "published",
"tags": tags or [],
"created": now.strftime("%Y-%m-%d %H:%M:%S UTC"),
"updated": now.strftime("%Y-%m-%d %H:%M:%S UTC"),
}
body = f"""---
{json.dumps(frontmatter, ensure_ascii=False)}
---
{content}
---
*自动生成于 {now.strftime('%Y-%m-%d %H:%M:%S')} UTC | 来源: {source}*
"""
filename = f"{_slugify(title)}.md"
filepath = LESSONS_DIR / filename
LESSONS_DIR.mkdir(parents=True, exist_ok=True)
filepath.write_text(body, encoding="utf-8")
print(f" ✅ {filepath}")
return filepath
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="贡献模板 — 创建标准 Lesson")
parser.add_argument("--batch", nargs=4, metavar=("title", "domain", "content", "tags"),
help="批量模式: 标题 领域 内容 标签(逗号分隔)")
args = parser.parse_args()
if args.batch:
batch(args.batch[0], args.batch[1], args.batch[2],
[t.strip() for t in args.batch[3].split(",") if t.strip()])
else:
interactive()