#!/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 # 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(".") lines[line_no] = %(#{"#" * level} #{name} #{text}) 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