review 0.9.0 → 1.0.0

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