review 5.1.1 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-tex.yml +6 -2
  3. data/.github/workflows/ruby-win.yml +6 -2
  4. data/.github/workflows/ruby.yml +6 -2
  5. data/.rubocop.yml +5 -319
  6. data/NEWS.ja.md +149 -0
  7. data/NEWS.md +149 -1
  8. data/README.md +9 -8
  9. data/bin/review +1 -1
  10. data/bin/review-catalog-converter +15 -15
  11. data/bin/review-check +7 -7
  12. data/bin/review-compile +14 -23
  13. data/bin/review-index +1 -1
  14. data/bin/review-preproc +29 -35
  15. data/bin/review-validate +2 -2
  16. data/doc/config.yml.sample +9 -1
  17. data/doc/config.yml.sample-simple +1 -1
  18. data/doc/format.ja.md +29 -3
  19. data/doc/format.md +32 -3
  20. data/doc/writing_vertical.ja.md +6 -0
  21. data/lib/review/book/base.rb +3 -3
  22. data/lib/review/book/book_unit.rb +13 -3
  23. data/lib/review/book/chapter.rb +1 -1
  24. data/lib/review/book/index.rb +7 -4
  25. data/lib/review/book/part.rb +12 -13
  26. data/lib/review/book/volume.rb +1 -1
  27. data/lib/review/builder.rb +92 -65
  28. data/lib/review/catalog.rb +6 -5
  29. data/lib/review/compiler.rb +76 -57
  30. data/lib/review/configure.rb +5 -2
  31. data/lib/review/epub2html.rb +12 -12
  32. data/lib/review/epubmaker/content.rb +1 -1
  33. data/lib/review/epubmaker/epubcommon.rb +47 -45
  34. data/lib/review/epubmaker/epubv2.rb +2 -1
  35. data/lib/review/epubmaker/epubv3.rb +5 -4
  36. data/lib/review/epubmaker/producer.rb +6 -7
  37. data/lib/review/epubmaker/reviewheaderlistener.rb +1 -1
  38. data/lib/review/epubmaker.rb +56 -67
  39. data/lib/review/exception.rb +7 -0
  40. data/lib/review/extentions/string.rb +1 -1
  41. data/lib/review/htmlbuilder.rb +90 -34
  42. data/lib/review/htmlutils.rb +17 -17
  43. data/lib/review/i18n.rb +3 -3
  44. data/lib/review/i18n.yml +6 -0
  45. data/lib/review/idgxmlbuilder.rb +61 -39
  46. data/lib/review/idgxmlmaker.rb +27 -26
  47. data/lib/review/img_math.rb +12 -18
  48. data/lib/review/index_builder.rb +94 -53
  49. data/lib/review/init.rb +4 -4
  50. data/lib/review/latexbuilder.rb +84 -76
  51. data/lib/review/lineinput.rb +3 -3
  52. data/lib/review/location.rb +1 -1
  53. data/lib/review/loggable.rb +27 -0
  54. data/lib/review/logger.rb +69 -21
  55. data/lib/review/makerhelper.rb +8 -4
  56. data/lib/review/markdownbuilder.rb +21 -12
  57. data/lib/review/pdfmaker.rb +63 -42
  58. data/lib/review/plaintextbuilder.rb +16 -15
  59. data/lib/review/preprocessor/directive.rb +35 -0
  60. data/lib/review/preprocessor/line.rb +34 -0
  61. data/lib/review/preprocessor/repository.rb +177 -0
  62. data/lib/review/preprocessor.rb +94 -296
  63. data/lib/review/rstbuilder.rb +12 -3
  64. data/lib/review/template.rb +5 -1
  65. data/lib/review/textmaker.rb +32 -31
  66. data/lib/review/textutils.rb +5 -6
  67. data/lib/review/tocprinter.rb +12 -7
  68. data/lib/review/topbuilder.rb +96 -19
  69. data/lib/review/update.rb +16 -8
  70. data/lib/review/version.rb +1 -1
  71. data/lib/review/volumeprinter.rb +9 -9
  72. data/lib/review/webmaker.rb +45 -46
  73. data/lib/review/webtocprinter.rb +10 -10
  74. data/lib/review/yamlloader.rb +35 -2
  75. data/review.gemspec +2 -1
  76. data/samples/sample-book/src/config.yml +0 -1
  77. data/samples/sample-book/src/lib/tasks/review.rake +3 -1
  78. data/samples/sample-book/src/lib/tasks/z01_copy_sty.rake +2 -1
  79. data/samples/syntax-book/ch02.re +9 -0
  80. data/samples/syntax-book/lib/tasks/z01_copy_sty.rake +2 -1
  81. data/templates/html/_titlepage.html.erb +9 -17
  82. data/templates/latex/config.erb +3 -0
  83. data/templates/latex/review-jlreq/review-base.sty +4 -5
  84. data/templates/latex/review-jlreq/review-jlreq.cls +39 -5
  85. data/templates/latex/review-jsbook/review-base.sty +9 -3
  86. data/templates/latex/review-jsbook/review-jsbook.cls +32 -5
  87. data/templates/opf/opf_manifest_epubv2.opf.erb +1 -1
  88. data/templates/opf/opf_manifest_epubv3.opf.erb +1 -1
  89. data/test/assets/syntax_book_index_detail.txt +10 -8
  90. data/test/assets/test_template.tex +4 -1
  91. data/test/assets/test_template_backmatter.tex +4 -1
  92. data/test/book_test_helper.rb +10 -10
  93. data/test/test_book_chapter.rb +25 -2
  94. data/test/test_builder.rb +10 -8
  95. data/test/test_epub3maker.rb +3 -3
  96. data/test/test_epubmaker.rb +27 -37
  97. data/test/test_epubmaker_cmd.rb +14 -3
  98. data/test/test_htmlbuilder.rb +111 -31
  99. data/test/test_idgxmlbuilder.rb +41 -33
  100. data/test/test_idgxmlmaker_cmd.rb +1 -1
  101. data/test/test_img_math.rb +11 -2
  102. data/test/test_index.rb +30 -4
  103. data/test/test_latexbuilder.rb +46 -25
  104. data/test/test_latexbuilder_v2.rb +18 -10
  105. data/test/test_markdownbuilder.rb +13 -0
  106. data/test/test_pdfmaker.rb +19 -0
  107. data/test/test_pdfmaker_cmd.rb +10 -10
  108. data/test/test_plaintextbuilder.rb +46 -22
  109. data/test/test_preprocessor.rb +188 -1
  110. data/test/test_rstbuilder.rb +13 -0
  111. data/test/test_textmaker_cmd.rb +1 -1
  112. data/test/test_topbuilder.rb +195 -29
  113. data/test/test_yamlloader.rb +28 -42
  114. metadata +11 -6
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2020 Minero Aoki, Kenshi Muto
1
+ # Copyright (c) 2009-2021 Minero Aoki, Kenshi Muto
2
2
  # Copyright (c) 2002-2007 Minero Aoki
3
3
  #
4
4
  # This program is free software.
@@ -10,10 +10,13 @@ require 'review/extentions'
10
10
  require 'review/preprocessor'
11
11
  require 'review/exception'
12
12
  require 'review/location'
13
+ require 'review/loggable'
13
14
  require 'strscan'
14
15
 
15
16
  module ReVIEW
16
17
  class Compiler
18
+ include Loggable
19
+
17
20
  MAX_HEADLINE_LEVEL = 6
18
21
 
19
22
  def initialize(builder)
@@ -24,6 +27,12 @@ module ReVIEW
24
27
 
25
28
  ## to decide escaping/non-escaping for text
26
29
  @command_name_stack = []
30
+
31
+ @logger = ReVIEW.logger
32
+
33
+ @ignore_errors = builder.is_a?(ReVIEW::IndexBuilder)
34
+
35
+ @compile_errors = nil
27
36
  end
28
37
 
29
38
  attr_reader :builder, :previous_list_type
@@ -35,7 +44,7 @@ module ReVIEW
35
44
 
36
45
  def non_escaped_commands
37
46
  if @builder.highlight?
38
- %i[list emlist listnum emlistnum cmd]
47
+ %i[list emlist listnum emlistnum cmd source]
39
48
  else
40
49
  []
41
50
  end
@@ -44,6 +53,10 @@ module ReVIEW
44
53
  def compile(chap)
45
54
  @chapter = chap
46
55
  do_compile
56
+ if @compile_errors
57
+ raise ApplicationError, "#{location.filename} cannot be compiled."
58
+ end
59
+
47
60
  @builder.result
48
61
  end
49
62
 
@@ -58,7 +71,7 @@ module ReVIEW
58
71
  attr_reader :name
59
72
 
60
73
  def check_args(args)
61
- unless @argc_spec === args.size
74
+ unless @argc_spec === args.size # rubocop:disable Style/CaseEquality
62
75
  raise CompileError, "wrong # of parameters (block command //#{@name}, expect #{@argc_spec} but #{args.size})"
63
76
  end
64
77
 
@@ -183,6 +196,8 @@ module ReVIEW
183
196
  defminicolumn :notice, 0..1
184
197
 
185
198
  defsingle :footnote, 2
199
+ defsingle :endnote, 2
200
+ defsingle :printendnotes, 0
186
201
  defsingle :noindent, 0
187
202
  defsingle :blankline, 0
188
203
  defsingle :pagebreak, 0
@@ -207,6 +222,7 @@ module ReVIEW
207
222
  definline :table
208
223
  definline :eq
209
224
  definline :fn
225
+ definline :endnote
210
226
  definline :kw
211
227
  definline :ruby
212
228
  definline :bou
@@ -261,6 +277,7 @@ module ReVIEW
261
277
  def do_compile
262
278
  f = LineInput.new(StringIO.new(@chapter.content))
263
279
  @builder.bind(self, @chapter, Location.new(@chapter.basename, f))
280
+ @previous_list_type = nil
264
281
 
265
282
  ## in minicolumn, such as note/info/alert...
266
283
  @minicolumn_name = nil
@@ -272,27 +289,27 @@ module ReVIEW
272
289
  f.gets # Nothing to do
273
290
  when /\A=+[\[\s{]/
274
291
  compile_headline(f.gets)
275
- @builder.previous_list_type = nil
292
+ @previous_list_type = nil
276
293
  when /\A\s+\*/
277
294
  compile_ulist(f)
278
- @builder.previous_list_type = 'ul'
295
+ @previous_list_type = 'ul'
279
296
  when /\A\s+\d+\./
280
297
  compile_olist(f)
281
- @builder.previous_list_type = 'ol'
298
+ @previous_list_type = 'ol'
282
299
  when /\A\s+:\s/
283
300
  compile_dlist(f)
284
- @builder.previous_list_type = 'dl'
301
+ @previous_list_type = 'dl'
285
302
  when /\A\s*:\s/
286
- warn 'Definition list starting with `:` is deprecated. It should start with ` : `.'
303
+ warn 'Definition list starting with `:` is deprecated. It should start with ` : `.', location: location
287
304
  compile_dlist(f)
288
- @builder.previous_list_type = 'dl'
305
+ @previous_list_type = 'dl'
289
306
  when %r{\A//\}}
290
307
  if in_minicolumn?
291
308
  _line = f.gets
292
309
  compile_minicolumn_end
293
310
  else
294
311
  f.gets
295
- error 'block end seen but not opened'
312
+ error 'block end seen but not opened', location: location
296
313
  end
297
314
  when %r{\A//[a-z]+}
298
315
  line = f.peek
@@ -307,45 +324,44 @@ module ReVIEW
307
324
  name, args, lines = read_command(f)
308
325
  syntax = syntax_descriptor(name)
309
326
  unless syntax
310
- error "unknown command: //#{name}"
311
- compile_unknown_command(args, lines)
327
+ error "unknown command: //#{name}", location: location
312
328
  @command_name_stack.pop
313
329
  next
314
330
  end
315
331
  compile_command(syntax, args, lines)
316
332
  @command_name_stack.pop
317
333
  end
318
- @builder.previous_list_type = nil
334
+ @previous_list_type = nil
319
335
  when %r{\A//}
320
336
  line = f.gets
321
- warn "`//' seen but is not valid command: #{line.strip.inspect}"
337
+ warn "`//' seen but is not valid command: #{line.strip.inspect}", location: location
322
338
  if block_open?(line)
323
- warn 'skipping block...'
339
+ warn 'skipping block...', location: location
324
340
  read_block(f, false)
325
341
  end
326
- @builder.previous_list_type = nil
342
+ @previous_list_type = nil
327
343
  else
328
344
  if f.peek.strip.empty?
329
345
  f.gets
330
346
  next
331
347
  end
332
348
  compile_paragraph(f)
333
- @builder.previous_list_type = nil
349
+ @previous_list_type = nil
334
350
  end
335
351
  end
336
352
  close_all_tagged_section
337
353
  rescue SyntaxError => e
338
- error e
354
+ error e, location: location
339
355
  end
340
356
 
341
357
  def compile_minicolumn_begin(name, caption = nil)
342
358
  mid = "#{name}_begin"
343
359
  unless @builder.respond_to?(mid)
344
- error "strategy does not support minicolumn: #{name}"
360
+ error "strategy does not support minicolumn: #{name}", location: location
345
361
  end
346
362
 
347
363
  if @minicolumn_name
348
- error "minicolumn cannot be nested: #{name}"
364
+ error "minicolumn cannot be nested: #{name}", location: location
349
365
  return
350
366
  end
351
367
  @minicolumn_name = name
@@ -355,7 +371,7 @@ module ReVIEW
355
371
 
356
372
  def compile_minicolumn_end
357
373
  unless @minicolumn_name
358
- error "minicolumn is not used: #{name}"
374
+ error "minicolumn is not used: #{name}", location: location
359
375
  return
360
376
  end
361
377
  name = @minicolumn_name
@@ -382,19 +398,19 @@ module ReVIEW
382
398
  open_tag = tag[1..-1]
383
399
  prev_tag_info = @tagged_section.pop
384
400
  if prev_tag_info.nil? || prev_tag_info.first != open_tag
385
- error "#{open_tag} is not opened."
401
+ error "#{open_tag} is not opened.", location: location
386
402
  end
387
403
  close_tagged_section(*prev_tag_info)
388
404
  else
389
405
  if caption.empty?
390
- warn 'headline is empty.'
406
+ warn 'headline is empty.', location: location
391
407
  end
392
408
  close_current_tagged_section(level)
393
409
  open_tagged_section(tag, level, label, caption)
394
410
  end
395
411
  else
396
412
  if caption.empty?
397
- warn 'headline is empty.'
413
+ warn 'headline is empty.', location: location
398
414
  end
399
415
  if @headline_indexs.size > (index + 1)
400
416
  @headline_indexs = @headline_indexs[0..index]
@@ -425,7 +441,7 @@ module ReVIEW
425
441
  def open_tagged_section(tag, level, label, caption)
426
442
  mid = "#{tag}_begin"
427
443
  unless @builder.respond_to?(mid)
428
- error "builder does not support tagged section: #{tag}"
444
+ error "builder does not support tagged section: #{tag}", location: location
429
445
  headline(level, label, caption)
430
446
  return
431
447
  end
@@ -438,7 +454,7 @@ module ReVIEW
438
454
  if @builder.respond_to?(mid)
439
455
  @builder.__send__(mid, level)
440
456
  else
441
- error "builder does not support block op: #{mid}"
457
+ error "builder does not support block op: #{mid}", location: location
442
458
  end
443
459
  end
444
460
 
@@ -451,7 +467,7 @@ module ReVIEW
451
467
  def compile_ulist(f)
452
468
  level = 0
453
469
  f.while_match(/\A\s+\*|\A\#@/) do |line|
454
- next if line =~ /\A\#@/
470
+ next if /\A\#@/.match?(line)
455
471
 
456
472
  buf = [text(line.sub(/\*+/, '').strip)]
457
473
  f.while_match(/\A\s+(?!\*)\S/) do |cont|
@@ -467,7 +483,7 @@ module ReVIEW
467
483
  elsif level < current_level # down
468
484
  level_diff = current_level - level
469
485
  if level_diff != 1
470
- error 'too many *.'
486
+ error 'too many *.', location: location
471
487
  end
472
488
  level = current_level
473
489
  @builder.ul_begin { level }
@@ -494,7 +510,7 @@ module ReVIEW
494
510
  def compile_olist(f)
495
511
  @builder.ol_begin
496
512
  f.while_match(/\A\s+\d+\.|\A\#@/) do |line|
497
- next if line =~ /\A\#@/
513
+ next if /\A\#@/.match?(line)
498
514
 
499
515
  num = line.match(/(\d+)\./)[1]
500
516
  buf = [text(line.sub(/\d+\./, '').strip)]
@@ -556,12 +572,12 @@ module ReVIEW
556
572
  f.until_match(%r{\A//\}}) do |line|
557
573
  if ignore_inline
558
574
  buf.push(line.chomp)
559
- elsif line !~ /\A\#@/
575
+ elsif !/\A\#@/.match?(line)
560
576
  buf.push(text(line.rstrip, true))
561
577
  end
562
578
  end
563
579
  unless f.peek.to_s.start_with?('//}')
564
- error "unexpected EOF (block begins at: #{head})"
580
+ error "unexpected EOF (block begins at: #{head})", location: location
565
581
  return buf
566
582
  end
567
583
  f.gets # discard terminator
@@ -581,7 +597,7 @@ module ReVIEW
581
597
  words << w2
582
598
  end
583
599
  unless scanner.eos?
584
- error "argument syntax error: #{scanner.rest} in #{str.inspect}"
600
+ error "argument syntax error: #{scanner.rest} in #{str.inspect}", location: location
585
601
  return []
586
602
  end
587
603
  words
@@ -589,37 +605,32 @@ module ReVIEW
589
605
 
590
606
  def compile_command(syntax, args, lines)
591
607
  unless @builder.respond_to?(syntax.name)
592
- error "builder does not support command: //#{syntax.name}"
593
- compile_unknown_command(args, lines)
608
+ error "builder does not support command: //#{syntax.name}", location: location
594
609
  return
595
610
  end
596
611
  begin
597
612
  syntax.check_args(args)
598
613
  rescue CompileError => e
599
- error e.message
614
+ error e.message, location: location
600
615
  args = ['(NoArgument)'] * syntax.min_argc
601
616
  end
602
617
  if syntax.block_allowed?
603
618
  compile_block(syntax, args, lines)
604
619
  else
605
620
  if lines
606
- error "block is not allowed for command //#{syntax.name}; ignore"
621
+ error "block is not allowed for command //#{syntax.name}; ignore", location: location
607
622
  end
608
623
  compile_single(syntax, args)
609
624
  end
610
625
  end
611
626
 
612
- def compile_unknown_command(args, lines)
613
- @builder.unknown_command(args, lines)
614
- end
615
-
616
627
  def compile_block(syntax, args, lines)
617
628
  @builder.__send__(syntax.name, (lines || default_block(syntax)), *args)
618
629
  end
619
630
 
620
631
  def default_block(syntax)
621
632
  if syntax.block_required?
622
- error "block is required for //#{syntax.name}; use empty block"
633
+ error "block is required for //#{syntax.name}; use empty block", location: location
623
634
  end
624
635
  []
625
636
  end
@@ -632,8 +643,8 @@ module ReVIEW
632
643
  str.gsub(/@<(\w+)>([$|])(.+?)(\2)/) do
633
644
  op = $1
634
645
  arg = $3
635
- if arg =~ /[\x01\x02\x03\x04]/
636
- error "invalid character in '#{str}'"
646
+ if /[\x01\x02\x03\x04]/.match?(arg)
647
+ error "invalid character in '#{str}'", location: location
637
648
  end
638
649
  replaced = arg.tr('@', "\x01").tr('\\', "\x02").tr('{', "\x03").tr('}', "\x04")
639
650
  "@<#{op}>{#{replaced}}"
@@ -655,23 +666,23 @@ module ReVIEW
655
666
  words = replace_fence(str).split(/(@<\w+>\{(?:[^}\\]|\\.)*?\})/, -1)
656
667
  words.each do |w|
657
668
  if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
658
- error "`@<xxx>' seen but is not valid inline op: #{w}"
669
+ error "`@<xxx>' seen but is not valid inline op: #{w}", location: location
659
670
  end
660
671
  end
661
672
  result = ''
662
673
  until words.empty?
663
- if in_non_escaped_command? && block_mode
664
- result << revert_replace_fence(words.shift)
665
- else
666
- result << @builder.nofunc_text(revert_replace_fence(words.shift))
667
- end
674
+ result << if in_non_escaped_command? && block_mode
675
+ revert_replace_fence(words.shift)
676
+ else
677
+ @builder.nofunc_text(revert_replace_fence(words.shift))
678
+ end
668
679
  break if words.empty?
669
680
 
670
681
  result << compile_inline(revert_replace_fence(words.shift.gsub(/\\\}/, '}').gsub(/\\\\/, '\\')))
671
682
  end
672
683
  result
673
- rescue => e
674
- error e.message
684
+ rescue StandardError => e
685
+ error e.message, location: location
675
686
  end
676
687
  public :text # called from builder
677
688
 
@@ -685,8 +696,8 @@ module ReVIEW
685
696
  end
686
697
 
687
698
  @builder.__send__("inline_#{op}", arg)
688
- rescue => e
689
- error e.message
699
+ rescue StandardError => e
700
+ error e.message, location: location
690
701
  @builder.nofunc_text(str)
691
702
  end
692
703
 
@@ -698,12 +709,20 @@ module ReVIEW
698
709
  @builder.minicolumn_block_name?(name)
699
710
  end
700
711
 
701
- def warn(msg)
702
- @builder.warn msg
712
+ def ignore_errors?
713
+ @ignore_errors
703
714
  end
704
715
 
705
- def error(msg)
706
- @builder.error msg
716
+ def location
717
+ @builder.location
718
+ end
719
+
720
+ ## override
721
+ def error(msg, location: nil)
722
+ return if ignore_errors? # for IndexBuilder
723
+
724
+ @compile_errors = true
725
+ super
707
726
  end
708
727
  end
709
728
  end # module ReVIEW
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2012-2021 Masanori Kado, Masayoshi Takahashi, Kenshi Muto
2
+ # Copyright (c) 2012-2022 Masanori Kado, Masayoshi Takahashi, Kenshi Muto
3
3
  #
4
4
  # This program is free software.
5
5
  # You can distribute or modify this program under the terms of
@@ -141,6 +141,9 @@ module ReVIEW
141
141
  'force_include_images' => [],
142
142
  'cover_linear' => nil,
143
143
  'back_footnote' => nil
144
+ },
145
+ 'textmaker' => {
146
+ 'th_bold' => nil
144
147
  }
145
148
  ]
146
149
  conf.maker = nil
@@ -155,7 +158,7 @@ module ReVIEW
155
158
  begin
156
159
  loader = ReVIEW::YAMLLoader.new
157
160
  conf.deep_merge!(loader.load_file(yamlfile))
158
- rescue => e
161
+ rescue StandardError => e
159
162
  raise ReVIEW::ConfigError, "yaml error #{e.message}"
160
163
  end
161
164
  end
@@ -13,7 +13,7 @@ require 'review/version'
13
13
 
14
14
  begin
15
15
  require 'cgi/escape'
16
- rescue
16
+ rescue StandardError
17
17
  require 'cgi/util'
18
18
  end
19
19
 
@@ -62,10 +62,10 @@ EOT
62
62
  def parse_epub(epubname)
63
63
  Zip::File.open(epubname) do |zio|
64
64
  zio.each do |entry|
65
- if entry.name =~ /.+\.opf\Z/
65
+ if /.+\.opf\Z/.match?(entry.name)
66
66
  opf = entry.get_input_stream.read
67
67
  @opfxml = REXML::Document.new(opf)
68
- elsif entry.name =~ /.+\.x?html\Z/
68
+ elsif /.+\.x?html\Z/.match?(entry.name)
69
69
  @htmls[entry.name.sub('OEBPS/', '')] = entry.get_input_stream.read.force_encoding('utf-8')
70
70
  end
71
71
  end
@@ -107,15 +107,15 @@ EOT
107
107
  end
108
108
 
109
109
  file, anc = href.split('#', 2)
110
- if anc
111
- if file.empty?
112
- anc = "#{sanitize(fname)}_#{sanitize(anc)}"
113
- else
114
- anc = "#{sanitize(file)}_#{sanitize(anc)}"
115
- end
116
- else
117
- anc = sanitize(file)
118
- end
110
+ anc = if anc
111
+ if file.empty?
112
+ "#{sanitize(fname)}_#{sanitize(anc)}"
113
+ else
114
+ "#{sanitize(file)}_#{sanitize(anc)}"
115
+ end
116
+ else
117
+ sanitize(file)
118
+ end
119
119
 
120
120
  e.attributes['href'] = "##{anc}"
121
121
  end
@@ -76,7 +76,7 @@ module ReVIEW
76
76
  if @id.nil?
77
77
  @id = @file.gsub(%r{[\\/. ]}, '-')
78
78
  end
79
- if @id =~ /\A[^a-z]/i
79
+ if /\A[^a-z]/i.match?(@id)
80
80
  @id = "rv-#{@id}"
81
81
  end
82
82
  @id = CGI.escape(@id).gsub('%', '_25_')
@@ -1,6 +1,6 @@
1
1
  # = epubcommon.rb -- super class for EPUBv2 and EPUBv3
2
2
  #
3
- # Copyright (c) 2010-2019 Kenshi Muto and Masayoshi Takahashi
3
+ # Copyright (c) 2010-2022 Kenshi Muto and Masayoshi Takahashi
4
4
  #
5
5
  # This program is free software.
6
6
  # You can distribute or modify this program under the terms of
@@ -27,6 +27,7 @@ module ReVIEW
27
27
  @contents = producer.contents
28
28
  @body_ext = nil
29
29
  @logger = ReVIEW.logger
30
+ @workdir = nil
30
31
  end
31
32
 
32
33
  attr_reader :config
@@ -36,7 +37,8 @@ module ReVIEW
36
37
  CGI.escapeHTML(str)
37
38
  end
38
39
 
39
- def produce(epubfile, basedir, tmpdir)
40
+ def produce(_epubfile, _basedir, _tmpdir, base_dir:)
41
+ @workdir = base_dir
40
42
  raise NotImplementedError # should be overridden
41
43
  end
42
44
 
@@ -70,7 +72,7 @@ module ReVIEW
70
72
  item = contents.find { |content| content.coverimage?(config['coverimage']) }
71
73
 
72
74
  unless item
73
- raise "coverimage #{config['coverimage']} not found. Abort."
75
+ raise ApplicationError, "coverimage #{config['coverimage']} not found. Abort."
74
76
  end
75
77
 
76
78
  %Q( <meta name="cover" content="#{item.id}"/>\n)
@@ -99,27 +101,43 @@ module ReVIEW
99
101
  end
100
102
  end
101
103
 
104
+ def template_name(localfile: 'layout.html.erb', systemfile: nil)
105
+ if @workdir
106
+ layoutfile = File.join(@workdir, 'layouts', localfile)
107
+ if File.exist?(layoutfile)
108
+ return layoutfile
109
+ end
110
+ end
111
+
112
+ if systemfile
113
+ return systemfile
114
+ end
115
+
116
+ if config['htmlversion'].to_i == 5
117
+ './html/layout-html5.html.erb'
118
+ else
119
+ './html/layout-xhtml1.html.erb'
120
+ end
121
+ end
122
+
102
123
  # Return cover content.
103
124
  # If Producer#config["coverimage"] is defined, it will be used for
104
125
  # the cover image.
105
126
  def cover
106
- @body_ext = config['epubversion'] >= 3 ? %Q( epub:type="cover") : ''
127
+ @body_ext = config['epubversion'] >= 3 ? %Q( epub:type="cover") : nil
107
128
 
108
129
  if config['coverimage']
109
130
  @coverimage_src = coverimage
110
- raise "coverimage #{config['coverimage']} not found. Abort." unless @coverimage_src
131
+ raise ApplicationError, "coverimage #{config['coverimage']} not found. Abort." unless @coverimage_src
111
132
  end
112
- @body = ReVIEW::Template.generate(path: './html/_cover.html.erb', binding: binding)
133
+ @body = ReVIEW::Template.generate(path: template_name(localfile: '_cover.html.erb', systemfile: 'html/_cover.html.erb'), binding: binding)
113
134
 
114
135
  @title = h(config.name_of('title'))
115
136
  @language = config['language']
116
137
  @stylesheets = config['stylesheet']
117
- template_path = if config['htmlversion'].to_i == 5
118
- './html/layout-html5.html.erb'
119
- else
120
- './html/layout-xhtml1.html.erb'
121
- end
122
- ReVIEW::Template.generate(path: template_path, binding: binding)
138
+ ret = ReVIEW::Template.generate(path: template_name, binding: binding)
139
+ @body_ext = nil
140
+ ret
123
141
  end
124
142
 
125
143
  # Return title (copying) content.
@@ -138,16 +156,11 @@ module ReVIEW
138
156
  if config.names_of('pbl')
139
157
  @publisher_str = join_with_separator(config.names_of('pbl'), ReVIEW::I18n.t('names_splitter'))
140
158
  end
141
- @body = ReVIEW::Template.generate(path: './html/_titlepage.html.erb', binding: binding)
159
+ @body = ReVIEW::Template.generate(path: template_name(localfile: '_titlepage.html.erb', systemfile: './html/_titlepage.html.erb'), binding: binding)
142
160
 
143
161
  @language = config['language']
144
162
  @stylesheets = config['stylesheet']
145
- template_path = if config['htmlversion'].to_i == 5
146
- './html/layout-html5.html.erb'
147
- else
148
- './html/layout-xhtml1.html.erb'
149
- end
150
- ReVIEW::Template.generate(path: template_path, binding: binding)
163
+ ReVIEW::Template.generate(path: template_name, binding: binding)
151
164
  end
152
165
 
153
166
  # Return colophon content.
@@ -155,24 +168,19 @@ module ReVIEW
155
168
  @title = h(ReVIEW::I18n.t('colophontitle'))
156
169
  @isbn_hyphen = isbn_hyphen
157
170
 
158
- @body = ReVIEW::Template.generate(path: './html/_colophon.html.erb', binding: binding)
171
+ @body = ReVIEW::Template.generate(path: template_name(localfile: '_colophon.html.erb', systemfile: './html/_colophon.html.erb'), binding: binding)
159
172
 
160
173
  @language = config['language']
161
174
  @stylesheets = config['stylesheet']
162
- template_path = if config['htmlversion'].to_i == 5
163
- './html/layout-html5.html.erb'
164
- else
165
- './html/layout-xhtml1.html.erb'
166
- end
167
- ReVIEW::Template.generate(path: template_path, binding: binding)
175
+ ReVIEW::Template.generate(path: template_name, binding: binding)
168
176
  end
169
177
 
170
178
  def isbn_hyphen
171
179
  str = config['isbn'].to_s
172
180
 
173
- if str =~ /\A\d{10}\Z/
181
+ if /\A\d{10}\Z/.match?(str)
174
182
  "#{str[0..0]}-#{str[1..5]}-#{str[6..8]}-#{str[9..9]}"
175
- elsif str =~ /\A\d{13}\Z/
183
+ elsif /\A\d{13}\Z/.match?(str)
176
184
  "#{str[0..2]}-#{str[3..3]}-#{str[4..8]}-#{str[9..11]}-#{str[12..12]}"
177
185
  end
178
186
  end
@@ -184,9 +192,9 @@ module ReVIEW
184
192
  items.each_with_index do |item, rev|
185
193
  editstr = edit == 0 ? ReVIEW::I18n.t('first_edition') : ReVIEW::I18n.t('nth_edition', (edit + 1).to_s)
186
194
  revstr = ReVIEW::I18n.t('nth_impression', (rev + 1).to_s)
187
- if item =~ /\A\d+-\d+-\d+\Z/
195
+ if /\A\d+-\d+-\d+\Z/.match?(item)
188
196
  @col_history << ReVIEW::I18n.t('published_by1', [date_to_s(item), editstr + revstr])
189
- elsif item =~ /\A(\d+-\d+-\d+)[\s ](.+)/
197
+ elsif /\A(\d+-\d+-\d+)[\s ](.+)/.match?(item)
190
198
  # custom date with string
191
199
  item.match(/\A(\d+-\d+-\d+)[\s ](.+)/) do |m|
192
200
  @col_history << ReVIEW::I18n.t('published_by3', [date_to_s(m[1]), m[2]])
@@ -199,7 +207,7 @@ module ReVIEW
199
207
  end
200
208
  end
201
209
 
202
- ReVIEW::Template.generate(path: './html/_colophon_history.html.erb', binding: binding)
210
+ ReVIEW::Template.generate(path: template_name(localfile: '_colophon_history.html.erb', systemfile: './html/_colophon_history.html.erb'), binding: binding)
203
211
  end
204
212
 
205
213
  def date_to_s(date)
@@ -211,22 +219,16 @@ module ReVIEW
211
219
  # Return own toc content.
212
220
  def mytoc
213
221
  @title = h(ReVIEW::I18n.t('toctitle'))
214
-
215
222
  @body = %Q( <h1 class="toc-title">#{h(ReVIEW::I18n.t('toctitle'))}</h1>\n)
216
- if config['epubmaker']['flattoc'].nil?
217
- @body << hierarchy_ncx('ul')
218
- else
219
- @body << flat_ncx('ul', config['epubmaker']['flattocindent'])
220
- end
223
+ @body << if config['epubmaker']['flattoc'].nil?
224
+ hierarchy_ncx('ul')
225
+ else
226
+ flat_ncx('ul', config['epubmaker']['flattocindent'])
227
+ end
221
228
 
222
229
  @language = config['language']
223
230
  @stylesheets = config['stylesheet']
224
- template_path = if config['htmlversion'].to_i == 5
225
- './html/layout-html5.html.erb'
226
- else
227
- './html/layout-xhtml1.html.erb'
228
- end
229
- ReVIEW::Template.generate(path: template_path, binding: binding)
231
+ ReVIEW::Template.generate(path: template_name, binding: binding)
230
232
  end
231
233
 
232
234
  def hierarchy_ncx(type)
@@ -334,11 +336,11 @@ module ReVIEW
334
336
  end
335
337
 
336
338
  contents.each do |item|
337
- next if item.file =~ /#/ # skip subgroup
339
+ next if /#/.match?(item.file) # skip subgroup
338
340
 
339
341
  fname = "#{basedir}/#{item.file}"
340
342
  unless File.exist?(fname)
341
- raise "#{fname} is not found."
343
+ raise ApplicationError, "#{fname} is not found."
342
344
  end
343
345
 
344
346
  FileUtils.mkdir_p(File.dirname("#{tmpdir}/OEBPS/#{item.file}"))
@@ -1,6 +1,6 @@
1
1
  # = epubv2.rb -- EPUB version 2 producer.
2
2
  #
3
- # Copyright (c) 2010-2017 Kenshi Muto and Masayoshi Takahashi
3
+ # Copyright (c) 2010-2022 Kenshi Muto and Masayoshi Takahashi
4
4
  #
5
5
  # This program is free software.
6
6
  # You can distribute or modify this program under the terms of
@@ -159,6 +159,7 @@ EOT
159
159
  # +basedir+ points the directory has contents.
160
160
  # +tmpdir+ defines temporary directory.
161
161
  def produce(epubfile, work_dir, tmpdir, base_dir:)
162
+ @workdir = base_dir
162
163
  produce_write_common(work_dir, tmpdir)
163
164
 
164
165
  ncx_file = "#{tmpdir}/OEBPS/#{config['bookname']}.ncx"