---
name: mark2pdf-caller-script
description: |
  创建调用 mark2pdf md2pdf 的外部脚本。当用户需要从其他目录批量转换 Markdown 为 PDF，
  且需要自定义预处理逻辑（如 JSON 格式化、frontmatter 注入）时使用此技能。
  触发场景：用户提到"调用 mark2pdf"、"批量转 PDF"、"外部目录转换 md"。
---

# mark2pdf 外部调用脚本编写指南

---

<Toc />

---

## 推荐工作流程

```
┌─────────────────────────────────────────────────────────────
│  Step 1: 在 mark2pdf 项目中编写脚本                             
│  位置: mark2pdf/_logs/调用/scripts/                           
└─────────────────────────────────────────────────────────────
                           ↓
┌─────────────────────────────────────────────────────────────
│  Step 2: 在 mark2pdf 中测试                                    
│  $ cd mark2pdf/_logs/调用/scripts                             
│  $ uv run your_script.py test.md --verbose                  
└─────────────────────────────────────────────────────────────
                           ↓
┌─────────────────────────────────────────────────────────────
│  Step 3: 拷贝到目标项目                                      
│  $ cp your_script.py /path/to/target/project/               
│  $ cp mark2pdf.yaml /path/to/target/project/  (可选)         
└─────────────────────────────────────────────────────────────
```

---

### 目录结构

```
mark2pdf/
└── _logs/调用/
    ├── 脚本编写说明.md      # 本文档
    ├── 简化外部调用方案.md  # 设计方案
    ├── scripts/             # 脚本开发目录（新建）
    │   ├── run_xxx.py       # 开发中的脚本
    │   ├── mark2pdf.yaml     # 测试用配置
    │   └── test/            # 测试用 md 文件
    └── run_mark2pdf_md2pdf.py  # 现有脚本参考
```

---

### 拷贝脚本

```bash
# 快速拷贝到目标目录
TARGET="/path/to/target/project"
cp mark2pdf/_logs/调用/scripts/run_xxx.py "$TARGET/"
cp mark2pdf/_logs/调用/scripts/mark2pdf.yaml "$TARGET/"  # 可选
```

---

## 核心架构

```
调用目录/
├── run_xxx.py          # 调用脚本（本指南生成的内容）
├── mark2pdf.yaml        # 配置文件（可选）
├── template/           # 本地模板（可选）
├── {input_dir}/        # 输入目录（含 .md 文件）
│   ├── tmp/            # 预处理后的临时文件
│   └── pdf/            # 输出 PDF
```

---

## 脚本模板

```python
# /// script
# dependencies = ["click", "pyyaml"]
# ///
"""外部调用 mark2pdf md2pdf"""
import re
import subprocess
import fnmatch
from pathlib import Path
from datetime import date
import click
import yaml

SCRIPT_DIR = Path(__file__).parent
CONFIG_FILE = "mark2pdf.yaml"


def load_config() -> dict:
    """加载配置"""
    p = SCRIPT_DIR / CONFIG_FILE
    return yaml.safe_load(p.read_text()) if p.exists() else {}


def resolve_template(config: dict) -> str | None:
    """解析模板：本地优先，否则系统模板"""
    t = config.get("template", {})
    if local := t.get("local"):
        p = SCRIPT_DIR / local
        if p.exists():
            return str(p.resolve())
    return t.get("name")


def get_frontmatter(config: dict, filename: str) -> dict:
    """获取 frontmatter 默认值（支持 overrides）"""
    fm = dict(config.get("frontmatter", {}))
    if fm.get("date") == "auto":
        fm["date"] = date.today().isoformat()
    for o in config.get("overrides", []):
        if fnmatch.fnmatch(filename, o.get("pattern", "")):
            fm.update(o.get("frontmatter", {}))
    return fm


def ensure_frontmatter(content: str, defaults: dict) -> str:
    """确保文档有 frontmatter"""
    fm_pat = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL)
    m = fm_pat.match(content)
    existing = yaml.safe_load(m.group(1)) if m else {}
    merged = {**defaults, **(existing or {})}
    fm_str = yaml.dump(merged, allow_unicode=True).strip()
    body = content[m.end():] if m else content
    return f"---\n{fm_str}\n---\n\n{body}"


# ============ 自定义预处理（按需修改） ============

def preprocess(content: str) -> str:
    """
    自定义预处理逻辑
    示例：将 ```json 代码块转为引用格式
    """
    # TODO: 根据需求实现
    return content


# ============ CLI ============

@click.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True))
@click.argument("files", nargs=-1, required=True)
@click.option("--input-dir", default="input", help="输入目录")
@click.option("--template", help="覆盖模板")
@click.option("--verbose", is_flag=True)
@click.pass_context
def cli(ctx, files, input_dir, template, verbose):
    """预处理 Markdown 并调用 md2pdf"""
    config = load_config()
    src = Path(input_dir)
    tmp, out = src / "tmp", src / "pdf"
    tmp.mkdir(exist_ok=True)
    out.mkdir(exist_ok=True)

    tpl = template or resolve_template(config)

    for f in files:
        md = src / f if not Path(f).is_absolute() else Path(f)
        if not md.exists():
            click.echo(f"跳过：{md}", err=True)
            continue

        content = md.read_text(encoding="utf-8")
        content = preprocess(content)
        content = ensure_frontmatter(content, get_frontmatter(config, md.name))

        (tmp / md.name).write_text(content, encoding="utf-8")

        cmd = ["md2pdf", md.name, "--indir", str(tmp), "--outdir", str(out), "-ft"]
        if tpl:
            cmd += ["--template", tpl]
        cmd += ctx.args

        if verbose:
            click.echo(f"$ {' '.join(cmd)}")
        r = subprocess.run(cmd, check=False)
        click.echo(f"{'✓' if r.returncode == 0 else '✗'} {md.name}")


if __name__ == "__main__":
    cli()
```

## 配置文件 `mark2pdf.yaml`

```yaml
# 模板配置
template:
  name: "default"           # 系统模板名
  # local: "template/my.typ"  # 或本地模板路径

# Frontmatter 默认值
frontmatter:
  toc-depth: 2
  author: "作者名"
  date: auto                # auto = 当前日期

# 按文件名覆盖
overrides:
  - pattern: "*考试*"
    frontmatter:
      title: "考试复习资料"
```

---

## 常用预处理函数

### JSON 代码块转引用

```python
import json

def convert_json_to_quote(content: str) -> str:
    """将 ```json 代码块转为 > 引用"""
    def repl(m):
        try:
            d = json.loads(m.group(1))
            lines = []
            if q := d.get("question"):
                lines.append(f"> {q}")
            for i, o in enumerate(d.get("options", [])):
                lines.append(f"> - {chr(65+i)}. {o}")
            if a := d.get("answer"):
                lines.append(f"> 答案：{a}")
            return "\n".join(lines)
        except:
            return "\n".join(f"> {l}" for l in m.group(1).splitlines())
    return re.sub(r"```json\s*\n(.*?)\n```", repl, content, flags=re.DOTALL)
```

### 移除链接保留文本

```python
def remove_links(content: str) -> str:
    """移除 Markdown 链接，保留文本"""
    return re.sub(r'\[([^\]]+)\]\([^)]+\)', r'\1', content)
```

---

## 前置条件

运行脚本前需安装 mark2pdf 全局命令：

```bash
cd /path/to/mark2pdf && uv pip install -e .
```

验证：`md2pdf --help`

---

## md2pdf 常用参数

| 参数 | 说明 |
|------|------|
| `--template NAME` | 指定模板 |
| `--coverimg` | 封面图片 |
| `--tc` | 简体转繁体 |
| `--filename-with-title` (`-ft`) | 用 frontmatter title 作输出名 |
| `--to-typst` | 输出 .typ 而非 PDF |
