review 0.6.0

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