#! /usr/bin/env ruby # -*- coding: UTF-8 -*- require 'redcarpet' module ImANerdRules class CustomRender < Redcarpet::Render::HTML def header(text, header_level) text =~ /([\d+\.?]+)/ "#{text}" end def image link, title, alt_text "" end end class Node include Enumerable @@glossary = {} # @@line_num = 0 attr_reader :name, :parent, :content def self.glossary @@glossary end def initialize name, lvl, parent @name = name @lvl = lvl @parent = parent @children = nil end def add name @children ||= [] @children << Node.new( name, @lvl + 1, self) @children[-1] end def lvl o=nil, r='' if not (o.nil? or @children.nil?) r = (@children.index(o) + 1).to_s + '.'*(r.length > 0 ? 1 : 0) + r end return r if @parent.nil? @parent.lvl(self, r) # tail recursive end def each &block yield self @children.each { |n| n.each &block } unless @children.nil? end def to_s r='' r += "\n#{lvl}" + ' ' * (10-lvl.length) + ' ' + @name return r if @children.nil? @children.inject(r) { |r,c| c.to_s(r) } end def set_meta name, value instance_variable_set("@#{name}", value) if not self.class.method_defined? name.to_sym self.class.define_method(name.to_sym) { instance_variable_get "@#{name}" } end end def load dirname out = false meta = false @content = '' File.read(File.join(dirname, @name+'.md')).each_line { |l| if l =~ /^---\s*/ meta = !meta out = true if not meta next end if meta if l =~ /(\w+)\s*:\s*(.+)/ set_meta $1, $2 end end Node.glossary.each { |k,v| r = /#{k}/ if l =~ r # @@line_num += 1 # l.gsub! r, " [#{k}](#glos#{v[0]}) " l.gsub! r, "[#{k}](#glos#{v[0]})" r = v[-1] if r.empty? or r[-1] != self r << self end end } @content += l if out } # FIXME # @content += "[toc](#ptoc)" end def href "#hdr#{lvl}" end def title_md "#{'#' * (@lvl+1)} #{lvl} #{title}" end def anchor "
" end def toc return '' if @lvl == 0 "#{' ' * @lvl} #{'*' * (@lvl > 0 ? 1 : 0)} #{lvl} [#{@title}](#{href})\n" end end class Doc < Node def initialize dirname @dirname = dirname super nil, 0, nil @silent = false end def error line, line_num, why puts "#{line_num} : >#{line}< : #{why}" exit 1 end def say what puts what unless @silent end def read_toc filename level = 0 node = self last = nil File.foreach(File.join @dirname, filename).with_index do |line, line_num| line.chomp! if line =~ /^\s*(-*)\s*(\w+)$/ l = $1.length entry = $2 else error line, line_num, "line not matching" end if l > (level + 1) error line, line_num, "too deep in one step (from #{level} to #{l})" elsif l == (level + 1) level = l node = last elsif l < level while level > l node = node.parent level -= 1 end end last = node.add entry end end def read_glossary filename g=1 File.foreach(File.join @dirname, filename).with_index do |line, line_num| line.chomp! if line =~ /^\s*(.+)\s:\s(.+)\s:\s(.+)\s*$/ Node.glossary[$1] = [g, $2, $3, []] g += 1 else error line, line_num, "line not matching" end end end def print_toc each { |n| puts "#{n.lvl}#{' ' * (10-n.lvl.length)} #{n.name}" } end def load dirname end def load_content header @header = header each { |n| n.load @dirname } end def content inject('') { |a,n| a+= n.toc } end def glossary c='

Glossary

' Node.glossary.keys.sort.each { |k| v = Node.glossary[k] n = select { |n| n.name == v[2]} error "--", 0, "glossary '#{k} does not link to a known chapter" if n.size != 1 n = n[0] c += "
#{k}
#{v[1]}
\n" c += "#{n.name}\n" o = v[-1].select{|e| e!=n }.collect{ |e| "#{e.name}" }.join(',') c += ' + ' + o unless o.empty? c += "

" } c + '
' end def gen_html outf, html_header, html_footer md = Redcarpet::Markdown.new(CustomRender, {}) File.open(outf,'w') { |f| f.write File.read(html_header) f.write md.render(File.read(File.join @dirname, @header)) each { |n| f.write md.render(n.title_md) + "\n#{n.anchor}\n" f.write md.render(n.content) + "\n
\n" } f.write glossary f.write File.read(html_footer) } say "#{outf} generated" end end end if __FILE__ == $0 doc = ImANerdRules::Doc.new './TankOnTank' doc.read_toc '_toc' doc.read_glossary '_glossary' doc.load_content 'header.md' doc.gen_html 'TankOnTank.html', 'header', 'footer' exit 0 end # EOF