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