review 0.6.0
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.
- data/COPYING +515 -0
- data/ChangeLog +1278 -0
- data/README.rdoc +21 -0
- data/Rakefile +42 -0
- data/VERSION +1 -0
- data/bin/review-check +190 -0
- data/bin/review-checkdep +63 -0
- data/bin/review-compile +165 -0
- data/bin/review-epubmaker +525 -0
- data/bin/review-index +108 -0
- data/bin/review-preproc +140 -0
- data/bin/review-validate +51 -0
- data/bin/review-vol +106 -0
- data/doc/format.re +486 -0
- data/doc/format.txt +434 -0
- data/doc/format_idg.txt +194 -0
- data/doc/format_sjis.txt +313 -0
- data/doc/sample.css +91 -0
- data/doc/sample.yaml +46 -0
- data/lib/lineinput.rb +155 -0
- data/lib/review/book.rb +580 -0
- data/lib/review/builder.rb +274 -0
- data/lib/review/compat.rb +22 -0
- data/lib/review/compiler.rb +483 -0
- data/lib/review/epubbuilder.rb +692 -0
- data/lib/review/ewbbuilder.rb +382 -0
- data/lib/review/exception.rb +21 -0
- data/lib/review/htmlbuilder.rb +370 -0
- data/lib/review/htmllayout.rb +19 -0
- data/lib/review/htmlutils.rb +27 -0
- data/lib/review/idgxmlbuilder.rb +1078 -0
- data/lib/review/index.rb +224 -0
- data/lib/review/latexbuilder.rb +420 -0
- data/lib/review/latexindex.rb +35 -0
- data/lib/review/latexutils.rb +52 -0
- data/lib/review/preprocessor.rb +520 -0
- data/lib/review/textutils.rb +19 -0
- data/lib/review/tocparser.rb +333 -0
- data/lib/review/tocprinter.rb +220 -0
- data/lib/review/topbuilder.rb +572 -0
- data/lib/review/unfold.rb +138 -0
- data/lib/review/volume.rb +66 -0
- data/lib/review.rb +4 -0
- data/review.gemspec +93 -0
- data/setup.rb +1587 -0
- data/test/test_epubbuilder.rb +73 -0
- data/test/test_helper.rb +2 -0
- data/test/test_htmlbuilder.rb +42 -0
- data/test/test_latexbuilder.rb +74 -0
- metadata +122 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
#
|
2
|
+
#
|
3
|
+
# Copyright (c) 2002-2009 Minero Aoki
|
4
|
+
#
|
5
|
+
# This program is free software.
|
6
|
+
# You can distribute or modify this program under the terms of
|
7
|
+
# the GNU LGPL, Lesser General Public License version 2.1.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'review/index'
|
11
|
+
require 'review/exception'
|
12
|
+
require 'stringio'
|
13
|
+
require 'nkf'
|
14
|
+
|
15
|
+
module ReVIEW
|
16
|
+
|
17
|
+
class Builder
|
18
|
+
|
19
|
+
def initialize(strict = false, *args)
|
20
|
+
@strict = strict
|
21
|
+
builder_init(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def builder_init(*args)
|
25
|
+
end
|
26
|
+
private :builder_init
|
27
|
+
|
28
|
+
def setParameter(param)
|
29
|
+
@param = param
|
30
|
+
end
|
31
|
+
|
32
|
+
def bind(compiler, chapter, location)
|
33
|
+
@compiler = compiler
|
34
|
+
@chapter = chapter
|
35
|
+
@location = location
|
36
|
+
@output = StringIO.new
|
37
|
+
@book = ReVIEW.book
|
38
|
+
builder_init_file
|
39
|
+
end
|
40
|
+
|
41
|
+
def builder_init_file
|
42
|
+
end
|
43
|
+
private :builder_init_file
|
44
|
+
|
45
|
+
def result
|
46
|
+
@output.string
|
47
|
+
end
|
48
|
+
|
49
|
+
def print(*s)
|
50
|
+
if @param["outencoding"] =~ /^EUC$/i
|
51
|
+
@output.print(NKF.nkf("-W, -e", *s))
|
52
|
+
elsif @param["outencoding"] =~ /^SJIS$/i
|
53
|
+
@output.print(NKF.nkf("-W, -s", *s))
|
54
|
+
elsif @param["outencoding"] =~ /^JIS$/i
|
55
|
+
@output.print(NKF.nkf("-W, -j", *s))
|
56
|
+
else
|
57
|
+
@output.print(*s)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def puts(*s)
|
62
|
+
if @param["outencoding"] =~ /^EUC$/i
|
63
|
+
@output.puts(NKF.nkf("-W, -e", *s))
|
64
|
+
elsif @param["outencoding"] =~ /^SJIS$/i
|
65
|
+
@output.puts(NKF.nkf("-W, -s", *s))
|
66
|
+
elsif @param["outencoding"] =~ /^JIS$/i
|
67
|
+
@output.puts(NKF.nkf("-W, -j", *s))
|
68
|
+
else
|
69
|
+
@output.puts(*s)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def list(lines, id, caption)
|
74
|
+
begin
|
75
|
+
list_header id, caption
|
76
|
+
rescue KeyError
|
77
|
+
error "no such list: #{id}"
|
78
|
+
end
|
79
|
+
list_body lines
|
80
|
+
end
|
81
|
+
|
82
|
+
def listnum(lines, id, caption)
|
83
|
+
begin
|
84
|
+
list_header id, caption
|
85
|
+
rescue KeyError
|
86
|
+
error "no such list: #{id}"
|
87
|
+
end
|
88
|
+
listnum_body lines
|
89
|
+
end
|
90
|
+
|
91
|
+
def source(lines, caption)
|
92
|
+
source_header caption
|
93
|
+
source_body lines
|
94
|
+
end
|
95
|
+
|
96
|
+
def image(lines, id, caption_or_metric, caption = nil)
|
97
|
+
if caption
|
98
|
+
metric = caption_or_metric
|
99
|
+
else
|
100
|
+
metric = nil
|
101
|
+
caption = caption_or_metric
|
102
|
+
end
|
103
|
+
if @chapter.image(id).bound?
|
104
|
+
image_image id, metric, caption
|
105
|
+
else
|
106
|
+
warn "image not bound: #{id}" if @strict
|
107
|
+
image_dummy id, caption, lines
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def table(lines, id = nil, caption = nil)
|
112
|
+
rows = []
|
113
|
+
sepidx = nil
|
114
|
+
lines.each_with_index do |line, idx|
|
115
|
+
if /\A[\=\-]{12}/ =~ line
|
116
|
+
# just ignore
|
117
|
+
#error "too many table separator" if sepidx
|
118
|
+
sepidx ||= idx
|
119
|
+
next
|
120
|
+
end
|
121
|
+
rows.push line.strip.split(/\t+/).map {|s| s.sub(/\A\./, '') }
|
122
|
+
end
|
123
|
+
rows = adjust_n_cols(rows)
|
124
|
+
|
125
|
+
begin
|
126
|
+
table_header id, caption unless caption.nil?
|
127
|
+
rescue KeyError => err
|
128
|
+
error "no such table: #{id}"
|
129
|
+
end
|
130
|
+
return if rows.empty?
|
131
|
+
table_begin rows.first.size
|
132
|
+
if sepidx
|
133
|
+
sepidx.times do
|
134
|
+
tr rows.shift.map {|s| th(compile_inline(s)) }
|
135
|
+
end
|
136
|
+
rows.each do |cols|
|
137
|
+
tr cols.map {|s| td(compile_inline(s)) }
|
138
|
+
end
|
139
|
+
else
|
140
|
+
rows.each do |cols|
|
141
|
+
h, *cs = *cols
|
142
|
+
tr [th(compile_inline(h))] + cs.map {|s| td(compile_inline(s)) }
|
143
|
+
end
|
144
|
+
end
|
145
|
+
table_end
|
146
|
+
end
|
147
|
+
|
148
|
+
def adjust_n_cols(rows)
|
149
|
+
rows.each do |cols|
|
150
|
+
while cols.last and cols.last.strip.empty?
|
151
|
+
cols.pop
|
152
|
+
end
|
153
|
+
end
|
154
|
+
n_maxcols = rows.map {|cols| cols.size }.max
|
155
|
+
rows.each do |cols|
|
156
|
+
cols.concat [''] * (n_maxcols - cols.size)
|
157
|
+
end
|
158
|
+
rows
|
159
|
+
end
|
160
|
+
private :adjust_n_cols
|
161
|
+
|
162
|
+
#def footnote(id, str)
|
163
|
+
# @footnotes.push [id, str]
|
164
|
+
#end
|
165
|
+
#
|
166
|
+
#def flush_footnote
|
167
|
+
# footnote_begin
|
168
|
+
# @footnotes.each do |id, str|
|
169
|
+
# footnote_item(id, str)
|
170
|
+
# end
|
171
|
+
# footnote_end
|
172
|
+
#end
|
173
|
+
|
174
|
+
def compile_inline(s)
|
175
|
+
@compiler.text(s)
|
176
|
+
end
|
177
|
+
|
178
|
+
def inline_chapref(id)
|
179
|
+
@chapter.env.chapter_index.display_string(id)
|
180
|
+
rescue KeyError
|
181
|
+
error "unknown chapter: #{id}"
|
182
|
+
nofunc_text("[UnknownChapter:#{id}]")
|
183
|
+
end
|
184
|
+
|
185
|
+
def inline_chap(id)
|
186
|
+
@chapter.env.chapter_index.number(id)
|
187
|
+
rescue KeyError
|
188
|
+
error "unknown chapter: #{id}"
|
189
|
+
nofunc_text("[UnknownChapter:#{id}]")
|
190
|
+
end
|
191
|
+
|
192
|
+
def inline_title(id)
|
193
|
+
@chapter.env.chapter_index.title(id)
|
194
|
+
rescue KeyError
|
195
|
+
error "unknown chapter: #{id}"
|
196
|
+
nofunc_text("[UnknownChapter:#{id}]")
|
197
|
+
end
|
198
|
+
|
199
|
+
def inline_list(id)
|
200
|
+
"リスト#{@chapter.list(id).number}"
|
201
|
+
rescue KeyError
|
202
|
+
error "unknown list: #{id}"
|
203
|
+
nofunc_text("[UnknownList:#{id}]")
|
204
|
+
end
|
205
|
+
|
206
|
+
def inline_img(id)
|
207
|
+
"図#{@chapter.image(id).number}"
|
208
|
+
rescue KeyError
|
209
|
+
error "unknown image: #{id}"
|
210
|
+
nofunc_text("[UnknownImage:#{id}]")
|
211
|
+
end
|
212
|
+
|
213
|
+
def inline_table(id)
|
214
|
+
"表#{@chapter.table(id).number}"
|
215
|
+
rescue KeyError
|
216
|
+
error "unknown table: #{id}"
|
217
|
+
nofunc_text("[UnknownTable:#{id}]")
|
218
|
+
end
|
219
|
+
|
220
|
+
def inline_fn(id)
|
221
|
+
@chapter.footnote(id).content
|
222
|
+
rescue KeyError
|
223
|
+
error "unknown footnote: #{id}"
|
224
|
+
nofunc_text("[UnknownFootnote:#{id}]")
|
225
|
+
end
|
226
|
+
|
227
|
+
def inline_bou(str)
|
228
|
+
text(str)
|
229
|
+
end
|
230
|
+
|
231
|
+
def inline_ruby(arg)
|
232
|
+
base, ruby = *arg.split(',', 2)
|
233
|
+
compile_ruby(base, ruby)
|
234
|
+
end
|
235
|
+
|
236
|
+
def inline_kw(arg)
|
237
|
+
word, alt = *arg.split(',', 2)
|
238
|
+
compile_kw(word, alt)
|
239
|
+
end
|
240
|
+
|
241
|
+
def inline_href(arg)
|
242
|
+
url, label = *arg.scan(/(?:(?:(?:\\\\)*\\,)|[^,\\]+)+/).map(&:lstrip)
|
243
|
+
url = url.gsub(/\\,/, ",").strip
|
244
|
+
compile_href(url, label)
|
245
|
+
end
|
246
|
+
|
247
|
+
def text(str)
|
248
|
+
str
|
249
|
+
end
|
250
|
+
|
251
|
+
def bibpaper(lines, id, caption)
|
252
|
+
puts "<div>"
|
253
|
+
bibpaper_header id, caption
|
254
|
+
unless lines.empty?
|
255
|
+
bibpaper_bibpaper id, caption, lines
|
256
|
+
end
|
257
|
+
puts "</div>"
|
258
|
+
end
|
259
|
+
|
260
|
+
def raw(str)
|
261
|
+
print str.gsub("\\n", "\n")
|
262
|
+
end
|
263
|
+
|
264
|
+
def warn(msg)
|
265
|
+
$stderr.puts "#{@location}: warning: #{msg}"
|
266
|
+
end
|
267
|
+
|
268
|
+
def error(msg)
|
269
|
+
raise ApplicationError, "#{@location}: error: #{msg}"
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
end # module ReVIEW
|
@@ -0,0 +1,22 @@
|
|
1
|
+
unless String.method_defined?(:lines)
|
2
|
+
# Ruby 1.8
|
3
|
+
class String
|
4
|
+
alias lines to_a
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
if String.method_defined?(:bytesize)
|
9
|
+
# Ruby 1.9
|
10
|
+
class String
|
11
|
+
alias charsize size
|
12
|
+
end
|
13
|
+
else
|
14
|
+
# Ruby 1.8
|
15
|
+
class String
|
16
|
+
alias bytesize size
|
17
|
+
|
18
|
+
def charsize
|
19
|
+
split(//).size
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,483 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2002-2007 Minero Aoki
|
3
|
+
# Copyright (c) 2009-2010 Minero Aoki, Kenshi Muto
|
4
|
+
#
|
5
|
+
# This program is free software.
|
6
|
+
# You can distribute or modify this program under the terms of
|
7
|
+
# the GNU LGPL, Lesser General Public License version 2.1.
|
8
|
+
#
|
9
|
+
|
10
|
+
require 'review/compat'
|
11
|
+
require 'review/preprocessor'
|
12
|
+
require 'review/exception'
|
13
|
+
require 'lineinput'
|
14
|
+
|
15
|
+
module ReVIEW
|
16
|
+
|
17
|
+
class Location
|
18
|
+
def initialize(filename, f)
|
19
|
+
@filename = filename
|
20
|
+
@f = f
|
21
|
+
end
|
22
|
+
|
23
|
+
attr_reader :filename
|
24
|
+
|
25
|
+
def lineno
|
26
|
+
@f.lineno
|
27
|
+
end
|
28
|
+
|
29
|
+
def string
|
30
|
+
"#{@filename}:#{@f.lineno}"
|
31
|
+
end
|
32
|
+
|
33
|
+
alias to_s string
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class Compiler
|
38
|
+
|
39
|
+
def initialize(strategy)
|
40
|
+
@strategy = strategy
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :strategy
|
44
|
+
|
45
|
+
def setParameter(param)
|
46
|
+
@param = param
|
47
|
+
@strategy.setParameter(@param)
|
48
|
+
end
|
49
|
+
|
50
|
+
def compile(chap)
|
51
|
+
@chapter = chap
|
52
|
+
@chapter.setParameter(@param)
|
53
|
+
do_compile
|
54
|
+
@strategy.result
|
55
|
+
end
|
56
|
+
|
57
|
+
class SyntaxElement
|
58
|
+
def initialize(name, type, argc, &block)
|
59
|
+
@name = name
|
60
|
+
@type = type
|
61
|
+
@argc_spec = argc
|
62
|
+
@checker = block
|
63
|
+
end
|
64
|
+
|
65
|
+
attr_reader :name
|
66
|
+
|
67
|
+
def check_args(args)
|
68
|
+
unless @argc_spec === args.size
|
69
|
+
raise CompileError, "wrong # of parameters (block command //#{@name}, expect #{@argc_spec} but #{args.size})"
|
70
|
+
end
|
71
|
+
@checker.call(*args) if @checker
|
72
|
+
end
|
73
|
+
|
74
|
+
def min_argc
|
75
|
+
case @argc_spec
|
76
|
+
when Range then @argc_spec.begin
|
77
|
+
when Integer then @argc_spec
|
78
|
+
else
|
79
|
+
raise TypeError, "argc_spec is not Range/Integer: #{inspect()}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def block_required?
|
84
|
+
@type == :block
|
85
|
+
end
|
86
|
+
|
87
|
+
def block_allowed?
|
88
|
+
@type == :block or @type == :optional
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
SYNTAX = {}
|
93
|
+
|
94
|
+
def Compiler.defblock(name, argc, optional = false, &block)
|
95
|
+
defsyntax name, (optional ? :optional : :block), argc, &block
|
96
|
+
end
|
97
|
+
|
98
|
+
def Compiler.defsingle(name, argc, &block)
|
99
|
+
defsyntax name, :line, argc, &block
|
100
|
+
end
|
101
|
+
|
102
|
+
def Compiler.defsyntax(name, type, argc, &block)
|
103
|
+
SYNTAX[name] = SyntaxElement.new(name, type, argc, &block)
|
104
|
+
end
|
105
|
+
|
106
|
+
def syntax_defined?(name)
|
107
|
+
SYNTAX.key?(name.to_sym)
|
108
|
+
end
|
109
|
+
|
110
|
+
def syntax_descriptor(name)
|
111
|
+
SYNTAX[name.to_sym]
|
112
|
+
end
|
113
|
+
|
114
|
+
class InlineSyntaxElement
|
115
|
+
def initialize(name)
|
116
|
+
@name = name
|
117
|
+
end
|
118
|
+
|
119
|
+
attr_reader :name
|
120
|
+
end
|
121
|
+
|
122
|
+
INLINE = {}
|
123
|
+
|
124
|
+
def Compiler.definline(name)
|
125
|
+
INLINE[name] = InlineSyntaxElement.new(name)
|
126
|
+
end
|
127
|
+
|
128
|
+
def inline_defined?(name)
|
129
|
+
INLINE.key?(name.to_sym)
|
130
|
+
end
|
131
|
+
|
132
|
+
defblock :read, 0
|
133
|
+
defblock :lead, 0
|
134
|
+
defblock :list, 2
|
135
|
+
defblock :emlist, 0..1
|
136
|
+
defblock :cmd, 0..1
|
137
|
+
defblock :table, 0..2
|
138
|
+
defblock :quote, 0
|
139
|
+
defblock :image, 2..3, true
|
140
|
+
defblock :source, 1
|
141
|
+
defblock :listnum, 2
|
142
|
+
defblock :emlistnum, 0..1
|
143
|
+
defblock :bibpaper, 2..3, true
|
144
|
+
defblock :address, 0
|
145
|
+
defblock :blockquote, 0
|
146
|
+
defblock :bpo, 0
|
147
|
+
defblock :flushright, 0
|
148
|
+
defblock :note, 0..1
|
149
|
+
|
150
|
+
defsingle :footnote, 2
|
151
|
+
defsingle :comment, 1
|
152
|
+
defsingle :noindent, 0
|
153
|
+
defsingle :linebreak, 0
|
154
|
+
defsingle :pagebreak, 0
|
155
|
+
defsingle :hr, 0
|
156
|
+
defsingle :parasep, 0
|
157
|
+
defsingle :label, 1
|
158
|
+
defsingle :raw, 1
|
159
|
+
defsingle :tsize, 1
|
160
|
+
|
161
|
+
definline :chapref
|
162
|
+
definline :chap
|
163
|
+
definline :title
|
164
|
+
definline :img
|
165
|
+
definline :list
|
166
|
+
definline :table
|
167
|
+
definline :fn
|
168
|
+
definline :kw
|
169
|
+
definline :ruby
|
170
|
+
definline :bou
|
171
|
+
definline :ami
|
172
|
+
definline :b
|
173
|
+
definline :dtp
|
174
|
+
definline :code
|
175
|
+
definline :bib
|
176
|
+
definline :href
|
177
|
+
definline :recipe
|
178
|
+
|
179
|
+
definline :abbr
|
180
|
+
definline :acronym
|
181
|
+
definline :cite
|
182
|
+
definline :dfn
|
183
|
+
definline :em
|
184
|
+
definline :kbd
|
185
|
+
definline :q
|
186
|
+
definline :samp
|
187
|
+
definline :strong
|
188
|
+
definline :var
|
189
|
+
definline :big
|
190
|
+
definline :small
|
191
|
+
definline :del
|
192
|
+
definline :ins
|
193
|
+
definline :sup
|
194
|
+
definline :sub
|
195
|
+
definline :tt
|
196
|
+
definline :i
|
197
|
+
|
198
|
+
definline :raw
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def do_compile
|
203
|
+
f = LineInput.new(Preprocessor::Strip.new(StringIO.new(@chapter.content)))
|
204
|
+
@strategy.bind self, @chapter, Location.new(@chapter.basename, f)
|
205
|
+
tagged_section_init
|
206
|
+
while f.next?
|
207
|
+
case f.peek
|
208
|
+
when /\A=+[\[\s\{]/
|
209
|
+
compile_headline f.gets
|
210
|
+
when %r<\A\s+\*>
|
211
|
+
compile_ulist f
|
212
|
+
when %r<\A\s+□>
|
213
|
+
compile_multichoice f
|
214
|
+
when %r<\A\s+○>
|
215
|
+
compile_singlechoice f
|
216
|
+
when %r<\A\s+\d+\.>
|
217
|
+
compile_olist f
|
218
|
+
when %r<\A:\s>
|
219
|
+
compile_dlist f
|
220
|
+
when %r<\A//\}>
|
221
|
+
error 'block end seen but not opened'
|
222
|
+
f.gets
|
223
|
+
when %r<\A//[a-z]+>
|
224
|
+
name, args, lines = read_command(f)
|
225
|
+
syntax = syntax_descriptor(name)
|
226
|
+
unless syntax
|
227
|
+
error "unknown command: //#{name}"
|
228
|
+
compile_unknown_command args, lines
|
229
|
+
next
|
230
|
+
end
|
231
|
+
compile_command syntax, args, lines
|
232
|
+
when %r<\A//>
|
233
|
+
line = f.gets
|
234
|
+
warn "`//' seen but is not valid command: #{line.strip.inspect}"
|
235
|
+
if block_open?(line)
|
236
|
+
warn "skipping block..."
|
237
|
+
read_block(f)
|
238
|
+
end
|
239
|
+
else
|
240
|
+
if f.peek.strip.empty?
|
241
|
+
f.gets
|
242
|
+
next
|
243
|
+
end
|
244
|
+
compile_paragraph f
|
245
|
+
end
|
246
|
+
end
|
247
|
+
close_all_tagged_section
|
248
|
+
end
|
249
|
+
|
250
|
+
def compile_headline(line)
|
251
|
+
m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(line)
|
252
|
+
level = m[1].size
|
253
|
+
tag = m[2]
|
254
|
+
label = m[3]
|
255
|
+
caption = m[4].strip
|
256
|
+
while @tagged_section.last and @tagged_section.last[1] >= level
|
257
|
+
close_tagged_section(* @tagged_section.pop)
|
258
|
+
end
|
259
|
+
if tag
|
260
|
+
open_tagged_section tag, level, label, caption
|
261
|
+
else
|
262
|
+
@strategy.headline level, label, @strategy.text(caption)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def headline(level, label, caption)
|
267
|
+
@strategy.headline level, label, @strategy.text(caption)
|
268
|
+
end
|
269
|
+
|
270
|
+
def tagged_section_init
|
271
|
+
@tagged_section = []
|
272
|
+
end
|
273
|
+
|
274
|
+
def open_tagged_section(tag, level, label, caption)
|
275
|
+
mid = "#{tag}_begin"
|
276
|
+
unless @strategy.respond_to?(mid)
|
277
|
+
error "strategy does not support tagged section: #{tag}"
|
278
|
+
headline level, label, caption
|
279
|
+
return
|
280
|
+
end
|
281
|
+
@tagged_section.push [tag, level]
|
282
|
+
@strategy.__send__ mid, level, label, @strategy.text(caption)
|
283
|
+
end
|
284
|
+
|
285
|
+
def close_tagged_section(tag, level)
|
286
|
+
mid = "#{tag}_end"
|
287
|
+
if @strategy.respond_to?(mid)
|
288
|
+
@strategy.__send__ mid, level
|
289
|
+
else
|
290
|
+
error "strategy does not support block op: #{mid}"
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def close_all_tagged_section
|
295
|
+
until @tagged_section.empty?
|
296
|
+
close_tagged_section(* @tagged_section.pop)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def compile_ulist(f)
|
301
|
+
@strategy.ul_begin
|
302
|
+
f.while_match(/\A\s+\*/) do |line|
|
303
|
+
buf = [text(line.sub(/\*/, '').strip)]
|
304
|
+
f.while_match(/\A\s+(?!\*)\S/) do |cont|
|
305
|
+
buf.push text(cont.strip)
|
306
|
+
end
|
307
|
+
@strategy.ul_item buf
|
308
|
+
end
|
309
|
+
@strategy.ul_end
|
310
|
+
end
|
311
|
+
|
312
|
+
def compile_multichoice(f)
|
313
|
+
@strategy.choice_multi_begin
|
314
|
+
f.while_match(/\A\s+□/) do |line|
|
315
|
+
buf = [text(line.sub(/□/, '').strip)]
|
316
|
+
f.while_match(/\A\s+(?!□)\S/) do |cont|
|
317
|
+
buf.push text(cont.strip)
|
318
|
+
end
|
319
|
+
@strategy.ul_item buf
|
320
|
+
end
|
321
|
+
@strategy.choice_multi_end
|
322
|
+
end
|
323
|
+
|
324
|
+
def compile_singlechoice(f)
|
325
|
+
@strategy.choice_single_begin
|
326
|
+
f.while_match(/\A\s+○/) do |line|
|
327
|
+
buf = [text(line.sub(/○/, '').strip)]
|
328
|
+
f.while_match(/\A\s+(?!○)\S/) do |cont|
|
329
|
+
buf.push text(cont.strip)
|
330
|
+
end
|
331
|
+
@strategy.ul_item buf
|
332
|
+
end
|
333
|
+
@strategy.choice_single_end
|
334
|
+
end
|
335
|
+
|
336
|
+
def compile_olist(f)
|
337
|
+
@strategy.ol_begin
|
338
|
+
f.while_match(/\A\s+\d+\./) do |line|
|
339
|
+
num = line.match(/(\d+)\./)[1]
|
340
|
+
buf = [text(line.sub(/\d+\./, '').strip)]
|
341
|
+
f.while_match(/\A\s+(?!\d+\.)\S/) do |cont|
|
342
|
+
buf.push text(cont.strip)
|
343
|
+
end
|
344
|
+
@strategy.ol_item buf, num
|
345
|
+
end
|
346
|
+
@strategy.ol_end
|
347
|
+
end
|
348
|
+
|
349
|
+
def compile_dlist(f)
|
350
|
+
@strategy.dl_begin
|
351
|
+
while /\A:/ =~ f.peek
|
352
|
+
@strategy.dt text(f.gets.sub(/:/, '').strip)
|
353
|
+
@strategy.dd f.break(/\A\S/).map {|line| text(line.strip) }
|
354
|
+
f.skip_blank_lines
|
355
|
+
end
|
356
|
+
@strategy.dl_end
|
357
|
+
end
|
358
|
+
|
359
|
+
def compile_paragraph(f)
|
360
|
+
buf = []
|
361
|
+
f.until_match(%r<\A//>) do |line|
|
362
|
+
break if line.strip.empty?
|
363
|
+
buf.push text(line.sub(/^(\t+)\s*/) {|m| "<!ESCAPETAB!>" * m.size}.strip.gsub(/<!ESCAPETAB!>/, "\t"))
|
364
|
+
end
|
365
|
+
@strategy.paragraph buf
|
366
|
+
end
|
367
|
+
|
368
|
+
def read_command(f)
|
369
|
+
line = f.gets
|
370
|
+
name = line.slice(/[a-z]+/).intern
|
371
|
+
args = parse_args(line.sub(%r<\A//[a-z]+>, '').rstrip.chomp('{'))
|
372
|
+
lines = block_open?(line) ? read_block(f) : nil
|
373
|
+
return name, args, lines
|
374
|
+
end
|
375
|
+
|
376
|
+
def block_open?(line)
|
377
|
+
line.rstrip[-1,1] == '{'
|
378
|
+
end
|
379
|
+
|
380
|
+
def read_block(f)
|
381
|
+
head = f.lineno
|
382
|
+
buf = []
|
383
|
+
f.until_match(%r<\A//\}>) do |line|
|
384
|
+
buf.push text(line.rstrip)
|
385
|
+
end
|
386
|
+
unless %r<\A//\}> =~ f.peek
|
387
|
+
error "unexpected EOF (block begins at: #{head})"
|
388
|
+
return buf
|
389
|
+
end
|
390
|
+
f.gets # discard terminator
|
391
|
+
buf
|
392
|
+
end
|
393
|
+
|
394
|
+
def parse_args(str)
|
395
|
+
return [] if str.empty?
|
396
|
+
unless str[0,1] == '[' and str[-1,1] == ']'
|
397
|
+
error "argument syntax error: #{str.inspect}"
|
398
|
+
return []
|
399
|
+
end
|
400
|
+
str[1..-2].split('][', -1)
|
401
|
+
end
|
402
|
+
|
403
|
+
def compile_command(syntax, args, lines)
|
404
|
+
unless @strategy.respond_to?(syntax.name)
|
405
|
+
error "strategy does not support command: //#{syntax.name}"
|
406
|
+
compile_unknown_command args, lines
|
407
|
+
return
|
408
|
+
end
|
409
|
+
begin
|
410
|
+
syntax.check_args args
|
411
|
+
rescue CompileError => err
|
412
|
+
error err.message
|
413
|
+
args = ['(NoArgument)'] * SYNTAX[name].min_argc
|
414
|
+
end
|
415
|
+
if syntax.block_allowed?
|
416
|
+
compile_block syntax, args, lines
|
417
|
+
else
|
418
|
+
if lines
|
419
|
+
error "block is not allowed for command //#{syntax.name}; ignore"
|
420
|
+
end
|
421
|
+
compile_single syntax, args
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def compile_unknown_command(args, lines)
|
426
|
+
@strategy.unknown_command args, lines
|
427
|
+
end
|
428
|
+
|
429
|
+
def compile_block(syntax, args, lines)
|
430
|
+
@strategy.__send__(syntax.name, (lines || default_block(syntax)), *args)
|
431
|
+
end
|
432
|
+
|
433
|
+
def default_block(syntax)
|
434
|
+
if syntax.block_required?
|
435
|
+
error "block is required for //#{syntax.name}; use empty block"
|
436
|
+
end
|
437
|
+
[]
|
438
|
+
end
|
439
|
+
|
440
|
+
def compile_single(syntax, args)
|
441
|
+
@strategy.__send__(syntax.name, *args)
|
442
|
+
end
|
443
|
+
|
444
|
+
def text(str)
|
445
|
+
return '' if str.empty?
|
446
|
+
words = str.split(/(@<\w+>\{(?:[^\}\\]+|\\.)*\})/, -1)
|
447
|
+
words.each do |w|
|
448
|
+
error "`@<xxx>' seen but is not valid inline op: #{w}" if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
|
449
|
+
end
|
450
|
+
result = @strategy.nofunc_text(words.shift)
|
451
|
+
until words.empty?
|
452
|
+
result << compile_inline(words.shift.gsub(/\\\}/, '}'))
|
453
|
+
result << @strategy.nofunc_text(words.shift)
|
454
|
+
end
|
455
|
+
result
|
456
|
+
end
|
457
|
+
public :text # called from strategy
|
458
|
+
|
459
|
+
def compile_inline(str)
|
460
|
+
op, arg = /\A@<(\w+)>\{(.*?)\}\z/.match(str).captures
|
461
|
+
unless inline_defined?(op)
|
462
|
+
raise CompileError, "no such inline op: #{op}"
|
463
|
+
end
|
464
|
+
unless @strategy.respond_to?("inline_#{op}")
|
465
|
+
raise "strategy does not support inline op: @<#{op}>"
|
466
|
+
end
|
467
|
+
@strategy.__send__("inline_#{op}", arg)
|
468
|
+
rescue => err
|
469
|
+
error err.message
|
470
|
+
@strategy.nofunc_text(str)
|
471
|
+
end
|
472
|
+
|
473
|
+
def warn(msg)
|
474
|
+
@strategy.warn msg
|
475
|
+
end
|
476
|
+
|
477
|
+
def error(msg)
|
478
|
+
@strategy.error msg
|
479
|
+
end
|
480
|
+
|
481
|
+
end
|
482
|
+
|
483
|
+
end # module ReVIEW
|