review 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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)