review 2.4.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +20 -5
  3. data/.travis.yml +2 -1
  4. data/NEWS.ja.md +93 -0
  5. data/NEWS.md +77 -0
  6. data/README.md +1 -1
  7. data/bin/review +38 -12
  8. data/bin/review-compile +106 -88
  9. data/bin/review-epubmaker +6 -1
  10. data/bin/review-init +21 -1
  11. data/bin/review-textmaker +16 -0
  12. data/doc/config.yml.sample +6 -1
  13. data/doc/format.ja.md +23 -0
  14. data/doc/format.md +20 -2
  15. data/doc/quickstart.ja.md +8 -4
  16. data/doc/quickstart.md +11 -8
  17. data/lib/review/book/base.rb +29 -18
  18. data/lib/review/book/index.rb +10 -5
  19. data/lib/review/builder.rb +58 -33
  20. data/lib/review/catalog.rb +30 -0
  21. data/lib/review/compiler.rb +53 -19
  22. data/lib/review/configure.rb +15 -14
  23. data/lib/review/epubmaker.rb +15 -4
  24. data/lib/review/htmlbuilder.rb +56 -24
  25. data/lib/review/idgxmlbuilder.rb +17 -7
  26. data/lib/review/latexbuilder.rb +113 -38
  27. data/lib/review/markdownbuilder.rb +12 -5
  28. data/lib/review/md2inaobuilder.rb +3 -1
  29. data/lib/review/pdfmaker.rb +23 -9
  30. data/lib/review/plaintextbuilder.rb +683 -0
  31. data/lib/review/rstbuilder.rb +30 -10
  32. data/lib/review/textmaker.rb +158 -0
  33. data/lib/review/textutils.rb +10 -1
  34. data/lib/review/topbuilder.rb +32 -417
  35. data/lib/review/version.rb +1 -1
  36. data/lib/review/webmaker.rb +29 -8
  37. data/review.gemspec +3 -4
  38. data/templates/html/layout-xhtml1.html.erb +0 -2
  39. data/templates/latex/layout.tex.erb +6 -4
  40. data/templates/web/html/layout-xhtml1.html.erb +0 -2
  41. data/test/book_test_helper.rb +1 -0
  42. data/test/run_test.rb +1 -1
  43. data/test/sample-book/src/Rakefile +19 -3
  44. data/test/syntax-book/Rakefile +19 -3
  45. data/test/test_catalog.rb +45 -0
  46. data/test/test_compiler.rb +8 -2
  47. data/test/test_htmlbuilder.rb +22 -0
  48. data/test/test_idgxmlbuilder.rb +22 -0
  49. data/test/test_index.rb +31 -0
  50. data/test/test_latexbuilder.rb +48 -16
  51. data/test/test_plaintextbuilder.rb +390 -0
  52. data/test/test_textutils.rb +2 -0
  53. data/test/test_topbuilder.rb +23 -1
  54. metadata +13 -7
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2008-2017 Minero Aoki, Kenshi Muto
1
+ # Copyright (c) 2008-2018 Minero Aoki, Kenshi Muto
2
2
  # 2002-2007 Minero Aoki
3
3
  #
4
4
  # This program is free software.
@@ -261,6 +261,8 @@ module ReVIEW
261
261
  else
262
262
  I18n.t('column', compile_inline(chapter.column(id).caption))
263
263
  end
264
+ rescue KeyError
265
+ error "unknown column: #{id}"
264
266
  end
265
267
 
266
268
  def inline_list(id)
@@ -270,6 +272,8 @@ module ReVIEW
270
272
  else
271
273
  "<span type='list'>#{I18n.t('list')}#{I18n.t('format_number', [get_chap(chapter), chapter.list(id).number])}</span>"
272
274
  end
275
+ rescue KeyError
276
+ error "unknown list: #{id}"
273
277
  end
274
278
 
275
279
  def list_header(id, caption, _lang)
@@ -373,6 +377,8 @@ module ReVIEW
373
377
  else
374
378
  "<span type='table'>#{I18n.t('table')}#{I18n.t('format_number', [get_chap(chapter), chapter.table(id).number])}</span>"
375
379
  end
380
+ rescue KeyError
381
+ error "unknown table: #{id}"
376
382
  end
377
383
 
378
384
  def inline_img(id)
@@ -382,6 +388,8 @@ module ReVIEW
382
388
  else
383
389
  "<span type='image'>#{I18n.t('image')}#{I18n.t('format_number', [get_chap(chapter), chapter.image(id).number])}</span>"
384
390
  end
391
+ rescue KeyError
392
+ error "unknown image: #{id}"
385
393
  end
386
394
 
387
395
  def inline_imgref(id)
@@ -600,6 +608,8 @@ module ReVIEW
600
608
 
601
609
  def inline_fn(id)
602
610
  %Q(<footnote>#{compile_inline(@chapter.footnote(id).content.strip)}</footnote>)
611
+ rescue KeyError
612
+ error "unknown footnote: #{id}"
603
613
  end
604
614
 
605
615
  def compile_ruby(base, ruby)
@@ -747,9 +757,8 @@ module ReVIEW
747
757
  @noindent = true
748
758
  end
749
759
 
750
- def linebreak
751
- # FIXME: pが閉じちゃってるので一度戻らないといけないが、難しい…。
752
- puts '<br />'
760
+ def blankline
761
+ puts '<p/>'
753
762
  end
754
763
 
755
764
  def pagebreak
@@ -1064,7 +1073,6 @@ module ReVIEW
1064
1073
  end
1065
1074
  rescue KeyError
1066
1075
  error "unknown chapter: #{id}"
1067
- nofunc_text("[UnknownChapter:#{id}]")
1068
1076
  end
1069
1077
 
1070
1078
  def inline_chap(id)
@@ -1075,7 +1083,6 @@ module ReVIEW
1075
1083
  end
1076
1084
  rescue KeyError
1077
1085
  error "unknown chapter: #{id}"
1078
- nofunc_text("[UnknownChapter:#{id}]")
1079
1086
  end
1080
1087
 
1081
1088
  def inline_title(id)
@@ -1087,7 +1094,6 @@ module ReVIEW
1087
1094
  end
1088
1095
  rescue KeyError
1089
1096
  error "unknown chapter: #{id}"
1090
- nofunc_text("[UnknownChapter:#{id}]")
1091
1097
  end
1092
1098
 
1093
1099
  def source_header(caption)
@@ -1118,6 +1124,8 @@ module ReVIEW
1118
1124
 
1119
1125
  def inline_bib(id)
1120
1126
  %Q(<span type='bibref' idref='#{id}'>[#{@chapter.bibpaper(id).number}]</span>)
1127
+ rescue KeyError
1128
+ error "unknown bib: #{id}"
1121
1129
  end
1122
1130
 
1123
1131
  def inline_hd_chap(chap, id)
@@ -1128,6 +1136,8 @@ module ReVIEW
1128
1136
  end
1129
1137
  end
1130
1138
  I18n.t('chapter_quote', compile_inline(chap.headline(id).caption))
1139
+ rescue KeyError
1140
+ error "unknown headline: #{id}"
1131
1141
  end
1132
1142
 
1133
1143
  def inline_recipe(id)
@@ -1,6 +1,6 @@
1
1
  # Copyright (c) 2002-2007 Minero Aoki
2
2
  # 2008-2009 Minero Aoki, Kenshi Muto
3
- # 2010-2017 Minero Aoki, Kenshi Muto, TAKAHASHI Masayoshi
3
+ # 2010-2018 Minero Aoki, Kenshi Muto, TAKAHASHI Masayoshi
4
4
  #
5
5
  # This program is free software.
6
6
  # You can distribute or modify this program under the terms of
@@ -17,7 +17,9 @@ module ReVIEW
17
17
  include LaTeXUtils
18
18
  include TextUtils
19
19
 
20
- %i[dtp hd_chap].each { |e| Compiler.definline(e) }
20
+ %i[dtp hd_chap].each do |e|
21
+ Compiler.definline(e)
22
+ end
21
23
 
22
24
  Compiler.defsingle(:latextsize, 1)
23
25
 
@@ -34,6 +36,7 @@ module ReVIEW
34
36
  @ol_num = nil
35
37
  @first_line_num = nil
36
38
  @sec_counter = SecCounter.new(5, @chapter)
39
+ @foottext = {}
37
40
  setup_index
38
41
  initialize_metachars(@book.config['texcommand'])
39
42
  end
@@ -44,7 +47,9 @@ module ReVIEW
44
47
  @index_mecab = nil
45
48
  return true unless @book.config['pdfmaker']['makeindex']
46
49
 
47
- @index_db = load_idxdb(@book.config['pdfmaker']['makeindex_dic']) if @book.config['pdfmaker']['makeindex_dic']
50
+ if @book.config['pdfmaker']['makeindex_dic']
51
+ @index_db = load_idxdb(@book.config['pdfmaker']['makeindex_dic'])
52
+ end
48
53
  return true unless @book.config['pdfmaker']['makeindex_mecab']
49
54
  begin
50
55
  require 'MeCab'
@@ -99,15 +104,20 @@ module ReVIEW
99
104
  def headline(level, label, caption)
100
105
  _, anchor = headline_prefix(level)
101
106
  headline_name = HEADLINE[level]
102
- headline_name = 'part' if @chapter.is_a? ReVIEW::Book::Part
103
- prefix = if level > @book.config['secnolevel'] || (@chapter.number.to_s.empty? && level > 1)
104
- '*'
105
- else
106
- ''
107
- end
107
+ if @chapter.is_a? ReVIEW::Book::Part
108
+ headline_name = 'part'
109
+ end
110
+ prefix = ''
111
+ if level > @book.config['secnolevel'] || (@chapter.number.to_s.empty? && level > 1)
112
+ prefix = '*'
113
+ end
108
114
  blank unless @output.pos == 0
115
+ @doc_status[:caption] = true
109
116
  puts macro(headline_name + prefix, compile_inline(caption))
110
- puts "\\addcontentsline{toc}{#{headline_name}}{#{compile_inline(caption)}}" if prefix == '*' && level <= @book.config['toclevel'].to_i
117
+ @doc_status[:caption] = nil
118
+ if prefix == '*' && level <= @book.config['toclevel'].to_i
119
+ puts "\\addcontentsline{toc}{#{headline_name}}{#{compile_inline(caption)}}"
120
+ end
111
121
  if level == 1
112
122
  puts macro('label', chapter_label)
113
123
  else
@@ -120,7 +130,9 @@ module ReVIEW
120
130
 
121
131
  def nonum_begin(level, _label, caption)
122
132
  blank unless @output.pos == 0
133
+ @doc_status[:caption] = true
123
134
  puts macro(HEADLINE[level] + '*', compile_inline(caption))
135
+ @doc_status[:caption] = nil
124
136
  puts macro('addcontentsline', 'toc', HEADLINE[level], compile_inline(caption))
125
137
  end
126
138
 
@@ -129,7 +141,9 @@ module ReVIEW
129
141
 
130
142
  def notoc_begin(level, _label, caption)
131
143
  blank unless @output.pos == 0
144
+ @doc_status[:caption] = true
132
145
  puts macro(HEADLINE[level] + '*', compile_inline(caption))
146
+ @doc_status[:caption] = nil
133
147
  end
134
148
 
135
149
  def notoc_end(level)
@@ -147,25 +161,33 @@ module ReVIEW
147
161
 
148
162
  def column_begin(level, label, caption)
149
163
  blank
164
+ @doc_status[:column] = true
150
165
  puts "\\begin{reviewcolumn}\n"
151
166
  if label
152
167
  puts "\\hypertarget{#{column_label(label)}}{}"
153
168
  else
154
169
  puts "\\hypertarget{#{column_label(caption)}}{}"
155
170
  end
171
+ @doc_status[:caption] = true
156
172
  puts macro('reviewcolumnhead', nil, compile_inline(caption))
157
- puts "\\addcontentsline{toc}{#{HEADLINE[level]}}{#{compile_inline(caption)}}" if level <= @book.config['toclevel'].to_i
173
+ @doc_status[:caption] = nil
174
+ if level <= @book.config['toclevel'].to_i
175
+ puts "\\addcontentsline{toc}{#{HEADLINE[level]}}{#{compile_inline(caption)}}"
176
+ end
158
177
  end
159
178
 
160
179
  def column_end(_level)
161
180
  puts "\\end{reviewcolumn}\n"
162
181
  blank
182
+ @doc_status[:column] = nil
163
183
  end
164
184
 
165
185
  def captionblock(_type, lines, caption)
166
186
  puts "\\begin{reviewminicolumn}\n"
167
- puts "\\reviewminicolumntitle{#{compile_inline(caption)}}\n" if caption
187
+ @doc_status[:caption] = true
188
+ puts "\\reviewminicolumntitle{#{compile_inline(caption)}}\n" if caption.present?
168
189
 
190
+ @doc_status[:caption] = nil
169
191
  blocked_lines = split_paragraph(lines)
170
192
  puts blocked_lines.join("\n\n")
171
193
 
@@ -174,9 +196,11 @@ module ReVIEW
174
196
 
175
197
  def box(lines, caption = nil)
176
198
  blank
177
- puts macro('reviewboxcaption', compile_inline(caption)) if caption
199
+ puts macro('reviewboxcaption', compile_inline(caption)) if caption.present?
178
200
  puts '\begin{reviewbox}'
179
- lines.each { |line| puts detab(line) }
201
+ lines.each do |line|
202
+ puts detab(line)
203
+ end
180
204
  puts '\end{reviewbox}'
181
205
  blank
182
206
  end
@@ -238,7 +262,9 @@ module ReVIEW
238
262
 
239
263
  def paragraph(lines)
240
264
  blank
241
- lines.each { |line| puts line }
265
+ lines.each do |line|
266
+ puts line
267
+ end
242
268
  blank
243
269
  end
244
270
 
@@ -305,7 +331,8 @@ module ReVIEW
305
331
  end
306
332
 
307
333
  def common_code_block(id, lines, command, caption, _lang)
308
- if caption
334
+ @doc_status[:caption] = true
335
+ if caption.present?
309
336
  if command =~ /emlist/ || command =~ /cmd/ || command =~ /source/
310
337
  puts macro(command + 'caption', compile_inline(caption))
311
338
  else
@@ -320,8 +347,11 @@ module ReVIEW
320
347
  end
321
348
  end
322
349
  end
350
+ @doc_status[:caption] = nil
323
351
  body = ''
324
- lines.each_with_index { |line, idx| body.concat(yield(line, idx)) }
352
+ lines.each_with_index do |line, idx|
353
+ body.concat(yield(line, idx))
354
+ end
325
355
  puts macro('begin', command)
326
356
  print body
327
357
  puts macro('end', command)
@@ -329,7 +359,9 @@ module ReVIEW
329
359
  end
330
360
 
331
361
  def common_code_block_lst(_id, lines, command, title, caption, lang, first_line_num: 1)
332
- print '\vspace{-1.5em}' if title == 'title' && caption.blank?
362
+ if title == 'title' && caption.blank?
363
+ print '\vspace{-1.5em}'
364
+ end
333
365
  body = lines.inject('') { |i, j| i + detab(unescape_latex(j)) + "\n" }
334
366
  args = make_code_block_args(title, caption, lang, first_line_num: first_line_num)
335
367
  puts %Q(\\begin{#{command}}[#{args}])
@@ -350,7 +382,9 @@ module ReVIEW
350
382
  end
351
383
  lexer = lang if lang.present?
352
384
  args = %Q(#{title}={#{caption_str}},language={#{lexer}})
353
- args += ",firstnumber=#{first_line_num}" if first_line_num != 1
385
+ if first_line_num != 1
386
+ args += ",firstnumber=#{first_line_num}"
387
+ end
354
388
  args
355
389
  end
356
390
 
@@ -379,13 +413,15 @@ module ReVIEW
379
413
  def image_image(id, caption, metric)
380
414
  metrics = parse_metric('latex', metric)
381
415
  # image is always bound here
382
- puts '\begin{reviewimage}'
416
+ puts "\\begin{reviewimage}%%#{id}"
383
417
  if metrics.present?
384
418
  puts "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}"
385
419
  else
386
420
  puts "\\includegraphics[width=\\maxwidth]{#{@chapter.image(id).path}}"
387
421
  end
422
+ @doc_status[:caption] = true
388
423
  puts macro('caption', compile_inline(caption)) if caption.present?
424
+ @doc_status[:caption] = nil
389
425
  puts macro('label', image_label(id))
390
426
  puts '\end{reviewimage}'
391
427
  end
@@ -395,9 +431,13 @@ module ReVIEW
395
431
  puts '\begin{reviewdummyimage}'
396
432
  # path = @chapter.image(id).path
397
433
  puts "--[[path = #{id} (#{existence(id)})]]--"
398
- lines.each { |line| puts detab(line.rstrip) }
434
+ lines.each do |line|
435
+ puts detab(line.rstrip)
436
+ end
399
437
  puts macro('label', image_label(id))
400
- puts compile_inline(caption)
438
+ @doc_status[:caption] = true
439
+ puts macro('caption', compile_inline(caption)) if caption.present?
440
+ @doc_status[:caption] = nil
401
441
  puts '\end{reviewdummyimage}'
402
442
  end
403
443
 
@@ -444,7 +484,7 @@ module ReVIEW
444
484
  metrics = parse_metric('latex', metric)
445
485
 
446
486
  if @chapter.image(id).path
447
- puts '\begin{reviewimage}'
487
+ puts "\\begin{reviewimage}%%#{id}"
448
488
  if metrics.present?
449
489
  puts "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}"
450
490
  else
@@ -454,10 +494,14 @@ module ReVIEW
454
494
  warn "image not bound: #{id}"
455
495
  puts '\begin{reviewdummyimage}'
456
496
  puts "--[[path = #{id} (#{existence(id)})]]--"
457
- lines.each { |line| puts detab(line.rstrip) }
497
+ lines.each do |line|
498
+ puts detab(line.rstrip)
499
+ end
458
500
  end
459
501
 
502
+ @doc_status[:caption] = true
460
503
  puts macro('reviewindepimagecaption', %Q(#{I18n.t('numberless_image')}#{I18n.t('caption_prefix')}#{compile_inline(caption)})) if caption.present?
504
+ @doc_status[:caption] = nil
461
505
 
462
506
  if @chapter.image(id).path
463
507
  puts '\end{reviewimage}'
@@ -483,15 +527,19 @@ module ReVIEW
483
527
  rows = adjust_n_cols(rows)
484
528
 
485
529
  begin
486
- table_header id, caption unless caption.nil?
530
+ table_header id, caption if caption.present?
487
531
  rescue KeyError
488
532
  error "no such table: #{id}"
489
533
  end
490
534
  return if rows.empty?
491
535
  table_begin rows.first.size
492
536
  if sepidx
493
- sepidx.times { tr(rows.shift.map { |s| th(s) }) }
494
- rows.each { |cols| tr(cols.map { |s| td(s) }) }
537
+ sepidx.times do
538
+ tr(rows.shift.map { |s| th(s) })
539
+ end
540
+ rows.each do |cols|
541
+ tr(cols.map { |s| td(s) })
542
+ end
495
543
  else
496
544
  rows.each do |cols|
497
545
  h, *cs = *cols
@@ -505,14 +553,18 @@ module ReVIEW
505
553
  if id.nil?
506
554
  if caption.present?
507
555
  @table_caption = true
508
- puts '\begin{table}[h]'
556
+ @doc_status[:caption] = true
557
+ puts "\\begin{table}[h]%%#{id}"
509
558
  puts macro('reviewtablecaption*', compile_inline(caption))
559
+ @doc_status[:caption] = nil
510
560
  end
511
561
  else
512
562
  if caption.present?
513
563
  @table_caption = true
514
- puts '\begin{table}[h]'
564
+ @doc_status[:caption] = true
565
+ puts "\\begin{table}[h]%%#{id}"
515
566
  puts macro('reviewtablecaption', compile_inline(caption))
567
+ @doc_status[:caption] = nil
516
568
  end
517
569
  puts macro('label', table_label(id))
518
570
  end
@@ -542,7 +594,7 @@ module ReVIEW
542
594
 
543
595
  def th(s)
544
596
  ## use shortstack for @<br>
545
- if /\\\\/i =~ s
597
+ if /\\\\/ =~ s
546
598
  macro('reviewth', macro('shortstack[l]', s))
547
599
  else
548
600
  macro('reviewth', s)
@@ -584,8 +636,10 @@ module ReVIEW
584
636
  begin
585
637
  if caption.present?
586
638
  @table_caption = true
587
- puts '\begin{table}[h]'
639
+ @doc_status[:caption] = true
640
+ puts "\\begin{table}[h]%%#{id}"
588
641
  puts macro('reviewimgtablecaption', compile_inline(caption))
642
+ @doc_status[:caption] = nil
589
643
  end
590
644
  puts macro('label', table_label(id))
591
645
  rescue ReVIEW::KeyError
@@ -601,7 +655,7 @@ module ReVIEW
601
655
  def imgtable_image(id, _caption, metric)
602
656
  metrics = parse_metric('latex', metric)
603
657
  # image is always bound here
604
- puts '\begin{reviewimage}'
658
+ puts "\\begin{reviewimage}%%#{id}"
605
659
  if metrics.present?
606
660
  puts "\\includegraphics[#{metrics}]{#{@chapter.image(id).path}}"
607
661
  else
@@ -627,7 +681,9 @@ module ReVIEW
627
681
  def texequation(lines)
628
682
  blank
629
683
  puts macro('begin', 'equation*')
630
- lines.each { |line| puts unescape_latex(line) }
684
+ lines.each do |line|
685
+ puts unescape_latex(line)
686
+ end
631
687
  puts macro('end', 'equation*')
632
688
  blank
633
689
  end
@@ -644,12 +700,16 @@ module ReVIEW
644
700
 
645
701
  def direct(lines, fmt)
646
702
  return unless fmt == 'latex'
647
- lines.each { |line| puts line }
703
+ lines.each do |line|
704
+ puts line
705
+ end
648
706
  end
649
707
 
650
708
  def comment(lines, comment = nil)
651
709
  lines ||= []
652
- lines.unshift comment unless comment.blank?
710
+ unless comment.blank?
711
+ lines.unshift comment
712
+ end
653
713
  return true unless @book.config['draft']
654
714
  str = lines.join('\par ')
655
715
  puts macro('pdfcomment', escape(str))
@@ -667,8 +727,8 @@ module ReVIEW
667
727
  puts '\pagebreak'
668
728
  end
669
729
 
670
- def linebreak
671
- puts '\\\\'
730
+ def blankline
731
+ puts '\vspace*{\baselineskip}'
672
732
  end
673
733
 
674
734
  def noindent
@@ -722,6 +782,8 @@ module ReVIEW
722
782
  else
723
783
  macro('reviewlistref', I18n.t('format_number', [get_chap(chapter), chapter.list(id).number]))
724
784
  end
785
+ rescue KeyError
786
+ error "unknown list: #{id}"
725
787
  end
726
788
 
727
789
  def inline_table(id)
@@ -731,6 +793,8 @@ module ReVIEW
731
793
  else
732
794
  macro('reviewtableref', I18n.t('format_number', [get_chap(chapter), chapter.table(id).number]), table_label(id, chapter))
733
795
  end
796
+ rescue KeyError
797
+ error "unknown table: #{id}"
734
798
  end
735
799
 
736
800
  def inline_img(id)
@@ -740,18 +804,27 @@ module ReVIEW
740
804
  else
741
805
  macro('reviewimageref', I18n.t('format_number', [get_chap(chapter), chapter.image(id).number]), image_label(id, chapter))
742
806
  end
807
+ rescue KeyError
808
+ error "unknown image: #{id}"
743
809
  end
744
810
 
745
811
  def footnote(id, content)
746
- puts macro("footnotetext[#{@chapter.footnote(id).number}]", compile_inline(content.strip)) if @book.config['footnotetext']
812
+ if @book.config['footnotetext'] || @foottext[id]
813
+ puts macro("footnotetext[#{@chapter.footnote(id).number}]", compile_inline(content.strip))
814
+ end
747
815
  end
748
816
 
749
817
  def inline_fn(id)
750
818
  if @book.config['footnotetext']
751
819
  macro("footnotemark[#{@chapter.footnote(id).number}]", '')
820
+ elsif @doc_status[:caption] || @doc_status[:table] || @doc_status[:column]
821
+ @foottext[id] = @chapter.footnote(id).number
822
+ macro('protect\\footnotemark', '')
752
823
  else
753
824
  macro('footnote', compile_inline(@chapter.footnote(id).content.strip))
754
825
  end
826
+ rescue KeyError
827
+ error "unknown footnote: #{id}"
755
828
  end
756
829
 
757
830
  BOUTEN = '・'.freeze
@@ -852,6 +925,8 @@ module ReVIEW
852
925
  macro('reviewcolumnref',
853
926
  I18n.t('chapter_quote', compile_inline(chapter.column(id).caption)),
854
927
  column_label(id, chapter))
928
+ rescue KeyError
929
+ error "unknown column: #{id}"
855
930
  end
856
931
 
857
932
  def inline_raw(str)