You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

82 lines
2.1 KiB

  1. #!/usr/bin/env ruby
  2. # A little Markdown filter that scans your document for headings,
  3. # numbers them, adds anchors, and inserts a table of contents.
  4. #
  5. # To use it, make sure the headings you want numbered and linked are
  6. # in this format:
  7. #
  8. # ### Title ###
  9. #
  10. # I.e. they must have an equal number of octothorpes around the title
  11. # text. (In Markdown, `#` means `h1`, `##` means `h2`, and so on.)
  12. # The table of contents will be inserted before the first such
  13. # heading.
  14. #
  15. # Released into the public domain.
  16. # Sam Stephenson <sstephenson@gmail.com>
  17. # 2011-04-30
  18. def mdtoc(markdown)
  19. titles = []
  20. lines = markdown.split($/)
  21. start = nil
  22. # First pass: Scan the Markdown source looking for titles of the
  23. # format: `### Title ###`. Record the line number, header level
  24. # (number of octothorpes), and text of each matching title.
  25. lines.each_with_index do |line, line_no|
  26. if line.match(/^(\#{1,6})\s+(.+?)\s+\1$/)
  27. titles << [line_no, $1.length, $2]
  28. start ||= line_no
  29. end
  30. end
  31. last_section = nil
  32. last_level = nil
  33. # Second pass: Iterate over all matched titles and compute their
  34. # corresponding section numbers. Then replace the titles with
  35. # annotated anchors.
  36. titles.each do |title_info|
  37. line_no, level, text = title_info
  38. if last_section
  39. section = last_section.dup
  40. if last_level < level
  41. section << 1
  42. else
  43. (last_level - level).times { section.pop }
  44. section[-1] += 1
  45. end
  46. else
  47. section = [1]
  48. end
  49. name = section.join(".")
  50. lines[line_no] = %(#{"#" * level} <a name="section_#{name}"></a> #{name} #{text})
  51. title_info << section
  52. last_section = section
  53. last_level = level
  54. end
  55. # Third pass: Iterate over matched titles once more to produce the
  56. # table of contents. Then insert it immediately above the first
  57. # matched title.
  58. if start
  59. toc = titles.map do |(line_no, level, text, section)|
  60. name = section.join(".")
  61. %(#{" " * (section.length * 3)}* [#{name} #{text}](#section_#{name}))
  62. end + [""]
  63. lines.insert(start, *toc)
  64. end
  65. lines.join("\n")
  66. end
  67. if __FILE__ == $0
  68. puts mdtoc($<.read)
  69. end