summaryrefslogtreecommitdiffstats
path: root/bin/ImANerdRules.rb
diff options
context:
space:
mode:
Diffstat (limited to 'bin/ImANerdRules.rb')
-rwxr-xr-xbin/ImANerdRules.rb209
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