review-retrovert 0.9.7 → 0.9.8

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +9 -0
  3. data/.editorconfig +3 -0
  4. data/.github/workflows/retrovert.yml +20 -5
  5. data/.gitignore +1 -0
  6. data/.vscode/launch.json +48 -0
  7. data/Gemfile.lock +13 -4
  8. data/Makefile +24 -0
  9. data/docker/review.Dockerfile +10 -0
  10. data/lib/review/retrovert/converter.rb +374 -111
  11. data/lib/review/retrovert/reviewcompat.rb +57 -0
  12. data/lib/review/retrovert/reviewdef.rb +70 -0
  13. data/lib/review/retrovert/sty/{ird.sty → ird.sty.erb} +9 -1
  14. data/lib/review/retrovert/sty/review-retrovert-custom.sty.erb +6 -0
  15. data/lib/review/retrovert/utils.rb +35 -0
  16. data/lib/review/retrovert/version.rb +1 -1
  17. data/lib/review/retrovert/yamlconfig.rb +0 -1
  18. data/package-lock.json +6 -0
  19. data/package.json +1 -0
  20. data/review-retrovert.gemspec +5 -1
  21. data/testdata/mybook/.gitignore +3 -1
  22. data/testdata/mybook/.textlintrc +11 -0
  23. data/testdata/mybook/Rakefile +8 -0
  24. data/testdata/mybook/catalog.yml +36 -10
  25. data/testdata/mybook/config-starter.yml +134 -9
  26. data/testdata/mybook/config.yml +44 -15
  27. data/testdata/mybook/contents/00-preface.re +48 -2
  28. data/testdata/mybook/contents/01-install.re +38 -5
  29. data/testdata/mybook/contents/02-tutorial.re +333 -113
  30. data/testdata/mybook/contents/03-syntax.re +2370 -373
  31. data/testdata/mybook/contents/04-customize.re +424 -88
  32. data/testdata/mybook/contents/05-faq.re +288 -10
  33. data/testdata/mybook/contents/06-bestpractice.re +431 -59
  34. data/testdata/mybook/contents/91-compare.re +402 -2
  35. data/testdata/mybook/contents/92-filelist.re +14 -8
  36. data/testdata/mybook/contents/93-background.re +10 -10
  37. data/testdata/mybook/contents/99-postface.re +2 -1
  38. data/testdata/mybook/contents/r0-root.re +1 -1
  39. data/testdata/mybook/css/webstyle.css +180 -2
  40. data/testdata/mybook/data/terms.txt +3 -0
  41. data/testdata/mybook/data/words.txt +15 -0
  42. data/testdata/mybook/images/03-syntax/index-page.png +0 -0
  43. data/testdata/mybook/images/03-syntax/order-detail.png +0 -0
  44. data/testdata/mybook/images/04-customize/section_decoration_samples.png +0 -0
  45. data/testdata/mybook/images/05-faq/dummy-image.png +0 -0
  46. data/testdata/mybook/images/06-bestpractice/section_title_wlines.png +0 -0
  47. data/testdata/mybook/images/avatar-b.png +0 -0
  48. data/testdata/mybook/images/avatar-g.png +0 -0
  49. data/testdata/mybook/images/avatar-r.png +0 -0
  50. data/testdata/mybook/images/caution-icon.png +0 -0
  51. data/testdata/mybook/images/info-icon.png +0 -0
  52. data/testdata/mybook/images/warning-icon.png +0 -0
  53. data/testdata/mybook/layouts/layout.html5.erb +3 -1
  54. data/testdata/mybook/layouts/layout.tex.erb +265 -379
  55. data/testdata/mybook/layouts/layout.tex.erb.orig +386 -0
  56. data/testdata/mybook/lib/ruby/review-book.rb +64 -0
  57. data/testdata/mybook/lib/ruby/review-builder.rb +902 -63
  58. data/testdata/mybook/lib/ruby/review-compiler.rb +675 -22
  59. data/testdata/mybook/lib/ruby/review-epubbuilder.rb +33 -0
  60. data/testdata/mybook/lib/ruby/review-epubmaker.rb +10 -7
  61. data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +354 -66
  62. data/testdata/mybook/lib/ruby/review-latexbuilder.rb +429 -146
  63. data/testdata/mybook/lib/ruby/review-maker.rb +117 -6
  64. data/testdata/mybook/lib/ruby/review-markdownbuilder.rb +945 -0
  65. data/testdata/mybook/lib/ruby/review-markdownmaker.rb +91 -0
  66. data/testdata/mybook/lib/ruby/review-monkeypatch.rb +2 -0
  67. data/testdata/mybook/lib/ruby/review-pdfmaker.rb +160 -82
  68. data/testdata/mybook/lib/ruby/review-webmaker.rb +20 -5
  69. data/testdata/mybook/lib/tasks/review.rake +14 -0
  70. data/testdata/mybook/lib/tasks/starter.rake +148 -4
  71. data/testdata/mybook/sty/indexstyle.ist +25 -0
  72. data/testdata/mybook/sty/mytextsize.sty +29 -0
  73. data/testdata/mybook/sty/mytitlepage.sty +34 -11
  74. data/testdata/mybook/sty/review-base.sty +276 -0
  75. data/testdata/mybook/sty/starter-codeblock.sty +237 -106
  76. data/testdata/mybook/sty/starter-color.sty +72 -17
  77. data/testdata/mybook/sty/starter-heading.sty +60 -13
  78. data/testdata/mybook/sty/starter-misc.sty +894 -0
  79. data/testdata/mybook/sty/starter-note.sty +67 -14
  80. data/testdata/mybook/sty/starter-talklist.sty +105 -0
  81. data/testdata/mybook/sty/starter-util.sty +35 -0
  82. data/testdata/mybook/sty/starter.sty +8 -526
  83. metadata +80 -4
@@ -5,6 +5,7 @@
5
5
  ##
6
6
 
7
7
  require 'pathname'
8
+ require 'open3'
8
9
 
9
10
  require 'review/logger'
10
11
  require 'review/version'
@@ -34,9 +35,10 @@ module ReVIEW
34
35
  @config.maker = @maker_name
35
36
  @config.check_version(ReVIEW::VERSION) # may raise ReVIEW::ConfigError
36
37
  #
37
- yamlfile = File.join(@basedir, 'config-starter.yml')
38
- yamldata = File.open(yamlfile) {|f| YAML.safe_load(f) }
39
- @starter_config = yamldata['starter']
38
+ #yamlfile = File.join(@basedir, 'config-starter.yml')
39
+ #yamldata = File.open(yamlfile) {|f| YAML.safe_load(f) }
40
+ #@starter_config = yamldata['starter']
41
+ @starter_config = @config['starter']
40
42
  end
41
43
 
42
44
  def self.execute(*args)
@@ -57,7 +59,7 @@ module ReVIEW
57
59
  maker = nil
58
60
  begin
59
61
  maker = self.new(config_filename, cmdopts)
60
- maker.generate()
62
+ return maker.generate()
61
63
  rescue ApplicationError, ReVIEW::ConfigError => ex
62
64
  raise if maker && maker.config['debug']
63
65
  error(ex.message)
@@ -87,6 +89,8 @@ module ReVIEW
87
89
  config.deep_merge!(additionals)
88
90
  I18n.setup(config['language'])
89
91
  #
92
+ validate_config(config)
93
+ #
90
94
  if ENV['STARTER_CHAPTER'].present?
91
95
  modify_config(config)
92
96
  end
@@ -116,6 +120,76 @@ module ReVIEW
116
120
  return book
117
121
  end
118
122
 
123
+ def validate_config(config)
124
+ _validate_obsoletes(config)
125
+ _validate_pdffiles(config)
126
+ end
127
+
128
+ private
129
+
130
+ def _validate_obsoletes(config)
131
+ d = config['pdfmaker']
132
+ return unless d
133
+ obsolete_keys = ['coverpdf_files', 'backcoverpdf_files', 'coverpdf_option']
134
+ if obsolete_keys.any? {|key| d.key?(key) }
135
+ errmsg = CONFIG_PDFFILES_ERRMSG
136
+ $stderr.puts "\033[0;31m\n#{errmsg}\033[0m"
137
+ error "obsolete config entry exists."
138
+ end
139
+ end
140
+
141
+ CONFIG_PDFFILES_ERRMSG = <<END
142
+ ***** ERROR *****
143
+ 'config.yml' contains old entries.
144
+
145
+ Please move the followings from 'config.yml' ...
146
+
147
+ * coverpdf_files:
148
+ * backcoverpdf_files:
149
+ * coverpdf_option:
150
+
151
+ into 'config-starter.yml' with renaming to ...
152
+
153
+ * frontcover_pdffile:
154
+ * backcover_pdffile:
155
+ * includepdf_option:
156
+ END
157
+
158
+ def _validate_pdffiles(config)
159
+ d = config['starter']
160
+ return unless d
161
+ imagedir = config['imagedir']
162
+ pdffile_keys = ['frontcover_pdffile', 'backcover_pdffile', 'titlepage_pdffile', 'colophon_pdffile']
163
+ pdffile_keys.each do |key|
164
+ [d[key]].flatten.compact.each do |pdffile|
165
+ pdffile = pdffile.gsub(/(<.*?>)?\*?\z/, '')
166
+ errmsg = _validate_pdffile(File.join(imagedir, pdffile))
167
+ error "(config-starter.yml) '#{key}: #{pdffile}': #{errmsg}" if errmsg
168
+ end
169
+ end
170
+ #
171
+ key = 'includepdf_option'; val = d[key]
172
+ if val.present?
173
+ val =~ /\Aoffset=/ or
174
+ error "(config-starter.yml) '#{key}: #{val}': unexpected value. only 'offset=...' available here."
175
+ end
176
+ end
177
+
178
+ def _validate_pdffile(filepath)
179
+ filepath =~ /\.pdf\z/i or
180
+ return "expected pdf file ('*.pdf'), but not."
181
+ return _validate_file(filepath)
182
+ end
183
+
184
+ def _validate_file(filepath)
185
+ File.exist?(filepath) or return "file not found."
186
+ File.file?(filepath) or return "not a file."
187
+ File.readable?(filepath) or return "cannot read (permission denied?)."
188
+ return nil
189
+ end
190
+
191
+ protected
192
+
119
193
  def system_or_raise(*args)
120
194
  Kernel.system(*args) or raise("failed to run command: #{args.join(' ')}")
121
195
  end
@@ -145,16 +219,48 @@ module ReVIEW
145
219
  path
146
220
  end
147
221
 
148
- def run_cmd(cmd)
222
+ def echoback(cmd)
149
223
  puts ""
150
224
  puts "[#{@maker_name}]$ #{cmd}"
151
- return Kernel.system(cmd)
225
+ end
226
+
227
+ def run_cmd(cmd)
228
+ time = @_usr_bin_time ? "time " : ""
229
+ echoback("#{time}#{cmd}")
230
+ return Kernel::system(cmd)
152
231
  end
153
232
 
154
233
  def run_cmd!(cmd)
155
234
  run_cmd(cmd) or raise("failed to run command: #{cmd}")
156
235
  end
157
236
 
237
+ def run_cmd_capturing_output(cmd, append=nil)
238
+ time = @_usr_bin_time ? "time " : ""
239
+ echoback("#{time}#{cmd}#{append}")
240
+ output, status = Open3.capture2(cmd)
241
+ return output, status.success?
242
+ end
243
+
244
+ def usr_bin_time(&b)
245
+ result = nil
246
+ @_usr_bin_time = true
247
+ start_t = Time.now
248
+ ptime1 = Process.times
249
+ result = yield
250
+ end_t = Time.now
251
+ ptime2 = Process.times
252
+ @_usr_bin_time = false
253
+ #
254
+ real_time = end_t - start_t
255
+ user_time = ptime2.cutime - ptime1.cutime
256
+ sys_time = ptime2.cstime - ptime1.cstime
257
+ format = "%12.2f real %12.2f user %12.2f sys"
258
+ $stderr.puts format % [real_time, user_time, sys_time]
259
+ #
260
+ return result
261
+ end
262
+ protected :usr_bin_time
263
+
158
264
  def call_hook(hookname)
159
265
  d = @config[@maker_name]
160
266
  return unless d.is_a?(Hash)
@@ -183,6 +289,11 @@ module ReVIEW
183
289
  return File.join(c['bindir'], c['ruby_install_name']) + c['EXEEXT'].to_s
184
290
  end
185
291
 
292
+ def colored_errmsg(msg)
293
+ #return msg unless $stderr.tty?
294
+ return "\033[0;31m#{msg}\033[0m"
295
+ end
296
+
186
297
  end
187
298
 
188
299
 
@@ -0,0 +1,945 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ##
4
+ ## Markdownへ変換する
5
+ ##
6
+
7
+ require_relative './review-builder'
8
+
9
+
10
+ module ReVIEW
11
+
12
+
13
+ class MarkdownBuilder < Builder
14
+
15
+ def target_name
16
+ "markdown"
17
+ end
18
+
19
+ def extname
20
+ ".md"
21
+ end
22
+
23
+ def headline(level, label, caption)
24
+ blank2() unless @output.pos == 0
25
+ print "#{'#' * level} "
26
+ print compile_inline(caption||'')
27
+ #print " {##{label}}" if label
28
+ puts ""
29
+ blank()
30
+ end
31
+
32
+ def notoc_begin(level, label, caption)
33
+ blank2() unless @output.pos == 0
34
+ print "#{'#' * level} "
35
+ print compile_inline(caption||'')
36
+ #print " {##{label}}" if label
37
+ puts ""
38
+ blank()
39
+ end
40
+
41
+ def notoc_end(level)
42
+ end
43
+
44
+ def column_begin(level, label, caption)
45
+ blank2()
46
+ puts "[column] #{compile_inline(caption||'')}"
47
+ end
48
+
49
+ def column_end(_level)
50
+ truncate_blank()
51
+ puts "[/column]"
52
+ blank2()
53
+ end
54
+
55
+ def blank()
56
+ puts "" unless @output.string.end_with?("\n\n")
57
+ end
58
+
59
+ def blank2()
60
+ if @output.string.end_with?("\n\n\n")
61
+ elsif @output.string.end_with?("\n\n")
62
+ puts ""
63
+ else
64
+ puts ""
65
+ puts ""
66
+ end
67
+ end
68
+
69
+ def twospaces()
70
+ if truncate_if_endwith?("\n")
71
+ puts " "
72
+ return true
73
+ else
74
+ false
75
+ end
76
+ end
77
+
78
+ def nofunc_text(str)
79
+ if within_codeblock?() || within_context?(:code)
80
+ str
81
+ else
82
+ escape(str)
83
+ end
84
+ end
85
+
86
+ def escape(str)
87
+ str.to_s.gsub(/[&<>"']/, HTML_ESCAPE_TABLE)
88
+ end
89
+
90
+ HTML_ESCAPE_TABLE = {
91
+ '&' => '&amp;',
92
+ '<' => '&lt;',
93
+ '>' => '&gt;',
94
+ '"' => '&quot;',
95
+ "'" => '&#039;',
96
+ }
97
+
98
+ ## 画像
99
+
100
+ def indepimage(lines, id, caption='', metric=nil)
101
+ image = @chapter.image(id)
102
+ image.bound? or
103
+ error "//image[#{id}]: image not found."
104
+ image_filepath = image.path
105
+ blank()
106
+ puts "![#{compile_inline(caption||'')}](#{image_filepath})"
107
+ if caption.present?
108
+ puts "<span class=\"caption\">▲#{compile_inline(caption)}</span>"
109
+ end
110
+ blank()
111
+ end
112
+
113
+ def imgtable_image(id, caption, metric)
114
+ metrics = parse_metric('html', metric)
115
+ puts "<img src=\"#{@chapter.image(id).path.sub(%r{\A\./}, '')}\"#{metrics} />"
116
+ end
117
+
118
+ protected
119
+
120
+ def _render_image(id, image_filepath, caption, opts)
121
+ puts "![#{compile_inline(caption||'')}](#{image_filepath})"
122
+ end
123
+
124
+ ## コードブロック(//program, //terminal, //output)
125
+
126
+ def _render_codeblock(blockname, lines, id, caption_str, opts)
127
+ blank()
128
+ if caption_str.present?
129
+ puts "<span class=\"caption\">▼#{compile_inline(caption_str)}</span>"
130
+ puts ""
131
+ end
132
+ lang = opts['lang']
133
+ lang ||= 'terminal' if blockname == 'terminal' || blockname == 'cmd'
134
+ puts "```#{lang}"
135
+ lines.each do |line|
136
+ puts compile_inline(line)
137
+ end
138
+ puts "```"
139
+ blank()
140
+ end
141
+
142
+ def _add_indent_mark(lines, indent_width)
143
+ space = " "
144
+ rexp = /\A( +)/
145
+ #
146
+ width = indent_width
147
+ mark = _codeblock_indentmark() # ex: '{\starterindentmark}'
148
+ indent = space * (width - 1) + mark
149
+ nchar = space.length
150
+ return lines.map {|line|
151
+ line.sub(rexp) {
152
+ m, n = ($1.length / nchar - 1).divmod width
153
+ "#{space}#{indent * m}#{space * n}"
154
+ }
155
+ }
156
+ end
157
+
158
+ public
159
+
160
+ def list(lines, id=nil, caption=nil, lang=nil)
161
+ _codeblock("list", lines, id, caption, _codeblock_optstr(lang, false))
162
+ end
163
+ def listnum(lines, id=nil, caption=nil, lang=nil)
164
+ _codeblock("listnum", lines, id, caption, _codeblock_optstr(lang, true))
165
+ end
166
+ def emlist(lines, caption=nil, lang=nil)
167
+ _codeblock("emlist", lines, nil, caption, _codeblock_optstr(lang, false))
168
+ end
169
+ def emlistnum(lines, caption=nil, lang=nil)
170
+ _codeblock("emlistnum", lines, nil, caption, _codeblock_optstr(lang, true))
171
+ end
172
+ def source(lines, caption=nil, lang=nil)
173
+ _codeblock("source", lines, nil, caption, _codeblock_optstr(lang, false))
174
+ end
175
+ def cmd(lines, caption=nil, lang=nil)
176
+ lang ||= "shell-session"
177
+ _codeblock("cmd", lines, nil, caption, _codeblock_optstr(lang, false))
178
+ end
179
+ def _codeblock_optstr(lang, lineno_flag)
180
+ arr = []
181
+ arr << lang if lang
182
+ if lineno_flag
183
+ first_line_num = line_num()
184
+ arr << "lineno=#{first_line_num}"
185
+ arr << "linenowidth=0"
186
+ end
187
+ return arr.join(",")
188
+ end
189
+ private :_codeblock_optstr
190
+
191
+ protected
192
+
193
+ ## @<secref>{}
194
+
195
+ def _build_secref(chap, num, title, parent_title)
196
+ s = ""
197
+ ## 親セクションのタイトルがあれば使う
198
+ if parent_title
199
+ s << "「%s」内の" % parent_title # TODO: I18n化
200
+ end
201
+ ## 対象セクションへのリンクを作成する
202
+ if @book.config['chapterlink']
203
+ filename = "#{chap.id}#{extname()}"
204
+ s << "[「#{title}」](#{filename})"
205
+ else
206
+ s << "「#{title}」"
207
+ end
208
+ return s
209
+ end
210
+
211
+ public
212
+
213
+ def ul_begin()
214
+ n = _enter_ul()
215
+ blank() if n == 1
216
+ end
217
+
218
+ def ul_end()
219
+ n = _exit_ul()
220
+ blank() if n == 1
221
+ end
222
+
223
+ def ul_item(lines)
224
+ n = _depth_ul()
225
+ sp = nil
226
+ lines.each do |line|
227
+ if !sp
228
+ puts "#{' ' * (n-1)}* #{line.strip}"
229
+ sp = ' ' * n
230
+ else
231
+ puts "#{sp}#{line.strip}"
232
+ end
233
+ end
234
+ end
235
+
236
+ def _enter_ul
237
+ @_ul_depth ||= 0
238
+ @_ul_depth += 1
239
+ return @_ul_depth
240
+ end
241
+
242
+ def _exit_ul
243
+ n = @_ul_depth
244
+ @_ul_depth -= 1
245
+ return n
246
+ end
247
+
248
+ def _depth_ul
249
+ return @_ul_depth
250
+ end
251
+
252
+ ## 順序つきリスト
253
+
254
+ def ol_begin(start_num=nil)
255
+ @_ol_types ||= [] # stack
256
+ case start_num
257
+ when nil
258
+ type = "1"; start = 1
259
+ when /\A(\d+)\.\z/
260
+ type = "1"; start = $1.to_i
261
+ when /\A([A-Z])\.\z/
262
+ type = "A"; start = $1.ord - 'A'.ord + 1
263
+ when /\A([a-z])\.\z/
264
+ type = "a"; start = $1.ord - 'a'.ord + 1
265
+ else
266
+ type = nil; start = nil
267
+ end
268
+ @_ol_types.push(type)
269
+ blank() if @_ol_types.length == 1
270
+ end
271
+
272
+ def ol_end()
273
+ blank() if @_ol_types.length == 1
274
+ ol = !! @_ol_types.pop()
275
+ _ = ol
276
+ end
277
+
278
+ def ol_item(lines, num)
279
+ n = @_ol_types.length
280
+ sp = nil
281
+ lines.each do |line|
282
+ if !sp
283
+ #s = num.length == 1 ? '. ' : '. '
284
+ #puts "#{num}#{s}#{line.strip}"
285
+ #sp = ' ' * n
286
+ puts "#{num}. #{line.strip}"
287
+ sp = ' ' * "#{num}. ".length
288
+ else
289
+ puts "#{sp}#{line.strip}"
290
+ end
291
+ end
292
+ end
293
+
294
+ def ol_item_begin(lines, num)
295
+ n = @_ol_types.length
296
+ sp = nil
297
+ lines.each do |line|
298
+ if !sp
299
+ puts "#{' ' * (n-1)}- #{num} #{line.strip}"
300
+ sp = ' ' * n
301
+ else
302
+ puts "#{sp}#{line.strip}"
303
+ end
304
+ end
305
+ end
306
+
307
+ def ol_item_end()
308
+ end
309
+
310
+ def dl_begin()
311
+ blank()
312
+ puts "<dl>"
313
+ end
314
+
315
+ def dl_end()
316
+ puts "</dl>"
317
+ blank()
318
+ end
319
+
320
+ def dt(word)
321
+ #puts word
322
+ #puts "#{word}::"
323
+ puts "<dt>#{word}</dt>"
324
+ end
325
+
326
+ def dl_dd_begin()
327
+ puts "<dd>"
328
+ end
329
+
330
+ def dl_dd_end()
331
+ puts "</dd>"
332
+ end
333
+
334
+ ## 画像横に文章
335
+ def _render_sideimage(filepath, imagewidth, opts, &b)
336
+ blank()
337
+ puts "<img src=\"#{filepath}\">"
338
+ blank()
339
+ yield
340
+ end
341
+
342
+ ## 入れ子可能なブロック命令
343
+
344
+ def on_minicolumn(type, caption=nil, &b)
345
+ ## ・タイトルと1つ目の文の間に空行がないと、
346
+ ## textlintにタイトルも含めた1つの文だと認識されてしまい、
347
+ ## 「一文が長すぎる」というエラーが頻発する。
348
+ ## ・タイトルには末尾に「。」がないので、そのままだと
349
+ ## 「文末に「。」がない」というエラーが頻発する。
350
+ ## ・タイトルを <b></b> で囲むとこのエラーだけが出なくなる。
351
+ ## (他のエラーは出るまま。)
352
+ with_context(:minicolumn) do
353
+ blank2()
354
+ puts "[#{type}] <b>#{compile_inline(caption||'')}</a>"
355
+ blank()
356
+ yield
357
+ blank()
358
+ puts "[/#{type}]"
359
+ blank2()
360
+ end
361
+ end
362
+ protected :on_minicolumn
363
+
364
+ def paragraph(lines)
365
+ lines.each do |line|
366
+ puts line
367
+ end
368
+ blank()
369
+ end
370
+
371
+ #### ブロック命令
372
+
373
+ def footnote(id, str)
374
+ @_fn_count ||= 0
375
+ @_fn_labels ||= {}
376
+ n = @_fn_labels[id]
377
+ blank() # necessary?
378
+ puts "[^#{n || 9999}]: #{compile_inline(str||'')}"
379
+ blank() # necessary?
380
+ end
381
+
382
+ def texequation(lines, label=nil, caption=nil)
383
+ blank()
384
+ puts "```math"
385
+ puts lines
386
+ puts "```"
387
+ blank()
388
+ end
389
+
390
+ ## 章 (Chapter) の概要
391
+ def on_abstract_block()
392
+ blank()
393
+ puts "[abstract]"; twospaces()
394
+ yield
395
+ truncate_blank(); twospaces()
396
+ puts "[/abstract]"
397
+ blank()
398
+ end
399
+
400
+ ## 章 (Chapter) の著者
401
+ def on_chapterauthor_block(name)
402
+ puts "(著:#{name})"
403
+ blank()
404
+ end
405
+
406
+ ## 会話リスト
407
+ def _render_talklist(opts, &b)
408
+ blank()
409
+ yield
410
+ blank()
411
+ end
412
+
413
+ ## 会話項目
414
+ def on_talk_block(imagefile, name=nil, text=nil, &b)
415
+ if imagefile.present?
416
+ find_image_filepath(imagefile) or
417
+ error "//talk[#{imagefile}]: image file not found."
418
+ print "* [#{imagefile}] "
419
+ elsif name
420
+ print "* [#{name}] "
421
+ else
422
+ print "* "
423
+ end
424
+ if text.nil?
425
+ yield
426
+ else
427
+ puts compile_inline(text||'')
428
+ end
429
+ end
430
+
431
+ ## キーと説明文のリスト
432
+ def _render_desclist(opts, &b)
433
+ blank()
434
+ bkup = @_desclist_opts
435
+ @_desclist_opts = opts
436
+ yield
437
+ @_desclist_opts = bkup
438
+ blank()
439
+ end
440
+
441
+ ## キーと説明文
442
+ def _render_desc(key, &b)
443
+ opts = @_desclist_opts
444
+ puts "#{compile_inline(key)}:: "
445
+ yield
446
+ end
447
+
448
+ ## 縦方向の空きを入れる
449
+ def _render_vspace(size)
450
+ blank()
451
+ end
452
+
453
+ ## 章タイトルを独立したページに
454
+ def makechaptitlepage(option=nil)
455
+ blank()
456
+ puts ''
457
+ end
458
+
459
+ ## 縦方向のスペースがなければ改ページ
460
+ def _render_needvspace(height)
461
+ blank()
462
+ puts ''
463
+ end
464
+
465
+ ## 段(Paragraph)の終わりにスペースを入れる
466
+ def paragraphend()
467
+ blank()
468
+ end
469
+
470
+ ## 小段(Subparagraph)の終わりにスペースを入れる(あれば)
471
+ def subparagraphend()
472
+ blank()
473
+ end
474
+
475
+ ## 引用(複数段落に対応)
476
+ def blockquote(lines)
477
+ blank()
478
+ puts "[quote]"; twospaces()
479
+ puts lines
480
+ truncate_blank(); twospaces()
481
+ puts "[/quote]"
482
+ blank()
483
+ end
484
+
485
+ ## 引用(//quote{ ... //})
486
+ ## (入れ子対応なので、中に箇条書きや別のブロックを入れられる)
487
+ def on_quote_block()
488
+ blank()
489
+ puts "[quote]"; twospaces()
490
+ yield
491
+ truncate_blank(); twospaces()
492
+ puts "[/quote]"
493
+ blank()
494
+ end
495
+ def quote(lines)
496
+ on_quote_block() do
497
+ puts lines
498
+ end
499
+ end
500
+
501
+ ## 引用 (====[quote] ... ====[/quote])
502
+ ## (ブロック構文ではないので、中に別のブロックや箇条書きを入れられる)
503
+ def quote_begin(level, label, caption)
504
+ blank()
505
+ end
506
+ def quote_end(level)
507
+ blank()
508
+ end
509
+
510
+ ## ノート(//note{ ... //})
511
+ ## (入れ子対応なので、中に箇条書きや別のブロックを入れられる)
512
+ def on_note_block(label=nil, caption=nil)
513
+ ## ・ノートのタイトルと1つ目の文の間に空行がないと、
514
+ ## textlintにタイトルも含めた1つの文だと認識されてしまい、
515
+ ## 「一文が長すぎる」というエラーが頻発する。
516
+ ## ・ノートのタイトルには末尾に「。」がないので、そのままだと
517
+ ## 「文末に「。」がない」というエラーが頻発する。
518
+ ## ・タイトルを <b></b> で囲むとこのエラーだけが出なくなる。
519
+ ## (他のエラーは出るまま。)
520
+ with_context(:minicolumn) do
521
+ blank2()
522
+ caption, label = label, nil if caption.nil?
523
+ puts "[note] <b>#{compile_inline(caption||'')}</a>"
524
+ blank()
525
+ yield
526
+ blank()
527
+ puts "[/note]"
528
+ blank2()
529
+ end
530
+ end
531
+ def note(lines, label=nil, caption=nil)
532
+ on_quote_block(label, caption) do
533
+ puts lines
534
+ end
535
+ end
536
+
537
+ ## ノート (====[note] ... ====[/note])
538
+ ## (ブロック構文ではないので、中に別のブロックや箇条書きを入れられる)
539
+ def note_begin(level, label, caption)
540
+ blank()
541
+ puts "[note] #{compile_inline(caption||'')}"; twospaces()
542
+ end
543
+ def note_end(level)
544
+ truncate_blank(); twospaces()
545
+ puts "[/note]"
546
+ blank()
547
+ end
548
+
549
+ ## テーブル
550
+ def table(lines, id=nil, caption=nil, option=nil)
551
+ super
552
+ end
553
+
554
+ def table_header(id, caption, options)
555
+ if id.present?
556
+ begin
557
+ num = @chapter.table(id).number
558
+ rescue KeyError
559
+ error "no such table: #{id}"
560
+ end
561
+ s1 = I18n.t('table')
562
+ s2 = get_chap() \
563
+ ? I18n.t('format_number_header', [get_chap(), num]) \
564
+ : I18n.t('format_number_header_without_chapter', [num])
565
+ s3 = I18n.t('caption_prefix')
566
+ puts "<p class=\"caption\">#{s1}#{s2}#{s3}#{compile_inline(caption||'')}</p>"
567
+ elsif caption.present?
568
+ puts "<p class=\"caption\">#{compile_inline(caption||'')}</p>"
569
+ end
570
+ end
571
+
572
+ def table_begin(_ncols, fontsize: nil)
573
+ if fontsize
574
+ puts "<table style=\"font-size:#{fontsize}\">"
575
+ else
576
+ puts "<table>"
577
+ end
578
+ end
579
+
580
+ def table_end
581
+ puts "</table>"
582
+ end
583
+
584
+ def th(s)
585
+ "<th>#{s}</th>"
586
+ end
587
+
588
+ def td(s)
589
+ "<td>#{s}</td>"
590
+ end
591
+
592
+ protected
593
+
594
+ def normalize_id(id)
595
+ id
596
+ end
597
+
598
+ def _table_before(id, caption, opts)
599
+ class_ = opts[:csv] ? "table table-nohline" : "table"
600
+ if id.present?
601
+ puts "<div id=\"#{normalize_id(id)}\" class=\"#{class_}\">"
602
+ else
603
+ puts "<div class=\"#{class_}\">"
604
+ end
605
+ end
606
+
607
+ def _table_after(id, caption, opts)
608
+ puts "</div>"
609
+ end
610
+
611
+ def _table_bottom(hline: false)
612
+ end
613
+
614
+ def _table_tr(cells, hline: false)
615
+ if hline
616
+ "<tr class=\"hline\">#{cells.join}</tr>"
617
+ else
618
+ "<tr>#{cells.join}</tr>"
619
+ end
620
+ end
621
+
622
+ public
623
+
624
+ ## //imgtable
625
+ def imgtable(lines, id, caption=nil, option=nil)
626
+ super
627
+ end
628
+
629
+ protected
630
+
631
+ def _render_imgtable(id, caption, opts)
632
+ puts "<div id=\"#{normalize_id(id)}\" class=\"imgtable image\">"
633
+ table_header(id, caption, opts)
634
+ puts " <div>"
635
+ yield
636
+ puts " </div>"
637
+ puts "</div>"
638
+ end
639
+
640
+ def _render_imgtable_caption(caption)
641
+ end
642
+
643
+ def _render_imgtable_label(id)
644
+ end
645
+
646
+ public
647
+
648
+ #### インライン命令
649
+
650
+ def inline_fn(id)
651
+ @_fn_count ||= 0
652
+ @_fn_labels ||= {}
653
+ n = (@_fn_count += 1)
654
+ @_fn_labels[id] = n
655
+ return "[^#{n}]"
656
+ end
657
+
658
+ ## 改段落(箇条書き内では空行を入れられないため)
659
+ def inline_par(arg)
660
+ #blank()
661
+ "<br>"
662
+ end
663
+
664
+ ## ファイル名
665
+ def inline_file(str)
666
+ "`#{str}`"
667
+ end
668
+ def on_inline_file
669
+ "`#{yield}`"
670
+ end
671
+
672
+ ## ユーザ入力
673
+ def inline_userinput(str)
674
+ str
675
+ end
676
+ def on_inline_userinput
677
+ yield
678
+ end
679
+
680
+ ## 引数をそのまま表示 (No Operation)
681
+ def inline_nop(str)
682
+ str || ""
683
+ end
684
+ alias inline_letitgo inline_nop
685
+
686
+ ## 目立たせない(@<strong>{} の反対)
687
+ def inline_weak(str)
688
+ str
689
+ end
690
+ def on_inline_weak
691
+ yield
692
+ end
693
+
694
+ ## 文字を小さくする
695
+ def inline_small(str); str; end
696
+ def inline_xsmall(str); str; end
697
+ def inline_xxsmall(str); str; end
698
+ def on_inline_small(); yield; end
699
+ def on_inline_xsmall(); yield; end
700
+ def on_inline_xxsmall(); yield; end
701
+
702
+ ## 文字を大きくする
703
+ def inline_large(str); str; end
704
+ def inline_xlarge(str); str; end
705
+ def inline_xxlarge(str); str; end
706
+ def on_inline_large(); yield; end
707
+ def on_inline_xlarge(); yield; end
708
+ def on_inline_xxlarge(); yield; end
709
+
710
+ ## 文字を大きくした@<strong>{}
711
+ def inline_strong(str); str; end
712
+ def inline_xstrong(str); str; end
713
+ def inline_xxstrong(str); str; end
714
+ def on_inline_strong(); yield; end
715
+ def on_inline_xstrong(); yield; end
716
+ def on_inline_xxstrong(); yield; end
717
+
718
+ ## コードブロック中で折り返し箇所を手動で指定する
719
+ def inline_foldhere(arg)
720
+ ""
721
+ end
722
+
723
+ ## ターミナルでのカーソル(背景が白、文字が黒)
724
+ def inline_cursor(str)
725
+ "[#{str}]"
726
+ end
727
+
728
+ ## nestable inline commands
729
+
730
+ def on_inline_i() ; "*#{yield}*" ; end
731
+ #def on_inline_b() ; "**#{yield}**" ; end
732
+ #def on_inline_code() ; "`#{yield}`" ; end
733
+ def on_inline_tt() ; "<tt>#{yield}</tt>" ; end
734
+ def on_inline_del() ; "~~#{yield}~~" ; end
735
+ def on_inline_sub() ; "<sub>#{yield}</sub>" ; end
736
+ def on_inline_sup() ; "<sup>#{yield}</sup>" ; end
737
+ def on_inline_em() ; "<em>#{yield}</em>" ; end
738
+ def on_inline_strong(); "**#{yield}**" ; end
739
+ def on_inline_u() ; "<u>#{yield}</u>" ; end
740
+ def on_inline_ami() ; "<span class=\"ami\">#{yield}</span>"; end
741
+ def on_inline_balloon(); "← #{yield}"; end
742
+
743
+ def on_inline_b()
744
+ if within_codeblock?()
745
+ #"<b>#{yield}</b>"
746
+ yield
747
+ else
748
+ "**#{yield}**"
749
+ end
750
+ end
751
+
752
+ def on_inline_code()
753
+ with_context(:code) do
754
+ return "`#{yield}`"
755
+ end
756
+ end
757
+
758
+ ## 「“」と「”」で囲む
759
+ def on_inline_qq()
760
+ "“#{yield}”"
761
+ end
762
+
763
+ def build_inline_href(url, escaped_label) # compile_href()をベースに改造
764
+ if escaped_label.present?
765
+ "[#{escaped_label}](#{url})"
766
+ else
767
+ url
768
+ end
769
+ end
770
+
771
+ def inline_hlink(str)
772
+ url, label = str.split(/, /, 2)
773
+ flag_link = @book.config['externallink']
774
+ return _inline_hyperlink(url, escape(label), flag_link)
775
+ end
776
+
777
+ def _inline_hyperlink(url, escaped_label, flag_link)
778
+ if flag_link
779
+ label = escaped_label || url
780
+ "<a href=\"#{url}\">#{label}</a>"
781
+ elsif escaped_label
782
+ I18n.t('external_link', [escaped_label, url])
783
+ else
784
+ escape_html(url)
785
+ end
786
+ end
787
+ private :_inline_hyperlink
788
+
789
+ def build_inline_ruby(escaped_word, escaped_yomi) # compile_ruby()をベースに改造
790
+ "#{escaped_word}(#{escaped_yomi})"
791
+ end
792
+
793
+ protected
794
+
795
+ ## ノートを参照する
796
+ def build_noteref(chapter, label, caption)
797
+ "ノート「#{compile_inline(caption||'')}」"
798
+ end
799
+
800
+ ## 数式を参照する
801
+ def build_eq(chapter, label, number)
802
+ s = "#{I18n.t('equation')}#{chapter.number}.#{number}"
803
+ "<a>#{escape(s)}</a>"
804
+ end
805
+
806
+ public
807
+
808
+ ##########
809
+
810
+ def noindent()
811
+ end
812
+
813
+ def clearpage
814
+ blank2()
815
+ end
816
+
817
+ def on_flushright_block()
818
+ blank()
819
+ puts "[flushright]"; twospaces()
820
+ yield
821
+ truncate_blank(); twospaces()
822
+ puts "[/flushright]"
823
+ blank()
824
+ end
825
+
826
+ def on_centering_block()
827
+ blank()
828
+ puts "[centering]"; twospaces()
829
+ yield
830
+ truncate_blank(); twospaces()
831
+ puts "[/centering]"
832
+ blank()
833
+ end
834
+
835
+ def on_center_block()
836
+ blank()
837
+ puts "[center]"; twospaces()
838
+ yield
839
+ truncate_blank(); twospaces()
840
+ puts "[/center]"
841
+ blank()
842
+ end
843
+
844
+ def on_textleft_block()
845
+ blank()
846
+ puts "[textleft]"; twospaces()
847
+ yield
848
+ truncate_blank(); twospaces()
849
+ puts "[/textleft]"
850
+ blank()
851
+ end
852
+
853
+ def on_textright_block()
854
+ blank()
855
+ puts "[textright]"; twospaces()
856
+ yield
857
+ truncate_blank(); twospaces()
858
+ puts "[/textright]"
859
+ blank()
860
+ end
861
+
862
+ def on_textcenter_block()
863
+ blank()
864
+ puts "[textcenter]"; twospaces()
865
+ yield
866
+ truncate_blank(); twospaces()
867
+ puts "[/textcenter]"
868
+ blank()
869
+ end
870
+
871
+ def sampleoutputbegin(caption=nil)
872
+ blank()
873
+ if caption.present?
874
+ puts "<span class=\"caption\">▽#{compile_inline(caption)}</span>"
875
+ blank()
876
+ end
877
+ puts "----------------------------------------"
878
+ blank()
879
+ end
880
+
881
+ def sampleoutputend()
882
+ blank()
883
+ puts "----------------------------------------"
884
+ blank()
885
+ end
886
+
887
+
888
+ def on_inline_B()
889
+ "**#{yield}**"
890
+ end
891
+
892
+ def inline_br(_)
893
+ "<br>"
894
+ end
895
+
896
+ def inline_clearpage(_)
897
+ blank2()
898
+ end
899
+
900
+ def inline_m(str)
901
+ "$#{str}$"
902
+ end
903
+
904
+ def inline_icon(imagefile)
905
+ filepath = find_image_filepath(imagefile)
906
+ "<img src=\"#{filepath}\">"
907
+ end
908
+
909
+ def inline_par(arg)
910
+ "\n\n"
911
+ end
912
+
913
+ def inline_hd_chap(chap, id)
914
+ n = chap.headline_index.number(id)
915
+ if chap.number and @book.config['secnolevel'] >= n.split('.').size
916
+ str = I18n.t('chapter_quote', "#{n} #{compile_inline(chap.headline(id).caption)}")
917
+ else
918
+ str = I18n.t('chapter_quote', compile_inline(chap.headline(id).caption))
919
+ end
920
+ if @book.config['chapterlink']
921
+ anchor = 'h' + n.gsub('.', '-')
922
+ %Q(<a href="#{chap.id}#{extname}##{anchor}">#{str}</a>)
923
+ else
924
+ str
925
+ end
926
+ rescue KeyError
927
+ error "unknown headline: #{id}"
928
+ end
929
+
930
+ def inline_TeX(_)
931
+ "TeX"
932
+ end
933
+
934
+ def inline_LaTeX(_)
935
+ "LaTeX"
936
+ end
937
+
938
+ def inline_hearts(_)
939
+ ":heart:"
940
+ end
941
+
942
+ end
943
+
944
+
945
+ end