verku 0.8.0.p
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.gitignore +49 -0
- data/BUILD.md +6 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.md +22 -0
- data/README.md +149 -0
- data/Rakefile +72 -0
- data/VERSION +1 -0
- data/bin/verku +5 -0
- data/lib/verku.rb +56 -0
- data/lib/verku/adapters/markdown.rb +44 -0
- data/lib/verku/cli.rb +93 -0
- data/lib/verku/dependency.rb +19 -0
- data/lib/verku/exporter.rb +77 -0
- data/lib/verku/extensions/string.rb +19 -0
- data/lib/verku/generator.rb +55 -0
- data/lib/verku/parser.rb +85 -0
- data/lib/verku/parser/epub.rb +187 -0
- data/lib/verku/parser/html.rb +245 -0
- data/lib/verku/parser/mobi.rb +17 -0
- data/lib/verku/parser/pdf.rb +54 -0
- data/lib/verku/parser/txt.rb +1 -0
- data/lib/verku/stats.rb +114 -0
- data/lib/verku/stream.rb +27 -0
- data/lib/verku/toc.rb +6 -0
- data/lib/verku/toc/epub.rb +41 -0
- data/lib/verku/toc/html.rb +78 -0
- data/lib/verku/version.rb +10 -0
- data/templates/config.erb +80 -0
- data/templates/cover.jpg +0 -0
- data/templates/dp-logo.png +0 -0
- data/templates/epub/back.erb +22 -0
- data/templates/epub/copyright.erb +46 -0
- data/templates/epub/cover.erb +12 -0
- data/templates/epub/cover.html +12 -0
- data/templates/epub/page.erb +15 -0
- data/templates/epub/user.css +500 -0
- data/templates/extras.tex +1 -0
- data/templates/html/copyright.erb +46 -0
- data/templates/html/layout.css +352 -0
- data/templates/html/layout.erb +45 -0
- data/templates/html/syntax.css +58 -0
- data/templates/html/thanks.erb +21 -0
- data/templates/html/user.css +7 -0
- data/templates/latex.erb +416 -0
- data/templates/merovex-logo.jpg +0 -0
- data/templates/merovex-logo.png +0 -0
- data/templates/pdf/layout.erb +418 -0
- data/templates/rakefile.rb +103 -0
- data/templates/readme.erb +3 -0
- data/templates/text/01-Getting-Started.md +27 -0
- data/templates/text/02-Creating-Chapters.md +22 -0
- data/templates/text/03-Generating-Output.md +2 -0
- data/templates/text/10-Test-Markdown.md +157 -0
- data/test/helper.rb +34 -0
- data/test/test_bookmaker.rb +7 -0
- data/verku.gemspec +142 -0
- metadata +317 -0
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
module Verku
|
3
|
+
module Parser
|
4
|
+
class HTML < Base
|
5
|
+
def content
|
6
|
+
raw = []
|
7
|
+
entries.keys.each do |chapter|
|
8
|
+
text = "\n\n## Chapter\n\n"
|
9
|
+
sections = []
|
10
|
+
entries[chapter].each do |section|
|
11
|
+
sections << read_content(section)[0]
|
12
|
+
# sections << "<p>#{read_content(section)[0].split(/\n{2,}/).map do |s|
|
13
|
+
# s.gsub!(/%.*/, '')
|
14
|
+
# s.squish
|
15
|
+
# end.join("</p>\n\n<p>")}</p>"
|
16
|
+
end
|
17
|
+
text << sections.join("\n\n* * *\n\n")
|
18
|
+
raw << "<div class='chapter'>\n#{text.to_html}\n</div>\n"
|
19
|
+
end
|
20
|
+
raw
|
21
|
+
end
|
22
|
+
def parse
|
23
|
+
puts "-- Exporting HTML"
|
24
|
+
html = parse_layout(content)
|
25
|
+
toc = TOC::HTML.generate(html)
|
26
|
+
locals = config.merge({
|
27
|
+
:contents => toc.content,
|
28
|
+
:toc => toc.to_html,
|
29
|
+
})
|
30
|
+
output = render_template(root_dir.join("_templates/html/layout.erb"), locals)
|
31
|
+
f = File.open(root_dir.join("builds/#{name}.html"), 'w')
|
32
|
+
f.write(output)
|
33
|
+
f.close
|
34
|
+
true
|
35
|
+
rescue Exception
|
36
|
+
p $!, $@
|
37
|
+
false
|
38
|
+
end
|
39
|
+
def parse_layout(chapters)
|
40
|
+
output = ''
|
41
|
+
chapters.each do |text|
|
42
|
+
# text.gsub!("{%", "{")
|
43
|
+
# text.gsub!(/%.*/,'')
|
44
|
+
text = text.split("\n\n").map{|s| s.gsub("\n", " ")}.join("\n\n")
|
45
|
+
text.gsub!(/``(.*?'?)''/) { "“#{$1}”"}
|
46
|
+
text.gsub!(/``(.*?'?)"/) { "“#{$1}”"}
|
47
|
+
text.gsub!(/``/, "“")
|
48
|
+
text.gsub!(/\b'\b/) { "’" }
|
49
|
+
text.gsub!(/`(.*?)'/) { "‘#{$1}’"}
|
50
|
+
# \{([^\}]+?)\} Within the curly braces.
|
51
|
+
text.gsub!(/\\pf?break\{\}/,"<hr />")
|
52
|
+
text.gsub!(/\\pf?break/,"<hr />")
|
53
|
+
text.gsub!(/\\%/,'%')
|
54
|
+
text.gsub!(/\\`e/,'é')
|
55
|
+
text.gsub!(/\\textgreater\{?\}?/, ">")
|
56
|
+
text.gsub!(/\\Bophendze/,'Bophendze')
|
57
|
+
text.gsub!(/\\ldots\{\}/,"…")
|
58
|
+
text.gsub!(/\\Dash\{\}/, "—")
|
59
|
+
text.gsub!(/\\Dash/, "—")
|
60
|
+
text.gsub!(/\\begin\{quote\}(.*?)\\end\{quote\}/m) { "<blockquote>#{$1.strip}</blockquote>"}
|
61
|
+
text.gsub!(/<\/blockquote>\s+?<blockquote>/m, "\n")
|
62
|
+
text.gsub!(/\\begin\{([^\}]+?)\}(.*?)\\end\{[^\}]+?\}/m) { "<div class='#{$1.strip}'>#{$2.strip}</div>"}
|
63
|
+
text.gsub!(/\\section\{([^\}]+?)\}/m) { "<h3>#{$1.strip}</h3>"}
|
64
|
+
['Character','Equipment','Organization','Index'].each do |s|
|
65
|
+
text.gsub!(/\\#{s}\{[^\}]+?\}\{([^\}]+?)\}/) { "<span class='#{s.downcase}'>#{$1.strip}</span>"}
|
66
|
+
text.gsub!(/\\#{s}\{([^\}]+?)\}/) { ""}
|
67
|
+
# text.gsub!(/\\#{s}\{([^\}]+?)\}/) { "<span class='#{s.downcase}'>#{$1.strip}</span>"}
|
68
|
+
end
|
69
|
+
text.gsub!(/\\footnote\{([^\}]+?)\}/m) { ""}
|
70
|
+
text.gsub!(/\\emph\{([^\}]+?)\}/m) { "<em>#{$1.strip}</em>"}
|
71
|
+
text.gsub!(/\\thought\{([^\}]+?)\}/m) { "<em>#{$1.strip}</em>"}
|
72
|
+
text.gsub!(/\\(.*?)\{([^\}]+?)\}/) { "<span class='#{$1.downcase}'>#{$2.strip}</span>"}
|
73
|
+
text.gsub!(/\\(.*?)\{([^\}]+?)\}/) { "<span class='#{$1.downcase}'>#{$2.strip}</span>"}
|
74
|
+
text.gsub!(/<\/span>\{[^\}]+?}/, "</span>")
|
75
|
+
text.gsub!(/<p><h([1-6])>(.*?)<\/h[1-6]><\/p>/) { "<h#{$1}>#{$2.strip}</h#{$1}>"}
|
76
|
+
text.gsub!(/(\S+)~(\S+)/) { "#{$1} #{$2}"}
|
77
|
+
output << text
|
78
|
+
end
|
79
|
+
output.gsub!(/\n\n+/, "\n\n")
|
80
|
+
return output
|
81
|
+
end
|
82
|
+
end
|
83
|
+
# # List of directories that should be skipped.
|
84
|
+
# #
|
85
|
+
# IGNORE_DIR = %w[. .. .svn]
|
86
|
+
#
|
87
|
+
# # Files that should be skipped.
|
88
|
+
# #
|
89
|
+
# IGNORE_FILES = /^(CHANGELOG|TOC)\..*?$/
|
90
|
+
#
|
91
|
+
# # List of recognized extensions.
|
92
|
+
# #
|
93
|
+
# EXTENSIONS = %w[md mkdn markdown]
|
94
|
+
#
|
95
|
+
# class << self
|
96
|
+
# # The footnote index control. We have to manipulate footnotes
|
97
|
+
# # because each chapter starts from 1, so we have duplicated references.
|
98
|
+
# #
|
99
|
+
# attr_accessor :footnote_index
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# # Parse all files and save the parsed content
|
103
|
+
# # to <tt>output/book_name.html</tt>.
|
104
|
+
# #
|
105
|
+
# def parse
|
106
|
+
# reset_footnote_index!
|
107
|
+
#
|
108
|
+
# # File.open(root_dir.join("builds/#{name}.html"), "w") do |file|
|
109
|
+
# # file << parse_layout(content)
|
110
|
+
# # end
|
111
|
+
# true
|
112
|
+
# rescue Exception
|
113
|
+
# false
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# def reset_footnote_index!
|
117
|
+
# self.class.footnote_index = 1
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# private
|
121
|
+
# def chapter_files(entry)
|
122
|
+
# # Chapters can be files outside a directory.
|
123
|
+
# if File.file?(entry)
|
124
|
+
# [entry]
|
125
|
+
# else
|
126
|
+
# Dir.glob("#{entry}/**/*.{#{EXTENSIONS.join(",")}}").sort
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# # Check if path is a valid entry.
|
131
|
+
# # Files/directories that start with a dot or underscore will be skipped.
|
132
|
+
# #
|
133
|
+
# def valid_entry?(entry)
|
134
|
+
# entry !~ /^(\.|_)/ && (valid_directory?(entry) || valid_file?(entry))
|
135
|
+
# end
|
136
|
+
#
|
137
|
+
# # Check if path is a valid directory.
|
138
|
+
# #
|
139
|
+
# def valid_directory?(entry)
|
140
|
+
# File.directory?(source.join(entry)) && !IGNORE_DIR.include?(File.basename(entry))
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# # Check if path is a valid file.
|
144
|
+
# #
|
145
|
+
# def valid_file?(entry)
|
146
|
+
# ext = File.extname(entry).gsub(/\./, "").downcase
|
147
|
+
# File.file?(source.join(entry)) && EXTENSIONS.include?(ext) && entry !~ IGNORE_FILES
|
148
|
+
# end
|
149
|
+
#
|
150
|
+
# # Render +file+ considering its extension.
|
151
|
+
# #
|
152
|
+
# def render_file(file, plain_syntax = false)
|
153
|
+
# file_format = format(file)
|
154
|
+
#
|
155
|
+
# content = Verku::Syntax.render(root_dir, file_format, File.read(file), plain_syntax)
|
156
|
+
#
|
157
|
+
# content = case file_format
|
158
|
+
# when :markdown
|
159
|
+
# Markdown.to_html(content)
|
160
|
+
# when :textile
|
161
|
+
# RedCloth.convert(content)
|
162
|
+
# else
|
163
|
+
# content
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# render_footnotes(content, plain_syntax)
|
167
|
+
# end
|
168
|
+
#
|
169
|
+
# def render_footnotes(content, plain_syntax = false)
|
170
|
+
# html = Nokogiri::HTML(content)
|
171
|
+
# footnotes = html.css("p[id^='fn']")
|
172
|
+
#
|
173
|
+
# return content if footnotes.empty?
|
174
|
+
#
|
175
|
+
# reset_footnote_index! unless self.class.footnote_index
|
176
|
+
#
|
177
|
+
# footnotes.each do |fn|
|
178
|
+
# index = self.class.footnote_index
|
179
|
+
# actual_index = fn["id"].gsub(/[^\d]/, "")
|
180
|
+
#
|
181
|
+
# fn.set_attribute("id", "_fn#{index}")
|
182
|
+
#
|
183
|
+
# html.css("a[href='#fn#{actual_index}']").each do |link|
|
184
|
+
# link.set_attribute("href", "#_fn#{index}")
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# html.css("a[href='#fnr#{actual_index}']").each do |link|
|
188
|
+
# link.set_attribute("href", "#_fnr#{index}")
|
189
|
+
# end
|
190
|
+
#
|
191
|
+
# html.css("[id=fnr#{actual_index}]").each do |tag|
|
192
|
+
# tag.set_attribute("id", "_fnr#{index}")
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# self.class.footnote_index += 1
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# html.css("body").inner_html
|
199
|
+
# end
|
200
|
+
#
|
201
|
+
# def format(file)
|
202
|
+
# case File.extname(file).downcase
|
203
|
+
# when ".markdown", ".mkdn", ".md"
|
204
|
+
# :markdown
|
205
|
+
# when ".textile"
|
206
|
+
# :textile
|
207
|
+
# else
|
208
|
+
# :html
|
209
|
+
# end
|
210
|
+
# end
|
211
|
+
#
|
212
|
+
# # Parse layout file, making available all configuration entries.
|
213
|
+
# #
|
214
|
+
# def parse_layout(html)
|
215
|
+
# puts "parse layout."
|
216
|
+
# toc = TOC::HTML.generate(html)
|
217
|
+
# locals = config.merge({
|
218
|
+
# :content => toc.content,
|
219
|
+
# :toc => toc.to_html,
|
220
|
+
# :changelog => render_changelog
|
221
|
+
# })
|
222
|
+
# render_template(root_dir.join("_templates/html/layout.erb"), locals)
|
223
|
+
# end
|
224
|
+
#
|
225
|
+
# # Render changelog file.
|
226
|
+
# # This file can be used to inform any book change.
|
227
|
+
# #
|
228
|
+
# def render_changelog
|
229
|
+
# changelog = Dir[root_dir.join("text/CHANGELOG.*")].first
|
230
|
+
# return render_file(changelog) if changelog
|
231
|
+
# nil
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# # Render all +files+ from a given chapter.
|
235
|
+
# #
|
236
|
+
# def render_chapter(files, plain_syntax = false)
|
237
|
+
# String.new.tap do |chapter|
|
238
|
+
# files.each do |file|
|
239
|
+
# chapter << render_file(file, plain_syntax) << "\n\n"
|
240
|
+
# end
|
241
|
+
# end
|
242
|
+
# end
|
243
|
+
# end
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Verku
|
2
|
+
module Parser
|
3
|
+
class Mobi < Base
|
4
|
+
def parse
|
5
|
+
puts "-- Exporting MOBI"
|
6
|
+
spawn_command ["kindlegen", epub_file.to_s,]
|
7
|
+
true
|
8
|
+
rescue Exception
|
9
|
+
p $!, $@
|
10
|
+
false
|
11
|
+
end
|
12
|
+
def epub_file
|
13
|
+
root_dir.join("builds/#{name}.epub")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'kramdown'
|
2
|
+
|
3
|
+
module Verku
|
4
|
+
module Parser
|
5
|
+
class PDF < Base
|
6
|
+
def content
|
7
|
+
raw = []
|
8
|
+
entries.keys.each do |chapter|
|
9
|
+
title = (chapter.empty?) ? "Untitled" : chapter.split('_')[1]
|
10
|
+
title = 'Untitled' if title.nil?
|
11
|
+
raw << "\\Chapter{#{title.gsub('-',' ')}}\n\n"
|
12
|
+
entries[chapter].each do |section|
|
13
|
+
s = read_content(section)[0].to_latex.gsub(/\$(\\index\{[^\$]*?\})\$/) {"#{$1}"}
|
14
|
+
raw << "#{s}\n\n* * *"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
raw
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse
|
21
|
+
puts "-- Exporting PDF"
|
22
|
+
locals = config.merge({ :contents => parse_layout(content) })
|
23
|
+
locals['copyright'].gsub!("(C)", "\\copyright{}")
|
24
|
+
output = render_template(root_dir.join("_templates/pdf/layout.erb"), locals)
|
25
|
+
File.open(root_dir.join(tex_file), 'w').write(output)
|
26
|
+
puts " - Pass 1"
|
27
|
+
spawn_command ["xelatex", tex_file.to_s,]
|
28
|
+
puts " - Pass 2"
|
29
|
+
spawn_command ["xelatex", tex_file.to_s,]
|
30
|
+
if config['is_final'].to_i == 0
|
31
|
+
puts " - Pass 3 - Indexing"
|
32
|
+
spawn_command ["makeindex #{name}.idx"]
|
33
|
+
# spawn_command ["makeglossaries #{name}.glo"]
|
34
|
+
spawn_command ["xelatex", tex_file.to_s,]
|
35
|
+
spawn_command ["rm *ilg *ind "]
|
36
|
+
end
|
37
|
+
|
38
|
+
spawn_command ["rm *.glo *.idx *.log *.out *.toc *aux *ist"]
|
39
|
+
spawn_command ["mv #{name}.pdf builds/#{name}.pdf"]
|
40
|
+
true
|
41
|
+
rescue Exception
|
42
|
+
p $!, $@
|
43
|
+
false
|
44
|
+
end
|
45
|
+
def parse_layout(text)
|
46
|
+
text = text.join("\n\n")
|
47
|
+
text.gsub!('* * *', "\n\n\\pbreak{}\n\n")
|
48
|
+
end
|
49
|
+
def tex_file
|
50
|
+
root_dir.join("builds/#{name}.tex")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
txt.rb
|
data/lib/verku/stats.rb
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
class Fixnum
|
2
|
+
def day
|
3
|
+
self * (60 * 60 * 24) # seconds * hours * minutes
|
4
|
+
end
|
5
|
+
def ago
|
6
|
+
Time.now - self
|
7
|
+
end
|
8
|
+
end
|
9
|
+
require 'json'
|
10
|
+
module Verku
|
11
|
+
class Stats
|
12
|
+
attr_reader :root_dir
|
13
|
+
|
14
|
+
def initialize(root_dir)
|
15
|
+
@root_dir = root_dir
|
16
|
+
@files = Dir["#{root_dir}/text/**/[0-9]*.tex"]
|
17
|
+
@words = 0
|
18
|
+
@progress = (File.exist?(log)) ? JSON.parse(File.open(log,'r').read).clone : {}
|
19
|
+
end
|
20
|
+
|
21
|
+
def log
|
22
|
+
"#{root_dir}/.wordcount"
|
23
|
+
end
|
24
|
+
|
25
|
+
def target
|
26
|
+
Verku.config(@root_dir)['wordcount']
|
27
|
+
end
|
28
|
+
def now
|
29
|
+
Date.today.to_s
|
30
|
+
end
|
31
|
+
def words
|
32
|
+
if @words == 0
|
33
|
+
most_recent = @files.max_by {|f| File.mtime(f)}
|
34
|
+
if !@progress[now].nil? and File.mtime(log) > File.mtime(most_recent)
|
35
|
+
@progress[now]
|
36
|
+
else
|
37
|
+
detex = "/usr/texbin/detex"
|
38
|
+
|
39
|
+
file = Tempfile.new('foo.tex')
|
40
|
+
file.write(text)
|
41
|
+
file.close
|
42
|
+
@progress[now] = `detex #{file.path}| wc -w`.to_i
|
43
|
+
file.unlink
|
44
|
+
# Do previous day's progress...if nil.
|
45
|
+
@progress[Date.yesterday.to_s] = @progress[now] if @progress.keys.count == 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if (lasttime != Date.yesterday.to_s)
|
49
|
+
# Update the record.
|
50
|
+
n = lasttime
|
51
|
+
w = @progress[lasttime]
|
52
|
+
n.upto(Date.yesterday.to_s) do |k|
|
53
|
+
# puts "Upto #{k}: @progress[#{k}] = #{w}"
|
54
|
+
@progress[k] = w
|
55
|
+
end
|
56
|
+
end
|
57
|
+
File.open(log,'w').write( JSON.generate(@progress) )
|
58
|
+
@words = @progress[now]
|
59
|
+
end
|
60
|
+
def lasttime
|
61
|
+
p = nil
|
62
|
+
@progress.keys.sort.each do |k|
|
63
|
+
break if k == now
|
64
|
+
p = k
|
65
|
+
end
|
66
|
+
p
|
67
|
+
end
|
68
|
+
def yesterday
|
69
|
+
@progress[lasttime]
|
70
|
+
end
|
71
|
+
def today
|
72
|
+
@words - @progress[lasttime]
|
73
|
+
end
|
74
|
+
def text
|
75
|
+
@text = nil
|
76
|
+
@text = @files.map{|f| File.open(f,'r').read}.join("\n\n\n") if @text.nil?
|
77
|
+
end
|
78
|
+
def remaining
|
79
|
+
target - words
|
80
|
+
end
|
81
|
+
|
82
|
+
# def html
|
83
|
+
# @html ||= Nokogiri::HTML(content)
|
84
|
+
# end
|
85
|
+
|
86
|
+
# def words
|
87
|
+
# @words ||= text.split(" ").size
|
88
|
+
# end
|
89
|
+
|
90
|
+
# def chapters
|
91
|
+
# @chapters ||= html.css(".chapter").size
|
92
|
+
# end
|
93
|
+
|
94
|
+
# def images
|
95
|
+
# @images ||= html.css("img").size
|
96
|
+
# end
|
97
|
+
|
98
|
+
# def footnotes
|
99
|
+
# @footnotes ||= html.css("p.footnote").size
|
100
|
+
# end
|
101
|
+
|
102
|
+
# def links
|
103
|
+
# @links ||= html.css("[href^='http']").size
|
104
|
+
# end
|
105
|
+
|
106
|
+
# def code_blocks
|
107
|
+
# @code_blocks ||= html.css("pre").size
|
108
|
+
# end
|
109
|
+
|
110
|
+
# def content
|
111
|
+
# @content ||= Parser::HTML.new(root_dir).content
|
112
|
+
# end
|
113
|
+
end
|
114
|
+
end
|
data/lib/verku/stream.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Verku
|
2
|
+
class Stream
|
3
|
+
attr_accessor :listener, :content
|
4
|
+
attr_reader :html
|
5
|
+
|
6
|
+
def initialize(content, listener)
|
7
|
+
@content = content
|
8
|
+
@listener = listener
|
9
|
+
@html = Nokogiri::HTML.parse(content)
|
10
|
+
end
|
11
|
+
|
12
|
+
def parse
|
13
|
+
traverse(html)
|
14
|
+
end
|
15
|
+
|
16
|
+
def traverse(node)
|
17
|
+
node.children.each do |child|
|
18
|
+
emit(child)
|
19
|
+
traverse(child)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def emit(node)
|
24
|
+
listener.send(:tag, node) if node.name =~ /h[1-6]/
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|