Skip to content

Instantly share code, notes, and snippets.

@rshaker
Created July 15, 2025 22:59
Show Gist options
  • Select an option

  • Save rshaker/13bfbb6a67af5e93d24615c6f29b5f02 to your computer and use it in GitHub Desktop.

Select an option

Save rshaker/13bfbb6a67af5e93d24615c6f29b5f02 to your computer and use it in GitHub Desktop.
Markdown Table of Contents Generator
#!/usr/bin/env node
/**
* Markdown Table of Contents Generator
*
* This script generates a table of contents from a Markdown file,
* reading from stdin or a file and outputting to stdout.
*
* Usage:
* ./generate-toc.js [input_file]
* cat input_file.md | ./generate-toc.js
*/
const fs = require('fs');
const readline = require('readline');
// Regular expression to match Markdown headings
// Captures:
// - group 1: heading level (number of #)
// - group 2: heading text
const headingRegex = /^(#{1,6})\s+(.+)$/;
// Parse command line arguments
const inputFile = process.argv[2];
// Setup input stream (file or stdin)
const inputStream = inputFile
? fs.createReadStream(inputFile)
: process.stdin;
const rl = readline.createInterface({
input: inputStream,
crlfDelay: Infinity
});
// Store heading info
const headings = [];
// Process each line
rl.on('line', (line) => {
const match = line.match(headingRegex);
if (match) {
const level = match[1].length;
const text = match[2].trim();
headings.push({ level, text });
}
});
// Generate TOC after reading all lines
rl.on('close', () => {
console.log('## Table of Contents\n');
headings.forEach(heading => {
// Skip level 1 (title) if present
if (heading.level === 1) return;
// Create anchor from heading text
const anchor = createAnchor(heading.text);
// Create indentation based on heading level
const indent = ' '.repeat(heading.level - 2);
// Output TOC line
console.log(`${indent}- [${heading.text}](#${anchor})`);
});
});
/**
* Converts heading text to a GitHub-compatible anchor
*
* @param {string} text - The heading text
* @returns {string} - The anchor string
*/
function createAnchor(text) {
return text
.toLowerCase()
// Replace non-alphanumeric characters (except spaces) with nothing
.replace(/[^\w\s-]/g, '')
// Replace spaces with hyphens
.replace(/\s+/g, '-')
// Remove consecutive hyphens
.replace(/-+/g, '-')
// Remove leading/trailing hyphens
.replace(/^-+|-+$/g, '');
}
#!/bin/bash
# Markdown Table of Contents Generator
#
# This script generates a table of contents from a Markdown file,
# reading from stdin or a file and outputting to stdout.
#
# Usage:
# ./generate-toc.sh [input_file]
# cat input_file.md | ./generate-toc.sh
# Function to convert heading text to a GitHub-compatible anchor
create_anchor() {
echo "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^[:alnum:] -]//g' | tr ' ' '-' | sed -E 's/-+/-/g' | sed -E 's/^-+|-+$//g'
}
# Echo the TOC header
echo "## Table of Contents"
echo ""
# Process input from file or stdin
{
if [ -n "$1" ] && [ -f "$1" ]; then
cat "$1"
else
cat
fi
} | while IFS= read -r line || [ -n "$line" ]; do
# Check if line is a heading (starts with #)
if [[ "$line" =~ ^(#+)[[:space:]]+(.+)$ ]]; then
heading_level="${#BASH_REMATCH[1]}"
heading_text="${BASH_REMATCH[2]}"
# Skip level 1 (title) if present
if [ "$heading_level" -eq 1 ]; then
continue
fi
# Create anchor from heading text
anchor=$(create_anchor "$heading_text")
# Create indentation based on heading level
indent=$(printf "%$((($heading_level - 2) * 2))s" "")
# Output TOC line
echo "${indent}- [${heading_text}](#${anchor})"
fi
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment