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,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
|