toc.sh 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. #!/usr/bin/env bash
  2. set -Eeuo pipefail
  3. self="$(basename "$0")"
  4. usage() {
  5. cat <<-EOU
  6. usage: $self path/to/README.md
  7. eg: $self README.md
  8. WARNING: if README.md has the TOC-replacement comments,
  9. README.md.bak will be clobbered and the TOC will be inserted
  10. EOU
  11. }
  12. readme="${1:-}"
  13. if ! shift || [ ! -f "$readme" ]; then usage >&2; exit 1; fi
  14. toc="$(
  15. gawk '
  16. # ignore comments in code blocks, which are not headers but look like them
  17. /^```/ { ignore = !ignore }
  18. /^#/ && !ignore {
  19. level = length($1)
  20. $1 = ""
  21. gsub(/^[[:space:]]|[[:space:]]$/, "")
  22. ++levelCounter[level]
  23. for (i in levelCounter) {
  24. if (i > level) {
  25. levelCounter[i] = 0
  26. }
  27. }
  28. prefix = levelCounter[level] ".\t"
  29. for (i = 1; i < level; ++i) {
  30. prefix = "\t" prefix
  31. }
  32. hash = tolower($0)
  33. gsub(/['"'"'./`]/, "", hash)
  34. gsub(/[^a-z0-9]+/, "-", hash)
  35. gsub(/^-|-$/, "", hash)
  36. printf "%s[%s](#%s)\n", prefix, $0, hash
  37. }
  38. ' "$readme"
  39. )"
  40. toFile="${readme}.bak"
  41. gawk -v toFile="$toFile" -v toc="$toc" '
  42. BEGIN { printf "" > toFile }
  43. /^<!-- AUTOGENERATED TOC -->$/ {
  44. inToc = !inToc
  45. seenToc = 1
  46. if (inToc) {
  47. print >> toFile
  48. print "" >> toFile
  49. print toc >> toFile
  50. print "" >> toFile
  51. print >> toFile
  52. }
  53. next
  54. }
  55. !inToc { print >> toFile }
  56. END { if (!seenToc) { close(toFile); printf "" > toFile } }
  57. ' "$readme"
  58. if [ -s "$toFile" ]; then
  59. mv "$toFile" "$readme"
  60. else
  61. rm "$toFile"
  62. echo "$toc"
  63. fi