From 3a2684e4d774963ed71ab51b52cbf44a48b3f009 Mon Sep 17 00:00:00 2001 From: satyaborg Date: Wed, 24 Jun 2026 23:16:41 +1000 Subject: [PATCH] chore: ascii-spec-diagrams --- README.md | 4 +- scripts/devloop_test.sh | 147 +------ skills/devloop-spec/SKILL.md | 33 +- .../devloop-spec/references/spec-template.md | 8 +- skills/devloop-spec/scripts/render.sh | 405 ------------------ 5 files changed, 38 insertions(+), 559 deletions(-) delete mode 100755 skills/devloop-spec/scripts/render.sh diff --git a/README.md b/README.md index 0714658..d409e7f 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ When you pick a spec from the interactive menu, devloop uses your configured run ## Specs -A good spec is short, concrete, and verifiable. Start from [`skills/devloop-spec/references/spec-template.md`](skills/devloop-spec/references/spec-template.md). The bundled `devloop-spec` skill can also render a sibling HTML companion with [`skills/devloop-spec/scripts/render.sh`](skills/devloop-spec/scripts/render.sh). +A good spec is short, concrete, and verifiable. Start from [`skills/devloop-spec/references/spec-template.md`](skills/devloop-spec/references/spec-template.md). Strict mode is on by default: specs need `## Acceptance criteria`, and reviews must pass both the spec gate and engineering quality gate. @@ -57,7 +57,7 @@ Strict mode is on by default: specs need `## Acceptance criteria`, and reviews m Devloop ships two agent skills, installed into `~/.claude/skills` and `~/.agents/skills`: -- [`devloop-spec`](skills/devloop-spec/SKILL.md) — turns a rough idea, notes, a URL, or an interview into one concrete, devloop-ready spec, with optional HTML rendering. +- [`devloop-spec`](skills/devloop-spec/SKILL.md) — turns a rough idea, notes, a URL, or an interview into one concrete, devloop-ready spec. - [`devloop-review`](skills/devloop-review/SKILL.md) — judges each pass against the spec and engineering quality gates, returning ACCEPT, REJECT, or UNCLEAR with fix instructions. ## Runtime diff --git a/scripts/devloop_test.sh b/scripts/devloop_test.sh index fee9cac..8b98b85 100755 --- a/scripts/devloop_test.sh +++ b/scripts/devloop_test.sh @@ -36,22 +36,7 @@ equals() { [[ "$actual" == "$expected" ]] || fail "$label expected [$expected], got [$actual]" } -count_occurrences() { - local file="$1" - local needle="$2" - awk -v needle="$needle" ' - { - line = $0 - while ((idx = index(line, needle)) > 0) { - count++ - line = substr(line, idx + length(needle)) - } - } - END { print count + 0 } - ' "$file" -} - -bash -n "$REPO_ROOT/devloop" "$SCRIPTS_DIR/install.sh" "$SCRIPTS_DIR/uninstall.sh" "$SCRIPTS_DIR/skill_helpers.sh" "$SCRIPTS_DIR/release.sh" "$REMOTE_INSTALLER" "$REPO_ROOT/site/public/install" "$REPO_ROOT/skills/devloop-spec/scripts/render.sh" +bash -n "$REPO_ROOT/devloop" "$SCRIPTS_DIR/install.sh" "$SCRIPTS_DIR/uninstall.sh" "$SCRIPTS_DIR/skill_helpers.sh" "$SCRIPTS_DIR/release.sh" "$REMOTE_INSTALLER" "$REPO_ROOT/site/public/install" ok "bash syntax" DEVLOOP_LIB=1 @@ -99,6 +84,9 @@ contains "$readme_text" "cd devloop" "README source install" contains "$readme_text" "./scripts/install.sh" "README source install" contains "$readme_text" "\`devloop update\`" "README command table" not_contains "$readme_text" "\`devloop $obsolete_update_command\`" "README command table" +not_contains "$readme_text" "render.py" "README Python spec renderer" +not_contains "$readme_text" "render.sh" "README spec renderer" +not_contains "$readme_text" "sibling HTML companion" "README spec renderer" ok "README install docs" skill_path="$("$REPO_ROOT/devloop" spec --skill-path)" @@ -106,6 +94,22 @@ skill_path="$("$REPO_ROOT/devloop" spec --skill-path)" contains "$("$REPO_ROOT/devloop" spec --print-skill)" "name: devloop-spec" "spec skill" ok "spec skill path" +spec_skill_text="$(cat "$REPO_ROOT/skills/devloop-spec/SKILL.md")" +spec_template_text="$(cat "$REPO_ROOT/skills/devloop-spec/references/spec-template.md")" +contains "$spec_skill_text" "ASCII" "spec skill ASCII diagrams" +contains "$spec_skill_text" "plain code fence" "spec skill plain diagram fence" +not_contains "$spec_skill_text" "mermaid" "spec skill Mermaid guidance" +not_contains "$spec_skill_text" "Mermaid" "spec skill Mermaid guidance" +not_contains "$spec_skill_text" "render.py" "spec skill Python renderer" +not_contains "$spec_skill_text" "python3" "spec skill Python renderer" +not_contains "$spec_skill_text" "render.sh" "spec skill renderer" +not_contains "$spec_skill_text" "HTML companion" "spec skill renderer" +contains "$spec_template_text" $'\n```\n current' "spec template plain ASCII fence" +contains "$spec_template_text" "Current behavior -> Implementation change -> Expected outcome" "spec template ASCII schematic" +not_contains "$spec_template_text" '```mermaid' "spec template Mermaid fence" +not_contains "$spec_template_text" "flowchart LR" "spec template Mermaid syntax" +ok "spec ASCII diagram guidance" + contains "$(cat "$REPO_ROOT/README.md")" "\`devloop --create-pr \`" "README PR mode" contains "$(cat "$REPO_ROOT/README.md")" "maintain a draft PR (requires \`gh\`)" "README PR mode" ok "README PR guidance" @@ -149,113 +153,6 @@ ok "skill metadata" work=$(mktemp -d "${TMPDIR:-/tmp}/devloop-test.XXXXXX") trap 'rm -rf "$work"' EXIT -renderer_script="$REPO_ROOT/skills/devloop-spec/scripts/render.sh" -[[ -x "$renderer_script" ]] || fail "missing executable bash spec renderer" - -regular_renderer_fixture="$work/spec-render-fixture.md" -cat > "$regular_renderer_fixture" <<'SPEC' ---- -status: draft -type: feat -created: 2026-06-16 -pr: null ---- - -# Renderer Fixture -Render the spec with light styling and robust escaping. - -## Problem -Renderer regressions are hard to spot from markdown alone. - -```markdown -## This is inside a code fence -- not a real section -& -``` - -The paragraph after the fenced heading still belongs to Problem. - -## Outcome -The HTML companion renders the spec sections. - -## Scope -- In: renderer fixture -- Out: browser automation - -## Behavior -### Happy path -1. Run the bundled renderer. -2. HTML is written next to the markdown. - -### Edge cases -- HTML-sensitive fenced content is escaped. - -## Acceptance criteria -1. The generated HTML uses the light theme. - -## Test plan -- Red: Not applicable for fixture. -- Green: `skills/devloop-spec/scripts/render.sh ` -- Full: `bash scripts/devloop_test.sh` -- Coverage: Not applicable for Bash fixture. - -## Constraints -- Must: keep markdown canonical. -- Avoid: editing generated HTML by hand. -- Existing convention: derived files sit beside the source markdown. - -## Notes -No gaps. -SPEC -if "$renderer_script" >/tmp/devloop-renderer-usage.out 2>&1; then - fail "spec renderer accepted missing argument" -fi -if "$renderer_script" "$regular_renderer_fixture" "$regular_renderer_fixture" >/tmp/devloop-renderer-usage.out 2>&1; then - fail "spec renderer accepted extra argument" -fi -renderer_output="$("$renderer_script" "$regular_renderer_fixture")" -[[ -f "$renderer_output" ]] || fail "spec renderer did not create HTML" -contains "$(cat "$renderer_output")" "--bg: #ffffff" "spec renderer light theme" -not_contains "$(cat "$renderer_output")" 'This is inside a code fence' "spec renderer fenced heading" -contains "$(cat "$renderer_output")" '</pre><script>alert("x")</script>&' "spec renderer escaped fenced HTML" -contains "$(cat "$renderer_output")" "The paragraph after the fenced heading still belongs to Problem." "spec renderer fenced content" -contains "$(cat "$renderer_output")" $'
\n Acceptance criteria' "spec renderer acceptance open" -not_contains "$(cat "$renderer_output")" "mermaid.esm.min.mjs" "spec renderer without Mermaid" -expected_renderer_output="$(cd -P "$(dirname "$regular_renderer_fixture")" >/dev/null 2>&1 && pwd)/$(basename "${regular_renderer_fixture%.md}.html")" -equals "$renderer_output" "$expected_renderer_output" "spec renderer output path" - -mermaid_renderer_fixture="$work/spec-render-mermaid-fixture.md" -cat > "$mermaid_renderer_fixture" <<'SPEC' ---- -status: draft -type: feat -created: 2026-06-16 -pr: null ---- - -# Mermaid Renderer Fixture -Render Mermaid fences without requiring local dependencies. - -```mermaid -flowchart LR - Modes["Gmail/Outlook | IMAP"] --> Result["Rendered HTML"] - Danger[""] --> Result -``` - -## Problem -Mermaid should render in the browser when diagrams are present. - -## Acceptance criteria -1. The generated HTML imports Mermaid exactly once. -SPEC -mermaid_renderer_output="$("$renderer_script" "$mermaid_renderer_fixture")" -[[ -f "$mermaid_renderer_output" ]] || fail "Mermaid spec renderer did not create HTML" -contains "$(cat "$mermaid_renderer_output")" '
' "spec renderer Mermaid pre"
-contains "$(cat "$mermaid_renderer_output")" 'Modes["Gmail/Outlook | IMAP"]' "spec renderer Mermaid pass-through"
-contains "$(cat "$mermaid_renderer_output")" 'Danger["</pre>"]' "spec renderer Mermaid escaping"
-equals "$(count_occurrences "$mermaid_renderer_output" "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs")" "1" "spec renderer Mermaid import count"
-ok "spec renderer"
-
 equals "$(sed -n '1p' "$REPO_ROOT/site/public/VERSION")" "$version" "site VERSION matches root VERSION"
 ok "site version file"
 
@@ -1299,12 +1196,12 @@ PATH="$install_path" command -v gum >/dev/null 2>&1 || fail "installer did not m
 PATH="$install_path" command -v fzf >/dev/null 2>&1 || fail "installer did not make fzf available"
 [[ -f "$install_home/.agents/skills/devloop-spec/SKILL.md" ]] || fail "installer did not install Codex spec skill"
 [[ -f "$install_home/.agents/skills/devloop-spec/references/spec-template.md" ]] || fail "installer did not install Codex spec template reference"
-[[ -x "$install_home/.agents/skills/devloop-spec/scripts/render.sh" ]] || fail "installer did not install Codex spec renderer"
+[[ ! -e "$install_home/.agents/skills/devloop-spec/scripts/render.sh" ]] || fail "installer installed removed Codex spec renderer"
 [[ ! -e "$install_home/.agents/skills/devloop-spec/scripts/render.py" ]] || fail "installer installed removed Codex Python spec renderer"
 [[ -f "$install_home/.agents/skills/devloop-review/SKILL.md" ]] || fail "installer did not install Codex review skill"
 [[ -f "$install_home/.agents/skills/devloop-review/.devloop-checksum" ]] || fail "installer did not write Codex checksum"
 [[ -f "$install_home/.claude/skills/devloop-spec/SKILL.md" ]] || fail "installer did not install Claude spec skill"
-[[ -x "$install_home/.claude/skills/devloop-spec/scripts/render.sh" ]] || fail "installer did not install Claude spec renderer"
+[[ ! -e "$install_home/.claude/skills/devloop-spec/scripts/render.sh" ]] || fail "installer installed removed Claude spec renderer"
 [[ ! -e "$install_home/.claude/skills/devloop-spec/scripts/render.py" ]] || fail "installer installed removed Claude Python spec renderer"
 [[ -f "$install_home/.claude/skills/devloop-review/SKILL.md" ]] || fail "installer did not install Claude review skill"
 [[ -f "$install_home/.claude/skills/devloop-review/.devloop-checksum" ]] || fail "installer did not write Claude checksum"
diff --git a/skills/devloop-spec/SKILL.md b/skills/devloop-spec/SKILL.md
index 3134db3..e67e6c2 100644
--- a/skills/devloop-spec/SKILL.md
+++ b/skills/devloop-spec/SKILL.md
@@ -11,12 +11,11 @@ Produce exactly one implementation spec that conforms to the devloop standard. T
 
 Use this skill for both cold-start interviews and distilling existing material. Do not hand off to a separate interview or spec-writing skill.
 
-The markdown spec is the source of truth that `devloop` will use as implementation input. If a file is written and the environment supports it, also render the optional HTML companion.
+The markdown spec is the source of truth that `devloop` will use as implementation input. Author diagrams as ASCII art inside plain code fences so terminal readers, GitHub, and plain text show the same schematic.
 
 Available resources:
 
 - `references/spec-template.md`: read when drafting or validating the spec shape.
-- `scripts/render.sh`: run after writing a markdown spec to create a sibling HTML companion.
 
 ## Scope Guard
 
@@ -70,7 +69,7 @@ If the spec depends on something cheap to verify, verify it rather than assertin
 
 Read `references/spec-template.md` when creating the draft. Every section appears in this order. Frontmatter uses exactly these fields:
 
-```markdown
+````markdown
 ---
 status: draft
 type: feat|fix|chore
@@ -81,10 +80,10 @@ pr: null
 # 
 
 
-```mermaid
-flowchart LR
-  Current["Current behavior"] --> Change["Implementation change"]
-  Change --> Result["Expected outcome"]
+```
+  current             change                 result
+  -------             ------                 ------
+  Current behavior -> Implementation change -> Expected outcome
 ```
 
 ## Problem
@@ -121,14 +120,14 @@ flowchart LR
 
 ## Notes
 
-```
+````
 
 - `created` is today's date.
 - Infer `type`: `feat` for new capability, `fix` for broken behavior, and `chore` for maintenance, docs, tests, dependencies, or refactors.
 - The H1 is a concise title, not a sentence.
 - The subtitle is a plain one-sentence line directly under the H1.
-- The Mermaid schematic is optional but preferred when it clarifies architecture, data flow, ownership, or before/after behavior. Omit it if it would be decorative or speculative.
-- In Mermaid flowchart node labels, quote labels containing `|`. Use `Node["A | B"]`, not `Node[A | B]`.
+- The ASCII schematic is optional but preferred when it clarifies architecture, data flow, ownership, or before/after behavior. Omit it if it would be decorative or speculative.
+- Put schematics inside a plain code fence: three backticks on a line by themselves, ASCII diagram lines, then three closing backticks.
 - Under `## Behavior`, use `### Happy path` and `### Edge cases` H3 headings, not plain `Happy path:` labels.
 - Acceptance criteria must be singular, verifiable, and observable.
 - Include concrete paths, commands, APIs, and behaviors when the context provides them.
@@ -157,18 +156,6 @@ Do not hard-code personal paths. Use this precedence:
 
 Do not wrap the spec in a code fence unless the caller explicitly asks for a fenced snippet.
 
-## HTML Companion
-
-When a markdown spec is written to a file, render the interactive HTML companion if the bundled script is available:
-
-```bash
-scripts/render.sh 
-```
-
-Run the command from the skill directory, or resolve `scripts/render.sh` relative to this skill's `SKILL.md`. The script writes `.html` next to the markdown.
-
-If Mermaid fails in the browser, fix the markdown source and rerun the renderer. If rendering cannot run in the current environment, keep the markdown spec and say HTML was not generated.
-
 ## Signoff
 
-After writing, report the spec path, HTML path if generated, inferred `type`, acceptance criteria, and remaining gaps. Offer `devloop --create-pr ` as the next handoff when a file path exists.
+After writing, report the spec path, inferred `type`, acceptance criteria, and remaining gaps. Offer `devloop --create-pr ` as the next handoff when a file path exists.
diff --git a/skills/devloop-spec/references/spec-template.md b/skills/devloop-spec/references/spec-template.md
index 136da50..789df13 100644
--- a/skills/devloop-spec/references/spec-template.md
+++ b/skills/devloop-spec/references/spec-template.md
@@ -8,10 +8,10 @@ pr: null
 # 
 
 
-```mermaid
-flowchart LR
-  Current["Current behavior"] --> Change["Implementation change"]
-  Change --> Result["Expected outcome"]
+```
+  current             change                 result
+  -------             ------                 ------
+  Current behavior -> Implementation change -> Expected outcome
 ```
 
 ## Problem
diff --git a/skills/devloop-spec/scripts/render.sh b/skills/devloop-spec/scripts/render.sh
deleted file mode 100755
index f976dac..0000000
--- a/skills/devloop-spec/scripts/render.sh
+++ /dev/null
@@ -1,405 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-if [ "$#" -ne 1 ]; then
-  printf 'usage: render.sh \n' >&2
-  exit 2
-fi
-
-src="$1"
-if [ ! -f "$src" ]; then
-  printf 'not found: %s\n' "$src" >&2
-  exit 1
-fi
-
-src_dir="$(cd -P "$(dirname "$src")" >/dev/null 2>&1 && pwd)"
-src_base="$(basename "$src")"
-src_path="$src_dir/$src_base"
-out_path="$src_dir/${src_base%.*}.html"
-tmp_path="$out_path.tmp.$$"
-
-cleanup() {
-  rm -f "$tmp_path"
-}
-trap cleanup EXIT
-
-awk -v src_path="$src_path" -v stem="${src_base%.*}" '
-function trim(s) {
-  sub(/^[[:space:]]+/, "", s)
-  sub(/[[:space:]]+$/, "", s)
-  return s
-}
-
-function append_line(text, line) {
-  return text == "" ? line : text "\n" line
-}
-
-function append_html(text, part) {
-  if (part == "") {
-    return text
-  }
-  return text == "" ? part : text "\n" part
-}
-
-function html_escape(s) {
-  gsub(/&/, "\\&", s)
-  gsub(//, "\\>", s)
-  gsub(/"/, "\\"", s)
-  return s
-}
-
-function render_inline(s) {
-  return html_escape(s)
-}
-
-function is_ordered(line) {
-  return line ~ /^[[:space:]]*[0-9]+[.][[:space:]]+/
-}
-
-function is_unordered(line) {
-  return line ~ /^[[:space:]]*-[[:space:]]+/
-}
-
-function render_list(block, title,    lines, n, i, line, text, current, ordered, tag, class_attr, out, title_lower) {
-  n = split(block, lines, "\n")
-  ordered = is_ordered(lines[1])
-  tag = ordered ? "ol" : "ul"
-  title_lower = tolower(title)
-  class_attr = ""
-  if (index(title_lower, "acceptance") > 0) {
-    class_attr = " class=\"ac\""
-  } else if (ordered) {
-    class_attr = " class=\"steps\""
-  }
-  out = "<" tag class_attr ">"
-  current = ""
-  for (i = 1; i <= n; i++) {
-    line = lines[i]
-    if ((ordered && is_ordered(line)) || (!ordered && is_unordered(line))) {
-      if (current != "") {
-        out = out "
  • " render_inline(current) "
  • " - } - text = line - if (ordered) { - sub(/^[[:space:]]*[0-9]+[.][[:space:]]+/, "", text) - } else { - sub(/^[[:space:]]*-[[:space:]]+(\[[[:space:]]\][[:space:]]+)?/, "", text) - } - current = trim(text) - } else if (trim(line) != "" && current != "") { - current = current " " trim(line) - } - } - if (current != "") { - out = out "
  • " render_inline(current) "
  • " - } - return out "" -} - -function render_code_block(block, lines, n, first, lang, content, end, i) { - n = split(block, lines, "\n") - first = lines[1] - lang = trim(substr(first, 4)) - content = "" - end = n - if (n > 1 && lines[n] ~ /^```/) { - end = n - 1 - } - for (i = 2; i <= end; i++) { - content = append_line(content, lines[i]) - } - if (lang == "mermaid") { - return "
    " html_escape(content) "
    " - } - return "
    " html_escape(content) "
    " -} - -function render_block(block, title, lines, first, title_text, paragraph, i) { - block = trim(block) - if (block == "") { - return "" - } - split(block, lines, "\n") - first = lines[1] - if (first ~ /^```/) { - return render_code_block(block) - } - if (first ~ /^###[[:space:]]+/) { - title_text = first - sub(/^###[[:space:]]+/, "", title_text) - return "

    " render_inline(trim(title_text)) "

    " - } - if (is_unordered(first) || is_ordered(first)) { - return render_list(block, title) - } - if (trim(block) == "---") { - return "
    " - } - paragraph = "" - for (i = 1; i <= split(block, lines, "\n"); i++) { - paragraph = paragraph == "" ? trim(lines[i]) : paragraph " " trim(lines[i]) - } - return "

    " render_inline(paragraph) "

    " -} - -function render_blocks(text, title, lines, n, i, line, label, block, in_fence, out, title_lower) { - n = split(text, lines, "\n") - block = "" - in_fence = 0 - out = "" - title_lower = tolower(title) - for (i = 1; i <= n; i++) { - line = lines[i] - if (line ~ /^```/) { - if (in_fence) { - block = append_line(block, line) - in_fence = 0 - out = append_html(out, render_block(block, title)) - block = "" - } else { - out = append_html(out, render_block(block, title)) - block = line - in_fence = 1 - } - continue - } - if (in_fence) { - block = append_line(block, line) - continue - } - label = tolower(trim(line)) - if (line ~ /^###[[:space:]]+/ || (title_lower == "behavior" && (label == "happy path:" || label == "edge cases:"))) { - out = append_html(out, render_block(block, title)) - if (title_lower == "behavior" && (label == "happy path:" || label == "edge cases:")) { - sub(/:$/, "", line) - out = append_html(out, "

    " render_inline(trim(line)) "

    ") - } else { - out = append_html(out, render_block(line, title)) - } - block = "" - continue - } - if (trim(line) == "") { - out = append_html(out, render_block(block, title)) - block = "" - } else { - block = append_line(block, line) - } - } - out = append_html(out, render_block(block, title)) - return out -} - -function extract_preamble(text, lines, n, i, line, stripped, h1_idx, subtitle_idx) { - n = split(text, lines, "\n") - doc_h1 = "" - doc_subtitle = "" - doc_intro = "" - h1_idx = 0 - subtitle_idx = 0 - for (i = 1; i <= n; i++) { - line = lines[i] - if (doc_h1 == "" && line ~ /^#[[:space:]]+/) { - doc_h1 = trim(substr(line, 3)) - h1_idx = i - break - } - } - for (i = 1; i <= n; i++) { - if (i == h1_idx) { - continue - } - stripped = trim(lines[i]) - if (stripped == "") { - continue - } - if (stripped ~ /^```/ || stripped ~ /^#/ || stripped ~ /^-/ || stripped ~ /^[*]/ || stripped ~ /^>/ || stripped == "---" || stripped ~ /^[0-9]+[.][[:space:]]+/) { - break - } - doc_subtitle = stripped - subtitle_idx = i - break - } - for (i = 1; i <= n; i++) { - if (i == h1_idx || i == subtitle_idx) { - continue - } - doc_intro = append_line(doc_intro, lines[i]) - } -} - -function meta_line( out) { - out = "" - if (fm_created != "") { - out = "created " fm_created - } - if (fm_pr != "") { - out = out == "" ? "" : out " | " - out = out (fm_pr == "[]" || fm_pr == "null" ? "pr: none" : "pr: " fm_pr) - } - out = out == "" ? "spec" : out " | spec" - return out -} - -function section_is_open(title, idx, lower) { - lower = tolower(title) - return idx < 3 || lower == "problem" || lower == "outcome" || lower == "scope" || lower == "behavior" || lower == "acceptance criteria" -} - -function render_section(title, body, idx, inner, attrs) { - inner = render_blocks(body, title) - attrs = section_is_open(title, idx) ? " class=\"section open\" open" : " class=\"section\"" - return "\n " html_escape(title) "\n
    " inner "
    \n
    " -} - -function print_css() { - print "" -} - -function print_mermaid_script() { - print "" -} - -{ - lines[NR] = $0 - if ($0 ~ /^```[[:space:]]*mermaid[[:space:]]*$/) { - has_mermaid = 1 - } -} - -END { - body_start = 1 - if (lines[1] == "---") { - for (i = 2; i <= NR; i++) { - if (lines[i] == "---") { - frontmatter_end = i - break - } - } - if (frontmatter_end > 0) { - for (i = 2; i < frontmatter_end; i++) { - colon = index(lines[i], ":") - if (colon > 0) { - key = trim(substr(lines[i], 1, colon - 1)) - value = trim(substr(lines[i], colon + 1)) - if (key == "created") { - fm_created = value - } else if (key == "pr") { - fm_pr = value - } - } - } - body_start = frontmatter_end + 1 - } - } - - in_fence = 0 - current = 0 - for (i = body_start; i <= NR; i++) { - line = lines[i] - if (line ~ /^```/) { - in_fence = !in_fence - } - if (!in_fence && line ~ /^##[[:space:]]+/) { - current++ - section_title[current] = trim(substr(line, 4)) - section_body[current] = "" - } else if (current > 0) { - section_body[current] = append_line(section_body[current], line) - } else { - preamble = append_line(preamble, line) - } - } - - extract_preamble(preamble) - if (doc_h1 == "") { - doc_h1 = stem - } - sections_html = render_blocks(doc_intro, "") - for (i = 1; i <= current; i++) { - sections_html = append_html(sections_html, render_section(section_title[i], section_body[i], i - 1)) - } - - print "" - print "" - print "" - print "" - print "" - print "" html_escape(doc_h1) "" - print_css() - print "" - print "" - print "
    " - print "
    " - print "
    " html_escape(meta_line()) "
    " - print "

    " html_escape(doc_h1) "

    " - if (doc_subtitle != "") { - print "

    " render_inline(doc_subtitle) "

    " - } - print "
    " - print sections_html - print "" - print "
    " - if (has_mermaid) { - print_mermaid_script() - } - print "" - print "" -} -' "$src_path" > "$tmp_path" - -mv "$tmp_path" "$out_path" -trap - EXIT -printf '%s\n' "$out_path"