2011-08-11 15:21:15 -04:00
|
|
|
#!/usr/bin/env ruby
|
|
|
|
|
|
|
|
# A little Markdown filter that scans your document for headings,
|
|
|
|
# numbers them, adds anchors, and inserts a table of contents.
|
|
|
|
#
|
|
|
|
# To use it, make sure the headings you want numbered and linked are
|
|
|
|
# in this format:
|
|
|
|
#
|
|
|
|
# ### Title ###
|
|
|
|
#
|
|
|
|
# I.e. they must have an equal number of octothorpes around the title
|
|
|
|
# text. (In Markdown, `#` means `h1`, `##` means `h2`, and so on.)
|
|
|
|
# The table of contents will be inserted before the first such
|
|
|
|
# heading.
|
|
|
|
#
|
|
|
|
# Released into the public domain.
|
|
|
|
# Sam Stephenson <sstephenson@gmail.com>
|
|
|
|
# 2011-04-30
|
|
|
|
|
|
|
|
def mdtoc(markdown)
|
|
|
|
titles = []
|
|
|
|
lines = markdown.split($/)
|
|
|
|
start = nil
|
|
|
|
|
|
|
|
# First pass: Scan the Markdown source looking for titles of the
|
|
|
|
# format: `### Title ###`. Record the line number, header level
|
|
|
|
# (number of octothorpes), and text of each matching title.
|
|
|
|
lines.each_with_index do |line, line_no|
|
|
|
|
if line.match(/^(\#{1,6})\s+(.+?)\s+\1$/)
|
|
|
|
titles << [line_no, $1.length, $2]
|
|
|
|
start ||= line_no
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
last_section = nil
|
|
|
|
last_level = nil
|
|
|
|
|
|
|
|
# Second pass: Iterate over all matched titles and compute their
|
|
|
|
# corresponding section numbers. Then replace the titles with
|
|
|
|
# annotated anchors.
|
|
|
|
titles.each do |title_info|
|
|
|
|
line_no, level, text = title_info
|
|
|
|
|
|
|
|
if last_section
|
|
|
|
section = last_section.dup
|
|
|
|
|
|
|
|
if last_level < level
|
|
|
|
section << 1
|
|
|
|
else
|
|
|
|
(last_level - level).times { section.pop }
|
|
|
|
section[-1] += 1
|
|
|
|
end
|
|
|
|
else
|
|
|
|
section = [1]
|
|
|
|
end
|
|
|
|
|
|
|
|
name = section.join(".")
|
2011-08-11 15:26:53 -04:00
|
|
|
lines[line_no] = %(#{"#" * level} <a name="section_#{name}"></a> #{name} #{text})
|
2011-08-11 15:21:15 -04:00
|
|
|
|
|
|
|
title_info << section
|
|
|
|
last_section = section
|
|
|
|
last_level = level
|
|
|
|
end
|
|
|
|
|
|
|
|
# Third pass: Iterate over matched titles once more to produce the
|
|
|
|
# table of contents. Then insert it immediately above the first
|
|
|
|
# matched title.
|
|
|
|
if start
|
|
|
|
toc = titles.map do |(line_no, level, text, section)|
|
|
|
|
name = section.join(".")
|
|
|
|
%(#{" " * (section.length * 3)}* [#{name} #{text}](#section_#{name}))
|
|
|
|
end + [""]
|
|
|
|
|
|
|
|
lines.insert(start, *toc)
|
|
|
|
end
|
|
|
|
|
|
|
|
lines.join("\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
if __FILE__ == $0
|
|
|
|
puts mdtoc($<.read)
|
|
|
|
end
|