diff options
-rwxr-xr-x | bin/ImANerdRules.rb | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/bin/ImANerdRules.rb b/bin/ImANerdRules.rb new file mode 100755 index 0000000..2443f51 --- /dev/null +++ b/bin/ImANerdRules.rb @@ -0,0 +1,209 @@ +#! /usr/bin/env ruby +# -*- coding: UTF-8 -*- + +require 'redcarpet' + +module ImANerdRules + + class CustomRender < Redcarpet::Render::HTML + def header(text, header_level) + text =~ /([\d+\.?]+)/ + "<h#{header_level} id=\"hdr#{$1}\">#{text}</h#{header_level}>" + 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, "<span id='ref#{@@line_num}'> [#{k}](#glos#{v[0]}) </span>" + 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 + "<div id='#{@lvl == 0 ? 'ptoc' : 'p'+lvl}'>" + 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='<h2>Glossary</h2><dl>' + 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 += "<dt id='glos#{v[0]}'>#{k}</dt><dd>#{v[1]}</br>\n" + c += "<a href='#{n.href}'>#{n.name}</a>\n" + o = v[-1].select{|e| e!=n }.collect{ |e| "<a href='#{e.href}'>#{e.name}</a>" }.join(',') + c += ' + ' + o unless o.empty? + c += "</dd></br>" + } + c + '</dl>' + 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</div>\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 |