review-retrovert 0.3.3 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/retrovert.yml +39 -0
- data/.gitignore +2 -2
- data/.ruby-version +1 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +67 -1
- data/README.md +6 -3
- data/lib/review/retrovert/converter.rb +231 -6
- data/lib/review/retrovert/version.rb +1 -1
- data/review-retrovert.gemspec +1 -1
- data/testdata/mybook/.gitignore +7 -0
- data/testdata/mybook/README.md +43 -0
- data/testdata/mybook/Rakefile +16 -0
- data/testdata/mybook/catalog.yml +33 -0
- data/testdata/mybook/config-starter.yml +139 -0
- data/testdata/mybook/config.yml +375 -0
- data/testdata/mybook/contents/00-preface.re +83 -0
- data/testdata/mybook/contents/01-install.re +272 -0
- data/testdata/mybook/contents/02-tutorial.re +1008 -0
- data/testdata/mybook/contents/03-syntax.re +2613 -0
- data/testdata/mybook/contents/04-customize.re +728 -0
- data/testdata/mybook/contents/05-faq.re +328 -0
- data/testdata/mybook/contents/06-bestpractice.re +971 -0
- data/testdata/mybook/contents/91-compare.re +18 -0
- data/testdata/mybook/contents/92-filelist.re +119 -0
- data/testdata/mybook/contents/93-background.re +267 -0
- data/testdata/mybook/contents/99-postface.re +38 -0
- data/testdata/mybook/css/normalize.css +349 -0
- data/testdata/mybook/css/webstyle.css +514 -0
- data/testdata/mybook/images/03-syntax/favicon-16x16.png +0 -0
- data/testdata/mybook/images/03-syntax/figure_heretop.png +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon1.jpg +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon2.jpg +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon3.jpg +0 -0
- data/testdata/mybook/images/03-syntax/tw-icon4.jpg +0 -0
- data/testdata/mybook/images/04-customize/caption_pagebreak.png +0 -0
- data/testdata/mybook/images/04-customize/chaptitlepage_sample.png +0 -0
- data/testdata/mybook/images/05-faq/codeblock_rpadding1.png +0 -0
- data/testdata/mybook/images/05-faq/codeblock_rpadding2.png +0 -0
- data/testdata/mybook/images/06-bestpractice/figure_heretop.png +0 -0
- data/testdata/mybook/images/06-bestpractice/font_beramono.png +0 -0
- data/testdata/mybook/images/06-bestpractice/heading_design1.png +0 -0
- data/testdata/mybook/images/06-bestpractice/heading_design2.png +0 -0
- data/testdata/mybook/images/06-bestpractice/margin_book.png +0 -0
- data/testdata/mybook/images/06-bestpractice/multiline-title.png +0 -0
- data/testdata/mybook/images/06-bestpractice/preface_numbered.png +0 -0
- data/testdata/mybook/images/06-bestpractice/program_border.png +0 -0
- data/testdata/mybook/images/06-bestpractice/sechead_design_4.png +0 -0
- data/testdata/mybook/images/06-bestpractice/titlepage-samples.png +0 -0
- data/testdata/mybook/images/93-background/bug913.png +0 -0
- data/testdata/mybook/images/93-background/slide2.png +0 -0
- data/testdata/mybook/images/cover_a5.pdf +0 -0
- data/testdata/mybook/images/cover_b5.pdf +0 -0
- data/testdata/mybook/images/tw-icon.jpg +0 -0
- data/testdata/mybook/layouts/layout.epub.erb +29 -0
- data/testdata/mybook/layouts/layout.html5.erb +106 -0
- data/testdata/mybook/layouts/layout.tex.erb +546 -0
- data/testdata/mybook/lib/hooks/beforetexcompile.rb +55 -0
- data/testdata/mybook/lib/ruby/review-builder.rb +503 -0
- data/testdata/mybook/lib/ruby/review-cli.rb +58 -0
- data/testdata/mybook/lib/ruby/review-compiler.rb +523 -0
- data/testdata/mybook/lib/ruby/review-epubmaker.rb +606 -0
- data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +661 -0
- data/testdata/mybook/lib/ruby/review-latexbuilder.rb +782 -0
- data/testdata/mybook/lib/ruby/review-maker.rb +235 -0
- data/testdata/mybook/lib/ruby/review-monkeypatch.rb +91 -0
- data/testdata/mybook/lib/ruby/review-pdfmaker.rb +468 -0
- data/testdata/mybook/lib/ruby/review-textbuilder.rb +36 -0
- data/testdata/mybook/lib/ruby/review-tocparser.rb +285 -0
- data/testdata/mybook/lib/ruby/review-webmaker.rb +433 -0
- data/testdata/mybook/lib/tasks/mytasks.rake +31 -0
- data/testdata/mybook/lib/tasks/review.rake +142 -0
- data/testdata/mybook/lib/tasks/review.rake.orig +72 -0
- data/testdata/mybook/lib/tasks/starter.rake +326 -0
- data/testdata/mybook/locale.yml +6 -0
- data/testdata/mybook/review-ext.rb +206 -0
- data/testdata/mybook/sty/jumoline.sty +310 -0
- data/testdata/mybook/sty/mycolophon.sty +81 -0
- data/testdata/mybook/sty/mystyle.sty +8 -0
- data/testdata/mybook/sty/mytextsize.sty +61 -0
- data/testdata/mybook/sty/mytitlepage.sty +103 -0
- data/testdata/mybook/sty/reviewmacro.sty +60 -0
- data/testdata/mybook/sty/starter-codeblock.sty +332 -0
- data/testdata/mybook/sty/starter-color.sty +79 -0
- data/testdata/mybook/sty/starter-font.sty +112 -0
- data/testdata/mybook/sty/starter-heading.sty +514 -0
- data/testdata/mybook/sty/starter-note.sty +127 -0
- data/testdata/mybook/sty/starter-section.sty +262 -0
- data/testdata/mybook/sty/starter-toc.sty +72 -0
- data/testdata/mybook/sty/starter.sty +554 -0
- data/testdata/mybook/style.css +597 -0
- metadata +100 -3
@@ -0,0 +1,55 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
##
|
5
|
+
## *.re を *.tex に変換したあとに実行されるスクリプト。
|
6
|
+
## 第1引数:作業用展開ディレクトリ
|
7
|
+
## 第2引数:呼び出しを実行したディレクトリ
|
8
|
+
##
|
9
|
+
|
10
|
+
|
11
|
+
##
|
12
|
+
## @arg texdir 作業用展開ディレクトリ
|
13
|
+
## @arg srcdir 呼び出しを実行したディレクトリ
|
14
|
+
##
|
15
|
+
def main(texdir, srcdir)
|
16
|
+
Dir.glob("#{texdir}/*.tex").each do |filename|
|
17
|
+
s1 = File.open(filename, "rt:utf-8") {|f| f.read }
|
18
|
+
s2 = fix_reviewcolumn(s1) # コラムをいろいろ修正
|
19
|
+
if s1 != s2
|
20
|
+
origfile = filename + ".orig"
|
21
|
+
File.rename(filename, origfile) unless File.exist?(origfile)
|
22
|
+
File.open(filename, "w:utf-8") {|f| f.write(s2) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
##
|
28
|
+
## コラム(「==[column] タイトル」)を変換したあとのLaTeXコードを修正する
|
29
|
+
##
|
30
|
+
def fix_reviewcolumn(content)
|
31
|
+
return content.gsub(/^\\begin\{reviewcolumn\}$.*?^\\end\{reviewcolumn\}$/m) {|str|
|
32
|
+
##
|
33
|
+
## これ↓だと脚注が消えてしまうので
|
34
|
+
##
|
35
|
+
## \begin{reviewcolumn}
|
36
|
+
## 本文\footnotemark{}
|
37
|
+
## \footnotetext[1]{脚注} % ←コラム内に脚注がある
|
38
|
+
## \end{reviewcolumn}
|
39
|
+
##
|
40
|
+
## こう↓修正する
|
41
|
+
##
|
42
|
+
## \begin{reviewcolumn}
|
43
|
+
## 本文\footnotemark{}
|
44
|
+
## \end{reviewcolumn}
|
45
|
+
## \footnotetext[1]{脚注} % ←コラム外に脚注を移動する
|
46
|
+
##
|
47
|
+
fntexts = []
|
48
|
+
str = str.gsub(/^\\footnotetext\[\d+\]\{.*?\}\r?\n/m) {|s| fntexts << s; "" }
|
49
|
+
str << "\n" << fntexts.join() if fntexts
|
50
|
+
##
|
51
|
+
str
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
main(*ARGV)
|
@@ -0,0 +1,503 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
###
|
4
|
+
### ReVIEW::Builderクラスを拡張する
|
5
|
+
###
|
6
|
+
|
7
|
+
require 'review/builder'
|
8
|
+
|
9
|
+
|
10
|
+
module ReVIEW
|
11
|
+
|
12
|
+
defined?(Builder) or raise "internal error: Builder not found."
|
13
|
+
|
14
|
+
|
15
|
+
class Builder
|
16
|
+
|
17
|
+
## Re:VIEW3で追加されたもの
|
18
|
+
def on_inline_balloon(arg)
|
19
|
+
return "← #{yield}"
|
20
|
+
end
|
21
|
+
|
22
|
+
## ul_item_begin() だけあって ol_item_begin() がないのはどうかと思う。
|
23
|
+
## ol の入れ子がないからといって、こういう非対称な設計はやめてほしい。
|
24
|
+
def ol_item_begin(lines, _num)
|
25
|
+
ol_item(lines, _num)
|
26
|
+
end
|
27
|
+
def ol_item_end()
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def truncate_if_endwith?(str)
|
33
|
+
sio = @output # StringIO object
|
34
|
+
if sio.string.end_with?(str)
|
35
|
+
pos = sio.pos - str.length
|
36
|
+
sio.seek(pos)
|
37
|
+
sio.truncate(pos)
|
38
|
+
true
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def enter_context(key)
|
45
|
+
@doc_status[key] = true
|
46
|
+
end
|
47
|
+
|
48
|
+
def exit_context(key)
|
49
|
+
@doc_status[key] = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def with_context(key)
|
53
|
+
enter_context(key)
|
54
|
+
return yield
|
55
|
+
ensure
|
56
|
+
exit_context(key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def within_context?(key)
|
60
|
+
return @doc_status[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
def within_codeblock?
|
64
|
+
d = @doc_status
|
65
|
+
d[:program] || d[:terminal] \
|
66
|
+
|| d[:list] || d[:emlist] || d[:listnum] || d[:emlistnum] \
|
67
|
+
|| d[:cmd] || d[:source]
|
68
|
+
end
|
69
|
+
|
70
|
+
## 入れ子可能なブロック命令
|
71
|
+
|
72
|
+
public
|
73
|
+
|
74
|
+
def on_note_block caption=nil, &b; on_minicolumn :note , caption, &b; end
|
75
|
+
def on_memo_block caption=nil, &b; on_minicolumn :memo , caption, &b; end
|
76
|
+
def on_tip_block caption=nil, &b; on_minicolumn :tip , caption, &b; end
|
77
|
+
def on_info_block caption=nil, &b; on_minicolumn :info , caption, &b; end
|
78
|
+
def on_warning_block caption=nil, &b; on_minicolumn :warning , caption, &b; end
|
79
|
+
def on_important_block caption=nil, &b; on_minicolumn :important, caption, &b; end
|
80
|
+
def on_caution_block caption=nil, &b; on_minicolumn :caution , caption, &b; end
|
81
|
+
def on_notice_block caption=nil, &b; on_minicolumn :notice , caption, &b; end
|
82
|
+
|
83
|
+
def on_minicolumn(type, caption=nil, &b)
|
84
|
+
raise NotImplementedError.new("#{self.class.name}#on_minicolumn(): not implemented yet.")
|
85
|
+
end
|
86
|
+
protected :on_minicolumn
|
87
|
+
|
88
|
+
def on_sideimage_block(imagefile, imagewidth, option_str=nil, &b)
|
89
|
+
raise NotImplementedError.new("#{self.class.name}#on_sideimage_block(): not implemented yet.")
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_sideimage_args(imagefile, imagewidth, option_str)
|
93
|
+
opts = {}
|
94
|
+
if option_str.present?
|
95
|
+
option_str.split(',').each do |kv|
|
96
|
+
kv.strip!
|
97
|
+
next if kv.empty?
|
98
|
+
kv =~ /(\w[-\w]*)=(.*)/ or
|
99
|
+
error "//sideimage: [#{option_str}]: invalid option string."
|
100
|
+
opts[$1] = $2
|
101
|
+
end
|
102
|
+
end
|
103
|
+
#
|
104
|
+
opts.each do |k, v|
|
105
|
+
case k
|
106
|
+
when 'side'
|
107
|
+
v == 'L' || v == 'R' or
|
108
|
+
error "//sideimage: [#{option_str}]: 'side=' should be 'L' or 'R'."
|
109
|
+
when 'boxwidth'
|
110
|
+
v =~ /\A\d+(\.\d+)?(%|mm|cm|zw)\z/ or
|
111
|
+
error "//sideimage: [#{option_str}]: 'boxwidth=' invalid (expected such as 10%, 30mm, 3.0cm, or 5zw)"
|
112
|
+
when 'sep'
|
113
|
+
v =~ /\A\d+(\.\d+)?(%|mm|cm|zw)\z/ or
|
114
|
+
error "//sideimage: [#{option_str}]: 'sep=' invalid (expected such as 2%, 5mm, 0.5cm, or 1zw)"
|
115
|
+
when 'border'
|
116
|
+
v =~ /\A(on|off)\z/ or
|
117
|
+
error "//sideimage: [#{option_str}]: 'border=' should be 'on' or 'off'"
|
118
|
+
opts[k] = v == 'on' ? true : false
|
119
|
+
else
|
120
|
+
error "//sideimage: [#{option_str}]: unknown option '#{k}=#{v}'."
|
121
|
+
end
|
122
|
+
end
|
123
|
+
#
|
124
|
+
imagefile.present? or
|
125
|
+
error "//sideimage: 1st option (image file) required."
|
126
|
+
imagewidth.present? or
|
127
|
+
error "//sideimage: 2nd option (image width) required."
|
128
|
+
imagewidth =~ /\A\d+(\.\d+)?(%|mm|cm|zw|pt)\z/ or
|
129
|
+
error "//sideimage: [#{imagewidth}]: invalid image width (expected such as: 30mm, 3.0cm, 5zw, or 100pt)"
|
130
|
+
#
|
131
|
+
return imagefile, imagewidth, opts
|
132
|
+
end
|
133
|
+
protected :validate_sideimage_args
|
134
|
+
|
135
|
+
## コードブロック(//program, //terminal)
|
136
|
+
|
137
|
+
CODEBLOCK_OPTIONS = {
|
138
|
+
'fold' => true,
|
139
|
+
'lineno' => false,
|
140
|
+
'linenowidth' => -1,
|
141
|
+
'eolmark' => false,
|
142
|
+
'foldmark' => true,
|
143
|
+
'lang' => nil,
|
144
|
+
}
|
145
|
+
|
146
|
+
## プログラム用ブロック命令
|
147
|
+
## ・//list と似ているが、長い行を自動的に折り返す
|
148
|
+
## ・seqsplit.styの「\seqsplit{...}」コマンドを使っている
|
149
|
+
def program(lines, id=nil, caption=nil, optionstr=nil)
|
150
|
+
_codeblock('program', lines, id, caption, optionstr)
|
151
|
+
end
|
152
|
+
|
153
|
+
## ターミナル用ブロック命令
|
154
|
+
## ・//cmd と似ているが、長い行を自動的に折り返す
|
155
|
+
## ・seqsplit.styの「\seqsplit{...}」コマンドを使っている
|
156
|
+
def terminal(lines, id=nil, caption=nil, optionstr=nil)
|
157
|
+
_codeblock('terminal', lines, id, caption, optionstr)
|
158
|
+
end
|
159
|
+
|
160
|
+
protected
|
161
|
+
|
162
|
+
def _codeblock(blockname, lines, id, caption, optionstr)
|
163
|
+
raise NotImplementedError.new("#{self.class.name}#_codeblock(): not implemented yet.")
|
164
|
+
end
|
165
|
+
|
166
|
+
def _each_block_option(option_str)
|
167
|
+
option_str.split(',').each do |kvs|
|
168
|
+
k, v = kvs.split('=', 2)
|
169
|
+
yield k, v
|
170
|
+
end if option_str && !option_str.empty?
|
171
|
+
end
|
172
|
+
|
173
|
+
def _parse_codeblock_optionstr(optionstr, blockname) # parse 'fold={on|off},...'
|
174
|
+
opts = {}
|
175
|
+
return opts if optionstr.nil? || optionstr.empty?
|
176
|
+
vals = {nil=>true, 'on'=>true, 'off'=>false}
|
177
|
+
optionstr.split(',').each_with_index do |x, i|
|
178
|
+
x = x.strip()
|
179
|
+
## //list[][][1] は //list[][][lineno=1] とみなす
|
180
|
+
if x =~ /\A[0-9]+\z/
|
181
|
+
opts['lineno'] = x.to_i
|
182
|
+
next
|
183
|
+
end
|
184
|
+
#
|
185
|
+
x =~ /\A([-\w]+)(?:=(.*))?\z/ or
|
186
|
+
raise "//#{blockname}[][][#{x}]: invalid option format."
|
187
|
+
k, v = $1, $2
|
188
|
+
case k
|
189
|
+
when 'fold', 'eolmark', 'foldmark'
|
190
|
+
if vals.key?(v)
|
191
|
+
opts[k] = vals[v]
|
192
|
+
else
|
193
|
+
raise "//#{blockname}[][][#{x}]: expected 'on' or 'off'."
|
194
|
+
end
|
195
|
+
when 'lineno'
|
196
|
+
if vals.key?(v)
|
197
|
+
opts[k] = vals[v]
|
198
|
+
elsif v =~ /\A\d+\z/
|
199
|
+
opts[k] = v.to_i
|
200
|
+
elsif v =~ /\A\d+-?\d*(?:\&+\d+-?\d*)*\z/
|
201
|
+
opts[k] = v
|
202
|
+
else
|
203
|
+
raise "//#{blockname}[][][#{x}]: expected line number pattern."
|
204
|
+
end
|
205
|
+
when 'linenowidth'
|
206
|
+
if v =~ /\A-?\d+\z/
|
207
|
+
opts[k] = v.to_i
|
208
|
+
else
|
209
|
+
raise "//#{blockname}[][][#{x}]: expected integer value."
|
210
|
+
end
|
211
|
+
when 'fontsize'
|
212
|
+
if v =~ /\A((x-|xx-)?small|(x-|xx-)?large)\z/
|
213
|
+
opts[k] = v
|
214
|
+
else
|
215
|
+
raise "//#{blockname}[][][#{x}]: expected small/x-small/xx-small."
|
216
|
+
end
|
217
|
+
when 'indentwidth'
|
218
|
+
if v =~ /\A\d+\z/
|
219
|
+
opts[k] = v.to_i
|
220
|
+
else
|
221
|
+
raise "//#{blockname}[][][#{x}]: expected integer (>=0)."
|
222
|
+
end
|
223
|
+
when 'lang'
|
224
|
+
if v
|
225
|
+
opts[k] = v
|
226
|
+
else
|
227
|
+
raise "//#{blockname}[][][#{x}]: requires option value."
|
228
|
+
end
|
229
|
+
else
|
230
|
+
if i == 0
|
231
|
+
opts['lang'] = v
|
232
|
+
else
|
233
|
+
raise "//#{blockname}[][][#{x}]: unknown option."
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
return opts
|
238
|
+
end
|
239
|
+
|
240
|
+
def _build_caption_str(id, caption)
|
241
|
+
str = ""
|
242
|
+
with_context(:caption) do
|
243
|
+
str = compile_inline(caption) if caption.present?
|
244
|
+
end
|
245
|
+
if id.present?
|
246
|
+
number = _build_caption_number(id)
|
247
|
+
prefix = "#{I18n.t('list')}#{number}#{I18n.t('caption_prefix')}"
|
248
|
+
str = "#{prefix}#{str}"
|
249
|
+
end
|
250
|
+
return str
|
251
|
+
end
|
252
|
+
|
253
|
+
def _build_caption_number(id)
|
254
|
+
chapter = get_chap()
|
255
|
+
number = @chapter.list(id).number
|
256
|
+
return chapter.nil? \
|
257
|
+
? I18n.t('format_number_header_without_chapter', [number]) \
|
258
|
+
: I18n.t('format_number_header', [chapter, number])
|
259
|
+
rescue KeyError
|
260
|
+
error "no such list: #{id}"
|
261
|
+
end
|
262
|
+
|
263
|
+
public
|
264
|
+
|
265
|
+
## インライン命令のバグ修正
|
266
|
+
|
267
|
+
def inline_raw(str)
|
268
|
+
name = target_name()
|
269
|
+
if str =~ /\A\|(.*?)\|/
|
270
|
+
str = $'
|
271
|
+
return "" unless $1.split(',').any? {|s| s.strip == name }
|
272
|
+
end
|
273
|
+
return str.gsub('\\n', "\n")
|
274
|
+
end
|
275
|
+
|
276
|
+
def inline_embed(str)
|
277
|
+
name = target_name()
|
278
|
+
if str =~ /\A\|(.*?)\|/
|
279
|
+
str = $'
|
280
|
+
return "" unless $1.split(',').any? {|s| s.strip == name }
|
281
|
+
end
|
282
|
+
return str
|
283
|
+
end
|
284
|
+
|
285
|
+
## インライン命令を入れ子に対応させる
|
286
|
+
|
287
|
+
def on_inline_href
|
288
|
+
escaped_str, items = yield true
|
289
|
+
url = label = nil
|
290
|
+
separator1 = /, /
|
291
|
+
separator2 = /(?<=[^\\}]),/ # 「\\,」はセパレータと見なさない
|
292
|
+
[separator1, separator2].each do |sep|
|
293
|
+
pair = items[0].split(sep, 2)
|
294
|
+
if pair.length == 2
|
295
|
+
url = pair[0]
|
296
|
+
label = escaped_str.split(sep, 2)[-1] # 「,」がエスケープされない前提
|
297
|
+
break
|
298
|
+
end
|
299
|
+
end
|
300
|
+
url ||= items[0]
|
301
|
+
url = url.gsub(/\\,/, ',') # 「\\,」を「,」に置換
|
302
|
+
return build_inline_href(url, label)
|
303
|
+
end
|
304
|
+
|
305
|
+
def on_inline_ruby
|
306
|
+
escaped_str = yield
|
307
|
+
arr = escaped_str.split(', ')
|
308
|
+
if arr.length > 1 # ex: @<ruby>{小鳥遊, たかなし}
|
309
|
+
yomi = arr.pop().strip()
|
310
|
+
word = arr.join(', ')
|
311
|
+
elsif escaped_str =~ /,([^,]*)\z/ # ex: @<ruby>{小鳥遊,たかなし}
|
312
|
+
word, yomi = $`, $1.strip()
|
313
|
+
else
|
314
|
+
error "@<ruby>: missing yomi, should be '@<ruby>{word, yomi}' style."
|
315
|
+
end
|
316
|
+
return build_inline_ruby(word, yomi)
|
317
|
+
end
|
318
|
+
|
319
|
+
## 節 (Section) や項 (Subsection) を参照する。
|
320
|
+
## 引数 id が節や項のラベルでないなら、エラー。
|
321
|
+
## 使い方: @<secref>{label}
|
322
|
+
def inline_secref(id) # 参考:ReVIEW::Builder#inline_hd(id)
|
323
|
+
## 本来、こういった処理はParserで行うべきであり、Builderで行うのはおかしい。
|
324
|
+
## しかしRe:VIEWのアーキテクチャがよくないせいで、こうせざるを得ない。無念。
|
325
|
+
sec_id = id
|
326
|
+
chapter = nil
|
327
|
+
if id =~ /\A([^|]+)\|(.+)/
|
328
|
+
chap_id = $1; sec_id = $2
|
329
|
+
chapter = @book.contents.detect {|chap| chap.id == chap_id } or
|
330
|
+
error "@<secref>{#{id}}: chapter '#{chap_id}' not found."
|
331
|
+
end
|
332
|
+
begin
|
333
|
+
_inline_secref(chapter || @chapter, sec_id)
|
334
|
+
rescue KeyError
|
335
|
+
error "@<secref>{#{id}}: section (or subsection) not found."
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
private
|
340
|
+
|
341
|
+
def _inline_secref(chap, id)
|
342
|
+
sec_id = chap.headline(id).id
|
343
|
+
num, title = _get_secinfo(chap, sec_id)
|
344
|
+
level = num.split('.').length
|
345
|
+
#
|
346
|
+
secnolevel = @book.config['secnolevel']
|
347
|
+
if secnolevel + 1 < level
|
348
|
+
error "'secnolevel: #{secnolevel}' should be >= #{level-1} in config.yml"
|
349
|
+
## config.ymlの「secnolevel:」が3以上なら、項 (Subsection) にも
|
350
|
+
## 番号がつく。なので、節 (Section) のタイトルは必要ない。
|
351
|
+
elsif secnolevel + 1 > level
|
352
|
+
parent_title = nil
|
353
|
+
## そうではない場合は、節 (Section) と項 (Subsection) とを組み合わせる。
|
354
|
+
## たとえば、"「1.1 イントロダクション」内の「はじめに」" のように。
|
355
|
+
elsif secnolevel + 1 == level
|
356
|
+
parent_id = sec_id.sub(/\|[^|]+\z/, '')
|
357
|
+
_, parent_title = _get_secinfo(chap, parent_id)
|
358
|
+
else
|
359
|
+
raise "not reachable"
|
360
|
+
end
|
361
|
+
#
|
362
|
+
return _build_secref(chap, num, title, parent_title)
|
363
|
+
end
|
364
|
+
|
365
|
+
def _get_secinfo(chap, id) # 参考:ReVIEW::LATEXBuilder#inline_hd_chap()
|
366
|
+
num = chap.headline_index.number(id)
|
367
|
+
caption = compile_inline(chap.headline(id).caption)
|
368
|
+
if chap.number && @book.config['secnolevel'] >= num.split('.').size
|
369
|
+
caption = "#{chap.headline_index.number(id)} #{caption}"
|
370
|
+
end
|
371
|
+
#title = I18n.t('chapter_quote', caption)
|
372
|
+
title = caption
|
373
|
+
return num, title
|
374
|
+
end
|
375
|
+
|
376
|
+
def _build_secref(chap, num, title, parent_title)
|
377
|
+
raise NotImplementedError.new("#{self.class.name}#_build_secref(): not implemented yet.")
|
378
|
+
end
|
379
|
+
|
380
|
+
##
|
381
|
+
|
382
|
+
public
|
383
|
+
|
384
|
+
## ノートを参照する
|
385
|
+
def inline_noteref(label)
|
386
|
+
begin
|
387
|
+
chapter, label = parse_reflabel(label)
|
388
|
+
rescue KeyError => ex
|
389
|
+
error "@<noteref>{#{label}}: #{ex.message}"
|
390
|
+
end
|
391
|
+
begin
|
392
|
+
item = (chapter || @chapter).note(label)
|
393
|
+
rescue KeyError => ex
|
394
|
+
error "@<noteref>{#{label}}: note not found."
|
395
|
+
end
|
396
|
+
build_noteref(chapter, label, item.caption)
|
397
|
+
end
|
398
|
+
|
399
|
+
## 数式を参照する
|
400
|
+
def inline_eq(label)
|
401
|
+
begin
|
402
|
+
chapter, label = parse_reflabel(label)
|
403
|
+
rescue KeyError => ex
|
404
|
+
error "@<eq>{#{label}}: #{ex.message}"
|
405
|
+
end
|
406
|
+
begin
|
407
|
+
item = (chapter || @chapter).equation(label)
|
408
|
+
rescue KeyError => ex
|
409
|
+
error "@<eq>{#{label}}: equation not found."
|
410
|
+
end
|
411
|
+
build_eq(chapter || @chapter, label, item.number)
|
412
|
+
end
|
413
|
+
|
414
|
+
protected
|
415
|
+
|
416
|
+
def build_noteref(chapter, label, caption)
|
417
|
+
raise NotImplementedError.new("#{self.class.name}#build_noteref(): not implemented yet.")
|
418
|
+
end
|
419
|
+
|
420
|
+
def build_eq(chapter, label, caption)
|
421
|
+
raise NotImplementedError.new("#{self.class.name}#build_noteref(): not implemented yet.")
|
422
|
+
end
|
423
|
+
|
424
|
+
def parse_reflabel(label)
|
425
|
+
## 本来ならParserで行うべきだけど仕方ない。
|
426
|
+
chapter = nil
|
427
|
+
if label =~ /\A([^|]+)\|(.+)/
|
428
|
+
chap_id = $1; label = $2
|
429
|
+
chapter = @book.contents.detect {|chap| chap.id == chap_id } or
|
430
|
+
raise KeyError.new("chapter '#{chap_id}' not found.")
|
431
|
+
return chapter, label
|
432
|
+
end
|
433
|
+
return chapter, label
|
434
|
+
end
|
435
|
+
|
436
|
+
##
|
437
|
+
|
438
|
+
protected
|
439
|
+
|
440
|
+
def find_image_filepath(image_id)
|
441
|
+
finder = get_image_finder()
|
442
|
+
filepath = finder.find_path(image_id)
|
443
|
+
return filepath
|
444
|
+
end
|
445
|
+
|
446
|
+
def get_image_finder()
|
447
|
+
imagedir = "#{@book.basedir}/#{@book.config['imagedir']}"
|
448
|
+
types = @book.image_types
|
449
|
+
builder = @book.config['builder']
|
450
|
+
chap_id = @chapter.id
|
451
|
+
return ReVIEW::Book::ImageFinder.new(imagedir, chap_id, builder, types)
|
452
|
+
end
|
453
|
+
|
454
|
+
end
|
455
|
+
|
456
|
+
|
457
|
+
##
|
458
|
+
## 行番号を生成するクラス。
|
459
|
+
##
|
460
|
+
## gen = LineNumberGenerator.new("1-3&8-10&15-")
|
461
|
+
## p gen.each.take(15).to_a
|
462
|
+
## #=> [1, 2, 3, nil, 8, 9, 10, nil, 15, 16, 17, 18, 19, 20, 21]
|
463
|
+
##
|
464
|
+
class LineNumberGenerator
|
465
|
+
|
466
|
+
def initialize(arg)
|
467
|
+
@ranges = []
|
468
|
+
inf = Float::INFINITY
|
469
|
+
case arg
|
470
|
+
when true ; @ranges << (1 .. inf)
|
471
|
+
when Integer ; @ranges << (arg .. inf)
|
472
|
+
when /\A(\d+)\z/ ; @ranges << (arg.to_i .. inf)
|
473
|
+
else
|
474
|
+
arg.split('&', -1).each do |str|
|
475
|
+
case str
|
476
|
+
when /\A\z/
|
477
|
+
@ranges << nil
|
478
|
+
when /\A(\d+)\z/
|
479
|
+
@ranges << ($1.to_i .. $1.to_i)
|
480
|
+
when /\A(\d+)\-(\d+)?\z/
|
481
|
+
start = $1.to_i
|
482
|
+
end_ = $2 ? $2.to_i : inf
|
483
|
+
@ranges << (start..end_)
|
484
|
+
else
|
485
|
+
raise ArgumentError.new("'#{strpat}': invalid lineno format")
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|
489
|
+
end
|
490
|
+
|
491
|
+
def each(&block)
|
492
|
+
return enum_for(:each) unless block_given?
|
493
|
+
for range in @ranges
|
494
|
+
range.each(&block) if range
|
495
|
+
yield nil
|
496
|
+
end
|
497
|
+
nil
|
498
|
+
end
|
499
|
+
|
500
|
+
end
|
501
|
+
|
502
|
+
|
503
|
+
end
|