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.
- 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 +68 -2
- data/README.md +6 -3
- data/lib/review/retrovert/cli.rb +2 -0
- data/lib/review/retrovert/converter.rb +255 -16
- 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
|