review 2.4.0 → 2.5.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 (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)