review 4.0.0 → 4.1.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby-win.yml +39 -0
  3. data/.github/workflows/ruby.yml +27 -0
  4. data/.rubocop.yml +10 -7
  5. data/Dockerfile +21 -5
  6. data/NEWS.ja.md +101 -1
  7. data/NEWS.md +102 -2
  8. data/README.md +10 -7
  9. data/appveyor.yml +0 -20
  10. data/bin/review-compile +1 -1
  11. data/bin/review-validate +1 -1
  12. data/doc/config.yml.sample +12 -1
  13. data/doc/config.yml.sample-simple +1 -0
  14. data/doc/format.ja.md +16 -4
  15. data/doc/format.md +2 -1
  16. data/doc/quickstart.ja.md +40 -23
  17. data/doc/quickstart.md +33 -15
  18. data/lib/review/book/base.rb +8 -7
  19. data/lib/review/book/index.rb +50 -56
  20. data/lib/review/book/index/item.rb +1 -1
  21. data/lib/review/builder.rb +26 -4
  22. data/lib/review/compiler.rb +6 -3
  23. data/lib/review/configure.rb +5 -3
  24. data/lib/review/epubmaker.rb +15 -2
  25. data/lib/review/extentions/string.rb +0 -4
  26. data/lib/review/htmlbuilder.rb +4 -15
  27. data/lib/review/idgxmlbuilder.rb +10 -19
  28. data/lib/review/idgxmlmaker.rb +10 -3
  29. data/lib/review/init.rb +8 -1
  30. data/lib/review/latexbuilder.rb +13 -6
  31. data/lib/review/pdfmaker.rb +24 -9
  32. data/lib/review/rstbuilder.rb +2 -2
  33. data/lib/review/textmaker.rb +7 -1
  34. data/lib/review/tocparser.rb +6 -2
  35. data/lib/review/version.rb +1 -1
  36. data/lib/review/webmaker.rb +8 -1
  37. data/review.gemspec +2 -2
  38. data/samples/sample-book/src/.gitignore +1 -0
  39. data/samples/sample-book/src/config-ebook.yml +4 -0
  40. data/samples/sample-book/src/config-jlreq-ebook.yml +4 -0
  41. data/samples/sample-book/src/config.yml +1 -1
  42. data/samples/sample-book/src/lib/tasks/review.rake +10 -6
  43. data/samples/syntax-book/ch01.re +4 -2
  44. data/samples/syntax-book/ch02.re +8 -16
  45. data/samples/syntax-book/config-jlreq-lualatex.yml +4 -0
  46. data/samples/syntax-book/config-print.yml +3 -0
  47. data/samples/syntax-book/config.yml +1 -1
  48. data/samples/syntax-book/lib/tasks/review.rake +23 -8
  49. data/templates/latex/config.erb +6 -6
  50. data/templates/latex/review-jlreq/review-base.sty +14 -4
  51. data/templates/latex/review-jlreq/review-jlreq.cls +10 -1
  52. data/templates/latex/review-jlreq/review-style.sty +1 -1
  53. data/templates/latex/review-jsbook/review-base.sty +10 -0
  54. data/templates/latex/review-jsbook/review-jsbook.cls +1 -1
  55. data/templates/latex/review-jsbook/review-style.sty +1 -1
  56. data/test/assets/test_template.tex +6 -6
  57. data/test/assets/test_template_backmatter.tex +6 -6
  58. data/test/test_book.rb +8 -0
  59. data/test/test_book_chapter.rb +4 -2
  60. data/test/test_catalog.rb +1 -0
  61. data/test/test_epubmaker_cmd.rb +12 -5
  62. data/test/test_helper.rb +11 -6
  63. data/test/test_htmlbuilder.rb +80 -8
  64. data/test/test_idgxmlbuilder.rb +57 -2
  65. data/test/test_idgxmlmaker_cmd.rb +46 -0
  66. data/test/test_image_finder.rb +52 -70
  67. data/test/test_index.rb +12 -12
  68. data/test/test_latexbuilder.rb +171 -8
  69. data/test/test_latexbuilder_v2.rb +9 -7
  70. data/test/test_pdfmaker_cmd.rb +99 -5
  71. data/test/test_textmaker_cmd.rb +54 -0
  72. data/test/test_topbuilder.rb +59 -0
  73. data/vendor/jsclasses/LICENSE +1 -1
  74. data/vendor/jsclasses/jis/jsarticle.cls +53 -14
  75. data/vendor/jsclasses/jis/jsbook.cls +53 -14
  76. data/vendor/jsclasses/jis/jsclasses.dtx +84 -25
  77. data/vendor/jsclasses/jis/jslogo.dtx +4 -4
  78. data/vendor/jsclasses/jis/jslogo.sty +3 -3
  79. data/vendor/jsclasses/jis/jspf.cls +52 -13
  80. data/vendor/jsclasses/jis/jsreport.cls +53 -14
  81. data/vendor/jsclasses/jis/kiyou.cls +53 -14
  82. data/vendor/jsclasses/jis/okumacro.dtx +4 -5
  83. data/vendor/jsclasses/jis/okumacro.sty +3 -4
  84. data/vendor/jsclasses/jsarticle.cls +53 -14
  85. data/vendor/jsclasses/jsbook.cls +53 -14
  86. data/vendor/jsclasses/jsclasses.dtx +84 -25
  87. data/vendor/jsclasses/jsclasses.pdf +0 -0
  88. data/vendor/jsclasses/jslogo.dtx +4 -4
  89. data/vendor/jsclasses/jslogo.pdf +0 -0
  90. data/vendor/jsclasses/jslogo.sty +3 -3
  91. data/vendor/jsclasses/jspf.cls +52 -13
  92. data/vendor/jsclasses/jsreport.cls +53 -14
  93. data/vendor/jsclasses/kiyou.cls +53 -14
  94. data/vendor/jsclasses/okumacro.dtx +4 -5
  95. data/vendor/jsclasses/okumacro.pdf +0 -0
  96. data/vendor/jsclasses/okumacro.sty +3 -4
  97. metadata +15 -6
  98. data/samples/syntax-book/review-ext.rb +0 -14
@@ -18,18 +18,18 @@ module ReVIEW
18
18
  module Book
19
19
  class Index
20
20
  def self.parse(src, *args)
21
- items = []
21
+ index = self.new(*args)
22
22
  seq = 1
23
23
  src.grep(%r{\A//#{item_type}}) do |line|
24
24
  if id = line.slice(/\[(.*?)\]/, 1)
25
- items.push(ReVIEW::Book::Index::Item.new(id, seq))
25
+ index.add_item(ReVIEW::Book::Index::Item.new(id, seq))
26
26
  seq += 1
27
27
  if id.empty?
28
28
  ReVIEW.logger.warn "warning: no ID of #{item_type} in #{line}"
29
29
  end
30
30
  end
31
31
  end
32
- new(items, *args)
32
+ index
33
33
  end
34
34
 
35
35
  include Enumerable
@@ -38,19 +38,22 @@ module ReVIEW
38
38
  self.class.item_type
39
39
  end
40
40
 
41
- def initialize(items)
42
- @items = items
41
+ def initialize
43
42
  @index = {}
44
43
  @logger = ReVIEW.logger
45
- items.each do |item|
46
- if @index[item.id]
47
- @logger.warn "warning: duplicate ID: #{item.id} (#{item})"
48
- end
49
- @index[item.id] = item
50
- end
51
44
  @image_finder = nil
52
45
  end
53
46
 
47
+ def add_item(item)
48
+ if @index[item.id]
49
+ @logger.warn "warning: duplicate ID: #{item.id} (#{item})"
50
+ end
51
+ @index[item.id] = item
52
+ if item.class != ReVIEW::Book::Chapter
53
+ item.index = self
54
+ end
55
+ end
56
+
54
57
  def [](id)
55
58
  @index.fetch(id)
56
59
  rescue
@@ -60,7 +63,7 @@ module ReVIEW
60
63
  raise KeyError, "key '#{id}' is ambiguous for #{self.class}"
61
64
  end
62
65
 
63
- @items.each do |item|
66
+ @index.values.each do |item|
64
67
  if item.id.split('|').include?(id)
65
68
  return item
66
69
  end
@@ -73,7 +76,7 @@ module ReVIEW
73
76
  end
74
77
 
75
78
  def each(&block)
76
- @items.each(&block)
79
+ @index.values.each(&block)
77
80
  end
78
81
 
79
82
  def key?(id)
@@ -88,8 +91,9 @@ module ReVIEW
88
91
  end
89
92
 
90
93
  def number(id)
91
- chapter = @index.fetch(id)
94
+ chapter_item = @index.fetch(id)
92
95
  begin
96
+ chapter = chapter_item.content
93
97
  chapter.format_number
94
98
  rescue # part
95
99
  I18n.t('part', chapter.number)
@@ -97,9 +101,9 @@ module ReVIEW
97
101
  end
98
102
 
99
103
  def title(id)
100
- @index.fetch(id).title
104
+ @index.fetch(id).content.title
101
105
  rescue # non-file part
102
- @index.fetch(id).name
106
+ @index.fetch(id).content.name
103
107
  end
104
108
 
105
109
  def display_string(id)
@@ -131,32 +135,32 @@ module ReVIEW
131
135
 
132
136
  class FootnoteIndex < Index
133
137
  def self.parse(src)
134
- items = []
138
+ index = self.new
135
139
  seq = 1
136
140
  src.grep(%r{\A//footnote}) do |line|
137
141
  if m = /\[(.*?)\]\[(.*)\]/.match(line)
138
142
  m1 = m[1].gsub(/\\(\])/) { $1 }
139
143
  m2 = m[2].gsub(/\\(\])/) { $1 }
140
- items.push(Item.new(m1, seq, m2))
144
+ index.add_item(Item.new(m1, seq, m2))
141
145
  end
142
146
  seq += 1
143
147
  end
144
- new(items)
148
+ index
145
149
  end
146
150
  end
147
151
 
148
152
  class ImageIndex < Index
149
153
  def self.parse(src, *args)
150
- items = []
154
+ index = self.new(*args)
151
155
  seq = 1
152
156
  src.grep(%r{\A//#{item_type}}) do |line|
153
157
  # ex. ["//image", "id", "", "caption"]
154
158
  elements = line.split(/\[(.*?)\]/)
155
159
  if elements[1].present?
156
- if line =~ %r{\A//imgtable}
157
- items.push(ReVIEW::Book::Index::Item.new(elements[1], 0, elements[3]))
160
+ if line.start_with?('//imgtable')
161
+ index.add_item(ReVIEW::Book::Index::Item.new(elements[1], 0, elements[3]))
158
162
  else ## %r<\A//(image|graph)>
159
- items.push(ReVIEW::Book::Index::Item.new(elements[1], seq, elements[3]))
163
+ index.add_item(ReVIEW::Book::Index::Item.new(elements[1], seq, elements[3]))
160
164
  seq += 1
161
165
  end
162
166
  if elements[1] == ''
@@ -164,7 +168,7 @@ module ReVIEW
164
168
  end
165
169
  end
166
170
  end
167
- new(items, *args)
171
+ index
168
172
  end
169
173
 
170
174
  def self.item_type
@@ -173,14 +177,12 @@ module ReVIEW
173
177
 
174
178
  attr_reader :image_finder
175
179
 
176
- def initialize(items, chapid, basedir, types, builder)
177
- super(items)
178
- items.each do |item|
179
- item.index = self
180
- end
180
+ def initialize(chapid, basedir, types, builder)
181
+ super()
181
182
  @chapid = chapid
182
183
  @basedir = basedir
183
184
  @types = types
185
+ @logger = ReVIEW.logger
184
186
 
185
187
  @image_finder = ReVIEW::Book::ImageFinder.new(basedir, chapid, builder, types)
186
188
  end
@@ -191,44 +193,42 @@ module ReVIEW
191
193
  end
192
194
 
193
195
  class IconIndex < ImageIndex
194
- def initialize(items, chapid, basedir, types, builder)
195
- @items = items
196
+ def initialize(chapid, basedir, types, builder)
196
197
  @index = {}
197
- items.each { |item| @index[item.id] = item }
198
- items.each { |item| item.index = self }
199
198
  @chapid = chapid
200
199
  @basedir = basedir
201
200
  @types = types
201
+ @logger = ReVIEW.logger
202
202
 
203
203
  @image_finder = ImageFinder.new(basedir, chapid, builder, types)
204
204
  end
205
205
 
206
206
  def self.parse(src, *args)
207
- items = []
207
+ index = self.new(*args)
208
208
  seq = 1
209
209
  src.grep(/@<icon>/) do |line|
210
210
  line.gsub(/@<icon>\{(.+?)\}/) do
211
- items.push(ReVIEW::Book::Index::Item.new($1, seq))
211
+ index.add_item(ReVIEW::Book::Index::Item.new($1, seq))
212
212
  seq += 1
213
213
  end
214
214
  end
215
- new(items, *args)
215
+ index
216
216
  end
217
217
  end
218
218
 
219
219
  class BibpaperIndex < Index
220
220
  def self.parse(src)
221
- items = []
221
+ index = self.new
222
222
  seq = 1
223
223
  src.grep(%r{\A//bibpaper}) do |line|
224
224
  if m = /\[(.*?)\]\[(.*)\]/.match(line)
225
225
  m1 = m[1].gsub(/\\(.)/) { $1 }
226
226
  m2 = m[2].gsub(/\\(.)/) { $1 }
227
- items.push(Item.new(m1, seq, m2))
227
+ index.add_item(Item.new(m1, seq, m2))
228
228
  end
229
229
  seq += 1
230
230
  end
231
- new(items)
231
+ index
232
232
  end
233
233
  end
234
234
 
@@ -254,10 +254,9 @@ module ReVIEW
254
254
 
255
255
  class HeadlineIndex < Index
256
256
  HEADLINE_PATTERN = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/
257
- attr_reader :items
258
257
 
259
258
  def self.parse(src, chap)
260
- items = []
259
+ headline_index = self.new(chap)
261
260
  indexs = []
262
261
  headlines = []
263
262
  inside_column = false
@@ -267,7 +266,7 @@ module ReVIEW
267
266
  if line =~ %r{\A//[a-z]+.*\{\Z}
268
267
  inside_block = true
269
268
  next
270
- elsif line =~ %r{\A//\}}
269
+ elsif line.start_with?('//}')
271
270
  inside_block = nil
272
271
  next
273
272
  elsif inside_block
@@ -311,27 +310,22 @@ module ReVIEW
311
310
 
312
311
  if %w[nonum notoc nodisp].include?(m[2])
313
312
  headlines[index] = m[3].present? ? m[3].strip : m[4].strip
314
- items.push(Item.new(headlines.join('|'), nil, m[4].strip))
313
+ item_id = headlines.join('|')
314
+ headline_index.add_item(Item.new(item_id, nil, m[4].strip))
315
315
  else
316
316
  indexs[index] += 1
317
317
  headlines[index] = m[3].present? ? m[3].strip : m[4].strip
318
- items.push(Item.new(headlines.join('|'), indexs.dup, m[4].strip))
318
+ item_id = headlines.join('|')
319
+ headline_index.add_item(Item.new(item_id, indexs.dup, m[4].strip))
319
320
  end
320
321
  end
321
- new(items, chap)
322
+ headline_index
322
323
  end
323
324
 
324
- def initialize(items, chap)
325
- @items = items
325
+ def initialize(chap)
326
326
  @chap = chap
327
327
  @index = {}
328
328
  @logger = ReVIEW.logger
329
- items.each do |item|
330
- if @index[item.id]
331
- @logger.warn "warning: duplicate ID: #{item.id}"
332
- end
333
- @index[item.id] = item
334
- end
335
329
  end
336
330
 
337
331
  def number(id)
@@ -352,7 +346,7 @@ module ReVIEW
352
346
  COLUMN_PATTERN = /\A(=+)\[column\](?:\{(.+?)\})?(.*)/
353
347
 
354
348
  def self.parse(src, *_args)
355
- items = []
349
+ index = self.new
356
350
  seq = 1
357
351
  src.each do |line|
358
352
  m = COLUMN_PATTERN.match(line)
@@ -362,10 +356,10 @@ module ReVIEW
362
356
  caption = m[3].strip
363
357
  id = caption if id.nil? || id.empty?
364
358
 
365
- items.push(ReVIEW::Book::Index::Item.new(id, seq, caption))
359
+ index.add_item(ReVIEW::Book::Index::Item.new(id, seq, caption))
366
360
  seq += 1
367
361
  end
368
- new(items)
362
+ index
369
363
  end
370
364
  end
371
365
  end
@@ -27,7 +27,7 @@ module ReVIEW
27
27
  attr_reader :id
28
28
  attr_reader :number
29
29
  attr_reader :caption
30
- attr_writer :index # internal use only
30
+ attr_accessor :index # internal use only
31
31
 
32
32
  alias_method :content, :caption
33
33
 
@@ -56,8 +56,15 @@ module ReVIEW
56
56
  @tabwidth = nil
57
57
  @tsize = nil
58
58
  if @book && @book.config
59
- if @book.config['words_file']
60
- load_words(@book.config['words_file'])
59
+ if words_file_path = @book.config['words_file']
60
+ if words_file_path.is_a?(String)
61
+ words_files = [words_file_path]
62
+ else
63
+ words_files = words_file_path
64
+ end
65
+ words_files.each do |f|
66
+ load_words(f)
67
+ end
61
68
  end
62
69
  if @book.config['tabwidth']
63
70
  @tabwidth = @book.config['tabwidth']
@@ -151,7 +158,7 @@ module ReVIEW
151
158
  listnum_body(lines, lang)
152
159
  end
153
160
 
154
- def source(lines, caption, lang = nil)
161
+ def source(lines, caption = nil, lang = nil)
155
162
  source_header(caption)
156
163
  source_body(lines, lang)
157
164
  end
@@ -179,6 +186,21 @@ module ReVIEW
179
186
  table_end
180
187
  end
181
188
 
189
+ def table_row_separator_regexp
190
+ case @book.config['table_row_separator']
191
+ when 'tabs'
192
+ Regexp.new('\t+')
193
+ when 'singletab'
194
+ Regexp.new('\t')
195
+ when 'spaces'
196
+ Regexp.new('\s+')
197
+ when 'verticalbar'
198
+ Regexp.new('\s*\\' + escape('|') + '\s*')
199
+ else
200
+ error "Unknown value for 'table_row_separator', shold be: tabs, singletab, spaces, verticalbar"
201
+ end
202
+ end
203
+
182
204
  def parse_table_rows(lines)
183
205
  sepidx = nil
184
206
  rows = []
@@ -187,7 +209,7 @@ module ReVIEW
187
209
  sepidx ||= idx
188
210
  next
189
211
  end
190
- rows.push(line.strip.split(/\t+/).map { |s| s.sub(/\A\./, '') })
212
+ rows.push(line.strip.split(table_row_separator_regexp).map { |s| s.sub(/\A\./, '') })
191
213
  end
192
214
  rows = adjust_n_cols(rows)
193
215
  error 'no rows in the table' if rows.empty?
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2009-2019 Minero Aoki, Kenshi Muto
1
+ # Copyright (c) 2009-2020 Minero Aoki, Kenshi Muto
2
2
  # Copyright (c) 2002-2007 Minero Aoki
3
3
  #
4
4
  # This program is free software.
@@ -115,7 +115,7 @@ module ReVIEW
115
115
  defblock :emlist, 0..2
116
116
  defblock :cmd, 0..1
117
117
  defblock :table, 0..2
118
- defblock :imgtable, 0..2
118
+ defblock :imgtable, 0..3
119
119
  defblock :emtable, 0..1
120
120
  defblock :quote, 0
121
121
  defblock :image, 2..3, true
@@ -422,7 +422,10 @@ module ReVIEW
422
422
  def compile_dlist(f)
423
423
  @strategy.dl_begin
424
424
  while /\A\s*:/ =~ f.peek
425
+ # defer compile_inline to handle footnotes
426
+ @strategy.doc_status[:dt] = true
425
427
  @strategy.dt(text(f.gets.sub(/\A\s*:/, '').strip))
428
+ @strategy.doc_status[:dt] = nil
426
429
  desc = f.break(/\A(\S|\s*:|\s+\d+\.\s|\s+\*\s)/).map { |line| text(line.strip) }
427
430
  @strategy.dd(desc)
428
431
  f.skip_blank_lines
@@ -466,7 +469,7 @@ module ReVIEW
466
469
  buf.push(text(line.rstrip, true))
467
470
  end
468
471
  end
469
- unless %r{\A//\}} =~ f.peek
472
+ unless f.peek.to_s.start_with?('//}')
470
473
  error "unexpected EOF (block begins at: #{head})"
471
474
  return buf
472
475
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2012-2019 Masanori Kado, Masayoshi Takahashi, Kenshi Muto
2
+ # Copyright (c) 2012-2020 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
@@ -66,6 +66,7 @@ module ReVIEW
66
66
  'colophon_order' => %w[aut csl trl dsr ill cov edt pbl contact prt],
67
67
  'externallink' => true,
68
68
  'join_lines_by_lang' => nil, # experimental. default should be nil
69
+ 'table_row_separator' => 'tabs',
69
70
  # for IDGXML
70
71
  'tableopt' => nil,
71
72
  'listinfo' => nil,
@@ -74,7 +75,6 @@ module ReVIEW
74
75
  'structuredxml' => nil,
75
76
  'pt_to_mm_unit' => 0.3528, # DTP: 1pt = 0.3528mm, JIS: 1pt = 0.3514mm
76
77
  # for LaTeX
77
- 'image_scale2width' => true,
78
78
  'footnotetext' => nil,
79
79
  'texcommand' => 'uplatex',
80
80
  'texoptions' => '-interaction=nonstopmode -file-line-error -halt-on-error',
@@ -84,6 +84,7 @@ module ReVIEW
84
84
  'dvioptions' => '-d 5 -z 9',
85
85
  # for PDFMaker
86
86
  'pdfmaker' => {
87
+ 'image_scale2width' => true,
87
88
  'makeindex' => nil, # Make index page
88
89
  'makeindex_command' => 'mendex', # works only when makeindex is true
89
90
  'makeindex_options' => '-f -r -I utf8',
@@ -91,7 +92,8 @@ module ReVIEW
91
92
  'makeindex_dic' => nil,
92
93
  'makeindex_mecab' => true,
93
94
  'makeindex_mecab_opts' => '-Oyomi',
94
- 'use_cover_nombre' => true
95
+ 'use_cover_nombre' => true,
96
+ 'use_original_image_size' => nil
95
97
  },
96
98
  'imgmath_options' => {
97
99
  'format' => 'png',
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2010-2019 Kenshi Muto and Masayoshi Takahashi
1
+ # Copyright (c) 2010-2020 Kenshi Muto and Masayoshi Takahashi
2
2
  #
3
3
  # This program is free software.
4
4
  # You can distribute or modify this program under the terms of
@@ -73,6 +73,7 @@ module ReVIEW
73
73
  def parse_opts(args)
74
74
  cmd_config = {}
75
75
  opts = OptionParser.new
76
+ @buildonly = nil
76
77
 
77
78
  opts.banner = 'Usage: review-epubmaker [options] configfile [export_filename]'
78
79
  opts.version = ReVIEW::VERSION
@@ -81,6 +82,7 @@ module ReVIEW
81
82
  exit 0
82
83
  end
83
84
  opts.on('--[no-]debug', 'Keep temporary files.') { |debug| cmd_config['debug'] = debug }
85
+ opts.on('-y', '--only file1,file2,...', 'Build only specified files.') { |v| @buildonly = v.split(/\s*,\s*/).map { |m| m.strip.sub(/\.re\Z/, '') } }
84
86
 
85
87
  opts.parse!(args)
86
88
  if args.size < 1 || args.size > 2
@@ -394,6 +396,11 @@ module ReVIEW
394
396
  end
395
397
  end
396
398
 
399
+ if @buildonly && !@buildonly.include?(id)
400
+ warn "skip #{id}.re"
401
+ return
402
+ end
403
+
397
404
  htmlfile = "#{id}.#{@config['htmlext']}"
398
405
  write_buildlogtxt(basetmpdir, htmlfile, filename)
399
406
  log("Create #{htmlfile} from #{filename}.")
@@ -445,6 +452,13 @@ module ReVIEW
445
452
  path = File.join(basetmpdir, filename)
446
453
  htmlio = File.new(path)
447
454
  Document.parse_stream(htmlio, ReVIEWHeaderListener.new(headlines))
455
+ htmlio.close
456
+
457
+ if headlines.empty?
458
+ warn "#{filename} is discarded because there is no heading. Use `=[notoc]' or `=[nodisp]' to exclude headlines from the table of contents."
459
+ return
460
+ end
461
+
448
462
  properties = detect_properties(path)
449
463
  if properties.present?
450
464
  prop_str = ',properties=' + properties.join(' ')
@@ -472,7 +486,6 @@ module ReVIEW
472
486
  first = nil
473
487
  end
474
488
  end
475
- htmlio.close
476
489
  end
477
490
 
478
491
  def push_contents(_basetmpdir)