review 0.9.0 → 1.0.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 (49) hide show
  1. data/.travis.yml +9 -0
  2. data/ChangeLog +326 -0
  3. data/Rakefile +3 -5
  4. data/VERSION +1 -1
  5. data/bin/review-compile +50 -50
  6. data/bin/review-epubmaker +62 -75
  7. data/bin/review-epubmaker-ng +185 -0
  8. data/bin/review-index +2 -1
  9. data/bin/review-pdfmaker +158 -101
  10. data/bin/review-vol +6 -2
  11. data/doc/format.rdoc +111 -46
  12. data/doc/libepubmaker/sample.yaml +90 -0
  13. data/doc/quickstart.rdoc +188 -0
  14. data/doc/sample.yaml +8 -0
  15. data/lib/epubmaker.rb +28 -0
  16. data/lib/epubmaker/content.rb +82 -0
  17. data/lib/epubmaker/epubv2.rb +419 -0
  18. data/lib/epubmaker/epubv3.rb +249 -0
  19. data/lib/epubmaker/producer.rb +204 -0
  20. data/lib/epubmaker/resource.rb +66 -0
  21. data/lib/review.rb +1 -1
  22. data/lib/review/book.rb +27 -4
  23. data/lib/review/builder.rb +153 -20
  24. data/lib/review/compiler.rb +61 -10
  25. data/lib/review/ewbbuilder.rb +3 -2
  26. data/lib/review/htmlbuilder.rb +174 -67
  27. data/lib/review/i18n.rb +30 -0
  28. data/lib/review/i18n.yaml +23 -0
  29. data/lib/review/idgxmlbuilder.rb +110 -63
  30. data/lib/review/index.rb +34 -12
  31. data/lib/review/latexbuilder.rb +128 -33
  32. data/lib/review/latexutils.rb +18 -1
  33. data/lib/review/textbuilder.rb +17 -0
  34. data/lib/review/tocparser.rb +3 -1
  35. data/lib/review/tocprinter.rb +1 -0
  36. data/lib/review/topbuilder.rb +397 -198
  37. data/review.gemspec +101 -100
  38. data/test/test_book.rb +27 -0
  39. data/test/test_epubmaker.rb +507 -0
  40. data/test/test_helper.rb +8 -0
  41. data/test/test_htmlbuilder.rb +295 -10
  42. data/test/test_i18n.rb +64 -0
  43. data/test/test_idgxmlbuilder.rb +268 -10
  44. data/test/test_latexbuilder.rb +316 -20
  45. data/test/test_preprocessor.rb +23 -0
  46. data/test/test_topbuilder.rb +246 -0
  47. metadata +46 -53
  48. data/doc/format.re +0 -505
  49. data/test/test_index.rb +0 -15
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+ # = resource.rb -- Message resources for EPUBMaker.
3
+ #
4
+ # Copyright (c) 2010 Kenshi Muto
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 EPUBMaker
13
+
14
+ # EPUBMaker::Resource contains message translation resources for EPUBMaker.
15
+ class Resource
16
+ # Construct message resource object with using +params+["language"] value.
17
+ def initialize(params)
18
+ @hash = nil
19
+ begin
20
+ @hash = __send__ params["language"]
21
+ rescue
22
+ @hash = __send__ :en
23
+ end
24
+
25
+ @hash.each_pair do |k, v|
26
+ @hash[k] = params[k] unless params[k].nil?
27
+ end
28
+ end
29
+
30
+ # Return message translation for +key+.
31
+ def v(key)
32
+ return @hash[key]
33
+ end
34
+
35
+ private
36
+ # English messages.
37
+ def en
38
+ {
39
+ "toctitle" => "Table of Contents",
40
+ "covertitle" => "Cover",
41
+ "titlepagetitle" => "Title Page",
42
+ "colophontitle" => "Colophon",
43
+ "c-aut" => "Author",
44
+ "c-dsr" => "Designer",
45
+ "c-ill" => "Illustrator",
46
+ "c-edt" => "Editor",
47
+ "c-prt" => "Publisher",
48
+ }
49
+ end
50
+
51
+ # Japanese messages.
52
+ def ja
53
+ {
54
+ "toctitle" => "目次",
55
+ "covertitle" => "表紙",
56
+ "titlepagetitle" => "権利表記",
57
+ "colophontitle" => "奥付",
58
+ "c-aut" => "著 者",
59
+ "c-dsr" => "デザイン",
60
+ "c-ill" => "イラスト",
61
+ "c-edt" => "編 集",
62
+ "c-prt" => "発行所",
63
+ }
64
+ end
65
+ end
66
+ end
data/lib/review.rb CHANGED
@@ -1,4 +1,4 @@
1
+ require 'review/i18n'
1
2
  require 'review/exception'
2
- require 'review/env'
3
3
  require 'review/compiler'
4
4
  require 'review/htmlbuilder'
data/lib/review/book.rb CHANGED
@@ -32,7 +32,11 @@ module ReVIEW
32
32
  def Book.load_default
33
33
  %w( . .. ../.. ).each do |basedir|
34
34
  if File.file?("#{basedir}/CHAPS")
35
- return load(basedir)
35
+ book = load(basedir)
36
+ if File.file?("#{basedir}/config.rb")
37
+ book.instance_variable_set("@parameters", Parameters.load("#{basedir}/config.rb"))
38
+ end
39
+ return book
36
40
  end
37
41
  end
38
42
  new('.')
@@ -114,7 +118,17 @@ module ReVIEW
114
118
  end
115
119
 
116
120
  def read_CHAPS
117
- File.read("#{@basedir}/#{chapter_file}")
121
+ res = ""
122
+ File.open("#{@basedir}/#{chapter_file}") do |f|
123
+ while line = f.gets
124
+ if /\A#/ =~ line
125
+ next
126
+ end
127
+ line.gsub!(/#.*$/, "")
128
+ res << line
129
+ end
130
+ end
131
+ res
118
132
  rescue Errno::ENOENT
119
133
  Dir.glob("#{@basedir}/*#{ext()}").sort.join("\n")
120
134
  end
@@ -207,7 +221,7 @@ module ReVIEW
207
221
  end
208
222
 
209
223
  def mkchap(name)
210
- name += ext unless File.extname(name) == ext
224
+ name += ext if File.extname(name) == ""
211
225
  path = "#{@basedir}/#{name}"
212
226
  raise FileNotFound, "file not exist: #{path}" unless File.file?(path)
213
227
  Chapter.new(self, nil, name, path)
@@ -310,7 +324,7 @@ module ReVIEW
310
324
  @ext = params[:ext] || '.re'
311
325
  @image_dir = params[:image_dir] || 'images'
312
326
  @image_types = unify_exts(params[:image_types] ||
313
- %w( eps ai tif tiff png bmp jpg jpeg gif svg ))
327
+ %w( eps ai tif tiff png bmp jpg jpeg gif svg pdf ))
314
328
  @bib_file = params[:bib_file] || "bib#{@ext}"
315
329
  end
316
330
 
@@ -424,6 +438,7 @@ module ReVIEW
424
438
  @table_index = nil
425
439
  @footnote_index = nil
426
440
  @image_index = nil
441
+ @icon_index = nil
427
442
  @numberless_image_index = nil
428
443
  @indepimage_index = nil
429
444
  @headline_index = nil
@@ -537,6 +552,7 @@ module ReVIEW
537
552
 
538
553
  def image(id)
539
554
  return image_index()[id] if image_index().has_key?(id)
555
+ return icon_index()[id] if icon_index().has_key?(id)
540
556
  return numberless_image_index()[id] if numberless_image_index().has_key?(id)
541
557
  indepimage_index()[id]
542
558
  end
@@ -555,6 +571,13 @@ module ReVIEW
555
571
  @image_index
556
572
  end
557
573
 
574
+ def icon_index
575
+ @icon_index ||= IconIndex.parse(lines(), id(),
576
+ "#{book.basedir}#{@book.image_dir}",
577
+ @book.image_types)
578
+ @icon_index
579
+ end
580
+
558
581
  def indepimage_index
559
582
  @indepimage_index ||=
560
583
  IndepImageIndex.parse(lines(), id(),
@@ -11,10 +11,12 @@ require 'review/index'
11
11
  require 'review/exception'
12
12
  require 'stringio'
13
13
  require 'nkf'
14
+ require 'cgi'
14
15
 
15
16
  module ReVIEW
16
17
 
17
18
  class Builder
19
+ CAPTION_TITLES = %w(note memo tip info planning best important security caution term link notice point shoot reference practice expert)
18
20
 
19
21
  def pre_paragraph
20
22
  nil
@@ -196,21 +198,21 @@ module ReVIEW
196
198
  end
197
199
 
198
200
  def inline_list(id)
199
- "リスト#{@chapter.list(id).number}"
201
+ "#{I18n.t("list")}#{@chapter.list(id).number}"
200
202
  rescue KeyError
201
203
  error "unknown list: #{id}"
202
204
  nofunc_text("[UnknownList:#{id}]")
203
205
  end
204
206
 
205
207
  def inline_img(id)
206
- "図#{@chapter.image(id).number}"
208
+ "#{I18n.t("image")}#{@chapter.image(id).number}"
207
209
  rescue KeyError
208
210
  error "unknown image: #{id}"
209
211
  nofunc_text("[UnknownImage:#{id}]")
210
212
  end
211
213
 
212
214
  def inline_table(id)
213
- "表#{@chapter.table(id).number}"
215
+ "#{I18n.t("table")}#{@chapter.table(id).number}"
214
216
  rescue KeyError
215
217
  error "unknown table: #{id}"
216
218
  nofunc_text("[UnknownTable:#{id}]")
@@ -240,6 +242,7 @@ module ReVIEW
240
242
  def inline_href(arg)
241
243
  url, label = *arg.scan(/(?:(?:(?:\\\\)*\\,)|[^,\\]+)+/).map(&:lstrip)
242
244
  url = url.gsub(/\\,/, ",").strip
245
+ label = label.gsub(/\\,/, ",").strip if label
243
246
  compile_href(url, label)
244
247
  end
245
248
 
@@ -248,12 +251,12 @@ module ReVIEW
248
251
  end
249
252
 
250
253
  def bibpaper(lines, id, caption)
251
- puts "<div>"
252
254
  bibpaper_header id, caption
253
255
  unless lines.empty?
256
+ puts ""
254
257
  bibpaper_bibpaper id, caption, lines
255
258
  end
256
- puts "</div>"
259
+ puts ""
257
260
  end
258
261
 
259
262
  def inline_hd(id)
@@ -264,28 +267,44 @@ module ReVIEW
264
267
  end
265
268
 
266
269
  def raw(str)
267
- print str.gsub("\\n", "\n")
270
+ if matched = str.match(/\|(.*?)\|(.*)/)
271
+ builders = matched[1].split(/,/).map{|i| i.gsub(/\s/, '') }
272
+ c = self.class.to_s.gsub(/ReVIEW::/, '').gsub(/Builder/, '').downcase
273
+ if builders.include?(c)
274
+ print matched[2].gsub("\\n", "\n")
275
+ else
276
+ ""
277
+ end
278
+ else
279
+ warn "It is recommended to set builder(s)."
280
+ print str.gsub("\\n", "\n")
281
+ end
268
282
  end
269
283
 
270
284
  def find_pathes(id)
271
- if ReVIEW.book.param["subdirmode"].nil?
272
- re = /\A#{@chapter.name}-#{id}(?i:#{@book.image_types.join('|')})\z/x
285
+ if ReVIEW.book.param["subdirmode"]
286
+ re = /\A#{id}(?i:#{@chapter.name.join('|')})\z/x
273
287
  entries().select {|ent| re =~ ent }\
274
- .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
275
- .map {|ent| "#{@book.basedir}/#{ent}" }
276
- else
288
+ .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
289
+ .map {|ent| "#{@book.basedir}/#{@chapter.name}/#{ent}" }
290
+ elsif ReVIEW.book.param["singledirmode"]
277
291
  re = /\A#{id}(?i:#{@chapter.name.join('|')})\z/x
278
292
  entries().select {|ent| re =~ ent }\
279
- .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
280
- .map {|ent| "#{@book.asedir}/#{@chapter.name}/#{ent}" }
293
+ .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
294
+ .map {|ent| "#{@book.basedir}/#{ent}" }
295
+ else
296
+ re = /\A#{@chapter.name}-#{id}(?i:#{@book.image_types.join('|')})\z/x
297
+ entries().select {|ent| re =~ ent }\
298
+ .sort_by {|ent| @book.image_types.index(File.extname(ent).downcase) }\
299
+ .map {|ent| "#{@book.basedir}/#{ent}" }
281
300
  end
282
301
  end
283
-
302
+
284
303
  def entries
285
- if ReVIEW.book.param["subdirmode"].nil?
286
- @entries ||= Dir.entries(@book.basedir + @book.image_dir)
287
- else
304
+ if ReVIEW.book.param["subdirmode"]
288
305
  @entries ||= Dir.entries(File.join(@book.basedir + @book.image_dir, @chapter.name))
306
+ else
307
+ @entries ||= Dir.entries(@book.basedir + @book.image_dir)
289
308
  end
290
309
  rescue Errno::ENOENT
291
310
  @entries = []
@@ -299,11 +318,37 @@ module ReVIEW
299
318
  raise ApplicationError, "#{@location}: error: #{msg}"
300
319
  end
301
320
 
302
- def getChap(chapter = @chapter)
321
+ def handle_metric(str)
322
+ str
323
+ end
324
+
325
+ def result_metric(array)
326
+ array.join(',')
327
+ end
328
+
329
+ def parse_metric(type, metric)
330
+ return "" if metric.nil? || metric.empty?
331
+ params = metric.split(/,\s*/)
332
+ results = []
333
+ params.each do |p|
334
+ if p =~ /\A.+?::/
335
+ if p =~ /\A#{type}::/
336
+ p.sub!(/\A#{type}::/, '')
337
+ else
338
+ next
339
+ end
340
+ end
341
+ p = handle_metric(p)
342
+ results.push(p)
343
+ end
344
+ return result_metric(results)
345
+ end
346
+
347
+ def get_chap(chapter = @chapter)
303
348
  if ReVIEW.book.param["secnolevel"] > 0 && !chapter.number.nil? && !chapter.number.to_s.empty?
304
- return "#{chapter.number}."
349
+ return "#{chapter.number}"
305
350
  end
306
- return ""
351
+ return nil
307
352
  end
308
353
 
309
354
  def extract_chapter_id(chap_ref)
@@ -314,6 +359,94 @@ module ReVIEW
314
359
  return [@chapter, chap_ref]
315
360
  end
316
361
  end
362
+
363
+ def captionblock(type, lines, caption, specialstyle = nil)
364
+ raise NotImplementedError
365
+ end
366
+
367
+ CAPTION_TITLES.each do |name|
368
+ class_eval %Q{
369
+ def #{name}(lines, caption = nil)
370
+ captionblock("#{name}", lines, caption)
371
+ end
372
+ }
373
+ end
374
+
375
+ def graph(lines, id, command, caption = nil)
376
+ dir = @book.basedir + @book.image_dir
377
+ file = "#{@chapter.name}-#{id}.#{image_ext}"
378
+ if ReVIEW.book.param["subdirmode"]
379
+ dir = File.join(dir, @chapter.name)
380
+ file = "#{id}.#{image_ext}"
381
+ elsif ReVIEW.book.param["singledirmode"]
382
+ file = "#{id}.#{image_ext}"
383
+ end
384
+ file_path = File.join(dir, file)
385
+
386
+ line = CGI.unescapeHTML(lines.join("\n"))
387
+ cmds = {
388
+ :graphviz => "echo '#{line}' | dot -T#{image_ext} -o#{file_path}",
389
+ :gnuplot => "echo 'set terminal " +
390
+ "#{(image_ext == "eps") ? "postscript eps" : image_ext}\n" +
391
+ " set output \"#{file_path}\"\n#{line}' | gnuplot",
392
+ :blockdiag => "echo '#{line}' "+
393
+ "| blockdiag -a -T #{image_ext} -o #{file_path} /dev/stdin",
394
+ :aafigure => "echo '#{line}' | aafigure -t#{image_ext} -o#{file_path}",
395
+ }
396
+ cmd = cmds[command.to_sym]
397
+ warn cmd
398
+ system cmd
399
+
400
+ image(lines, id, caption)
401
+ end
402
+
403
+ def image_ext
404
+ raise NotImplementedError
405
+ end
406
+
407
+ def inline_include(file_name)
408
+ enc = inencoding()
409
+ compile_inline NKF.nkf("--#{enc}", File.open(file_name).read)
410
+ end
411
+
412
+ def include(file_name)
413
+ enc = inencoding()
414
+ File.foreach(file_name) do |line|
415
+ paragraph([NKF.nkf("--#{enc}", line)])
416
+ end
417
+ end
418
+
419
+ def inencoding
420
+ if ReVIEW.book.param["inencoding"].nil?
421
+ enc = "utf8"
422
+ else
423
+ # UTF-8, SJIS, EUC => utf8, sjis, euc
424
+ enc = ReVIEW.book.param["inencoding"].gsub(/-/, '').downcase
425
+ end
426
+ end
427
+ private :inencoding
428
+
429
+ def ul_item_begin(lines)
430
+ ul_item(lines)
431
+ end
432
+
433
+ def ul_item_end
434
+ end
435
+
436
+ def inline_raw(args)
437
+ if matched = args.match(/\|(.*?)\|(.*)/)
438
+ builders = matched[1].split(/,/).map{|i| i.gsub(/\s/, '') }
439
+ c = self.class.to_s.gsub(/ReVIEW::/, '').gsub(/Builder/, '').downcase
440
+ if builders.include?(c)
441
+ matched[2].gsub("\\n", "\n")
442
+ else
443
+ ""
444
+ end
445
+ else
446
+ warn "It is recommended to set builder(s)."
447
+ args.gsub("\\n", "\n")
448
+ end
449
+ end
317
450
  end
318
451
 
319
452
  end # module ReVIEW
@@ -143,6 +143,7 @@ module ReVIEW
143
143
  defblock :doorquote, 1
144
144
  defblock :talk, 0
145
145
  defblock :texequation, 0
146
+ defblock :graph, 1..3
146
147
 
147
148
  defblock :address, 0
148
149
  defblock :blockquote, 0
@@ -163,11 +164,13 @@ module ReVIEW
163
164
  defsingle :label, 1
164
165
  defsingle :raw, 1
165
166
  defsingle :tsize, 1
167
+ defsingle :include, 1
166
168
 
167
169
  definline :chapref
168
170
  definline :chap
169
171
  definline :title
170
172
  definline :img
173
+ definline :icon
171
174
  definline :list
172
175
  definline :table
173
176
  definline :fn
@@ -210,6 +213,8 @@ module ReVIEW
210
213
  definline :uchar
211
214
  definline :idx
212
215
  definline :hidx
216
+ definline :comment
217
+ definline :include
213
218
 
214
219
  private
215
220
 
@@ -228,8 +233,8 @@ module ReVIEW
228
233
  when %r<\A\s*:\s>
229
234
  compile_dlist f
230
235
  when %r<\A//\}>
231
- error 'block end seen but not opened'
232
236
  f.gets
237
+ error 'block end seen but not opened'
233
238
  when %r<\A//[a-z]+>
234
239
  name, args, lines = read_command(f)
235
240
  syntax = syntax_descriptor(name)
@@ -266,16 +271,25 @@ module ReVIEW
266
271
  caption = m[4].strip
267
272
  index = level - 1
268
273
  if tag
269
- open_tagged_section tag, level, label, caption
274
+ if tag !~ /\A\//
275
+ close_current_tagged_section(level)
276
+ open_tagged_section(tag, level, label, caption)
277
+ else
278
+ open_tag = tag[1..-1]
279
+ prev_tag_info = @tagged_section.pop
280
+ unless prev_tag_info.first == open_tag
281
+ raise CompileError, "#{open_tag} is not opened."
282
+ end
283
+ @strategy.warn("It is not recommended to use \"#{line.chomp}\"")
284
+ close_tagged_section(*prev_tag_info)
285
+ end
270
286
  else
271
287
  if @headline_indexs.size > (index + 1)
272
288
  @headline_indexs = @headline_indexs[0..index]
273
289
  end
274
290
  @headline_indexs[index] = 0 if @headline_indexs[index].nil?
275
291
  @headline_indexs[index] += 1
276
- while @tagged_section.last and @tagged_section.last[1] >= level
277
- close_tagged_section(* @tagged_section.pop)
278
- end
292
+ close_current_tagged_section(level)
279
293
  if ReVIEW.book.param["hdnumberingmode"]
280
294
  caption = @chapter.on_CHAPS? ? "#{@headline_indexs.join('.')} #{caption}" : caption
281
295
  warn "--hdnumberingmode is deprecated. use --level option."
@@ -284,6 +298,12 @@ module ReVIEW
284
298
  end
285
299
  end
286
300
 
301
+ def close_current_tagged_section(level)
302
+ while @tagged_section.last and @tagged_section.last[1] >= level
303
+ close_tagged_section(* @tagged_section.pop)
304
+ end
305
+ end
306
+
287
307
  def headline(level, label, caption)
288
308
  @strategy.headline level, label, caption
289
309
  end
@@ -319,15 +339,46 @@ module ReVIEW
319
339
  end
320
340
 
321
341
  def compile_ulist(f)
322
- @strategy.ul_begin
342
+ level = 0
323
343
  f.while_match(/\A\s+\*/) do |line|
324
- buf = [text(line.sub(/\*/, '').strip)]
344
+
345
+ buf = [text(line.sub(/\*+/, '').strip)]
325
346
  f.while_match(/\A\s+(?!\*)\S/) do |cont|
326
347
  buf.push text(cont.strip)
327
348
  end
328
- @strategy.ul_item buf
349
+
350
+ line =~ /\A\s+(\*+)/
351
+ current_level = $1.size
352
+ if level == current_level
353
+ @strategy.ul_item_end
354
+ # body
355
+ @strategy.ul_item_begin buf
356
+ elsif level < current_level # down
357
+ level_diff = current_level - level
358
+ level = current_level
359
+ (1..(level_diff - 1)).to_a.reverse.each do |i|
360
+ @strategy.ul_begin {i}
361
+ @strategy.ul_item_begin []
362
+ end
363
+ @strategy.ul_begin {level}
364
+ @strategy.ul_item_begin buf
365
+ elsif level > current_level # up
366
+ level_diff = level - current_level
367
+ level = current_level
368
+ (1..level_diff).to_a.reverse.each do |i|
369
+ @strategy.ul_item_end
370
+ @strategy.ul_end {level + i}
371
+ @strategy.ul_item_end
372
+ end
373
+ # body
374
+ @strategy.ul_item_begin buf
375
+ end
376
+ end
377
+
378
+ (1..level).to_a.reverse.each do |i|
379
+ @strategy.ul_item_end
380
+ @strategy.ul_end {i}
329
381
  end
330
- @strategy.ul_end
331
382
  end
332
383
 
333
384
  def compile_olist(f)
@@ -440,7 +491,7 @@ module ReVIEW
440
491
 
441
492
  def text(str)
442
493
  return '' if str.empty?
443
- words = str.split(/(@<\w+>\{(?:[^\}\\]+|\\.)*\})/, -1)
494
+ words = str.split(/(@<\w+>\{(?:[^\}\\]|\\.)*?\})/, -1)
444
495
  words.each do |w|
445
496
  error "`@<xxx>' seen but is not valid inline op: #{w}" if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
446
497
  end