toc.sh 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  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. # https://github.com/thlorenz/anchor-markdown-header/blob/56f77a232ab1915106ad1746b99333bf83ee32a2/anchor-markdown-header.js#L20-L30
  33. hash = tolower($0)
  34. gsub(/ /, "-", hash)
  35. gsub(/[\/?!:\[\]`.,()*"'"'"';{}+=<>~\$|#@&–—]/, "", hash)
  36. gsub(/[。?!,、;:“”【】()〔〕[]﹃﹄“ ”‘’﹁﹂—…-~《》〈〉「」]/, "", hash)
  37. printf "%s[%s](#%s)\n", prefix, $0, hash
  38. }
  39. ' "$readme"
  40. )"
  41. toFile="${readme}.bak"
  42. gawk -v toFile="$toFile" -v toc="$toc" '
  43. BEGIN { printf "" > toFile }
  44. /^<!-- AUTOGENERATED TOC -->$/ {
  45. inToc = !inToc
  46. seenToc = 1
  47. if (inToc) {
  48. print >> toFile
  49. print "" >> toFile
  50. print toc >> toFile
  51. print "" >> toFile
  52. print >> toFile
  53. }
  54. next
  55. }
  56. !inToc { print >> toFile }
  57. END { if (!seenToc) { close(toFile); printf "" > toFile } }
  58. ' "$readme"
  59. if [ -s "$toFile" ]; then
  60. mv "$toFile" "$readme"
  61. else
  62. rm "$toFile"
  63. echo "$toc"
  64. fi