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.
Files changed (50) hide show
  1. data/COPYING +515 -0
  2. data/ChangeLog +1278 -0
  3. data/README.rdoc +21 -0
  4. data/Rakefile +42 -0
  5. data/VERSION +1 -0
  6. data/bin/review-check +190 -0
  7. data/bin/review-checkdep +63 -0
  8. data/bin/review-compile +165 -0
  9. data/bin/review-epubmaker +525 -0
  10. data/bin/review-index +108 -0
  11. data/bin/review-preproc +140 -0
  12. data/bin/review-validate +51 -0
  13. data/bin/review-vol +106 -0
  14. data/doc/format.re +486 -0
  15. data/doc/format.txt +434 -0
  16. data/doc/format_idg.txt +194 -0
  17. data/doc/format_sjis.txt +313 -0
  18. data/doc/sample.css +91 -0
  19. data/doc/sample.yaml +46 -0
  20. data/lib/lineinput.rb +155 -0
  21. data/lib/review/book.rb +580 -0
  22. data/lib/review/builder.rb +274 -0
  23. data/lib/review/compat.rb +22 -0
  24. data/lib/review/compiler.rb +483 -0
  25. data/lib/review/epubbuilder.rb +692 -0
  26. data/lib/review/ewbbuilder.rb +382 -0
  27. data/lib/review/exception.rb +21 -0
  28. data/lib/review/htmlbuilder.rb +370 -0
  29. data/lib/review/htmllayout.rb +19 -0
  30. data/lib/review/htmlutils.rb +27 -0
  31. data/lib/review/idgxmlbuilder.rb +1078 -0
  32. data/lib/review/index.rb +224 -0
  33. data/lib/review/latexbuilder.rb +420 -0
  34. data/lib/review/latexindex.rb +35 -0
  35. data/lib/review/latexutils.rb +52 -0
  36. data/lib/review/preprocessor.rb +520 -0
  37. data/lib/review/textutils.rb +19 -0
  38. data/lib/review/tocparser.rb +333 -0
  39. data/lib/review/tocprinter.rb +220 -0
  40. data/lib/review/topbuilder.rb +572 -0
  41. data/lib/review/unfold.rb +138 -0
  42. data/lib/review/volume.rb +66 -0
  43. data/lib/review.rb +4 -0
  44. data/review.gemspec +93 -0
  45. data/setup.rb +1587 -0
  46. data/test/test_epubbuilder.rb +73 -0
  47. data/test/test_helper.rb +2 -0
  48. data/test/test_htmlbuilder.rb +42 -0
  49. data/test/test_latexbuilder.rb +74 -0
  50. 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