review 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|