review 4.1.0 → 4.2.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/.rubocop.yml +4 -1
  4. data/NEWS.ja.md +29 -0
  5. data/NEWS.md +29 -0
  6. data/bin/review-index +2 -89
  7. data/bin/review-vol +4 -78
  8. data/doc/config.yml.sample +18 -5
  9. data/doc/config.yml.sample-simple +1 -1
  10. data/doc/pdfmaker.ja.md +42 -0
  11. data/doc/pdfmaker.md +41 -0
  12. data/doc/quickstart.ja.md +8 -5
  13. data/doc/quickstart.md +7 -4
  14. data/lib/review/book/base.rb +2 -4
  15. data/lib/review/book/compilable.rb +1 -5
  16. data/lib/review/book/page_metric.rb +7 -7
  17. data/lib/review/book/part.rb +6 -3
  18. data/lib/review/book/volume.rb +3 -4
  19. data/lib/review/builder.rb +23 -10
  20. data/lib/review/compiler.rb +9 -9
  21. data/lib/review/configure.rb +6 -0
  22. data/lib/review/epubmaker.rb +1 -1
  23. data/lib/review/htmlbuilder.rb +56 -16
  24. data/lib/review/idgxmlbuilder.rb +63 -22
  25. data/lib/review/latexbuilder.rb +70 -19
  26. data/lib/review/makerhelper.rb +18 -1
  27. data/lib/review/pdfmaker.rb +8 -1
  28. data/lib/review/plaintextbuilder.rb +41 -11
  29. data/lib/review/textmaker.rb +1 -1
  30. data/lib/review/textutils.rb +2 -3
  31. data/lib/review/tocprinter.rb +231 -102
  32. data/lib/review/topbuilder.rb +47 -13
  33. data/lib/review/version.rb +1 -1
  34. data/lib/review/volumeprinter.rb +99 -0
  35. data/lib/review/webmaker.rb +1 -1
  36. data/lib/review/webtocprinter.rb +38 -35
  37. data/review.gemspec +1 -1
  38. data/samples/sample-book/src/config.yml +1 -1
  39. data/templates/web/html/layout-html5.html.erb +2 -2
  40. data/test/test_book.rb +1 -1
  41. data/test/test_book_part.rb +3 -3
  42. data/test/test_helper.rb +4 -1
  43. data/test/test_htmlbuilder.rb +179 -0
  44. data/test/test_idgxmlbuilder.rb +143 -0
  45. data/test/test_latexbuilder.rb +223 -0
  46. data/test/test_pdfmaker.rb +17 -0
  47. data/test/test_plaintextbuilder.rb +99 -0
  48. data/test/test_topbuilder.rb +116 -2
  49. data/test/test_webtocprinter.rb +66 -34
  50. metadata +3 -5
  51. data/lib/review/tocparser.rb +0 -275
  52. data/test/test_tocparser.rb +0 -25
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2018-2019 Kenshi Muto
1
+ # Copyright (c) 2018-2020 Kenshi Muto
2
2
  #
3
3
  # This program is free software.
4
4
  # You can distribute or modify this program under the terms of
@@ -140,13 +140,19 @@ module ReVIEW
140
140
  def list(lines, id, caption, lang = nil)
141
141
  blank
142
142
  begin
143
- list_header(id, caption, lang)
143
+ if caption_top?('list')
144
+ list_header(id, caption, lang)
145
+ blank
146
+ end
147
+ list_body(id, lines, lang)
148
+ unless caption_top?('list')
149
+ blank
150
+ list_header(id, caption, lang)
151
+ end
144
152
  rescue KeyError
145
153
  error "no such list: #{id}"
146
154
  end
147
155
  blank
148
- list_body(id, lines, lang)
149
- blank
150
156
  end
151
157
 
152
158
  def list_header(id, caption, _lang)
@@ -165,8 +171,13 @@ module ReVIEW
165
171
 
166
172
  def base_block(_type, lines, caption = nil)
167
173
  blank
168
- puts compile_inline(caption) if caption.present?
174
+ if caption_top?('list') && caption.present?
175
+ puts compile_inline(caption)
176
+ end
169
177
  puts lines.join("\n")
178
+ if !caption_top?('list') && caption.present?
179
+ puts compile_inline(caption)
180
+ end
170
181
  blank
171
182
  end
172
183
 
@@ -183,23 +194,34 @@ module ReVIEW
183
194
 
184
195
  def emlistnum(lines, caption = nil, _lang = nil)
185
196
  blank
186
- puts compile_inline(caption) if caption.present?
197
+ if caption_top?('list')
198
+ puts compile_inline(caption) if caption.present?
199
+ end
187
200
  lines.each_with_index do |line, i|
188
201
  puts((i + 1).to_s.rjust(2) + ": #{line}")
189
202
  end
203
+ unless caption_top?('list')
204
+ puts compile_inline(caption) if caption.present?
205
+ end
190
206
  blank
191
207
  end
192
208
 
193
209
  def listnum(lines, id, caption, lang = nil)
194
210
  blank
195
211
  begin
196
- list_header(id, caption, lang)
212
+ if caption_top?('list')
213
+ list_header(id, caption, lang)
214
+ blank
215
+ end
216
+ listnum_body(lines, lang)
217
+ unless caption_top?('list')
218
+ blank
219
+ list_header(id, caption, lang)
220
+ end
197
221
  rescue KeyError
198
222
  error "no such list: #{id}"
199
223
  end
200
224
  blank
201
- listnum_body(lines, lang)
202
- blank
203
225
  end
204
226
 
205
227
  def listnum_body(lines, _lang)
@@ -228,8 +250,9 @@ module ReVIEW
228
250
 
229
251
  def texequation(lines, id = nil, caption = '')
230
252
  blank
231
- texequation_header(id, caption)
253
+ texequation_header(id, caption) if caption_top?('equation')
232
254
  puts lines.join("\n")
255
+ texequation_header(id, caption) unless caption_top?('equation')
233
256
  blank
234
257
  end
235
258
 
@@ -251,6 +274,10 @@ module ReVIEW
251
274
  end
252
275
 
253
276
  def table_header(id, caption)
277
+ unless caption_top?('table')
278
+ blank
279
+ end
280
+
254
281
  if id.nil?
255
282
  puts compile_inline(caption)
256
283
  elsif get_chap
@@ -258,7 +285,10 @@ module ReVIEW
258
285
  else
259
286
  puts "#{I18n.t('table')}#{I18n.t('format_number_without_chapter', [@chapter.table(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}"
260
287
  end
261
- blank
288
+
289
+ if caption_top?('table')
290
+ blank
291
+ end
262
292
  end
263
293
 
264
294
  def table_begin(_ncols)
@@ -98,7 +98,7 @@ module ReVIEW
98
98
  end
99
99
 
100
100
  math_dir = "./#{@config['imagedir']}/_review_math_text"
101
- if @config['imgmath'] && File.exist?(File.join(math_dir, '__IMGMATH_BODY__.tex'))
101
+ if @config['imgmath'] && File.exist?(File.join(math_dir, '__IMGMATH_BODY__.map'))
102
102
  make_math_images(math_dir)
103
103
  end
104
104
  end
@@ -1,4 +1,4 @@
1
- # Copyright (c) 2008-2019 Minero Aoki, Kenshi Muto, Masayoshi Takahashi,
1
+ # Copyright (c) 2008-2020 Minero Aoki, Kenshi Muto, Masayoshi Takahashi,
2
2
  # KADO Masanori
3
3
  # 2002-2007 Minero Aoki
4
4
  #
@@ -98,9 +98,8 @@ module ReVIEW
98
98
 
99
99
  def defer_math_image(str, path, key)
100
100
  # for Re:VIEW >3
101
- File.open(File.join(File.dirname(path), '__IMGMATH_BODY__.tex'), 'a+') do |f|
101
+ File.open(File.join(File.dirname(path), "__IMGMATH_BODY__#{key}.tex"), 'w') do |f|
102
102
  f.puts str
103
- f.puts '\\clearpage'
104
103
  end
105
104
  File.open(File.join(File.dirname(path), '__IMGMATH_BODY__.map'), 'a+') do |f|
106
105
  f.puts key
@@ -1,148 +1,277 @@
1
- # Copyright (c) 2008-2017 Minero Aoki, Kenshi Muto
2
- # 2002-2007 Minero Aoki
1
+ # Copyright (c) 2008-2020 Minero Aoki, Kenshi Muto
2
+ # 1999-2007 Minero Aoki
3
3
  #
4
4
  # This program is free software.
5
5
  # You can distribute or modify this program under the terms of
6
6
  # the GNU LGPL, Lesser General Public License version 2.1.
7
- # For details of LGPL, see the file "COPYING".
7
+ # For details of the GNU LGPL, see the file "COPYING".
8
8
  #
9
9
 
10
- require 'review/htmlutils'
11
- require 'review/tocparser'
10
+ require 'review/book'
11
+ require 'review/version'
12
+ require 'optparse'
13
+ require 'review/plaintextbuilder'
12
14
 
13
15
  module ReVIEW
14
- class TOCPrinter
15
- def self.default_upper_level
16
- 99 # no one use 99 level nest
16
+ class PLAINTEXTTocBuilder < PLAINTEXTBuilder
17
+ def headline(level, label, caption)
18
+ if @chapter.is_a?(ReVIEW::Book::Part)
19
+ print "\x01H0\x01" # XXX: don't modify level value. level value will be handled in sec_counter#prefix()
20
+ else
21
+ print "\x01H#{level}\x01"
22
+ end
23
+ # embed header information for tocparser
24
+ super(level, label, caption)
17
25
  end
18
26
 
19
- def initialize(print_upper, param, out = $stdout)
20
- @print_upper = print_upper
21
- @config = param
22
- @out = out
27
+ def base_block(type, lines, caption = nil)
28
+ puts "\x01STARTLIST\x01"
29
+ super(type, lines, caption)
30
+ puts "\x01ENDLIST\x01"
23
31
  end
24
32
 
25
- def print_book(book)
26
- book.each_part { |part| print_part(part) }
33
+ def blank
34
+ @blank_seen = true
27
35
  end
36
+ end
28
37
 
29
- def print_part(part)
30
- part.each_chapter { |chap| print_chapter(chap) }
38
+ class TOCPrinter
39
+ def self.execute(*args)
40
+ Signal.trap(:INT) { exit 1 }
41
+ if RUBY_PLATFORM !~ /mswin(?!ce)|mingw|cygwin|bccwin/
42
+ Signal.trap(:PIPE, 'IGNORE')
43
+ end
44
+ new.execute(*args)
45
+ rescue Errno::EPIPE
46
+ exit 0
31
47
  end
32
48
 
33
- def print_chapter(chap)
34
- chap_node = TOCParser.chapter_node(chap)
35
- print_node(1, chap_node)
36
- print_children(chap_node)
49
+ def initialize
50
+ @logger = ReVIEW.logger
51
+ @config = ReVIEW::Configure.values
52
+ @yamlfile = 'config.yml'
53
+ @book = ReVIEW::Book::Base.load
54
+ @upper = 4
55
+ @indent = true
56
+ @buildonly = nil
57
+ @detail = nil
37
58
  end
38
59
 
39
- def print?(level)
40
- level <= @print_upper
60
+ def execute(*args)
61
+ parse_options(args)
62
+ @book.config = ReVIEW::Configure.values
63
+ unless File.readable?(@yamlfile)
64
+ @logger.error("No such fiile or can't open #{@yamlfile}.")
65
+ exit 1
66
+ end
67
+ @book.load_config(@yamlfile)
68
+ I18n.setup(@config['language'])
69
+
70
+ if @detail
71
+ begin
72
+ require 'unicode/eaw'
73
+ @calc_char_width = true
74
+ rescue LoadError
75
+ @logger.warn('not found unicode/eaw library. page volume may be unreliable.')
76
+ @calc_char_width = nil
77
+ end
78
+ end
79
+
80
+ print_result(build_result_array)
41
81
  end
42
- end
43
82
 
44
- class TextTOCPrinter < TOCPrinter
45
- private
83
+ def build_result_array
84
+ result_array = []
85
+ begin
86
+ @book.parts.each do |part|
87
+ if part.name.present? && (@buildonly.nil? || @buildonly.include?(part.name))
88
+ result_array.push({ part: 'start' })
89
+ if part.file?
90
+ result = build_chap(part)
91
+ result_array += parse_contents(part.name, @upper, result)
92
+ else
93
+ title = part.format_number + I18n.t('chapter_postfix') + part.title
94
+ result_array += [
95
+ { name: '', lines: 1, chars: title.size, list_lines: 0, text_lines: 1 },
96
+ { level: 0, headline: title, lines: 1, chars: title.size, list_lines: 0, text_lines: 1 }
97
+ ]
98
+ end
99
+ end
46
100
 
47
- def print_children(node)
48
- return unless print?(node.level + 1)
49
- node.each_section_with_index do |section, idx|
50
- unless section.blank?
51
- print_node(idx + 1, section)
52
- print_children(section)
101
+ part.chapters.each do |chap|
102
+ if @buildonly.nil? || @buildonly.include?(chap.name)
103
+ result = build_chap(chap)
104
+ result_array += parse_contents(chap.name, @upper, result)
105
+ end
106
+ end
107
+ if part.name.present? && (@buildonly.nil? || @buildonly.include?(part.name))
108
+ result_array.push({ part: 'end' })
109
+ end
53
110
  end
111
+ rescue ReVIEW::FileNotFound => e
112
+ @logger.error e
113
+ exit 1
54
114
  end
115
+
116
+ result_array
55
117
  end
56
118
 
57
- def print_node(number, node)
58
- if node.chapter?
59
- vol = node.volume
60
- @out.printf("%3s %3dKB %6dC %5dL %s (%s)\n",
61
- chapnumstr(node.number),
62
- vol.kbytes, vol.chars, vol.lines,
63
- node.label, node.chapter_id)
64
- else ## for section node
65
- @out.printf("%17s %5dL %s\n",
66
- '', node.estimated_lines,
67
- " #{' ' * (node.level - 1)}#{number} #{node.label}")
119
+ def print_result(result_array)
120
+ result_array.each do |result|
121
+ if result[:part]
122
+ next
123
+ end
124
+
125
+ if result[:name]
126
+ # file information
127
+ if @detail
128
+ puts '============================='
129
+ printf("%6dC %5dL %5dP %s\n", result[:chars], result[:lines], calc_pages(result).ceil, result[:name])
130
+ puts '-----------------------------'
131
+ end
132
+ next
133
+ end
134
+
135
+ # section information
136
+ if @detail
137
+ printf('%6dC %5dL %5.1fP ', result[:chars], result[:lines], calc_pages(result))
138
+ end
139
+ if @indent && result[:level]
140
+ print ' ' * (result[:level] == 0 ? 0 : result[:level] - 1)
141
+ end
142
+ puts result[:headline]
68
143
  end
69
144
  end
70
145
 
71
- def chapnumstr(n)
72
- n ? sprintf('%2d.', n) : ' '
146
+ def calc_pages(result)
147
+ p = 0
148
+ p += result[:list_lines].to_f / @book.page_metric.list.n_lines
149
+ p += result[:text_lines].to_f / @book.page_metric.text.n_lines
150
+ p
73
151
  end
74
152
 
75
- def volume_columns(level, volstr)
76
- cols = ['', '', '', nil]
77
- cols[level - 1] = volstr
78
- cols[0, 3] # does not display volume of level-4 section
153
+ def calc_linesize(l)
154
+ return l.size unless @calc_char_width
155
+ w = 0
156
+ l.split('').each do |c|
157
+ # XXX: should include A also?
158
+ if %i[Na H N].include?(Unicode::Eaw.property(c))
159
+ w += 0.5 # halfwidth
160
+ else
161
+ w += 1
162
+ end
163
+ end
164
+ w
79
165
  end
80
- end
81
166
 
82
- class HTMLTOCPrinter < TOCPrinter
83
- include HTMLUtils
167
+ def parse_contents(name, upper, content)
168
+ headline_array = []
169
+ counter = { lines: 0, chars: 0, list_lines: 0, text_lines: 0 }
170
+ listmode = nil
84
171
 
85
- def print_book(book)
86
- @out.puts '<ul class="book-toc">'
87
- book.each_part { |part| print_part(part) }
88
- @out.puts '</ul>'
89
- end
172
+ content.split("\n").each do |l|
173
+ if l.start_with?("\x01STARTLIST\x01")
174
+ listmode = true
175
+ next
176
+ elsif l.start_with?("\x01ENDLIST\x01")
177
+ listmode = nil
178
+ next
179
+ elsif l =~ /\A\x01H(\d)\x01/
180
+ # headline
181
+ level = $1.to_i
182
+ l = $'
183
+ if level <= upper
184
+ if counter[:chars] > 0
185
+ headline_array.push(counter)
186
+ end
187
+ headline = l
188
+ counter = {
189
+ level: level,
190
+ headline: headline,
191
+ lines: 1,
192
+ chars: headline.size,
193
+ list_lines: 0,
194
+ text_lines: 1
195
+ }
196
+ next
197
+ end
198
+ end
90
199
 
91
- def print_part(part)
92
- if part.number
93
- @out.puts li(part.title)
94
- end
95
- super
96
- end
200
+ counter[:lines] += 1
201
+ counter[:chars] += l.size
97
202
 
98
- def print_chapter(chap)
99
- chap_node = TOCParser.chapter_node(chap)
100
- ext = chap.book.config['htmlext'] || 'html'
101
- path = chap.path.sub(/\.re/, '.' + ext)
102
- label = if chap_node.number && chap.on_chaps?
103
- "#{chap.number} #{chap.title}"
104
- else
105
- chap.title
106
- end
107
- @out.puts li(a_name(path, escape_html(label)))
108
- return unless print?(2)
109
- if print?(3)
110
- @out.puts chap_sections_to_s(chap_node)
111
- else
112
- @out.puts chapter_to_s(chap_node)
203
+ if listmode
204
+ # code list: calculate line wrapping
205
+ if l.size == 0
206
+ counter[:list_lines] += 1
207
+ else
208
+ counter[:list_lines] += (calc_linesize(l) - 1) / @book.page_metric.list.n_columns + 1
209
+ end
210
+ else
211
+ # normal paragraph: calculate line wrapping
212
+ if l.size == 0
213
+ counter[:text_lines] += 1
214
+ else
215
+ counter[:text_lines] += (calc_linesize(l) - 1) / @book.page_metric.text.n_columns + 1
216
+ end
217
+ end
113
218
  end
114
- end
219
+ headline_array.push(counter)
115
220
 
116
- private
221
+ total_lines = 0
222
+ total_chars = 0
223
+ total_list_lines = 0
224
+ total_text_lines = 0
117
225
 
118
- def chap_sections_to_s(chap)
119
- return '' if chap.section_size < 1
120
- res = []
121
- res << '<ol>'
122
- chap.each_section { |sec| res << li(escape_html(sec.label)) }
123
- res << '</ol>'
124
- res.join("\n")
125
- end
226
+ headline_array.each do |h|
227
+ next unless h[:lines]
228
+ total_lines += h[:lines]
229
+ total_chars += h[:chars]
230
+ total_list_lines += h[:list_lines]
231
+ total_text_lines += h[:text_lines]
232
+ end
126
233
 
127
- def chapter_to_s(chap)
128
- res = []
129
- chap.each_section do |sec|
130
- res << li(escape_html(sec.label))
131
- next unless print?(4)
132
- next unless sec.section_size > 0
133
- res << '<ul>'
134
- sec.each_section { |node| res << li(escape_html(node.label)) }
135
- res << '</ul>'
136
- end
137
- res.join("\n")
234
+ headline_array.delete_if(&:empty?).
235
+ unshift({ name: name, lines: total_lines, chars: total_chars, list_lines: total_list_lines, text_lines: total_text_lines })
138
236
  end
139
237
 
140
- def li(content)
141
- "<li>#{content}</li>"
238
+ def build_chap(chap)
239
+ compiler = ReVIEW::Compiler.new(ReVIEW::PLAINTEXTTocBuilder.new)
240
+ begin
241
+ compiler.compile(@book.chapter(chap.name))
242
+ rescue ReVIEW::ApplicationError => e
243
+ @logger.error e
244
+ exit 1
245
+ end
142
246
  end
143
247
 
144
- def a_name(name, label)
145
- %Q(<a name="#{name}">#{label}</a>)
248
+ def parse_options(args)
249
+ opts = OptionParser.new
250
+ opts.version = ReVIEW::VERSION
251
+ opts.on('--yaml=YAML', 'Read configurations from YAML file.') { |yaml| @yamlfile = yaml }
252
+ opts.on('-y', '--only file1,file2,...', 'list only specified files.') do |v|
253
+ @buildonly = v.split(/\s*,\s*/).map { |m| m.strip.sub(/\.re\Z/, '') }
254
+ end
255
+ opts.on('-l', '--level N', 'list upto N level (default=4)') do |n|
256
+ @upper = n.to_i
257
+ end
258
+ opts.on('-d', '--detail', 'show characters and lines of each section.') do
259
+ @detail = true
260
+ end
261
+ opts.on('--noindent', "don't indent headlines.") do
262
+ @indent = nil
263
+ end
264
+ opts.on('--help', 'print this message and quit.') do
265
+ puts opts.help
266
+ exit 0
267
+ end
268
+ begin
269
+ opts.parse!(args)
270
+ rescue OptionParser::ParseError => e
271
+ @logger.error e.message
272
+ $stderr.puts opts.help
273
+ exit 1
274
+ end
146
275
  end
147
276
  end
148
277
  end