CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/speaker-toolkit

Six-skill presentation system: ingest talks into a rhetoric vault, run interactive clarification, generate a speaker profile, create presentations that match your documented patterns, produce the deck illustrations + thumbnail visual layer, and publish talk pages to a Jekyll shownotes site. Includes a 111-entry Presentation Patterns taxonomy (81 observable: 62 patterns + 19 antipatterns; 30 unobservable: 21 patterns + 9 antipatterns) for scoring, brainstorming, and go-live preparation.

75

Quality

94%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

markdown_deck.pyskills/vault-ingress/scripts/

#!/usr/bin/env python3
"""Deterministic reading of a markdown-authored slide deck.

Four tools author decks as markdown — presenterm, Slidev, Marp, reveal-md —
and each owns its own separator syntax and its own incremental-reveal markers.
This module reads the source text only: which tool wrote the deck, how many
slides the source declares, and where the author asked for a staged reveal. It
renders nothing and shells out to nothing.

The slide count here is a CROSS-CHECK, never the authority. Each renderer owns
its own pagination, so ``render-markdown-deck.py`` takes the authored slide
count from the exported page count and reports this module's count beside it.
A disagreement is surfaced, never reconciled: a wrong number that agrees with
itself is worse than two numbers that visibly do not.

Detection and segmentation both work from closed literal vocabularies declared
at the top of this file. Nothing here infers intent from prose.
"""

from __future__ import annotations

import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping, Sequence

import yaml


DECK_STRUCTURE_SCHEMA_VERSION = 1

PRESENTERM = "presenterm"
SLIDEV = "slidev"
MARP = "marp"
REVEAL_MD = "reveal-md"
FLAVORS: tuple[str, ...] = (PRESENTERM, SLIDEV, MARP, REVEAL_MD)

# A definitive marker names exactly one tool and is written by no other: the
# `marp` headmatter directive Marp requires, presenterm's own slide terminator,
# the Slidev CLI in a sibling manifest. One definitive hit decides the flavor.
# A corroborating marker is strong but not exclusive, and only decides when it
# is the sole flavor matched.
_DEFINITIVE_HEADMATTER_KEYS: dict[str, tuple[str, ...]] = {
    MARP: ("marp",),
}
_DEFINITIVE_BODY_MARKERS: dict[str, tuple[str, ...]] = {
    PRESENTERM: ("<!-- end_slide -->",),
}
_CORROBORATING_HEADMATTER_KEYS: dict[str, tuple[str, ...]] = {
    # Slidev headmatter keys no other tool reads.
    SLIDEV: (
        "colorSchema",
        "drawings",
        "exportFilename",
        "highlighter",
        "mdc",
        "routerMode",
        "themeConfig",
    ),
    # presenterm nests every knob under `options:` and its own `theme:` map.
    PRESENTERM: ("options",),
}
_CORROBORATING_BODY_MARKERS: dict[str, tuple[str, ...]] = {
    PRESENTERM: (
        "<!-- pause -->",
        "<!-- column_layout:",
        "<!-- column:",
        "<!-- reset_layout -->",
        "<!-- jump_to_middle -->",
        "<!-- incremental_lists:",
        "<!-- new_lines:",
        "<!-- no_footer -->",
    ),
    SLIDEV: (
        "<v-click",
        "<v-clicks",
        "<v-switch",
        "v-click=",
        "v-clicks=",
        "::right::",
        "::left::",
    ),
    REVEAL_MD: ("<!-- .slide:", "<!-- .element:"),
}
# The npm package that owns each flavor, read from a sibling `package.json`.
_MANIFEST_PACKAGES: dict[str, tuple[str, ...]] = {
    SLIDEV: ("@slidev/cli",),
    MARP: ("@marp-team/marp-cli",),
    REVEAL_MD: ("reveal-md",),
}

# Incremental-reveal markers: the author's own request for a staged reveal.
# These are structure, not observed motion — a build run is ordered cumulative
# content, never evidence that anything animated on screen.
#
# One compiled alternation per flavor, never a list of substrings counted
# independently: `<v-clicks>` contains `<v-click`, so counting both tokens
# scored one marker twice. A single left-to-right scan consumes each match, so
# overlapping spellings cannot double-count. Longest alternative first.
_REVEAL_PATTERNS: dict[str, re.Pattern[str] | None] = {
    PRESENTERM: re.compile(r"<!--\s*pause\s*-->"),
    SLIDEV: re.compile(r"<v-clicks?\b|<v-switch\b|v-clicks?\s*=|v-after\b"),
    REVEAL_MD: re.compile(r"""class\s*=\s*["']fragment"""),
    # Marp renders a fragmented list whole in a PDF export. Nothing in the
    # source declares a build the export preserves, so nothing is counted.
    MARP: None,
}
# Headmatter switches that make an explicit marker count a FLOOR rather than an
# exact one: the tool stages content the source never marks.
_IMPLICIT_REVEAL_SWITCHES: dict[str, tuple[tuple[str, ...], ...]] = {
    PRESENTERM: (("options", "incremental_lists"),),
}
# Slidev pulls slides from another file with a per-slide `src:` key. That one
# source slide can render as many, so a count taken here is a floor. Verified
# against the Slidev demo deck, whose `src: ./pages/imported-slides.md` slide
# reads as one and renders as however many the imported file holds.
_SLIDEV_IMPORT_KEY = re.compile(r"^src\s*:\s*(\S.*?)\s*$")

# Group 1 is the fence run itself, group 2 everything after it. Both are
# load-bearing: CommonMark closes a fence only with the same character, at
# least as long, and carrying no info string (#351).
_FENCE = re.compile(r"^\s{0,3}(`{3,}|~{3,})(.*)$")
_YAML_KEY = re.compile(r"^[A-Za-z_][A-Za-z0-9_.\-]*\s*:(\s|$)")
_HORIZONTAL_RULE = re.compile(r"^-{3,}\s*$")
_VERTICAL_RULE = re.compile(r"^--\s*$")
# presenterm renders a title slide from the headmatter when any of these is set.
# Verified against presenterm 0.16.1: `author:` alone adds a page, `theme:` and
# `options:` alone do not.
_PRESENTERM_INTRO_KEYS = ("title", "sub_title", "author", "authors")


class MarkdownDeckError(ValueError):
    """The deck source cannot be read as a markdown-authored deck."""


@dataclass(frozen=True)
class FlavorDecision:
    """Which tool authored the deck, and the literal that decided it."""

    flavor: str
    decided_by: str
    evidence: str

    def to_dict(self) -> dict[str, str]:
        return {
            "flavor": self.flavor,
            "decided_by": self.decided_by,
            "evidence": self.evidence,
        }


@dataclass(frozen=True)
class SlideStructure:
    """One authored slide's source span and its declared reveal markers."""

    index: int
    first_line: int
    last_line: int
    reveal_markers: int

    def to_dict(self) -> dict[str, int]:
        return {
            "index": self.index,
            "first_line": self.first_line,
            "last_line": self.last_line,
            "reveal_markers": self.reveal_markers,
        }


@dataclass(frozen=True)
class DeckStructure:
    """What the deck source declares, before any renderer has run."""

    flavor: str
    slides: tuple[SlideStructure, ...]
    headmatter_readable: bool
    reveal_markers_are_a_floor: bool
    floor_causes: tuple[str, ...]
    imported_files: tuple[str, ...]

    @property
    def slide_count(self) -> int:
        return len(self.slides)

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": DECK_STRUCTURE_SCHEMA_VERSION,
            "flavor": self.flavor,
            "slide_count": self.slide_count,
            "headmatter_readable": self.headmatter_readable,
            "slides_with_reveal_markers": sum(
                1 for slide in self.slides if slide.reveal_markers
            ),
            "reveal_marker_total": sum(slide.reveal_markers for slide in self.slides),
            "reveal_markers_are_a_floor": self.reveal_markers_are_a_floor,
            "floor_causes": list(self.floor_causes),
            # A deck that imports slides from another file renders more slides
            # than this reading counts, so `slide_count` above is a floor.
            "imported_files": list(self.imported_files),
            "slide_count_is_a_floor": bool(self.imported_files),
            "slides": [slide.to_dict() for slide in self.slides],
        }


def _fence_mask(lines: Sequence[str]) -> list[bool]:
    """Return, per line, whether it sits inside a fenced code block.

    Fence LENGTH is tracked, not just the character. A four-backtick fence is
    how a deck quotes markdown that itself contains a three-backtick block, and
    matching on the character alone closed the outer fence on the inner one:
    everything from there to the real close read as deck source, so a `---` or
    a `<!-- pause -->` in the quoted sample counted as a slide break or a
    reveal. The symptom was `source_slide_count` disagreeing with the render
    for a reason the receipt could not explain.

    An info string disqualifies a closing fence for the same reason — ```` ```py ````
    inside a quoted block is content, not a close.
    """
    inside = [False] * len(lines)
    open_fence: str | None = None
    open_length = 0
    for number, line in enumerate(lines):
        match = _FENCE.match(line)
        if open_fence is None:
            if match is not None:
                # A backtick fence's info string may not itself contain a
                # backtick, so a line like ```` ```a`b ```` opens nothing.
                if match.group(1)[0] == "`" and "`" in match.group(2):
                    continue
                open_fence = match.group(1)[0]
                open_length = len(match.group(1))
                inside[number] = True
            continue
        inside[number] = True
        if (
            match is not None
            and match.group(1)[0] == open_fence
            and len(match.group(1)) >= open_length
            and not match.group(2).strip()
        ):
            open_fence = None
            open_length = 0
    return inside


def _headmatter_span(lines: Sequence[str]) -> tuple[int, int] | None:
    """Return the inclusive line span of a leading frontmatter block."""
    if not lines or not _HORIZONTAL_RULE.match(lines[0]):
        return None
    for number in range(1, len(lines)):
        if _HORIZONTAL_RULE.match(lines[number]) or lines[number].rstrip() == "...":
            return (0, number)
    return None


def read_headmatter(source: str) -> tuple[Mapping[str, Any] | None, bool]:
    """Return the parsed leading frontmatter and whether it parsed at all."""
    lines = source.splitlines()
    span = _headmatter_span(lines)
    if span is None:
        return (None, True)
    block = "\n".join(lines[span[0] + 1 : span[1]])
    try:
        parsed = yaml.safe_load(block)
    except yaml.YAMLError:
        return (None, False)
    if parsed is None:
        return ({}, True)
    if not isinstance(parsed, Mapping):
        return (None, False)
    return (parsed, True)


def _manifest_packages(deck_path: Path) -> dict[str, str]:
    """Return flavor -> package for each deck tool the deck's sibling manifest declares.

    Only the deck's own directory is read. Walking up would pick a workspace
    root's manifest and name a tool that belongs to a different deck in the
    same repository.
    """
    found: dict[str, str] = {}
    try:
        text = (deck_path.parent / "package.json").read_text(encoding="utf-8")
    except (OSError, UnicodeDecodeError):
        return found
    for flavor, packages in _MANIFEST_PACKAGES.items():
        for package in packages:
            if f'"{package}"' in text:
                found.setdefault(flavor, package)
    return found


def _matched_flavors(
    source: str,
    headmatter: Mapping[str, Any] | None,
    markers: Mapping[str, tuple[str, ...]],
    keys: Mapping[str, tuple[str, ...]],
) -> dict[str, tuple[str, str]]:
    """Return flavor -> (decided_by, evidence) for every vocabulary that hits."""
    matched: dict[str, tuple[str, str]] = {}
    for flavor, key_names in keys.items():
        for key in key_names:
            if headmatter is not None and key in headmatter:
                matched.setdefault(flavor, ("headmatter_key", key))
    for flavor, literals in markers.items():
        for literal in literals:
            if literal in source:
                matched.setdefault(flavor, ("body_marker", literal))
    return matched


def detect_flavor(source: str, *, deck_path: Path | None = None) -> FlavorDecision:
    """Decide which tool authored the deck, or refuse to guess.

    Definitive vocabularies decide alone. Corroborating ones decide only when
    they match a single flavor. Anything else raises: an operator naming
    ``--flavor`` is a better answer than a coin flip that silently renders the
    deck with the wrong tool.
    """
    headmatter, _ = read_headmatter(source)
    definitive = _matched_flavors(
        source,
        headmatter,
        _DEFINITIVE_BODY_MARKERS,
        _DEFINITIVE_HEADMATTER_KEYS,
    )
    if deck_path is not None:
        for flavor, package in _manifest_packages(deck_path).items():
            definitive.setdefault(flavor, ("sibling_manifest", package))
    if len(definitive) == 1:
        flavor, (decided_by, evidence) = next(iter(definitive.items()))
        return FlavorDecision(flavor=flavor, decided_by=decided_by, evidence=evidence)
    if len(definitive) > 1:
        raise MarkdownDeckError(
            "deck carries definitive markers for more than one tool "
            f"({', '.join(sorted(definitive))}); pass --flavor to name the "
            "one that authored it"
        )
    corroborating = _matched_flavors(
        source,
        headmatter,
        _CORROBORATING_BODY_MARKERS,
        _CORROBORATING_HEADMATTER_KEYS,
    )
    if len(corroborating) == 1:
        flavor, (decided_by, evidence) = next(iter(corroborating.items()))
        return FlavorDecision(flavor=flavor, decided_by=decided_by, evidence=evidence)
    if len(corroborating) > 1:
        raise MarkdownDeckError(
            "deck carries markers for more than one tool "
            f"({', '.join(sorted(corroborating))}); pass --flavor to name the "
            "one that authored it"
        )
    raise MarkdownDeckError(
        "no marker in the deck names an authoring tool; pass --flavor with one "
        f"of {', '.join(FLAVORS)}"
    )


def _boundaries_presenterm(
    lines: Sequence[str],
    inside_fence: Sequence[bool],
    headmatter: Mapping[str, Any] | None,
) -> list[int]:
    """Return the first line of each slide, splitting on the slide terminator."""
    span = _headmatter_span(lines)
    start = 0 if span is None else span[1] + 1
    boundaries = [start]
    for number in range(start, len(lines)):
        if inside_fence[number]:
            continue
        if lines[number].strip() == "<!-- end_slide -->":
            boundaries.append(number + 1)
    if headmatter and any(key in headmatter for key in _PRESENTERM_INTRO_KEYS):
        # The headmatter renders as its own title slide ahead of the body.
        boundaries.insert(0, 0)
    return boundaries


def _boundaries_slidev(
    lines: Sequence[str],
    inside_fence: Sequence[bool],
) -> list[int]:
    """Return the first line of each slide for Slidev's separator syntax.

    A `---` opens a slide. When the line right after it reads as a YAML key and
    a later `---` closes the block before any other separator, that closing
    `---` is the slide's own frontmatter terminator, not a second slide.
    """
    span = _headmatter_span(lines)
    start = 0 if span is None else span[1] + 1
    boundaries = [start]
    number = start
    while number < len(lines):
        if inside_fence[number] or not _HORIZONTAL_RULE.match(lines[number]):
            number += 1
            continue
        boundaries.append(number + 1)
        following = number + 1
        if following < len(lines) and _YAML_KEY.match(lines[following]):
            closing = following
            while closing < len(lines):
                if not inside_fence[closing] and _HORIZONTAL_RULE.match(lines[closing]):
                    break
                closing += 1
            if closing < len(lines):
                boundaries[-1] = closing + 1
                number = closing + 1
                continue
        number += 1
    return boundaries


def _boundaries_ruled(
    lines: Sequence[str],
    inside_fence: Sequence[bool],
    *,
    vertical: bool,
) -> list[int]:
    """Return the first line of each slide for plain rule-separated decks."""
    span = _headmatter_span(lines)
    start = 0 if span is None else span[1] + 1
    boundaries = [start]
    for number in range(start, len(lines)):
        if inside_fence[number]:
            continue
        if _HORIZONTAL_RULE.match(lines[number]):
            boundaries.append(number + 1)
        elif vertical and _VERTICAL_RULE.match(lines[number]):
            boundaries.append(number + 1)
    return boundaries


def _nested(headmatter: Mapping[str, Any] | None, path: Sequence[str]) -> Any:
    current: Any = headmatter
    for key in path:
        if not isinstance(current, Mapping) or key not in current:
            return None
        current = current[key]
    return current


def read_deck(source: str, flavor: str) -> DeckStructure:
    """Return the slide spans and reveal markers the deck source declares."""
    if flavor not in FLAVORS:
        raise MarkdownDeckError(
            f"unknown flavor {flavor!r}; choose from {', '.join(FLAVORS)}"
        )
    lines = source.splitlines()
    inside_fence = _fence_mask(lines)
    headmatter, headmatter_readable = read_headmatter(source)
    if flavor == PRESENTERM:
        boundaries = _boundaries_presenterm(lines, inside_fence, headmatter)
    elif flavor == SLIDEV:
        boundaries = _boundaries_slidev(lines, inside_fence)
    else:
        boundaries = _boundaries_ruled(
            lines,
            inside_fence,
            vertical=flavor == REVEAL_MD,
        )
    # A deck that ends on a separator puts a boundary at EOF. That separator
    # closes the slide before it; treating it as the start of another invents
    # an empty slide with a first_line past the end of the file, and inflates
    # the count the render's page count is checked against.
    boundaries = [start for start in boundaries if start < len(lines)]
    pattern = _REVEAL_PATTERNS[flavor]
    slides: list[SlideStructure] = []
    for position, start in enumerate(boundaries):
        # A boundary is the index of a slide's FIRST line, so the next boundary
        # minus one is the separator that ended this slide — excluded from the
        # body. Reported line numbers are 1-based, which makes the same integer
        # both the exclusive 0-based body end and the inclusive 1-based last
        # content line. Named separately so neither reading has to be inferred.
        body_end = (
            boundaries[position + 1] - 1
            if position + 1 < len(boundaries)
            else len(lines)
        )
        body = "\n".join(
            line
            for number, line in enumerate(lines[start:body_end], start=start)
            if not inside_fence[number]
        )
        slides.append(
            SlideStructure(
                index=position + 1,
                first_line=start + 1,
                # A separator-only slide has no content line; report its own
                # first line rather than a span that runs backwards.
                last_line=max(body_end, start + 1),
                reveal_markers=(0 if pattern is None else len(pattern.findall(body))),
            )
        )
    floor_causes = tuple(
        ".".join(path)
        for path in _IMPLICIT_REVEAL_SWITCHES.get(flavor, ())
        if _nested(headmatter, path) is True
    )
    imported: list[str] = []
    if flavor == SLIDEV:
        for number, line in enumerate(lines):
            if inside_fence[number]:
                continue
            match = _SLIDEV_IMPORT_KEY.match(line)
            if match is not None:
                imported.append(match.group(1))
    return DeckStructure(
        flavor=flavor,
        slides=tuple(slides),
        headmatter_readable=headmatter_readable,
        reveal_markers_are_a_floor=bool(floor_causes),
        floor_causes=floor_causes,
        imported_files=tuple(imported),
    )


if __name__ == "__main__":  # pragma: no cover - module is imported, not run
    raise SystemExit("markdown_deck is a library; run render-markdown-deck.py instead")

skills

vault-ingress

SKILL.md

README.md

tile.json