#!/usr/bin/env bash set -Eeuo pipefail self="$(basename "$0")" usage() { cat <<-EOU usage: $self path/to/README.md eg: $self README.md WARNING: if README.md has the TOC-replacement comments, README.md.bak will be clobbered and the TOC will be inserted EOU } readme="${1:-}" if ! shift || [ ! -f "$readme" ]; then usage >&2; exit 1; fi toc="$( gawk ' # ignore comments in code blocks, which are not headers but look like them /^```/ { ignore = !ignore } /^#/ && !ignore { level = length($1) $1 = "" gsub(/^[[:space:]]|[[:space:]]$/, "") ++levelCounter[level] for (i in levelCounter) { if (i > level) { levelCounter[i] = 0 } } prefix = levelCounter[level] ".\t" for (i = 1; i < level; ++i) { prefix = "\t" prefix } # https://github.com/thlorenz/anchor-markdown-header/blob/56f77a232ab1915106ad1746b99333bf83ee32a2/anchor-markdown-header.js#L20-L30 hash = tolower($0) gsub(/ /, "-", hash) gsub(/[\/?!:\[\]`.,()*"'"'"';{}+=<>~\$|#@&–—]/, "", hash) gsub(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/, "", hash) printf "%s[%s](#%s)\n", prefix, $0, hash } ' "$readme" )" toFile="${readme}.bak" gawk -v toFile="$toFile" -v toc="$toc" ' BEGIN { printf "" > toFile } /^$/ { inToc = !inToc seenToc = 1 if (inToc) { print >> toFile print "" >> toFile print toc >> toFile print "" >> toFile print >> toFile } next } !inToc { print >> toFile } END { if (!seenToc) { close(toFile); printf "" > toFile } } ' "$readme" if [ -s "$toFile" ]; then mv "$toFile" "$readme" else rm "$toFile" echo "$toc" fi