feat: add myblog blog writer skill
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
---
|
||||
name: myblog-blog-writer
|
||||
description: Use when creating or updating Chinese VuePress Theme Hope blog posts for the MyBlog repository from a topic, including choosing the right content directory, drafting frontmatter, writing the article, updating sidebars when needed, and validating the build.
|
||||
---
|
||||
|
||||
# MyBlog Blog Writer
|
||||
|
||||
Use this skill when the user asks to write a MyBlog article from a topic, place a post in the right category, generate a VuePress blog draft, or update an existing MyBlog post.
|
||||
|
||||
## Repository checks
|
||||
|
||||
- Work in `/home/liumangmang/GiteaRepos/LiuMangMang/MyBlog` or confirm the current repository has `package.json`, `src/.vuepress/config.ts`, and `src/.vuepress/sidebar.ts`.
|
||||
- Respect `AGENTS.md`: VuePress v2, VuePress Theme Hope, TypeScript, Chinese zh-CN content, 2-space indentation, single quotes in TS.
|
||||
- Do not modify unrelated user changes. Check `git status --short` before editing.
|
||||
|
||||
## Planning helper
|
||||
|
||||
Run the bundled planner before creating a new article:
|
||||
|
||||
```bash
|
||||
python3 .codex/skills/myblog-blog-writer/scripts/plan_article.py "文章主题"
|
||||
```
|
||||
|
||||
Use the JSON result as a draft, then apply judgment from the existing tree and nearby posts. If `needs_confirmation` is true, ask the user which target directory to use before writing the article.
|
||||
|
||||
## New category strategy
|
||||
|
||||
- Prefer existing directories when the topic clearly matches one.
|
||||
- If the topic does not fit an existing directory, create a new subcategory under an existing top-level section. Do not create new top-level sections by default.
|
||||
- Allowed top-level targets for new subcategories:
|
||||
- `src/programming/`
|
||||
- `src/apps/`
|
||||
- `src/tools/`
|
||||
- `src/work/`
|
||||
- `src/ai/`
|
||||
- For low-confidence matches, choose the most suitable top-level section from the topic and create a `kebab-case` subdirectory there. Only set `needs_confirmation` when multiple top-level sections are equally reasonable.
|
||||
- New subcategory directory names should be `kebab-case`. Chinese names are allowed, but do not include spaces or special symbols.
|
||||
- Put the first article in the new subcategory directory. The article filename should still be generated from the article topic.
|
||||
- Create `README.md` in every new subcategory using VuePress Catalog format:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 自然中文分类名
|
||||
index: false
|
||||
icon: fa6-solid:file-lines
|
||||
category:
|
||||
- 分类
|
||||
---
|
||||
|
||||
<Catalog />
|
||||
```
|
||||
|
||||
- The README title should be natural Chinese and does not need to match the slug.
|
||||
- Default placement for unknown subcategories:
|
||||
- Technical/development/framework topics: `src/programming/<topic-slug>/`
|
||||
- Tools, productivity, clients: `src/tools/<topic-slug>/`
|
||||
- Self-hosted services and deployment: `src/apps/<topic-slug>/`
|
||||
- Work records, business, delivery: `src/work/project-summary/<topic-slug>/`
|
||||
- AI, models, agents: `src/ai/<topic-slug>/`
|
||||
- If a new category is under `src/ai/`, inspect and update `src/.vuepress/sidebar.ts` because the AI sidebar is hand-written.
|
||||
- Other sections generally use `children: 'structure'`; creating the directory and `README.md` is usually enough.
|
||||
|
||||
## Directory heuristics
|
||||
|
||||
- AI tools, LLM, Agent, Codex, Claude, OpenCode, ChatGPT: `src/ai/`
|
||||
- Docker, container images, Compose: `src/programming/docker/`
|
||||
- Java, Spring, Maven, JDK: `src/programming/backend/java/功能整理/`
|
||||
- Go, Gin, GORM, concurrency: match a subdirectory under `src/programming/backend/go/`
|
||||
- Linux, Mint, SSH, Nginx, VNC, system configuration: match a subdirectory under `src/programming/linux/`
|
||||
- Vue, frontend, CSS, HTML, VSCode, Cursor: match a subdirectory under `src/programming/frontend/`
|
||||
- Self-hosted services, Jellyfin, RustDesk, NAS apps: `src/apps/`
|
||||
- Windows tools, Scoop, WSL, MobaXterm, Google/Gitee tools: `src/tools/`
|
||||
- Work summaries, delivery notes, permissions, business records: `src/work/project-summary/`
|
||||
|
||||
## Research
|
||||
|
||||
Browse or otherwise verify current information before writing about topics that change over time, especially AI products, software versions, installation steps, deployment commands, pricing, APIs, or package names. Prefer official docs and release pages for technical facts.
|
||||
|
||||
## Article format
|
||||
|
||||
Use Markdown with YAML frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: 文章标题
|
||||
icon: fa6-solid:file-lines
|
||||
date: YYYY-MM-DD
|
||||
category:
|
||||
- 分类
|
||||
tag:
|
||||
- 标签
|
||||
---
|
||||
```
|
||||
|
||||
Every new article must include `<!-- more -->` after the opening summary. Write in Chinese, in a practical notes/tutorial style. Prefer this shape unless the topic demands otherwise:
|
||||
|
||||
- Background or problem
|
||||
- Applicable scenario
|
||||
- Step-by-step process
|
||||
- Common commands or configuration snippets
|
||||
- Troubleshooting
|
||||
- Summary
|
||||
|
||||
Keep headings clear and concrete. Match nearby posts for naming, tone, and icon style.
|
||||
|
||||
## Sidebar rules
|
||||
|
||||
- Most areas use `children: 'structure'`; no sidebar edit is needed there.
|
||||
- `src/ai/` currently has hand-written sidebar entries in `src/.vuepress/sidebar.ts`. Add new AI posts to the appropriate group when needed.
|
||||
- If the planner marks `requires_sidebar_update`, inspect `src/.vuepress/sidebar.ts` before editing.
|
||||
|
||||
## Validation
|
||||
|
||||
After modifying blog content or sidebar config, run:
|
||||
|
||||
```bash
|
||||
npm run docs:build
|
||||
```
|
||||
|
||||
If the build cannot run, report the exact blocker. For large or uncertain changes, run `npm run docs:dev` or `npm run docs:clean-dev` when useful.
|
||||
@@ -0,0 +1,7 @@
|
||||
interface:
|
||||
display_name: "MyBlog Blog Writer"
|
||||
short_description: "根据主题生成 MyBlog 中文文章"
|
||||
default_prompt: "Use $myblog-blog-writer to write a Chinese MyBlog article from this topic and place it in the right category."
|
||||
|
||||
policy:
|
||||
allow_implicit_invocation: true
|
||||
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
target_dir="${CODEX_HOME:-$HOME/.codex}/skills"
|
||||
target="${target_dir}/myblog-blog-writer"
|
||||
|
||||
mkdir -p "${target_dir}"
|
||||
|
||||
if [[ -e "${target}" && ! -L "${target}" ]]; then
|
||||
echo "Refusing to replace existing non-symlink: ${target}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ln -sfn "${skill_dir}" "${target}"
|
||||
echo "Linked ${target} -> ${skill_dir}"
|
||||
+448
@@ -0,0 +1,448 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Plan a MyBlog article path and frontmatter from a topic."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import datetime as dt
|
||||
import json
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[4]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Rule:
|
||||
keywords: tuple[str, ...]
|
||||
directory: str
|
||||
category: str
|
||||
tags: tuple[str, ...]
|
||||
icon: str
|
||||
requires_sidebar_update: bool = False
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class NewCategoryRule:
|
||||
keywords: tuple[str, ...]
|
||||
top_directory: str
|
||||
category: str
|
||||
tags: tuple[str, ...]
|
||||
icon: str
|
||||
requires_sidebar_update: bool = False
|
||||
nested_under_slug: str | None = None
|
||||
|
||||
|
||||
RULES: tuple[Rule, ...] = (
|
||||
Rule(
|
||||
('ai', 'llm', 'agent', 'codex', 'claude', 'opencode', 'chatgpt', 'openai', 'gemini', 'iflow', 'openclaw'),
|
||||
'src/ai',
|
||||
'AI',
|
||||
('AI', '工具'),
|
||||
'fa6-solid:robot',
|
||||
True,
|
||||
),
|
||||
Rule(
|
||||
('docker', '容器', '镜像', 'compose', 'docker-compose'),
|
||||
'src/programming/docker',
|
||||
'Docker',
|
||||
('Docker', '容器'),
|
||||
'mdi:docker',
|
||||
),
|
||||
Rule(
|
||||
('java', 'spring', 'spring boot', 'maven', 'jdk', 'jar'),
|
||||
'src/programming/backend/java/功能整理',
|
||||
'Java',
|
||||
('Java', '后端'),
|
||||
'mdi:language-java',
|
||||
),
|
||||
Rule(
|
||||
('gin', 'gorm'),
|
||||
'src/programming/backend/go/Web开发数据库',
|
||||
'Go',
|
||||
('Go', '后端'),
|
||||
'mdi:language-go',
|
||||
),
|
||||
Rule(
|
||||
('go', 'golang', '并发', 'goroutine', 'channel'),
|
||||
'src/programming/backend/go/Go并发模型',
|
||||
'Go',
|
||||
('Go', '并发'),
|
||||
'mdi:language-go',
|
||||
),
|
||||
Rule(
|
||||
('linux mint', 'mint'),
|
||||
'src/programming/linux/Linux_Mint',
|
||||
'Linux',
|
||||
('Linux Mint', '系统配置'),
|
||||
'simple-icons:linuxmint',
|
||||
),
|
||||
Rule(
|
||||
('linux', 'ssh', 'nginx', 'vnc', '系统配置', '凝思'),
|
||||
'src/programming/linux/基础',
|
||||
'Linux',
|
||||
('Linux', '运维'),
|
||||
'mdi:linux',
|
||||
),
|
||||
Rule(
|
||||
('vue', '前端'),
|
||||
'src/programming/frontend/vue',
|
||||
'前端',
|
||||
('Vue', '前端'),
|
||||
'mdi:vuejs',
|
||||
),
|
||||
Rule(
|
||||
('css',),
|
||||
'src/programming/frontend/css',
|
||||
'前端',
|
||||
('CSS', '前端'),
|
||||
'mdi:language-css3',
|
||||
),
|
||||
Rule(
|
||||
('html',),
|
||||
'src/programming/frontend/html',
|
||||
'前端',
|
||||
('HTML', '前端'),
|
||||
'mdi:language-html5',
|
||||
),
|
||||
Rule(
|
||||
('vscode', 'cursor'),
|
||||
'src/programming/frontend/tools',
|
||||
'前端工具',
|
||||
('工具', '前端'),
|
||||
'mdi:tools',
|
||||
),
|
||||
Rule(
|
||||
('自建', 'jellyfin', 'rustdesk', 'nas', '服务'),
|
||||
'src/apps',
|
||||
'应用',
|
||||
('自建服务', '应用'),
|
||||
'mdi:apps',
|
||||
),
|
||||
Rule(
|
||||
('windows', 'scoop', 'wsl', 'mobaxterm', 'google', 'gitee', '工具'),
|
||||
'src/tools',
|
||||
'工具',
|
||||
('工具',),
|
||||
'mdi:toolbox',
|
||||
),
|
||||
Rule(
|
||||
('工作总结', '项目交付', '权限', '业务记录', '项目总结'),
|
||||
'src/work/project-summary',
|
||||
'工作',
|
||||
('工作记录', '项目总结'),
|
||||
'mdi:book-open-page-variant',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
NEW_CATEGORY_RULES: tuple[NewCategoryRule, ...] = (
|
||||
NewCategoryRule(
|
||||
('ai', 'llm', 'agent', 'agents', '模型', '大模型', '智能体', 'codex', 'claude', 'opencode', 'chatgpt', 'openai', 'gemini', 'iflow', 'openclaw'),
|
||||
'src/ai',
|
||||
'AI',
|
||||
('AI', '工具'),
|
||||
'fa6-solid:robot',
|
||||
True,
|
||||
),
|
||||
NewCategoryRule(
|
||||
('自建', '部署', '服务', '相册', '影视', 'nas', 'docker compose', 'compose', 'immich', 'jellyfin', 'rustdesk'),
|
||||
'src/apps',
|
||||
'应用',
|
||||
('自建服务', '应用'),
|
||||
'mdi:apps',
|
||||
),
|
||||
NewCategoryRule(
|
||||
('工具', '效率', '客户端', '笔记', '浏览器', '插件', '工作流', 'obsidian', 'scoop', 'wsl', 'mobaxterm', 'google', 'gitee'),
|
||||
'src/tools',
|
||||
'工具',
|
||||
('工具', '效率'),
|
||||
'mdi:toolbox',
|
||||
),
|
||||
NewCategoryRule(
|
||||
('工作', '业务', '交付', '记录', '合同', '审批', '流程', '项目总结', '客户', '需求'),
|
||||
'src/work/project-summary',
|
||||
'工作',
|
||||
('工作记录', '项目总结'),
|
||||
'mdi:book-open-page-variant',
|
||||
),
|
||||
NewCategoryRule(
|
||||
('python', '数据分析', '框架', '开发', '编程', '后端', '前端', '语言', '环境搭建', '数据库', '算法'),
|
||||
'src/programming',
|
||||
'编程',
|
||||
('编程',),
|
||||
'fa6-solid:code',
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def normalize_topic(topic: str) -> str:
|
||||
return topic.strip()
|
||||
|
||||
|
||||
def slugify(topic: str) -> str:
|
||||
cleaned = re.sub(r'[\\/:*?"<>|#%{}[\]^`]+', '', topic.strip())
|
||||
cleaned = re.sub(r'\s+', '-', cleaned)
|
||||
cleaned = cleaned.strip('.-_')
|
||||
|
||||
ascii_slug = re.sub(r'[^a-zA-Z0-9\u4e00-\u9fff._-]+', '-', cleaned)
|
||||
ascii_slug = re.sub(r'-{2,}', '-', ascii_slug).strip('-')
|
||||
|
||||
if re.search(r'[\u4e00-\u9fff]', ascii_slug):
|
||||
return ascii_slug or '未命名文章'
|
||||
|
||||
return ascii_slug.lower() or 'untitled'
|
||||
|
||||
|
||||
def score_rule(topic_lower: str, rule: Rule) -> int:
|
||||
return sum(1 for keyword in rule.keywords if keyword.lower() in topic_lower)
|
||||
|
||||
|
||||
def specificity_score(topic_lower: str, rule: Rule) -> int:
|
||||
return sum(len(keyword) for keyword in rule.keywords if keyword.lower() in topic_lower)
|
||||
|
||||
|
||||
def score_new_category_rule(topic_lower: str, rule: NewCategoryRule) -> int:
|
||||
return sum(1 for keyword in rule.keywords if keyword.lower() in topic_lower)
|
||||
|
||||
|
||||
def new_category_specificity_score(topic_lower: str, rule: NewCategoryRule) -> int:
|
||||
return sum(len(keyword) for keyword in rule.keywords if keyword.lower() in topic_lower)
|
||||
|
||||
|
||||
def choose_rules(topic: str) -> tuple[list[tuple[Rule, int]], bool]:
|
||||
topic_lower = topic.lower()
|
||||
scored = [(rule, score_rule(topic_lower, rule)) for rule in RULES]
|
||||
matches = sorted(
|
||||
[(rule, score) for rule, score in scored if score > 0],
|
||||
key=lambda item: (item[1], specificity_score(topic_lower, item[0])),
|
||||
reverse=True,
|
||||
)
|
||||
if not matches:
|
||||
fallback = [
|
||||
Rule((), 'src/programming', '编程', ('编程',), 'fa6-solid:code'),
|
||||
Rule((), 'src/apps', '应用', ('自建服务', '应用'), 'mdi:apps'),
|
||||
Rule((), 'src/tools', '工具', ('工具',), 'mdi:toolbox'),
|
||||
Rule((), 'src/work/project-summary', '工作', ('工作记录',), 'mdi:book-open-page-variant'),
|
||||
Rule((), 'src/ai', 'AI', ('AI', '工具'), 'fa6-solid:robot', True),
|
||||
]
|
||||
return [(rule, 0) for rule in fallback], False
|
||||
|
||||
top_score = matches[0][1]
|
||||
top_specificity = specificity_score(topic_lower, matches[0][0])
|
||||
top_matches = [
|
||||
item
|
||||
for item in matches
|
||||
if item[1] == top_score and specificity_score(topic_lower, item[0]) == top_specificity
|
||||
]
|
||||
return matches[:3], len(top_matches) > 1
|
||||
|
||||
|
||||
def choose_new_category_rule(topic: str) -> tuple[NewCategoryRule, int, bool]:
|
||||
topic_lower = topic.lower()
|
||||
scored = [(rule, score_new_category_rule(topic_lower, rule)) for rule in NEW_CATEGORY_RULES]
|
||||
matches = sorted(
|
||||
[(rule, score) for rule, score in scored if score > 0],
|
||||
key=lambda item: (item[1], new_category_specificity_score(topic_lower, item[0])),
|
||||
reverse=True,
|
||||
)
|
||||
if not matches:
|
||||
return (
|
||||
NewCategoryRule(
|
||||
(),
|
||||
'src/programming',
|
||||
'编程',
|
||||
('编程',),
|
||||
'fa6-solid:code',
|
||||
),
|
||||
0,
|
||||
False,
|
||||
)
|
||||
|
||||
top_score = matches[0][1]
|
||||
top_specificity = new_category_specificity_score(topic_lower, matches[0][0])
|
||||
top_matches = [
|
||||
item
|
||||
for item in matches
|
||||
if item[1] == top_score and new_category_specificity_score(topic_lower, item[0]) == top_specificity
|
||||
]
|
||||
return matches[0][0], matches[0][1], len(top_matches) > 1
|
||||
|
||||
|
||||
def existing_files(directory: str) -> list[str]:
|
||||
path = REPO_ROOT / directory
|
||||
if not path.exists():
|
||||
return []
|
||||
return sorted(child.name for child in path.glob('*.md'))[:12]
|
||||
|
||||
|
||||
def infer_category_slug(topic: str, rule: NewCategoryRule) -> str:
|
||||
topic_lower = topic.lower()
|
||||
latin_keywords = [
|
||||
keyword
|
||||
for keyword in rule.keywords
|
||||
if re.search(r'[a-zA-Z0-9]', keyword) and keyword.lower() in topic_lower
|
||||
]
|
||||
if latin_keywords:
|
||||
return slugify(max(latin_keywords, key=len))
|
||||
|
||||
if '合同' in topic and '审批' in topic:
|
||||
return '合同审批'
|
||||
|
||||
chinese_keywords = [
|
||||
keyword
|
||||
for keyword in rule.keywords
|
||||
if re.search(r'[\u4e00-\u9fff]', keyword) and keyword in topic
|
||||
]
|
||||
if chinese_keywords:
|
||||
return slugify(''.join(chinese_keywords[:2]))
|
||||
|
||||
return slugify(topic)
|
||||
|
||||
|
||||
def frontmatter(topic: str, rule: Rule, date: str) -> dict[str, object]:
|
||||
return {
|
||||
'title': topic,
|
||||
'icon': rule.icon,
|
||||
'date': date,
|
||||
'category': [rule.category],
|
||||
'tag': list(rule.tags),
|
||||
}
|
||||
|
||||
|
||||
def new_category_readme_frontmatter(topic: str, rule: NewCategoryRule) -> dict[str, object]:
|
||||
return {
|
||||
'title': topic,
|
||||
'index': False,
|
||||
'icon': rule.icon,
|
||||
'category': [rule.category],
|
||||
}
|
||||
|
||||
|
||||
def article_frontmatter_from_new_category(
|
||||
topic: str,
|
||||
rule: NewCategoryRule,
|
||||
date: str,
|
||||
) -> dict[str, object]:
|
||||
return {
|
||||
'title': topic,
|
||||
'icon': rule.icon,
|
||||
'date': date,
|
||||
'category': [rule.category],
|
||||
'tag': list(rule.tags),
|
||||
}
|
||||
|
||||
|
||||
def build_new_category_suggestion(topic: str, date: str) -> tuple[dict[str, object], bool]:
|
||||
rule, score, ambiguous = choose_new_category_rule(topic)
|
||||
directory_slug = infer_category_slug(topic, rule)
|
||||
if rule.nested_under_slug is not None:
|
||||
directory = f'{rule.top_directory}/{rule.nested_under_slug}/{directory_slug}'
|
||||
else:
|
||||
directory = f'{rule.top_directory}/{directory_slug}'
|
||||
|
||||
filename = f'{slugify(topic)}.md'
|
||||
readme_path = f'{directory}/README.md'
|
||||
article_path = f'{directory}/{filename}'
|
||||
|
||||
return (
|
||||
{
|
||||
'top_directory': rule.top_directory,
|
||||
'directory': directory,
|
||||
'directory_slug': directory_slug,
|
||||
'readme_path': readme_path,
|
||||
'readme_frontmatter': new_category_readme_frontmatter(topic, rule),
|
||||
'readme_body': '<Catalog />',
|
||||
'article_path': article_path,
|
||||
'article_frontmatter': article_frontmatter_from_new_category(topic, rule, date),
|
||||
'requires_sidebar_update': rule.requires_sidebar_update,
|
||||
'score': score,
|
||||
},
|
||||
ambiguous,
|
||||
)
|
||||
|
||||
|
||||
def build_plan(topic: str, date: str) -> dict[str, object]:
|
||||
normalized = normalize_topic(topic)
|
||||
candidates, needs_confirmation = choose_rules(normalized)
|
||||
new_category_suggestion, new_category_ambiguous = build_new_category_suggestion(normalized, date)
|
||||
primary = candidates[0][0]
|
||||
filename = f'{slugify(normalized)}.md'
|
||||
suggested_directory = str(new_category_suggestion['directory'])
|
||||
broad_directory_match = primary.directory in {
|
||||
'src/ai',
|
||||
'src/apps',
|
||||
'src/tools',
|
||||
'src/work/project-summary',
|
||||
}
|
||||
should_create_new_category = (
|
||||
candidates[0][1] == 0
|
||||
or (broad_directory_match and not (REPO_ROOT / suggested_directory).exists())
|
||||
)
|
||||
recommended_directory = (
|
||||
str(new_category_suggestion['directory'])
|
||||
if should_create_new_category
|
||||
else primary.directory
|
||||
)
|
||||
path = (
|
||||
str(new_category_suggestion['article_path'])
|
||||
if should_create_new_category
|
||||
else f'{primary.directory}/{filename}'
|
||||
)
|
||||
recommended_frontmatter = (
|
||||
new_category_suggestion['article_frontmatter']
|
||||
if should_create_new_category
|
||||
else frontmatter(normalized, primary, date)
|
||||
)
|
||||
requires_sidebar_update = (
|
||||
bool(new_category_suggestion['requires_sidebar_update'])
|
||||
if should_create_new_category
|
||||
else primary.requires_sidebar_update
|
||||
)
|
||||
|
||||
return {
|
||||
'topic': normalized,
|
||||
'recommended_directory': recommended_directory,
|
||||
'filename': filename,
|
||||
'path': path,
|
||||
'frontmatter': recommended_frontmatter,
|
||||
'requires_sidebar_update': requires_sidebar_update,
|
||||
'should_create_new_category': should_create_new_category,
|
||||
'new_category_suggestion': new_category_suggestion,
|
||||
'needs_confirmation': (
|
||||
new_category_ambiguous if should_create_new_category else needs_confirmation
|
||||
),
|
||||
'candidates': [
|
||||
{
|
||||
'directory': rule.directory,
|
||||
'score': score,
|
||||
'category': rule.category,
|
||||
'tags': list(rule.tags),
|
||||
'requires_sidebar_update': rule.requires_sidebar_update,
|
||||
'existing_files_sample': existing_files(rule.directory),
|
||||
}
|
||||
for rule, score in candidates
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description='Plan a MyBlog article location.')
|
||||
parser.add_argument('topic', help='Article topic text')
|
||||
parser.add_argument(
|
||||
'--date',
|
||||
default=dt.date.today().isoformat(),
|
||||
help='Frontmatter date in YYYY-MM-DD format',
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
print(json.dumps(build_plan(args.topic, args.date), ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user