review 5.1.1 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-tex.yml +5 -1
  3. data/.github/workflows/ruby-win.yml +5 -1
  4. data/.github/workflows/ruby.yml +5 -1
  5. data/.rubocop.yml +6 -2
  6. data/NEWS.ja.md +48 -0
  7. data/NEWS.md +48 -1
  8. data/bin/review-compile +8 -15
  9. data/bin/review-preproc +28 -34
  10. data/doc/config.yml.sample +2 -0
  11. data/doc/writing_vertical.ja.md +6 -0
  12. data/lib/review/builder.rb +34 -40
  13. data/lib/review/compiler.rb +59 -43
  14. data/lib/review/epubmaker.rb +24 -38
  15. data/lib/review/epubmaker/producer.rb +3 -4
  16. data/lib/review/exception.rb +7 -0
  17. data/lib/review/htmlbuilder.rb +51 -20
  18. data/lib/review/idgxmlbuilder.rb +19 -18
  19. data/lib/review/idgxmlmaker.rb +12 -13
  20. data/lib/review/img_math.rb +11 -18
  21. data/lib/review/index_builder.rb +6 -15
  22. data/lib/review/latexbuilder.rb +38 -43
  23. data/lib/review/loggable.rb +27 -0
  24. data/lib/review/logger.rb +48 -0
  25. data/lib/review/makerhelper.rb +5 -1
  26. data/lib/review/markdownbuilder.rb +5 -4
  27. data/lib/review/pdfmaker.rb +23 -21
  28. data/lib/review/plaintextbuilder.rb +8 -8
  29. data/lib/review/preprocessor.rb +94 -296
  30. data/lib/review/preprocessor/directive.rb +35 -0
  31. data/lib/review/preprocessor/line.rb +34 -0
  32. data/lib/review/preprocessor/repository.rb +177 -0
  33. data/lib/review/rstbuilder.rb +1 -1
  34. data/lib/review/template.rb +5 -1
  35. data/lib/review/textmaker.rb +12 -13
  36. data/lib/review/tocprinter.rb +1 -1
  37. data/lib/review/topbuilder.rb +7 -7
  38. data/lib/review/version.rb +1 -1
  39. data/lib/review/webmaker.rb +13 -14
  40. data/review.gemspec +1 -1
  41. data/samples/sample-book/src/lib/tasks/review.rake +3 -1
  42. data/samples/sample-book/src/lib/tasks/z01_copy_sty.rake +2 -1
  43. data/samples/syntax-book/lib/tasks/z01_copy_sty.rake +2 -1
  44. data/templates/latex/review-jlreq/review-base.sty +3 -5
  45. data/templates/latex/review-jlreq/review-jlreq.cls +4 -3
  46. data/templates/latex/review-jsbook/review-base.sty +3 -3
  47. data/templates/latex/review-jsbook/review-jsbook.cls +1 -1
  48. data/test/test_builder.rb +8 -8
  49. data/test/test_epubmaker.rb +13 -8
  50. data/test/test_epubmaker_cmd.rb +13 -2
  51. data/test/test_htmlbuilder.rb +68 -26
  52. data/test/test_idgxmlbuilder.rb +31 -23
  53. data/test/test_latexbuilder.rb +30 -22
  54. data/test/test_latexbuilder_v2.rb +18 -10
  55. data/test/test_plaintextbuilder.rb +30 -22
  56. data/test/test_preprocessor.rb +188 -1
  57. data/test/test_topbuilder.rb +26 -18
  58. metadata +12 -8
@@ -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
@@ -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
 
@@ -261,6 +274,7 @@ module ReVIEW
261
274
  def do_compile
262
275
  f = LineInput.new(StringIO.new(@chapter.content))
263
276
  @builder.bind(self, @chapter, Location.new(@chapter.basename, f))
277
+ @previous_list_type = nil
264
278
 
265
279
  ## in minicolumn, such as note/info/alert...
266
280
  @minicolumn_name = nil
@@ -272,27 +286,27 @@ module ReVIEW
272
286
  f.gets # Nothing to do
273
287
  when /\A=+[\[\s{]/
274
288
  compile_headline(f.gets)
275
- @builder.previous_list_type = nil
289
+ @previous_list_type = nil
276
290
  when /\A\s+\*/
277
291
  compile_ulist(f)
278
- @builder.previous_list_type = 'ul'
292
+ @previous_list_type = 'ul'
279
293
  when /\A\s+\d+\./
280
294
  compile_olist(f)
281
- @builder.previous_list_type = 'ol'
295
+ @previous_list_type = 'ol'
282
296
  when /\A\s+:\s/
283
297
  compile_dlist(f)
284
- @builder.previous_list_type = 'dl'
298
+ @previous_list_type = 'dl'
285
299
  when /\A\s*:\s/
286
- warn 'Definition list starting with `:` is deprecated. It should start with ` : `.'
300
+ warn 'Definition list starting with `:` is deprecated. It should start with ` : `.', location: location
287
301
  compile_dlist(f)
288
- @builder.previous_list_type = 'dl'
302
+ @previous_list_type = 'dl'
289
303
  when %r{\A//\}}
290
304
  if in_minicolumn?
291
305
  _line = f.gets
292
306
  compile_minicolumn_end
293
307
  else
294
308
  f.gets
295
- error 'block end seen but not opened'
309
+ error 'block end seen but not opened', location: location
296
310
  end
297
311
  when %r{\A//[a-z]+}
298
312
  line = f.peek
@@ -307,45 +321,44 @@ module ReVIEW
307
321
  name, args, lines = read_command(f)
308
322
  syntax = syntax_descriptor(name)
309
323
  unless syntax
310
- error "unknown command: //#{name}"
311
- compile_unknown_command(args, lines)
324
+ error "unknown command: //#{name}", location: location
312
325
  @command_name_stack.pop
313
326
  next
314
327
  end
315
328
  compile_command(syntax, args, lines)
316
329
  @command_name_stack.pop
317
330
  end
318
- @builder.previous_list_type = nil
331
+ @previous_list_type = nil
319
332
  when %r{\A//}
320
333
  line = f.gets
321
- warn "`//' seen but is not valid command: #{line.strip.inspect}"
334
+ warn "`//' seen but is not valid command: #{line.strip.inspect}", location: location
322
335
  if block_open?(line)
323
- warn 'skipping block...'
336
+ warn 'skipping block...', location: location
324
337
  read_block(f, false)
325
338
  end
326
- @builder.previous_list_type = nil
339
+ @previous_list_type = nil
327
340
  else
328
341
  if f.peek.strip.empty?
329
342
  f.gets
330
343
  next
331
344
  end
332
345
  compile_paragraph(f)
333
- @builder.previous_list_type = nil
346
+ @previous_list_type = nil
334
347
  end
335
348
  end
336
349
  close_all_tagged_section
337
350
  rescue SyntaxError => e
338
- error e
351
+ error e, location: location
339
352
  end
340
353
 
341
354
  def compile_minicolumn_begin(name, caption = nil)
342
355
  mid = "#{name}_begin"
343
356
  unless @builder.respond_to?(mid)
344
- error "strategy does not support minicolumn: #{name}"
357
+ error "strategy does not support minicolumn: #{name}", location: location
345
358
  end
346
359
 
347
360
  if @minicolumn_name
348
- error "minicolumn cannot be nested: #{name}"
361
+ error "minicolumn cannot be nested: #{name}", location: location
349
362
  return
350
363
  end
351
364
  @minicolumn_name = name
@@ -355,7 +368,7 @@ module ReVIEW
355
368
 
356
369
  def compile_minicolumn_end
357
370
  unless @minicolumn_name
358
- error "minicolumn is not used: #{name}"
371
+ error "minicolumn is not used: #{name}", location: location
359
372
  return
360
373
  end
361
374
  name = @minicolumn_name
@@ -382,19 +395,19 @@ module ReVIEW
382
395
  open_tag = tag[1..-1]
383
396
  prev_tag_info = @tagged_section.pop
384
397
  if prev_tag_info.nil? || prev_tag_info.first != open_tag
385
- error "#{open_tag} is not opened."
398
+ error "#{open_tag} is not opened.", location: location
386
399
  end
387
400
  close_tagged_section(*prev_tag_info)
388
401
  else
389
402
  if caption.empty?
390
- warn 'headline is empty.'
403
+ warn 'headline is empty.', location: location
391
404
  end
392
405
  close_current_tagged_section(level)
393
406
  open_tagged_section(tag, level, label, caption)
394
407
  end
395
408
  else
396
409
  if caption.empty?
397
- warn 'headline is empty.'
410
+ warn 'headline is empty.', location: location
398
411
  end
399
412
  if @headline_indexs.size > (index + 1)
400
413
  @headline_indexs = @headline_indexs[0..index]
@@ -425,7 +438,7 @@ module ReVIEW
425
438
  def open_tagged_section(tag, level, label, caption)
426
439
  mid = "#{tag}_begin"
427
440
  unless @builder.respond_to?(mid)
428
- error "builder does not support tagged section: #{tag}"
441
+ error "builder does not support tagged section: #{tag}", location: location
429
442
  headline(level, label, caption)
430
443
  return
431
444
  end
@@ -438,7 +451,7 @@ module ReVIEW
438
451
  if @builder.respond_to?(mid)
439
452
  @builder.__send__(mid, level)
440
453
  else
441
- error "builder does not support block op: #{mid}"
454
+ error "builder does not support block op: #{mid}", location: location
442
455
  end
443
456
  end
444
457
 
@@ -467,7 +480,7 @@ module ReVIEW
467
480
  elsif level < current_level # down
468
481
  level_diff = current_level - level
469
482
  if level_diff != 1
470
- error 'too many *.'
483
+ error 'too many *.', location: location
471
484
  end
472
485
  level = current_level
473
486
  @builder.ul_begin { level }
@@ -561,7 +574,7 @@ module ReVIEW
561
574
  end
562
575
  end
563
576
  unless f.peek.to_s.start_with?('//}')
564
- error "unexpected EOF (block begins at: #{head})"
577
+ error "unexpected EOF (block begins at: #{head})", location: location
565
578
  return buf
566
579
  end
567
580
  f.gets # discard terminator
@@ -581,7 +594,7 @@ module ReVIEW
581
594
  words << w2
582
595
  end
583
596
  unless scanner.eos?
584
- error "argument syntax error: #{scanner.rest} in #{str.inspect}"
597
+ error "argument syntax error: #{scanner.rest} in #{str.inspect}", location: location
585
598
  return []
586
599
  end
587
600
  words
@@ -589,37 +602,32 @@ module ReVIEW
589
602
 
590
603
  def compile_command(syntax, args, lines)
591
604
  unless @builder.respond_to?(syntax.name)
592
- error "builder does not support command: //#{syntax.name}"
593
- compile_unknown_command(args, lines)
605
+ error "builder does not support command: //#{syntax.name}", location: location
594
606
  return
595
607
  end
596
608
  begin
597
609
  syntax.check_args(args)
598
610
  rescue CompileError => e
599
- error e.message
611
+ error e.message, location: location
600
612
  args = ['(NoArgument)'] * syntax.min_argc
601
613
  end
602
614
  if syntax.block_allowed?
603
615
  compile_block(syntax, args, lines)
604
616
  else
605
617
  if lines
606
- error "block is not allowed for command //#{syntax.name}; ignore"
618
+ error "block is not allowed for command //#{syntax.name}; ignore", location: location
607
619
  end
608
620
  compile_single(syntax, args)
609
621
  end
610
622
  end
611
623
 
612
- def compile_unknown_command(args, lines)
613
- @builder.unknown_command(args, lines)
614
- end
615
-
616
624
  def compile_block(syntax, args, lines)
617
625
  @builder.__send__(syntax.name, (lines || default_block(syntax)), *args)
618
626
  end
619
627
 
620
628
  def default_block(syntax)
621
629
  if syntax.block_required?
622
- error "block is required for //#{syntax.name}; use empty block"
630
+ error "block is required for //#{syntax.name}; use empty block", location: location
623
631
  end
624
632
  []
625
633
  end
@@ -633,7 +641,7 @@ module ReVIEW
633
641
  op = $1
634
642
  arg = $3
635
643
  if arg =~ /[\x01\x02\x03\x04]/
636
- error "invalid character in '#{str}'"
644
+ error "invalid character in '#{str}'", location: location
637
645
  end
638
646
  replaced = arg.tr('@', "\x01").tr('\\', "\x02").tr('{', "\x03").tr('}', "\x04")
639
647
  "@<#{op}>{#{replaced}}"
@@ -655,7 +663,7 @@ module ReVIEW
655
663
  words = replace_fence(str).split(/(@<\w+>\{(?:[^}\\]|\\.)*?\})/, -1)
656
664
  words.each do |w|
657
665
  if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
658
- error "`@<xxx>' seen but is not valid inline op: #{w}"
666
+ error "`@<xxx>' seen but is not valid inline op: #{w}", location: location
659
667
  end
660
668
  end
661
669
  result = ''
@@ -671,7 +679,7 @@ module ReVIEW
671
679
  end
672
680
  result
673
681
  rescue => e
674
- error e.message
682
+ error e.message, location: location
675
683
  end
676
684
  public :text # called from builder
677
685
 
@@ -686,7 +694,7 @@ module ReVIEW
686
694
 
687
695
  @builder.__send__("inline_#{op}", arg)
688
696
  rescue => e
689
- error e.message
697
+ error e.message, location: location
690
698
  @builder.nofunc_text(str)
691
699
  end
692
700
 
@@ -698,12 +706,20 @@ module ReVIEW
698
706
  @builder.minicolumn_block_name?(name)
699
707
  end
700
708
 
701
- def warn(msg)
702
- @builder.warn msg
709
+ def ignore_errors?
710
+ @ignore_errors
703
711
  end
704
712
 
705
- def error(msg)
706
- @builder.error msg
713
+ def location
714
+ @builder.location
715
+ end
716
+
717
+ ## override
718
+ def error(msg, location: nil)
719
+ return if ignore_errors? # for IndexBuilder
720
+
721
+ @compile_errors = true
722
+ super
707
723
  end
708
724
  end
709
725
  end # module ReVIEW
@@ -27,10 +27,12 @@ require 'review/epubmaker/epubv2'
27
27
  require 'review/epubmaker/epubv3'
28
28
  require 'review/epubmaker/reviewheaderlistener'
29
29
  require 'review/makerhelper'
30
+ require 'review/loggable'
30
31
 
31
32
  module ReVIEW
32
33
  class EPUBMaker
33
34
  include MakerHelper
35
+ include Loggable
34
36
  include ReVIEW::CallHook
35
37
 
36
38
  def initialize
@@ -42,19 +44,6 @@ module ReVIEW
42
44
  @basedir = nil
43
45
  end
44
46
 
45
- def error(msg)
46
- @logger.error msg
47
- exit 1
48
- end
49
-
50
- def warn(msg)
51
- @logger.warn msg
52
- end
53
-
54
- def log(msg)
55
- @logger.debug(msg)
56
- end
57
-
58
47
  def self.execute(*args)
59
48
  self.new.execute(*args)
60
49
  end
@@ -84,14 +73,14 @@ module ReVIEW
84
73
 
85
74
  def execute(*args)
86
75
  cmd_config, yamlfile, exportfile = parse_opts(args)
87
- error "#{yamlfile} not found." unless File.exist?(yamlfile)
76
+ error! "#{yamlfile} not found." unless File.exist?(yamlfile)
88
77
 
89
78
  @config = ReVIEW::Configure.create(maker: 'epubmaker',
90
79
  yamlfile: yamlfile,
91
80
  config: cmd_config)
92
81
  @producer = ReVIEW::EPUBMaker::Producer.new(@config)
93
82
  update_log_level
94
- log("Loaded yaml file (#{yamlfile}).")
83
+ debug("Loaded yaml file (#{yamlfile}).")
95
84
  @basedir = File.absolute_path(File.dirname(yamlfile))
96
85
 
97
86
  produce(yamlfile, exportfile)
@@ -129,12 +118,10 @@ module ReVIEW
129
118
  booktmpname = "#{bookname}-epub"
130
119
 
131
120
  @img_math = ReVIEW::ImgMath.new(@config)
132
- begin
133
- @config.check_version(ReVIEW::VERSION)
134
- rescue ReVIEW::ConfigError => e
121
+ unless @config.check_version(ReVIEW::VERSION, exception: false)
135
122
  warn e.message
136
123
  end
137
- log("#{bookname}.epub will be created.")
124
+ debug("#{bookname}.epub will be created.")
138
125
 
139
126
  FileUtils.rm_f("#{bookname}.epub")
140
127
  if @config['debug']
@@ -145,7 +132,7 @@ module ReVIEW
145
132
 
146
133
  basetmpdir = build_path
147
134
  begin
148
- log("Created first temporary directory as #{basetmpdir}.")
135
+ debug("Created first temporary directory as #{basetmpdir}.")
149
136
 
150
137
  call_hook('hook_beforeprocess', basetmpdir, base_dir: @basedir)
151
138
 
@@ -192,14 +179,14 @@ module ReVIEW
192
179
  epubtmpdir = File.join(basetmpdir, booktmpname)
193
180
  Dir.mkdir(epubtmpdir)
194
181
  end
195
- log('Call ePUB producer.')
182
+ debug('Call ePUB producer.')
196
183
  @producer.produce("#{bookname}.epub", basetmpdir, epubtmpdir, base_dir: @basedir)
197
- log('Finished.')
184
+ debug('Finished.')
198
185
  @logger.success("built #{bookname}.epub")
199
186
  rescue ApplicationError => e
200
187
  raise if @config['debug']
201
188
 
202
- error(e.message)
189
+ error! e.message
203
190
  ensure
204
191
  FileUtils.remove_entry_secure(basetmpdir) unless @config['debug']
205
192
  end
@@ -245,8 +232,8 @@ module ReVIEW
245
232
  end
246
233
  basedir = File.dirname(file)
247
234
  FileUtils.mkdir_p(File.join(destdir, basedir))
248
- log("Copy #{file} to the temporary directory.")
249
- FileUtils.cp(file, File.join(destdir, basedir))
235
+ debug("Copy #{file} to the temporary directory.")
236
+ FileUtils.cp(file, File.join(destdir, basedir), preserve: true)
250
237
  end
251
238
  else
252
239
  recursive_copy_files(resdir, destdir, allow_exts)
@@ -270,8 +257,8 @@ module ReVIEW
270
257
  recursive_copy_files(File.join(resdir, fname), File.join(destdir, fname), allow_exts)
271
258
  elsif fname =~ /\.(#{allow_exts.join('|')})\Z/i
272
259
  FileUtils.mkdir_p(destdir)
273
- log("Copy #{resdir}/#{fname} to the temporary directory.")
274
- FileUtils.cp(File.join(resdir, fname), destdir)
260
+ debug("Copy #{resdir}/#{fname} to the temporary directory.")
261
+ FileUtils.cp(File.join(resdir, fname), destdir, preserve: true)
275
262
  end
276
263
  end
277
264
  end
@@ -280,8 +267,7 @@ module ReVIEW
280
267
  def check_compile_status
281
268
  return unless @compile_errors
282
269
 
283
- $stderr.puts 'compile error, No EPUB file output.'
284
- exit 1
270
+ error! 'compile error, No EPUB file output.'
285
271
  end
286
272
 
287
273
  def build_body(basetmpdir, yamlfile)
@@ -323,7 +309,7 @@ module ReVIEW
323
309
  end
324
310
 
325
311
  def build_part(part, basetmpdir, htmlfile)
326
- log("Create #{htmlfile} from a template.")
312
+ debug("Create #{htmlfile} from a template.")
327
313
  File.open(File.join(basetmpdir, htmlfile), 'w') do |f|
328
314
  @part_number = part.number
329
315
  @part_title = part.name.strip
@@ -384,7 +370,7 @@ module ReVIEW
384
370
 
385
371
  htmlfile = "#{id}.#{@config['htmlext']}"
386
372
  write_buildlogtxt(basetmpdir, htmlfile, filename)
387
- log("Create #{htmlfile} from #{filename}.")
373
+ debug("Create #{htmlfile} from #{filename}.")
388
374
 
389
375
  if @config['params'].present?
390
376
  warn %Q('params:' in config.yml is obsoleted.)
@@ -398,8 +384,8 @@ module ReVIEW
398
384
  remove_hidden_title(basetmpdir, htmlfile)
399
385
  rescue => e
400
386
  @compile_errors = true
401
- warn "compile error in #{filename} (#{e.class})"
402
- warn e.message
387
+ error "compile error in #{filename} (#{e.class})"
388
+ error e.message
403
389
  end
404
390
  end
405
391
 
@@ -480,7 +466,7 @@ module ReVIEW
480
466
  @htmltoc.each_item do |level, file, title, args|
481
467
  next if level.to_i > @config['toclevel'] && args[:force_include].nil?
482
468
 
483
- log("Push #{file} to ePUB contents.")
469
+ debug("Push #{file} to ePUB contents.")
484
470
 
485
471
  params = { file: file,
486
472
  level: level.to_i,
@@ -504,9 +490,9 @@ module ReVIEW
504
490
 
505
491
  @config['stylesheet'].each do |sfile|
506
492
  unless File.exist?(sfile)
507
- error "stylesheet: #{sfile} is not found."
493
+ error! "stylesheet: #{sfile} is not found."
508
494
  end
509
- FileUtils.cp(sfile, basetmpdir)
495
+ FileUtils.cp(sfile, basetmpdir, preserve: true)
510
496
  @producer.contents.push(ReVIEW::EPUBMaker::Content.new(file: sfile))
511
497
  end
512
498
  end
@@ -514,10 +500,10 @@ module ReVIEW
514
500
  def copy_static_file(configname, destdir, destfilename: nil)
515
501
  destfilename ||= @config[configname]
516
502
  unless File.exist?(@config[configname])
517
- error "#{configname}: #{@config[configname]} is not found."
503
+ error! "#{configname}: #{@config[configname]} is not found."
518
504
  end
519
505
  FileUtils.cp(@config[configname],
520
- File.join(destdir, destfilename))
506
+ File.join(destdir, destfilename), preserve: true)
521
507
  end
522
508
 
523
509
  def copy_frontmatter(basetmpdir)