review-retrovert 0.2.2 → 0.9.1

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 (93) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/retrovert.yml +39 -0
  3. data/.gitignore +2 -2
  4. data/.ruby-version +1 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +68 -2
  7. data/README.md +6 -3
  8. data/lib/review/retrovert/cli.rb +2 -0
  9. data/lib/review/retrovert/converter.rb +255 -16
  10. data/lib/review/retrovert/version.rb +1 -1
  11. data/review-retrovert.gemspec +1 -1
  12. data/testdata/mybook/.gitignore +7 -0
  13. data/testdata/mybook/README.md +43 -0
  14. data/testdata/mybook/Rakefile +16 -0
  15. data/testdata/mybook/catalog.yml +33 -0
  16. data/testdata/mybook/config-starter.yml +139 -0
  17. data/testdata/mybook/config.yml +375 -0
  18. data/testdata/mybook/contents/00-preface.re +83 -0
  19. data/testdata/mybook/contents/01-install.re +272 -0
  20. data/testdata/mybook/contents/02-tutorial.re +1008 -0
  21. data/testdata/mybook/contents/03-syntax.re +2613 -0
  22. data/testdata/mybook/contents/04-customize.re +728 -0
  23. data/testdata/mybook/contents/05-faq.re +328 -0
  24. data/testdata/mybook/contents/06-bestpractice.re +971 -0
  25. data/testdata/mybook/contents/91-compare.re +18 -0
  26. data/testdata/mybook/contents/92-filelist.re +119 -0
  27. data/testdata/mybook/contents/93-background.re +267 -0
  28. data/testdata/mybook/contents/99-postface.re +38 -0
  29. data/testdata/mybook/css/normalize.css +349 -0
  30. data/testdata/mybook/css/webstyle.css +514 -0
  31. data/testdata/mybook/images/03-syntax/favicon-16x16.png +0 -0
  32. data/testdata/mybook/images/03-syntax/figure_heretop.png +0 -0
  33. data/testdata/mybook/images/03-syntax/tw-icon1.jpg +0 -0
  34. data/testdata/mybook/images/03-syntax/tw-icon2.jpg +0 -0
  35. data/testdata/mybook/images/03-syntax/tw-icon3.jpg +0 -0
  36. data/testdata/mybook/images/03-syntax/tw-icon4.jpg +0 -0
  37. data/testdata/mybook/images/04-customize/caption_pagebreak.png +0 -0
  38. data/testdata/mybook/images/04-customize/chaptitlepage_sample.png +0 -0
  39. data/testdata/mybook/images/05-faq/codeblock_rpadding1.png +0 -0
  40. data/testdata/mybook/images/05-faq/codeblock_rpadding2.png +0 -0
  41. data/testdata/mybook/images/06-bestpractice/figure_heretop.png +0 -0
  42. data/testdata/mybook/images/06-bestpractice/font_beramono.png +0 -0
  43. data/testdata/mybook/images/06-bestpractice/heading_design1.png +0 -0
  44. data/testdata/mybook/images/06-bestpractice/heading_design2.png +0 -0
  45. data/testdata/mybook/images/06-bestpractice/margin_book.png +0 -0
  46. data/testdata/mybook/images/06-bestpractice/multiline-title.png +0 -0
  47. data/testdata/mybook/images/06-bestpractice/preface_numbered.png +0 -0
  48. data/testdata/mybook/images/06-bestpractice/program_border.png +0 -0
  49. data/testdata/mybook/images/06-bestpractice/sechead_design_4.png +0 -0
  50. data/testdata/mybook/images/06-bestpractice/titlepage-samples.png +0 -0
  51. data/testdata/mybook/images/93-background/bug913.png +0 -0
  52. data/testdata/mybook/images/93-background/slide2.png +0 -0
  53. data/testdata/mybook/images/cover_a5.pdf +0 -0
  54. data/testdata/mybook/images/cover_b5.pdf +0 -0
  55. data/testdata/mybook/images/tw-icon.jpg +0 -0
  56. data/testdata/mybook/layouts/layout.epub.erb +29 -0
  57. data/testdata/mybook/layouts/layout.html5.erb +106 -0
  58. data/testdata/mybook/layouts/layout.tex.erb +546 -0
  59. data/testdata/mybook/lib/hooks/beforetexcompile.rb +55 -0
  60. data/testdata/mybook/lib/ruby/review-builder.rb +503 -0
  61. data/testdata/mybook/lib/ruby/review-cli.rb +58 -0
  62. data/testdata/mybook/lib/ruby/review-compiler.rb +523 -0
  63. data/testdata/mybook/lib/ruby/review-epubmaker.rb +606 -0
  64. data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +661 -0
  65. data/testdata/mybook/lib/ruby/review-latexbuilder.rb +782 -0
  66. data/testdata/mybook/lib/ruby/review-maker.rb +235 -0
  67. data/testdata/mybook/lib/ruby/review-monkeypatch.rb +91 -0
  68. data/testdata/mybook/lib/ruby/review-pdfmaker.rb +468 -0
  69. data/testdata/mybook/lib/ruby/review-textbuilder.rb +36 -0
  70. data/testdata/mybook/lib/ruby/review-tocparser.rb +285 -0
  71. data/testdata/mybook/lib/ruby/review-webmaker.rb +433 -0
  72. data/testdata/mybook/lib/tasks/mytasks.rake +31 -0
  73. data/testdata/mybook/lib/tasks/review.rake +142 -0
  74. data/testdata/mybook/lib/tasks/review.rake.orig +72 -0
  75. data/testdata/mybook/lib/tasks/starter.rake +326 -0
  76. data/testdata/mybook/locale.yml +6 -0
  77. data/testdata/mybook/review-ext.rb +206 -0
  78. data/testdata/mybook/sty/jumoline.sty +310 -0
  79. data/testdata/mybook/sty/mycolophon.sty +81 -0
  80. data/testdata/mybook/sty/mystyle.sty +8 -0
  81. data/testdata/mybook/sty/mytextsize.sty +61 -0
  82. data/testdata/mybook/sty/mytitlepage.sty +103 -0
  83. data/testdata/mybook/sty/reviewmacro.sty +60 -0
  84. data/testdata/mybook/sty/starter-codeblock.sty +332 -0
  85. data/testdata/mybook/sty/starter-color.sty +79 -0
  86. data/testdata/mybook/sty/starter-font.sty +112 -0
  87. data/testdata/mybook/sty/starter-heading.sty +514 -0
  88. data/testdata/mybook/sty/starter-note.sty +127 -0
  89. data/testdata/mybook/sty/starter-section.sty +262 -0
  90. data/testdata/mybook/sty/starter-toc.sty +72 -0
  91. data/testdata/mybook/sty/starter.sty +554 -0
  92. data/testdata/mybook/style.css +597 -0
  93. metadata +100 -3
@@ -0,0 +1,58 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ##
4
+ ## ReVIEW::PDFMaker から、CLIに関する機能を分離する
5
+ ##
6
+
7
+ require 'optparse'
8
+
9
+
10
+ module ReVIEW
11
+
12
+
13
+ class CLI
14
+
15
+ def initialize(script_name)
16
+ @script_name = script_name
17
+ end
18
+
19
+ def parse_opts(args)
20
+ parser = OptionParser.new
21
+ parser.banner = "Usage: #{@script_name} <config.yaml>"
22
+ parser.version = ReVIEW::VERSION
23
+ cmdopts = {}
24
+ parser.on('-h', '--help', 'Prints this message and quit.') do
25
+ cmdopts['help'] = true
26
+ end
27
+ parser.on('--[no-]debug', 'Keep temporary files.') do |debug|
28
+ cmdopts['debug'] = debug
29
+ end
30
+ parser.on('--ignore-errors', 'Ignore review-compile errors.') do
31
+ cmdopts['ignore-errors'] = true
32
+ end
33
+ yield parser, cmdopts if block_given?
34
+ @_cmdopt_parser = parser
35
+ parser.parse!(args)
36
+ #
37
+ return cmdopts, args if cmdopts['help']
38
+ #
39
+ if args.length < 1
40
+ raise OptionParser::InvalidOption.new("Config filename required.")
41
+ elsif args.length > 1
42
+ raise OptionParser::InvalidOption.new("Too many arguments.")
43
+ end
44
+ config_filename = args[0]
45
+ File.exist?(config_filename) or
46
+ raise OptionParser::InvalidOption.new("file '#{config_filename}' not found.")
47
+ #
48
+ return cmdopts, config_filename
49
+ end
50
+
51
+ def help_message
52
+ return @_cmdopt_parser.help
53
+ end
54
+
55
+ end
56
+
57
+
58
+ end
@@ -0,0 +1,523 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ###
4
+ ### ReVIEW::Compilerクラスとその関連クラスを拡張する
5
+ ###
6
+
7
+ require 'set'
8
+ require 'review/compiler'
9
+
10
+
11
+ module ReVIEW
12
+
13
+ defined?(Compiler) or raise "internal error: Compiler not found."
14
+
15
+
16
+ class Compiler
17
+
18
+ ## ブロック命令
19
+ defblock :program, 0..3 ## プログラム
20
+ defblock :terminal, 0..3 ## ターミナル
21
+ defblock :sideimage, 2..3 ## テキストの横に画像を表示
22
+ defblock :abstract, 0 ## 章の概要
23
+ defblock :list, 0..3 ## (上書き)
24
+ defblock :listnum, 0..3 ## (上書き)
25
+ defblock :note, 0..2 ## (上書き)
26
+ defblock :texequation, 0..2 ## (上書き)
27
+
28
+ defsingle :makechaptitlepage, 0..1 ## 章扉をつける
29
+ defsingle :needvspace, 2 ## 縦方向のスペースがなければ改ページ
30
+ defsingle :paragraphend, 0 ## 段の終わりにスペースを入れる
31
+ defsingle :subparagraphend, 0## 小段の終わりにスペースを入れる(あれば)
32
+
33
+ ## インライン命令
34
+ definline :balloon ## コード内でのふきだし説明(Re:VIEW3から追加)
35
+ definline :eq ## 数式を参照
36
+ definline :secref ## 節(Section)や項(Subsection)を参照
37
+ definline :noteref ## ノートを参照
38
+ definline :hlink ## @<href>{}の代わり
39
+ definline :file ## ファイル名
40
+ definline :userinput ## ユーザ入力
41
+ definline :nop ## 引数をそのまま表示 (No Operation)
42
+ definline :letitgo ## (nopのエイリアス名)
43
+ definline :foldhere ## 折り返し箇所を手動で指定
44
+ definline :cursor ## ターミナルでのカーソル
45
+ definline :weak ## 目立たせない(@<strong>{} の反対)
46
+ definline :small ## 文字サイズを小さく
47
+ definline :xsmall ## 文字サイズをもっと小さく
48
+ definline :xxsmall ## 文字サイズをもっともっと小さく
49
+ definline :large ## 文字サイズを大きく
50
+ definline :xlarge ## 文字サイズをもっと大きく
51
+ definline :xxlarge ## 文字サイズをもっともっと大きく
52
+ definline :xstrong ## 文字を大きくした@<strong>{}
53
+ definline :xxstrong ## 文字をもっと大きくした@<strong>{}
54
+
55
+ private
56
+
57
+ ## パーサを再帰呼び出しに対応させる
58
+
59
+ def do_compile
60
+ f = LineInput.new(StringIO.new(@chapter.content))
61
+ @strategy.bind self, @chapter, Location.new(@chapter.basename, f)
62
+ tagged_section_init
63
+ parse_document(f, false)
64
+ close_all_tagged_section
65
+ end
66
+
67
+ def parse_document(f, block_cmd)
68
+ while f.next?
69
+ case f.peek
70
+ when /\A\#@/
71
+ f.gets # Nothing to do
72
+ when /\A=+[\[\s\{]/
73
+ if block_cmd #+
74
+ line = f.gets #+
75
+ error "'#{line.strip}': should close '//#{block_cmd}' block before sectioning." #+
76
+ end #+
77
+ compile_headline f.gets
78
+ #when /\A\s+\*/ #-
79
+ # compile_ulist f #-
80
+ when LIST_ITEM_REXP #+
81
+ compile_list(f) #+
82
+ when /\A\s+\d+\./
83
+ compile_olist f
84
+ when /\A\s*:\s/
85
+ compile_dlist f
86
+ when %r{\A//\}}
87
+ return if block_cmd #+
88
+ f.gets
89
+ #error 'block end seen but not opened' #-
90
+ error "'//}': block-end found, but no block command opened." #+
91
+ #when %r{\A//[a-z]+} #-
92
+ # name, args, lines = read_command(f) #-
93
+ # syntax = syntax_descriptor(name) #-
94
+ # unless syntax #-
95
+ # error "unknown command: //#{name}" #-
96
+ # compile_unknown_command args, lines #-
97
+ # next #-
98
+ # end #-
99
+ # compile_command syntax, args, lines #-
100
+ when /\A\/\/\w+/ #+
101
+ parse_block_command(f) #+
102
+ when %r{\A//}
103
+ line = f.gets
104
+ warn "`//' seen but is not valid command: #{line.strip.inspect}"
105
+ if block_open?(line)
106
+ warn 'skipping block...'
107
+ read_block(f, false)
108
+ end
109
+ else
110
+ if f.peek.strip.empty?
111
+ f.gets
112
+ next
113
+ end
114
+ compile_paragraph f
115
+ end
116
+ end
117
+ end
118
+
119
+ ## コードブロックのタブ展開を、LaTeXコマンドの展開より先に行うよう変更。
120
+ ##
121
+ ## ・たとえば '\\' を '\\textbackslash{}' に展開してからタブを空白文字に
122
+ ## 展開しても、正しい展開にはならないことは明らか。先にタブを空白文字に
123
+ ## 置き換えてから、'\\' を '\\textbackslash{}' に展開すべき。
124
+ ## ・またタブ文字の展開は、本来はBuilderではなくCompilerで行うべきだが、
125
+ ## Re:VIEWの設計がまずいのでそうなっていない。
126
+ ## ・'//table' と '//embed' ではタブ文字の展開は行わない。
127
+ def read_block_for(cmdname, f) # 追加
128
+ disable_comment = cmdname == :embed # '//embed' では行コメントを読み飛ばさない
129
+ ignore_inline = cmdname == :embed # '//embed' ではインライン命令を解釈しない
130
+ enable_detab = cmdname !~ /\A(?:em)?table\z/ # '//table' ではタブ展開しない
131
+ f.enable_comment(false) if disable_comment
132
+ lines = read_block(f, ignore_inline, enable_detab)
133
+ f.enable_comment(true) if disable_comment
134
+ return lines
135
+ end
136
+ def read_block(f, ignore_inline, enable_detab=true) # 上書き
137
+ head = f.lineno
138
+ buf = []
139
+ builder = @strategy #+
140
+ f.until_match(%r{\A//\}}) do |line|
141
+ if ignore_inline
142
+ buf.push line
143
+ elsif line !~ /\A\#@/
144
+ #buf.push text(line.rstrip) #-
145
+ line = line.rstrip #+
146
+ line = builder.detab(line) if enable_detab #+
147
+ buf << text(line) #+
148
+ end
149
+ end
150
+ unless %r{\A//\}} =~ f.peek
151
+ error "unexpected EOF (block begins at: #{head})"
152
+ return buf
153
+ end
154
+ f.gets # discard terminator
155
+ buf
156
+ end
157
+
158
+ ## ブロック命令を入れ子可能に変更('//note' と '//quote')
159
+
160
+ def parse_block_command(f)
161
+ line = f.gets()
162
+ lineno = f.lineno
163
+ line =~ /\A\/\/(\w+)(\[.*\])?(\{)?$/ or
164
+ error "'#{line.strip}': invalid block command format."
165
+ cmdname = $1.intern; argstr = $2; curly = $3
166
+ ##
167
+ prev = @strategy.doc_status[cmdname]
168
+ @strategy.doc_status[cmdname] = true
169
+ ## 引数を取り出す
170
+ syntax = syntax_descriptor(cmdname) or
171
+ error "'//#{cmdname}': unknown command"
172
+ args = parse_args(argstr || "", cmdname)
173
+ begin
174
+ syntax.check_args args
175
+ rescue CompileError => err
176
+ error err.message
177
+ end
178
+ ## ブロックをとらないコマンドにブロックが指定されていたらエラー
179
+ if curly && !syntax.block_allowed?
180
+ error "'//#{cmdname}': this command should not take block (but given)."
181
+ end
182
+ ## ブロックの入れ子をサポートしてあれば、再帰的にパースする
183
+ handler = "on_#{cmdname}_block"
184
+ builder = @strategy
185
+ if builder.respond_to?(handler)
186
+ if curly
187
+ builder.__send__(handler, *args) do
188
+ parse_document(f, cmdname)
189
+ end
190
+ s = f.peek()
191
+ f.peek() =~ /\A\/\/}/ or
192
+ error "'//#{cmdname}': not closed (reached to EOF)"
193
+ f.gets() ## '//}' を読み捨てる
194
+ else
195
+ builder.__send__(handler, *args)
196
+ end
197
+ ## そうでなければ、従来と同じようにパースする
198
+ elsif builder.respond_to?(cmdname)
199
+ if !syntax.block_allowed?
200
+ builder.__send__(cmdname, *args)
201
+ elsif curly
202
+ lines = read_block_for(cmdname, f)
203
+ builder.__send__(cmdname, lines, *args)
204
+ else
205
+ lines = default_block(syntax)
206
+ builder.__send__(cmdname, lines, *args)
207
+ end
208
+ else
209
+ error "'//#{cmdname}': #{builder.class.name} not support this command"
210
+ end
211
+ ##
212
+ @strategy.doc_status[cmdname] = prev
213
+ end
214
+
215
+ ## 箇条書きの文法を拡張
216
+
217
+ LIST_ITEM_REXP = /\A( +)(\*+|\-+) +/ # '*' は unordred list、'-' は ordered list
218
+
219
+ def compile_list(f)
220
+ line = f.gets()
221
+ line =~ LIST_ITEM_REXP
222
+ indent = $1
223
+ char = $2[0]
224
+ $2.length == 1 or
225
+ error "#{$2[0]=='*'?'un':''}ordered list should start with level 1"
226
+ line = parse_list(f, line, indent, char, 1)
227
+ f.ungets(line)
228
+ end
229
+
230
+ def parse_list(f, line, indent, char, level)
231
+ if char != '*' && line =~ LIST_ITEM_REXP
232
+ start_num, _ = $'.lstrip().split(/\s+/, 2)
233
+ end
234
+ st = @strategy
235
+ char == '*' ? st.ul_begin { level } : st.ol_begin(start_num) { level }
236
+ while line =~ LIST_ITEM_REXP # /\A( +)(\*+|\-+) +/
237
+ $1 == indent or
238
+ error "mismatched indentation of #{$2[0]=='*'?'un':''}ordered list"
239
+ mark = $2
240
+ text = $'
241
+ if mark.length == level
242
+ break unless mark[0] == char
243
+ line = parse_item(f, text.lstrip(), indent, char, level)
244
+ elsif mark.length < level
245
+ break
246
+ else
247
+ raise "internal error"
248
+ end
249
+ end
250
+ char == '*' ? st.ul_end { level } : st.ol_end { level }
251
+ return line
252
+ end
253
+
254
+ def parse_item(f, text, indent, char, level)
255
+ if char != '*'
256
+ num, text = text.split(/\s+/, 2)
257
+ text ||= ''
258
+ end
259
+ #
260
+ buf = [parse_text(text)]
261
+ while (line = f.gets()) && line =~ /\A( +)/ && $1.length > indent.length
262
+ buf << parse_text(line)
263
+ end
264
+ #
265
+ st = @strategy
266
+ char == '*' ? st.ul_item_begin(buf) : st.ol_item_begin(buf, num)
267
+ rexp = LIST_ITEM_REXP # /\A( +)(\*+|\-+) +/
268
+ while line =~ rexp && $2.length > level
269
+ $2.length == level + 1 or
270
+ error "invalid indentation level of (un)ordred list"
271
+ line = parse_list(f, line, indent, $2[0], $2.length)
272
+ end
273
+ char == '*' ? st.ul_item_end() : st.ol_item_end()
274
+ #
275
+ return line
276
+ end
277
+
278
+ public
279
+
280
+ ## 入れ子のインライン命令をパースできるよう上書き
281
+ def parse_text(line)
282
+ stack = []
283
+ tag_name = nil
284
+ close_char = nil
285
+ items = [""]
286
+ nestable = true
287
+ scan_inline_command(line) do |text, s1, s2, s3|
288
+ if s1 # ex: '@<code>{', '@<b>{', '@<m>$'
289
+ if nestable
290
+ items << text
291
+ stack.push([tag_name, close_char, items])
292
+ s1 =~ /\A@<(\w+)>([{$|])\z/ or raise "internal error"
293
+ tag_name = $1
294
+ close_char = $2 == '{' ? '}' : $2
295
+ items = [""]
296
+ nestable = false if ignore_nested_inline_command?(tag_name)
297
+ else
298
+ items[-1] << text << s1
299
+ end
300
+ elsif s2 # '\}' or '\\' (not '\$' nor '\|')
301
+ text << (close_char == '}' ? s2[1] : s2)
302
+ items[-1] << text
303
+ elsif s3 # '}', '$', or '|'
304
+ items[-1] << text
305
+ if close_char == s3
306
+ items.delete_if {|x| x.empty? }
307
+ elem = [tag_name, {}, items]
308
+ tag_name, close_char, items = stack.pop()
309
+ items << elem << ""
310
+ nestable = true
311
+ else
312
+ items[-1] << s3
313
+ end
314
+ else
315
+ if items.length == 1 && items[-1].empty?
316
+ items[-1] = text
317
+ else
318
+ items[-1] << text
319
+ end
320
+ end
321
+ end
322
+ if tag_name
323
+ error "inline command '@<#{tag_name}>' not closed."
324
+ end
325
+ items.delete_if {|x| x.empty? }
326
+ #
327
+ return compile_inline_command(items)
328
+ end
329
+
330
+ alias text parse_text
331
+
332
+ private
333
+
334
+ def scan_inline_command(line)
335
+ pos = 0
336
+ line.scan(/(\@<\w+>[{$|])|(\\[\\}])|([}$|])/) do
337
+ m = Regexp.last_match
338
+ text = line[pos, m.begin(0)-pos]
339
+ pos = m.end(0)
340
+ yield text, $1, $2, $3
341
+ end
342
+ remained = pos == 0 ? line : line[pos..-1]
343
+ yield remained, nil, nil, nil
344
+ end
345
+
346
+ def compile_inline_command(items)
347
+ buf = ""
348
+ strategy = @strategy
349
+ items.each do |x|
350
+ case x
351
+ when String
352
+ buf << strategy.nofunc_text(x)
353
+ when Array
354
+ tag_name, attrs, children = x
355
+ op = tag_name
356
+ inline_defined?(op) or
357
+ raise CompileError, "no such inline op: #{op}"
358
+ if strategy.respond_to?("on_inline_#{op}")
359
+ buf << strategy.__send__("on_inline_#{op}") {|both_p|
360
+ if !both_p
361
+ compile_inline_command(children)
362
+ else
363
+ [compile_inline_command(children), children]
364
+ end
365
+ }
366
+ elsif strategy.respond_to?("inline_#{op}")
367
+ children.empty? || children.all? {|x| x.is_a?(String) } or
368
+ error "'@<#{op}>' does not support nested inline commands."
369
+
370
+ buf << strategy.__send__("inline_#{op}", children[0])
371
+ else
372
+ error "strategy does not support inline op: @<#{op}>"
373
+ end
374
+ else
375
+ raise "internal error: x=#{x.inspect}"
376
+ end
377
+ end
378
+ buf
379
+ end
380
+
381
+ def ignore_nested_inline_command?(tag_name)
382
+ return IGNORE_NESTED_INLINE_COMMANDS.include?(tag_name)
383
+ end
384
+
385
+ IGNORE_NESTED_INLINE_COMMANDS = Set.new(['m', 'raw', 'embed'])
386
+
387
+ end
388
+
389
+
390
+ ## コメント「#@#」を読み飛ばす(ただし //embed では読み飛ばさない)
391
+ class LineInput
392
+
393
+ def initialize(f)
394
+ super
395
+ @enable_comment = true
396
+ end
397
+
398
+ def enable_comment(flag)
399
+ @enable_comment = flag
400
+ end
401
+
402
+ def gets
403
+ line = super
404
+ if @enable_comment
405
+ while line && line =~ /\A\#\@\#/
406
+ line = super
407
+ end
408
+ end
409
+ return line
410
+ end
411
+
412
+ end
413
+
414
+
415
+ class Catalog
416
+
417
+ def parts_with_chaps
418
+ ## catalog.ymlの「CHAPS:」がnullのときエラーになるのを防ぐ
419
+ (@yaml['CHAPS'] || []).flatten.compact
420
+ end
421
+
422
+ end
423
+
424
+
425
+ class Book::ListIndex
426
+
427
+ ## '//program' と '//terminal' をサポートするよう拡張
428
+ def self.item_type # override
429
+ #'(list|listnum)' # original
430
+ '(list|listnum|program|terminal)'
431
+ end
432
+
433
+ ## '//list' や '//terminal' のラベル(第1引数)を省略できるよう拡張
434
+ def self.parse(src, *args) # override
435
+ items = []
436
+ seq = 1
437
+ src.grep(%r{\A//#{item_type}}) do |line|
438
+ if id = line.slice(/\[(.*?)\]/, 1)
439
+ next if id.empty? # 追加
440
+ items.push item_class.new(id, seq)
441
+ seq += 1
442
+ ReVIEW.logger.warn "warning: no ID of #{item_type} in #{line}" if id.empty?
443
+ end
444
+ end
445
+ new(items, *args)
446
+ end
447
+
448
+ end
449
+
450
+
451
+ ## ノートブロック(//note)のラベル用
452
+ class Book::NoteIndex < Book::Index # create new class for '//note'
453
+ Item = Struct.new(:id, :caption)
454
+
455
+ def self.parse(src_lines)
456
+ rexp = /\A\/\/note\[([^\]]+)\]\[(.*?[^\\])\]/ # $1: label, $2: caption
457
+ items = src_lines.grep(rexp) {|line|
458
+ label = $1.strip(); caption = $2.gsub(/\\\]/, ']')
459
+ next if label.empty?
460
+ label =~ /\A[-_a-zA-Z0-9]+\z/ or
461
+ error "'#{line}': invalid label (only alphabet, number, '_' or '-' available)"
462
+ Item.new(label, caption)
463
+ }.compact()
464
+ self.new(items)
465
+ end
466
+
467
+ end
468
+
469
+
470
+ ## 数式(//texequation)のラベル用
471
+ class Book::EquationIndex < Book::Index
472
+ def self.item_type
473
+ '(texequation)'
474
+ end
475
+ end
476
+
477
+
478
+ module Book::Compilable
479
+
480
+ def note(id)
481
+ note_index()[id]
482
+ end
483
+
484
+ def note_index
485
+ @note_index ||= Book::NoteIndex.parse(lines())
486
+ @note_index
487
+ end
488
+
489
+ def equation(id)
490
+ equation_index()[id]
491
+ end
492
+
493
+ def equation_index
494
+ @equation_index ||= Book::EquationIndex.parse(lines)
495
+ @equation_index
496
+ end
497
+
498
+ def content # override
499
+ ## //list[?] や //terminal[?] の '?' をランダム文字列に置き換える。
500
+ ## こうすると、重複しないラベルをいちいち指定しなくても、ソースコードや
501
+ ## ターミナルにリスト番号がつく。ただし @<list>{} での参照はできない。
502
+ unless @_done
503
+ pat = Book::ListIndex.item_type # == '(list|listnum|program|terminal)'
504
+ @content = @content.gsub(/^\/\/#{pat}\[\?\]/) { "//#{$1}[#{_random_label()}]" }
505
+ ## 改行コードを「\n」に統一する
506
+ @content = @content.gsub(/\r\n/, "\n")
507
+ ## (experimental) 範囲コメント('#@+++' '#@---')を行コメント('#@#')に変換
508
+ @content = @content.gsub(/^\#\@\+\+\+$.*?^\#\@\-\-\-$/m) { $&.gsub(/^/, '#@#') }
509
+ @_done = true
510
+ end
511
+ @content
512
+ end
513
+
514
+ module_function
515
+
516
+ def _random_label
517
+ "_" + rand().to_s[2..10]
518
+ end
519
+
520
+ end
521
+
522
+
523
+ end