#! /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} [#{@name}](#{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