summaryrefslogtreecommitdiffstats
path: root/bin/ImANerdRules.rb
blob: d3def303bcd8444bda6a909ec03b67c6ebf4b4de (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#! /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
        def image link, title, alt_text
            "<a data-fancybox='gallery' data-caption='#{title}' href='#{link}'><img src='#{link.gsub(/.png/,'_th.png')}'></a>"
        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} [#{@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='<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