BMDB - sgml/signature GitHub Wiki
Repository Root (github.com): ├── .github/ │ └── workflows/ │ ├── process-quotes.yml # Workflow 1: Wiki Markdown -> quotes.html │ └── hydrate-index.yml # Workflow 2: quotes.html -> hidden in index.html ├── index.html # Frontend page hosting boilerplate, #quote-display, & hydrated #quote-pool ├── quotes.js # Client-side randomizer reading from #quote-pool li └── quotes.html # Intermediate blockquote file auto-committed by Workflow 1 Associated Wiki Repository (github.com): └── Quotes.md # Source Markdown file containing list items • Wiki Page Name: "Quotes" (resolves to "Quotes.md" on the wiki; overridden automatically by payload.pages[0].page_name on gollum events). • Markdown Syntax: Unordered list items prefixed with "-", "*", or "+" (e.g., "- First quote"). • Intermediate Out: "quotes.html" containing .... • Hydration Target: "index.html" using and markers. • DOM Pool ID: "quote-pool" (hidden ...). • DOM Target ID: "quote-display" (visible container where active quotes render). • Timer Interval: 5000ms (5 seconds) between non-repeating random quotes. • Lucky Number: 3 random single digits (0-9) formatted as "#-#-#".
<?xml version="1.0" encoding="UTF-8"?>
<opml version="2.0">
<head>
<title>GitHub Wiki Quotes Pipeline (Terse Perl Hydration)</title>
<dateCreated>Sat, 07 Nov 2026 11:36:00 -0800</dateCreated>
</head>
<body>
<outline text="wiki-quotes-pipeline">
<![CDATA[
<!-- ======================================================================= -->
<!-- SECTION 1: WORKFLOW 1 (.github/workflows/process-quotes.yml) -->
<!-- ======================================================================= -->
name: Process Wiki Quotes
on:
gollum: # Triggers whenever a Wiki page is created or updated
workflow_dispatch:
inputs:
page_name:
description: 'Wiki page name (without .md)'
required: true
default: 'Quotes'
permissions:
contents: write # Required to commit quotes.html back to the repository via the Contents API
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
- name: Fetch Wiki Page & Commit HTML Blockquotes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_EVENT_PATH: ${{ github.event_path }}
INPUT_PAGE_NAME: ${{ inputs.page_name }}
run: |
cat << 'EOF' > main.go
package main
import (
"bufio"
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"html"
"io"
"net/http"
"os"
"regexp"
"strings"
)
type EventPayload struct {
Pages []struct {
PageName string `json:"page_name"`
} `json:"pages"`
}
type GitHubFileResponse struct {
SHA string `json:"sha"`
}
type GitHubCommitRequest struct {
Message string `json:"message"`
Content string `json:"content"`
SHA string `json:"sha,omitempty"`
}
func main() {
repo := os.Getenv("GITHUB_REPOSITORY")
token := os.Getenv("GITHUB_TOKEN")
eventPath := os.Getenv("GITHUB_EVENT_PATH")
targetPage := os.Getenv("INPUT_PAGE_NAME")
// 1. Parse the gollum event payload if available
if eventData, err := os.ReadFile(eventPath); err == nil {
var payload EventPayload
if json.Unmarshal(eventData, &payload) == nil && len(payload.Pages) > 0 {
targetPage = payload.Pages[0].PageName
}
}
if targetPage == "" {
targetPage = "Quotes"
}
// 2. Fetch raw Markdown directly from the Wiki without git clone
rawURL := fmt.Sprintf("https://githubusercontent.com", repo, targetPage)
req, err := http.NewRequest("GET", rawURL, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating request: %v\n", err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Accept", "application/vnd.github.v3.raw")
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching wiki page: %v\n", err)
os.Exit(1)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
fmt.Fprintf(os.Stderr, "Failed to fetch %s (%d): %s\n", rawURL, resp.StatusCode, string(body))
os.Exit(1)
}
// 3. Parse list items (-, *, +) into escaped HTML blockquotes
listRegex := regexp.MustCompile(`^\s*[-*+]\s+(.*)`)
var blockquotes []string
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
matches := listRegex.FindStringSubmatch(scanner.Text())
if len(matches) > 1 {
item := strings.TrimSpace(matches[1])
if item != "" {
blockquotes = append(blockquotes, fmt.Sprintf(" <blockquote>%s</blockquote>", html.EscapeString(item)))
}
}
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading wiki stream: %v\n", err)
os.Exit(1)
}
output := fmt.Sprintf("<div id=\"quote-pool\" style=\"display:none;\">\n%s\n</div>\n", strings.Join(blockquotes, "\n"))
// 4. Check if quotes.html already exists in the repo to retrieve its current SHA
contentsURL := fmt.Sprintf("https://github.com", repo)
getReq, _ := http.NewRequest("GET", contentsURL, nil)
getReq.Header.Set("Authorization", "Bearer "+token)
getReq.Header.Set("Accept", "application/vnd.github+json")
var existingSHA string
if getResp, err := http.DefaultClient.Do(getReq); err == nil {
defer getResp.Body.Close()
if getResp.StatusCode == http.StatusOK {
var fileInfo GitHubFileResponse
if json.NewDecoder(getResp.Body).Decode(&fileInfo) == nil {
existingSHA = fileInfo.SHA
}
}
}
// 5. Commit quotes.html directly via the GitHub Contents API
commitPayload := GitHubCommitRequest{
Message: fmt.Sprintf("chore: update quotes.html from wiki (%s)", targetPage),
Content: base64.StdEncoding.EncodeToString([]byte(output)),
SHA: existingSHA,
}
payloadBytes, _ := json.Marshal(commitPayload)
putReq, _ := http.NewRequest("PUT", contentsURL, bytes.NewReader(payloadBytes))
putReq.Header.Set("Authorization", "Bearer "+token)
putReq.Header.Set("Accept", "application/vnd.github+json")
putReq.Header.Set("Content-Type", "application/json")
putResp, err := http.DefaultClient.Do(putReq)
if err != nil {
fmt.Fprintf(os.Stderr, "Error committing file: %v\n", err)
os.Exit(1)
}
defer putResp.Body.Close()
if putResp.StatusCode != http.StatusOK && putResp.StatusCode != http.StatusCreated {
body, _ := io.ReadAll(putResp.Body)
fmt.Fprintf(os.Stderr, "GitHub API commit failed (%d): %s\n", putResp.StatusCode, string(body))
os.Exit(1)
}
fmt.Printf("Successfully committed %d blockquotes to quotes.html\n", len(blockquotes))
}
EOF
go run main.go
<!-- ======================================================================= -->
<!-- SECTION 2: WORKFLOW 2 (.github/workflows/hydrate-index.yml) -->
<!-- ======================================================================= -->
name: Hydrate Index
on:
workflow_run:
workflows: ["Process Wiki Quotes"]
types: [completed]
workflow_dispatch:
permissions:
contents: write
jobs:
hydrate:
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
perl -0777 -i -pe '
open F, "<", "quotes.html" or die $!;
my $li = join "\n", map { " <li>$_</li>" } (<F> =~ m{<blockquote>(.*?)</blockquote>}gs);
s{<!-- QUOTES:START -->.*?<!-- QUOTES:END -->}{<!-- QUOTES:START -->\n <ul id="quote-pool" hidden>\n$li\n </ul>\n <!-- QUOTES:END -->}s or die "Markers missing";
' index.html
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@://github.com"
git commit -am "chore: hydrate quotes" && git push || true
<!-- ======================================================================= -->
<!-- SECTION 3: HTML BODY TARGET SNIPPET (index.html) -->
<!-- ======================================================================= -->
<div id="quote-display">Loading quotes...</div>
<!-- ======================================================================= -->
<!-- SECTION 4: COMPANION JAVASCRIPT RANDOMIZER (quotes.js) -->
<!-- ======================================================================= -->
document.addEventListener("DOMContentLoaded", () => {
const displayContainer = document.getElementById("quote-display");
const listItems = document.querySelectorAll("#quote-pool li");
if (!displayContainer) return;
if (!listItems.length) {
showLuckyNumber();
return;
}
// Build pool from hydrated hidden <li> elements and track shown items
const availableQuotes = Array.from(listItems).map((li) => li.innerHTML.trim());
const shownQuotes = [];
function showNextQuote() {
if (availableQuotes.length === 0) {
clearInterval(displayInterval);
showLuckyNumber();
return;
}
// Pick a random unshown quote and remove it from the pool to avoid repeats
const randomIndex = Math.floor(Math.random() * availableQuotes.length);
const [selectedQuote] = availableQuotes.splice(randomIndex, 1);
// Track shown quote and render as a blockquote
shownQuotes.push(selectedQuote);
displayContainer.innerHTML = `<blockquote>${selectedQuote}</blockquote>`;
}
function showLuckyNumber() {
const d1 = Math.floor(Math.random() * 10);
const d2 = Math.floor(Math.random() * 10);
const d3 = Math.floor(Math.random() * 10);
displayContainer.innerHTML = `<p>Your lucky number for today is: ${d1}-${d2}-${d3}</p>`;
}
// Render the first quote immediately, then cycle every 5 seconds
showNextQuote();
const displayInterval = setInterval(showNextQuote, 5000);
});
<!-- ======================================================================= -->