BMDB - sgml/signature GitHub Wiki

Site

Assumptions

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 "#-#-#".

Code

<?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);
});

<!-- ======================================================================= -->
⚠️ **GitHub.com Fallback** ⚠️