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,35 @@
1
+ #
2
+ # $Id: latexindex.rb 2204 2006-03-18 06:10:26Z aamine $
3
+ #
4
+ # Copyright (c) 2002-2006 Minero Aoki
5
+ #
6
+ # This program is free software.
7
+ # You can distribute or modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ # For details of the GNU LGPL, see the file "COPYING".
10
+ #
11
+
12
+ module ReVIEW
13
+
14
+ class LaTeXIndex
15
+
16
+ def load(path)
17
+ table = {}
18
+ File.foreach(path) do |line|
19
+ key, value = *line.strip.split(/\t+/, 2)
20
+ table[key.sub(/\A%/, '')] = value
21
+ end
22
+ new(table)
23
+ end
24
+
25
+ def initialize(table)
26
+ @table = table
27
+ end
28
+
29
+ def [](key)
30
+ @table.fetch(key)
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,52 @@
1
+ #
2
+ # $Id: latexutils.rb 2204 2006-03-18 06:10:26Z aamine $
3
+ #
4
+ # Copyright (c) 2002-2006 Minero Aoki
5
+ #
6
+ # This program is free software.
7
+ # You can distribute or modify this program under the terms of
8
+ # the GNU LGPL, Lesser General Public License version 2.1.
9
+ # For details of the GNU LGPL, see the file "COPYING".
10
+ #
11
+
12
+ module ReVIEW
13
+
14
+ module LaTeXUtils
15
+
16
+ MATACHARS = {
17
+ '#' => '\symbol{"23}',
18
+ "$" => '\symbol{"24}',
19
+ '%' => '\%',
20
+ '&' => '\&',
21
+ '{' => '\{',
22
+ '}' => '\}',
23
+ '_' => '\symbol{"5F}',
24
+ '^' => '\textasciicircum{}',
25
+ '~' => '\textasciitilde{}',
26
+ '|' => '\textbar{}',
27
+ '<' => '\symbol{"3C}',
28
+ '>' => '\symbol{"3E}',
29
+ "\\" => '\symbol{"5C}'
30
+ }
31
+
32
+ METACHARS_RE = /[#{Regexp.escape(MATACHARS.keys.join(''))}]/
33
+
34
+ def escape_latex(str)
35
+ str.gsub(METACHARS_RE) {|s|
36
+ MATACHARS[s] or raise "unknown trans char: #{s}"
37
+ }
38
+ end
39
+
40
+ alias escape escape_latex
41
+
42
+ def escape_index(str)
43
+ str.gsub(/[@!|"]/) {|s| '"' + s }
44
+ end
45
+
46
+ def macro(name, *args)
47
+ "\\#{name}" + args.map {|a| "{#{a}}" }.join('')
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,520 @@
1
+ #
2
+ # $Id: preprocessor.rb 4250 2009-05-24 14:03:01Z aamine $
3
+ #
4
+ # Copyright (c) 2002-2009 Minero Aoki
5
+ # Copyright (c) 2010 Minero Aoki, Kenshi Muto
6
+ #
7
+ # This program is free software.
8
+ # You can distribute or modify this program under the terms of
9
+ # the GNU LGPL, Lesser General Public License version 2.1.
10
+ # For details of the GNU LGPL, see the file "COPYING".
11
+ #
12
+
13
+ require 'review/textutils'
14
+ require 'review/exception'
15
+
16
+ module ReVIEW
17
+
18
+ module ErrorUtils
19
+
20
+ def init_ErrorUtils(f)
21
+ @errutils_file = f
22
+ @errutils_err = false
23
+ end
24
+
25
+ def warn(msg)
26
+ if @param["outencoding"] =~ /^EUC$/
27
+ msg = NKF.nkf("-W -e", msg)
28
+ elsif @param["outencoding"] =~ /^SJIS$/
29
+ msg = NKF.nkf("-W -s", msg)
30
+ elsif @param["outencoding"] =~ /^JIS$/
31
+ msg = NKF.nkf("-W -j", msg)
32
+ end
33
+ $stderr.puts "#{location()}: warning: #{msg}"
34
+ end
35
+
36
+ def error(msg)
37
+ if @param["outencoding"] =~ /^EUC$/
38
+ msg = NKF.nkf("-W -e", msg)
39
+ elsif @param["outencoding"] =~ /^SJIS$/
40
+ msg = NKF.nkf("-W -s", msg)
41
+ elsif @param["outencoding"] =~ /^JIS$/
42
+ msg = NKF.nkf("-W -j", msg)
43
+ end
44
+ @errutils_err = true
45
+ raise ApplicationError, "#{location()}: #{msg}"
46
+ end
47
+
48
+ def location
49
+ "#{filename()}:#{lineno()}"
50
+ end
51
+
52
+ def filename
53
+ @errutils_file.path
54
+ end
55
+
56
+ def lineno
57
+ @errutils_file.lineno
58
+ end
59
+
60
+ end
61
+
62
+
63
+ class Preprocessor
64
+
65
+ include ErrorUtils
66
+
67
+ def Preprocessor.strip(f)
68
+ buf = ''
69
+ Strip.new(f).each do |line|
70
+ buf << line.rstrip << "\n"
71
+ end
72
+ buf
73
+ end
74
+
75
+ class Strip
76
+ def initialize(f)
77
+ @f = f
78
+ end
79
+
80
+ def path
81
+ @f.path
82
+ end
83
+
84
+ def lineno
85
+ @f.lineno
86
+ end
87
+
88
+ def gets
89
+ while line = @f.gets
90
+ return line unless /\A\#@/ =~ line
91
+ end
92
+ nil
93
+ end
94
+
95
+ def each
96
+ @f.each do |line|
97
+ yield line unless /\A\#@/ =~ line
98
+ end
99
+ end
100
+ end
101
+
102
+ def initialize(repo)
103
+ @repository = repo
104
+ end
105
+
106
+ def setParameter(param)
107
+ @param = param
108
+ end
109
+
110
+ def process(inf, outf)
111
+ init_ErrorUtils inf
112
+ @f = outf
113
+ begin
114
+ preproc inf
115
+ rescue Errno::ENOENT => err
116
+ error err.message
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ TYPES = %w( file range )
123
+
124
+ def preproc(f)
125
+ init_vars
126
+ while line = f.gets
127
+ case line
128
+ when /\A\#@\#/, /\A\#\#\#\#/
129
+ @f.print line
130
+
131
+ when /\A\#@defvar/
132
+ @f.print line
133
+ direc = parse_directive(line, 2)
134
+ defvar(*direc.args)
135
+
136
+ when /\A\#@mapoutput/
137
+ direc = parse_directive(line, 1, 'stderr')
138
+ @f.print line
139
+ get_output(expand(direc.arg), direc['stderr']).each do |out|
140
+ @f.print out.string
141
+ end
142
+ skip_list f
143
+
144
+ when /\A\#@mapfile/
145
+ direc = parse_directive(line, 1, 'eval')
146
+ path = expand(direc.arg)
147
+ ent = @repository.fetch_file(path)
148
+ ent = evaluate(path, ent) if direc['eval']
149
+ replace_block f, line, ent, false # FIXME: turn off lineno: tmp
150
+
151
+ when /\A\#@map(?:range)?/
152
+ direc = parse_directive(line, 2, 'unindent')
153
+ path = expand(direc.args[0])
154
+ ent = @repository.fetch_range(path, direc.args[1]) or
155
+ error "unknown range: #{path}: #{direc.args[1]}"
156
+ ent = (direc['unindent'] ? unindent(ent, direc['unindent']) : ent)
157
+ replace_block f, line, ent, false # FIXME: turn off lineno: tmp
158
+
159
+ when /\A\#@end/
160
+ error 'unbaranced #@end'
161
+
162
+ when /\A\#@/
163
+ op = line.slice(/@(\w+)/, 1)
164
+ #error "unkown directive: #{line.strip}" unless known_directive?(op)
165
+ warn "unkown directive: #{line.strip}" unless known_directive?(op)
166
+ @f.print line
167
+
168
+ when /\A\s*\z/ # empty line
169
+ @f.puts
170
+ else
171
+ @f.print line
172
+ end
173
+ end
174
+ end
175
+
176
+ private
177
+
178
+ KNOWN_DIRECTIVES = %w( require provide warn ok )
179
+
180
+ def known_directive?(op)
181
+ KNOWN_DIRECTIVES.index(op)
182
+ end
183
+
184
+ def replace_block(f, directive_line, newlines, with_lineno)
185
+ @f.print directive_line
186
+ newlines.each do |line|
187
+ print_number line.number if with_lineno
188
+ @f.print line.string
189
+ end
190
+ skip_list f
191
+ end
192
+
193
+ def print_number(num)
194
+ @f.printf '%4s ', (num ? num.to_s : '')
195
+ end
196
+
197
+ def skip_list(f)
198
+ begline = f.lineno
199
+ while line = f.gets
200
+ case line
201
+ when %r[\A\#@end]
202
+ @f.print line
203
+ return
204
+ when %r[\A//\}]
205
+ warn '//} seen in list'
206
+ @f.print line
207
+ return
208
+ when %r[\A\#@\w]
209
+ warn "#{line.slice(/\A\#@\w+/)} seen in list"
210
+ @f.print line
211
+ when %r[\A\#@]
212
+ @f.print line
213
+ end
214
+ end
215
+ error "list reached end of file (beginning line = #{begline})"
216
+ end
217
+
218
+ class Directive
219
+ def initialize(op, args, opts)
220
+ @op = op
221
+ @args = args
222
+ @opts = opts
223
+ end
224
+
225
+ attr_reader :op
226
+ attr_reader :args
227
+ attr_reader :opts
228
+
229
+ def arg
230
+ @args.first
231
+ end
232
+
233
+ def opt
234
+ @opts.first
235
+ end
236
+
237
+ def [](key)
238
+ @opts[key]
239
+ end
240
+ end
241
+
242
+ def parse_directive(line, argc, *optdecl)
243
+ m = /\A\#@(\w+)\((.*?)\)(?:\[(.*?)\])?\z/.match(line.strip) or
244
+ error "wrong directive: #{line.strip}"
245
+ op = m[1]
246
+ args = m[2].split(/,\s*/)
247
+ opts = parse_optargs(m[3])
248
+ return if argc == 0 and args.empty?
249
+ if argc == -1
250
+ # Any number of arguments are allowed.
251
+ elsif args.size != argc
252
+ error "wrong arg size"
253
+ end
254
+ if opts
255
+ wrong_opts = opts.keys - optdecl
256
+ unless wrong_opts.empty?
257
+ error "wrong option: #{wrong_opts.keys.join(' ')}"
258
+ end
259
+ end
260
+ Directive.new(op, args, opts || {})
261
+ end
262
+
263
+ def parse_optargs(str)
264
+ return nil unless str
265
+ table = {}
266
+ str.split(/,\s*/).each do |a|
267
+ name, spec = a.split(/=/, 2)
268
+ table[name] = optarg_value(spec)
269
+ end
270
+ table
271
+ end
272
+
273
+ def optarg_value(spec)
274
+ case spec
275
+ when 'true' then true # [name=true]
276
+ when 'false' then false # [name=false]
277
+ when 'nil' then nil # [name=nil]
278
+ when nil then true # [name]
279
+ when /^\d+$/ then $&.to_i # [name=8]
280
+ else # [name=val]
281
+ spec
282
+ end
283
+ end
284
+
285
+ def init_vars
286
+ @vartable = {}
287
+ end
288
+
289
+ def defvar(name, value)
290
+ @vartable[name] = value
291
+ end
292
+
293
+ def expand(str)
294
+ str.gsub(/\$\w+/) {|name|
295
+ s = @vartable[name.sub('$', '')]
296
+ s ? expand(s) : name
297
+ }
298
+ end
299
+
300
+ def unindent(chunk, n)
301
+ n = minimum_indent(chunk) unless n.kind_of?(Integer)
302
+ re = /\A#{' ' * n}/
303
+ chunk.map {|line| line.edit {|s| s.sub(re,'') } }
304
+ end
305
+
306
+ INF_INDENT = 9999
307
+
308
+ def minimum_indent(chunk)
309
+ n = chunk.map {|line| line.empty? ? INF_INDENT : line.num_indent }.min
310
+ n == INF_INDENT ? 0 : n
311
+ end
312
+
313
+ def check_ruby_syntax(rbfile)
314
+ status = spawn {
315
+ exec("ruby -c #{rbfile} 2>&1 > /dev/null")
316
+ }
317
+ error "syntax check failed: #{rbfile}" unless status.exitstatus == 0
318
+ end
319
+
320
+ def spawn
321
+ pid, status = *Process.waitpid2(fork { yield })
322
+ status
323
+ end
324
+
325
+ def evaluate(path, chunk)
326
+ outputs = get_output("ruby #{path}", false).split(/\n/).map {|s| s.strip }
327
+ chunk.map {|line|
328
+ if /\# \$\d+/ =~ line.string
329
+ # map result into source.
330
+ line.edit {|s|
331
+ s.sub(/\$(\d+)/) { outputs[$1.to_i - 1] }
332
+ }
333
+ else
334
+ line
335
+ end
336
+ }
337
+ end
338
+
339
+ require 'open3'
340
+
341
+ def get_output(cmd, use_stderr)
342
+ out = err = nil
343
+ Open3.popen3(cmd) {|stdin, stdout, stderr|
344
+ out = stdout.readlines
345
+ if use_stderr
346
+ out.concat stderr.readlines
347
+ else
348
+ err = stderr.readlines
349
+ end
350
+ }
351
+ if err and not err.empty?
352
+ $stderr.puts "[unexpected stderr message]"
353
+ err.each do |line|
354
+ $stderr.print line
355
+ end
356
+ error "get_output: got unexpected output"
357
+ end
358
+ num = 0
359
+ out.map {|line| Line.new(num += 1, line) }
360
+ end
361
+
362
+ end
363
+
364
+
365
+ class Line
366
+ def initialize(number, string)
367
+ @number = number
368
+ @string = string
369
+ end
370
+
371
+ attr_reader :number
372
+ attr_reader :string
373
+ alias to_s string
374
+
375
+ def edit
376
+ self.class.new(@number, yield(@string))
377
+ end
378
+
379
+ def empty?
380
+ @string.strip.empty?
381
+ end
382
+
383
+ def num_indent
384
+ @string.slice(/\A\s*/).size
385
+ end
386
+ end
387
+
388
+
389
+ class Repository
390
+
391
+ include TextUtils
392
+ include ErrorUtils
393
+
394
+ def initialize
395
+ @repository = {}
396
+ end
397
+
398
+ def fetch_file(file)
399
+ file_descripter(file)['file']
400
+ end
401
+
402
+ def fetch_range(file, name)
403
+ fetch(file, 'range', name)
404
+ end
405
+
406
+ def fetch(file, type, name)
407
+ table = file_descripter(file)[type] or return nil
408
+ table[name]
409
+ end
410
+
411
+ private
412
+
413
+ def file_descripter(fname)
414
+ @repository[fname] ||= parse_file(fname)
415
+ end
416
+
417
+ def parse_file(fname)
418
+ File.open(fname) {|f|
419
+ init_ErrorUtils f
420
+ return _parse_file(f)
421
+ }
422
+ end
423
+
424
+ def _parse_file(f)
425
+ whole = []
426
+ repo = {'file' => whole}
427
+ curr = {'WHOLE' => whole}
428
+ lineno = 1
429
+ yacchack = false # remove ';'-only lines.
430
+ opened = [['(not opened)', '(not opened)']] * 3
431
+
432
+ f.each do |line|
433
+ case line
434
+ when /(?:\A\#@|\#@@)([a-z]+)_(begin|end)\((.*)\)/
435
+ type = check_type($1)
436
+ direction = $2
437
+ spec = check_spec($3)
438
+ case direction
439
+ when 'begin'
440
+ key = "#{type}/#{spec}"
441
+ error "begin x2: #{key}" if curr[key]
442
+ (repo[type] ||= {})[spec] = curr[key] = []
443
+ when 'end'
444
+ curr.delete("#{type}/#{spec}") or
445
+ error "end before begin: #{type}/#{spec}"
446
+ else
447
+ raise 'must not happen'
448
+ end
449
+
450
+ when %r<(?:\A\#@|\#@@)([a-z]+)/(\w+)\{>
451
+ type = check_type($1)
452
+ spec = check_spec($2)
453
+ key = "#{type}/#{spec}"
454
+ error "begin x2: #{key}" if curr[key]
455
+ (repo[type] ||= {})[spec] = curr[key] = []
456
+ opened.push [type, spec]
457
+
458
+ when %r<(?:\A\#@|\#@@)([a-z]+)/(\w+)\}>
459
+ type = check_type($1)
460
+ spec = check_spec($2)
461
+ curr.delete("#{type}/#{spec}") or
462
+ error "end before begin: #{type}/#{spec}"
463
+ opened.delete "#{type}/#{spec}"
464
+
465
+ when %r<(?:\A\#@|\#@@)\}>
466
+ type, spec = opened.last
467
+ curr.delete("#{type}/#{spec}") or
468
+ error "closed before open: #{type}/#{spec}"
469
+ opened.pop
470
+
471
+ when /(?:\A\#@|\#@@)yacchack/
472
+ yacchack = true
473
+
474
+ when /\A\#@-/ # does not increment line number.
475
+ line = canonical($')
476
+ curr.each_value do |list|
477
+ list.push Line.new(nil, line)
478
+ end
479
+
480
+ else
481
+ next if yacchack and line.strip == ';'
482
+ line = canonical(line)
483
+ curr.each_value do |list|
484
+ list.push Line.new(lineno, line)
485
+ end
486
+ lineno += 1
487
+ end
488
+ end
489
+ if curr.size > 1
490
+ curr.delete 'WHOLE'
491
+ curr.each do |range, lines|
492
+ $stderr.puts "#{filename()}: unclosed range: #{range} (begin @#{lines.first.number})"
493
+ end
494
+ raise ApplicationError, "ERROR"
495
+ end
496
+
497
+ repo
498
+ end
499
+
500
+ def canonical(line)
501
+ detab(line).rstrip + "\n"
502
+ end
503
+
504
+ def check_type(type)
505
+ unless Preprocessor::TYPES.index(type)
506
+ error "wrong type: #{type.inspect}"
507
+ end
508
+ type
509
+ end
510
+
511
+ def check_spec(spec)
512
+ unless /\A\w+\z/ =~ spec
513
+ error "wrong spec: #{spec.inspect}"
514
+ end
515
+ spec
516
+ end
517
+
518
+ end
519
+
520
+ end