← Files note Studio mini|ROGNALIAARCHIVED FILE

skills/note-studio/scripts/count_article_chars.py

2.58 KB · Oct 3, 2026 · 06:32 UTC

↓ Download file

#!/usr/bin/env python3
"""Count Japanese article body characters without headings, tags, or whitespace."""

from __future__ import annotations

import argparse
import json
import re
import sys
from pathlib import Path


HEADING_PREFIXES = ("[大見出し]", "[小見出し]")
TAG_LINE_PATTERN = re.compile(r"^#[^\s#]+(?: #[^\s#]+){4,5}$")


def find_tag_line(lines: list[str]) -> tuple[int | None, int]:
    """Return the final dedicated note tag line index and its tag count."""
    for index in range(len(lines) - 1, -1, -1):
        stripped = lines[index].strip()
        if not stripped:
            continue
        if TAG_LINE_PATTERN.fullmatch(stripped):
            return index, len(stripped.split(" "))
        break
    return None, 0


def count_body(text: str) -> dict[str, int]:
    lines = text.splitlines()
    tag_line_index, tag_count = find_tag_line(lines)
    body_lines = [
        line
        for index, line in enumerate(lines)
        if index != tag_line_index
        and not line.lstrip().startswith(HEADING_PREFIXES)
    ]
    body = "\n".join(body_lines)
    return {
        "body_characters": sum(1 for char in body if not char.isspace()),
        "heading_count": sum(
            1 for line in lines if line.lstrip().startswith(HEADING_PREFIXES)
        ),
        "tag_count": tag_count,
        "total_characters": len(text),
    }


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="見出し行、末尾のnoteタグ行、空白文字を除いた本文文字数を数えます。"
    )
    parser.add_argument(
        "path",
        nargs="?",
        type=Path,
        help="本文ファイル。省略時は標準入力から読みます。",
    )
    parser.add_argument("--min", dest="minimum", type=int, default=2000)
    parser.add_argument("--max", dest="maximum", type=int, default=2400)
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    if args.minimum < 0 or args.maximum < args.minimum:
        raise SystemExit("文字数範囲が不正です。")

    text = (
        args.path.read_text(encoding="utf-8")
        if args.path is not None
        else sys.stdin.read()
    )
    result = count_body(text)
    result.update(
        {
            "minimum": args.minimum,
            "maximum": args.maximum,
            "within_range": args.minimum
            <= result["body_characters"]
            <= args.maximum,
        }
    )
    print(json.dumps(result, ensure_ascii=False, sort_keys=True))
    return 0 if result["within_range"] else 2


if __name__ == "__main__":
    raise SystemExit(main())

SHA-256: 12622cd75d24d4e3a2c0ae2d00c4b3c95c641ae30289a2ce0d3909420fc2c1d4