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.
- checksums.yaml +4 -4
- data/.dockerignore +9 -0
- data/.editorconfig +3 -0
- data/.github/workflows/retrovert.yml +20 -5
- data/.gitignore +1 -0
- data/.vscode/launch.json +48 -0
- data/Gemfile.lock +13 -4
- data/Makefile +24 -0
- data/docker/review.Dockerfile +10 -0
- data/lib/review/retrovert/converter.rb +374 -111
- data/lib/review/retrovert/reviewcompat.rb +57 -0
- data/lib/review/retrovert/reviewdef.rb +70 -0
- data/lib/review/retrovert/sty/{ird.sty → ird.sty.erb} +9 -1
- data/lib/review/retrovert/sty/review-retrovert-custom.sty.erb +6 -0
- data/lib/review/retrovert/utils.rb +35 -0
- data/lib/review/retrovert/version.rb +1 -1
- data/lib/review/retrovert/yamlconfig.rb +0 -1
- data/package-lock.json +6 -0
- data/package.json +1 -0
- data/review-retrovert.gemspec +5 -1
- data/testdata/mybook/.gitignore +3 -1
- data/testdata/mybook/.textlintrc +11 -0
- data/testdata/mybook/Rakefile +8 -0
- data/testdata/mybook/catalog.yml +36 -10
- data/testdata/mybook/config-starter.yml +134 -9
- data/testdata/mybook/config.yml +44 -15
- data/testdata/mybook/contents/00-preface.re +48 -2
- data/testdata/mybook/contents/01-install.re +38 -5
- data/testdata/mybook/contents/02-tutorial.re +333 -113
- data/testdata/mybook/contents/03-syntax.re +2370 -373
- data/testdata/mybook/contents/04-customize.re +424 -88
- data/testdata/mybook/contents/05-faq.re +288 -10
- data/testdata/mybook/contents/06-bestpractice.re +431 -59
- data/testdata/mybook/contents/91-compare.re +402 -2
- data/testdata/mybook/contents/92-filelist.re +14 -8
- data/testdata/mybook/contents/93-background.re +10 -10
- data/testdata/mybook/contents/99-postface.re +2 -1
- data/testdata/mybook/contents/r0-root.re +1 -1
- data/testdata/mybook/css/webstyle.css +180 -2
- data/testdata/mybook/data/terms.txt +3 -0
- data/testdata/mybook/data/words.txt +15 -0
- data/testdata/mybook/images/03-syntax/index-page.png +0 -0
- data/testdata/mybook/images/03-syntax/order-detail.png +0 -0
- data/testdata/mybook/images/04-customize/section_decoration_samples.png +0 -0
- data/testdata/mybook/images/05-faq/dummy-image.png +0 -0
- data/testdata/mybook/images/06-bestpractice/section_title_wlines.png +0 -0
- data/testdata/mybook/images/avatar-b.png +0 -0
- data/testdata/mybook/images/avatar-g.png +0 -0
- data/testdata/mybook/images/avatar-r.png +0 -0
- data/testdata/mybook/images/caution-icon.png +0 -0
- data/testdata/mybook/images/info-icon.png +0 -0
- data/testdata/mybook/images/warning-icon.png +0 -0
- data/testdata/mybook/layouts/layout.html5.erb +3 -1
- data/testdata/mybook/layouts/layout.tex.erb +265 -379
- data/testdata/mybook/layouts/layout.tex.erb.orig +386 -0
- data/testdata/mybook/lib/ruby/review-book.rb +64 -0
- data/testdata/mybook/lib/ruby/review-builder.rb +902 -63
- data/testdata/mybook/lib/ruby/review-compiler.rb +675 -22
- data/testdata/mybook/lib/ruby/review-epubbuilder.rb +33 -0
- data/testdata/mybook/lib/ruby/review-epubmaker.rb +10 -7
- data/testdata/mybook/lib/ruby/review-htmlbuilder.rb +354 -66
- data/testdata/mybook/lib/ruby/review-latexbuilder.rb +429 -146
- data/testdata/mybook/lib/ruby/review-maker.rb +117 -6
- data/testdata/mybook/lib/ruby/review-markdownbuilder.rb +945 -0
- data/testdata/mybook/lib/ruby/review-markdownmaker.rb +91 -0
- data/testdata/mybook/lib/ruby/review-monkeypatch.rb +2 -0
- data/testdata/mybook/lib/ruby/review-pdfmaker.rb +160 -82
- data/testdata/mybook/lib/ruby/review-webmaker.rb +20 -5
- data/testdata/mybook/lib/tasks/review.rake +14 -0
- data/testdata/mybook/lib/tasks/starter.rake +148 -4
- data/testdata/mybook/sty/indexstyle.ist +25 -0
- data/testdata/mybook/sty/mytextsize.sty +29 -0
- data/testdata/mybook/sty/mytitlepage.sty +34 -11
- data/testdata/mybook/sty/review-base.sty +276 -0
- data/testdata/mybook/sty/starter-codeblock.sty +237 -106
- data/testdata/mybook/sty/starter-color.sty +72 -17
- data/testdata/mybook/sty/starter-heading.sty +60 -13
- data/testdata/mybook/sty/starter-misc.sty +894 -0
- data/testdata/mybook/sty/starter-note.sty +67 -14
- data/testdata/mybook/sty/starter-talklist.sty +105 -0
- data/testdata/mybook/sty/starter-util.sty +35 -0
- data/testdata/mybook/sty/starter.sty +8 -526
- metadata +80 -4
|
@@ -15,33 +15,601 @@ module ReVIEW
|
|
|
15
15
|
|
|
16
16
|
class Compiler
|
|
17
17
|
|
|
18
|
+
#------------------------------ original code
|
|
19
|
+
|
|
20
|
+
=begin
|
|
21
|
+
|
|
22
|
+
def initialize(strategy)
|
|
23
|
+
@strategy = strategy
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :strategy
|
|
27
|
+
|
|
28
|
+
def compile(chap)
|
|
29
|
+
@chapter = chap
|
|
30
|
+
do_compile
|
|
31
|
+
@strategy.result
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class SyntaxElement
|
|
35
|
+
def initialize(name, type, argc, &block)
|
|
36
|
+
@name = name
|
|
37
|
+
@type = type
|
|
38
|
+
@argc_spec = argc
|
|
39
|
+
@checker = block
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
attr_reader :name
|
|
43
|
+
|
|
44
|
+
def check_args(args)
|
|
45
|
+
unless @argc_spec === args.size
|
|
46
|
+
raise CompileError, "wrong # of parameters (block command //#{@name}, expect #{@argc_spec} but #{args.size})"
|
|
47
|
+
end
|
|
48
|
+
if @checker
|
|
49
|
+
@checker.call(*args)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def min_argc
|
|
54
|
+
case @argc_spec
|
|
55
|
+
when Range then @argc_spec.begin
|
|
56
|
+
when Integer then @argc_spec
|
|
57
|
+
else
|
|
58
|
+
raise TypeError, "argc_spec is not Range/Integer: #{inspect}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def block_required?
|
|
63
|
+
@type == :block
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def block_allowed?
|
|
67
|
+
@type == :block or @type == :optional
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
SYNTAX = {}
|
|
72
|
+
|
|
73
|
+
def self.defblock(name, argc, optional = false, &block)
|
|
74
|
+
defsyntax name, (optional ? :optional : :block), argc, &block
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def self.defsingle(name, argc, &block)
|
|
78
|
+
defsyntax name, :line, argc, &block
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def self.defsyntax(name, type, argc, &block)
|
|
82
|
+
SYNTAX[name] = SyntaxElement.new(name, type, argc, &block)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.definline(name)
|
|
86
|
+
INLINE[name] = InlineSyntaxElement.new(name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def syntax_defined?(name)
|
|
90
|
+
SYNTAX.key?(name.to_sym)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def syntax_descriptor(name)
|
|
94
|
+
SYNTAX[name.to_sym]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class InlineSyntaxElement
|
|
98
|
+
def initialize(name)
|
|
99
|
+
@name = name
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
attr_reader :name
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
INLINE = {}
|
|
106
|
+
|
|
107
|
+
def inline_defined?(name)
|
|
108
|
+
INLINE.key?(name.to_sym)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
defblock :read, 0
|
|
112
|
+
defblock :lead, 0
|
|
113
|
+
defblock :list, 2..3
|
|
114
|
+
defblock :emlist, 0..2
|
|
115
|
+
defblock :cmd, 0..1
|
|
116
|
+
defblock :table, 0..2
|
|
117
|
+
defblock :imgtable, 0..2
|
|
118
|
+
defblock :emtable, 0..1
|
|
119
|
+
defblock :quote, 0
|
|
120
|
+
defblock :image, 2..3, true
|
|
121
|
+
defblock :source, 0..2
|
|
122
|
+
defblock :listnum, 2..3
|
|
123
|
+
defblock :emlistnum, 0..2
|
|
124
|
+
defblock :bibpaper, 2..3, true
|
|
125
|
+
defblock :doorquote, 1
|
|
126
|
+
defblock :talk, 0
|
|
127
|
+
defblock :texequation, 0
|
|
128
|
+
defblock :graph, 1..3
|
|
129
|
+
defblock :indepimage, 1..3, true
|
|
130
|
+
defblock :numberlessimage, 1..3, true
|
|
131
|
+
|
|
132
|
+
defblock :address, 0
|
|
133
|
+
defblock :blockquote, 0
|
|
134
|
+
defblock :bpo, 0
|
|
135
|
+
defblock :flushright, 0
|
|
136
|
+
defblock :centering, 0
|
|
137
|
+
defblock :note, 0..1
|
|
138
|
+
defblock :memo, 0..1
|
|
139
|
+
defblock :info, 0..1
|
|
140
|
+
defblock :important, 0..1
|
|
141
|
+
defblock :caution, 0..1
|
|
142
|
+
defblock :notice, 0..1
|
|
143
|
+
defblock :warning, 0..1
|
|
144
|
+
defblock :tip, 0..1
|
|
145
|
+
defblock :box, 0..1
|
|
146
|
+
defblock :comment, 0..1, true
|
|
147
|
+
defblock :embed, 0..1
|
|
148
|
+
|
|
149
|
+
defsingle :footnote, 2
|
|
150
|
+
defsingle :noindent, 0
|
|
151
|
+
defsingle :blankline, 0
|
|
152
|
+
defsingle :pagebreak, 0
|
|
153
|
+
defsingle :hr, 0
|
|
154
|
+
defsingle :parasep, 0
|
|
155
|
+
defsingle :label, 1
|
|
156
|
+
defsingle :raw, 1
|
|
157
|
+
defsingle :tsize, 1
|
|
158
|
+
defsingle :include, 1
|
|
159
|
+
defsingle :olnum, 1
|
|
160
|
+
defsingle :firstlinenum, 1
|
|
161
|
+
|
|
162
|
+
definline :chapref
|
|
163
|
+
definline :chap
|
|
164
|
+
definline :title
|
|
165
|
+
definline :img
|
|
166
|
+
definline :imgref
|
|
167
|
+
definline :icon
|
|
168
|
+
definline :list
|
|
169
|
+
definline :table
|
|
170
|
+
definline :fn
|
|
171
|
+
definline :kw
|
|
172
|
+
definline :ruby
|
|
173
|
+
definline :bou
|
|
174
|
+
definline :ami
|
|
175
|
+
definline :b
|
|
176
|
+
definline :dtp
|
|
177
|
+
definline :code
|
|
178
|
+
definline :bib
|
|
179
|
+
definline :hd
|
|
180
|
+
definline :href
|
|
181
|
+
definline :recipe
|
|
182
|
+
definline :column
|
|
183
|
+
definline :tcy
|
|
184
|
+
|
|
185
|
+
definline :abbr
|
|
186
|
+
definline :acronym
|
|
187
|
+
definline :cite
|
|
188
|
+
definline :dfn
|
|
189
|
+
definline :em
|
|
190
|
+
definline :kbd
|
|
191
|
+
definline :q
|
|
192
|
+
definline :samp
|
|
193
|
+
definline :strong
|
|
194
|
+
definline :var
|
|
195
|
+
definline :big
|
|
196
|
+
definline :small
|
|
197
|
+
definline :del
|
|
198
|
+
definline :ins
|
|
199
|
+
definline :sup
|
|
200
|
+
definline :sub
|
|
201
|
+
definline :tt
|
|
202
|
+
definline :i
|
|
203
|
+
definline :tti
|
|
204
|
+
definline :ttb
|
|
205
|
+
definline :u
|
|
206
|
+
definline :raw
|
|
207
|
+
definline :br
|
|
208
|
+
definline :m
|
|
209
|
+
definline :uchar
|
|
210
|
+
definline :idx
|
|
211
|
+
definline :hidx
|
|
212
|
+
definline :comment
|
|
213
|
+
definline :include
|
|
214
|
+
definline :tcy
|
|
215
|
+
definline :embed
|
|
216
|
+
definline :pageref
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
def do_compile
|
|
221
|
+
f = LineInput.new(StringIO.new(@chapter.content))
|
|
222
|
+
@strategy.bind self, @chapter, Location.new(@chapter.basename, f)
|
|
223
|
+
tagged_section_init
|
|
224
|
+
while f.next?
|
|
225
|
+
case f.peek
|
|
226
|
+
when /\A\#@/
|
|
227
|
+
f.gets # Nothing to do
|
|
228
|
+
when /\A=+[\[\s\{]/
|
|
229
|
+
compile_headline f.gets
|
|
230
|
+
when /\A\s+\*/
|
|
231
|
+
compile_ulist f
|
|
232
|
+
when /\A\s+\d+\./
|
|
233
|
+
compile_olist f
|
|
234
|
+
when /\A\s*:\s/
|
|
235
|
+
compile_dlist f
|
|
236
|
+
when %r{\A//\}}
|
|
237
|
+
f.gets
|
|
238
|
+
error 'block end seen but not opened'
|
|
239
|
+
when %r{\A//[a-z]+}
|
|
240
|
+
name, args, lines = read_command(f)
|
|
241
|
+
syntax = syntax_descriptor(name)
|
|
242
|
+
unless syntax
|
|
243
|
+
error "unknown command: //#{name}"
|
|
244
|
+
compile_unknown_command args, lines
|
|
245
|
+
next
|
|
246
|
+
end
|
|
247
|
+
compile_command syntax, args, lines
|
|
248
|
+
when %r{\A//}
|
|
249
|
+
line = f.gets
|
|
250
|
+
warn "`//' seen but is not valid command: #{line.strip.inspect}"
|
|
251
|
+
if block_open?(line)
|
|
252
|
+
warn 'skipping block...'
|
|
253
|
+
read_block(f, false)
|
|
254
|
+
end
|
|
255
|
+
else
|
|
256
|
+
if f.peek.strip.empty?
|
|
257
|
+
f.gets
|
|
258
|
+
next
|
|
259
|
+
end
|
|
260
|
+
compile_paragraph f
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
close_all_tagged_section
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def compile_headline(line)
|
|
267
|
+
@headline_indexs ||= [@chapter.number.to_i - 1]
|
|
268
|
+
m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(line)
|
|
269
|
+
level = m[1].size
|
|
270
|
+
tag = m[2]
|
|
271
|
+
label = m[3]
|
|
272
|
+
caption = m[4].strip
|
|
273
|
+
index = level - 1
|
|
274
|
+
if tag
|
|
275
|
+
if tag !~ %r{\A/}
|
|
276
|
+
if caption.empty?
|
|
277
|
+
warn 'headline is empty.'
|
|
278
|
+
end
|
|
279
|
+
close_current_tagged_section(level)
|
|
280
|
+
open_tagged_section(tag, level, label, caption)
|
|
281
|
+
else
|
|
282
|
+
open_tag = tag[1..-1]
|
|
283
|
+
prev_tag_info = @tagged_section.pop
|
|
284
|
+
if prev_tag_info.nil? || prev_tag_info.first != open_tag
|
|
285
|
+
error "#{open_tag} is not opened."
|
|
286
|
+
end
|
|
287
|
+
close_tagged_section(*prev_tag_info)
|
|
288
|
+
end
|
|
289
|
+
else
|
|
290
|
+
if caption.empty?
|
|
291
|
+
warn 'headline is empty.'
|
|
292
|
+
end
|
|
293
|
+
if @headline_indexs.size > (index + 1)
|
|
294
|
+
@headline_indexs = @headline_indexs[0..index]
|
|
295
|
+
end
|
|
296
|
+
if @headline_indexs[index].nil?
|
|
297
|
+
@headline_indexs[index] = 0
|
|
298
|
+
end
|
|
299
|
+
@headline_indexs[index] += 1
|
|
300
|
+
close_current_tagged_section(level)
|
|
301
|
+
@strategy.headline level, label, caption
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def close_current_tagged_section(level)
|
|
306
|
+
while @tagged_section.last and @tagged_section.last[1] >= level
|
|
307
|
+
close_tagged_section(* @tagged_section.pop)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def headline(level, label, caption)
|
|
312
|
+
@strategy.headline level, label, caption
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def tagged_section_init
|
|
316
|
+
@tagged_section = []
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def open_tagged_section(tag, level, label, caption)
|
|
320
|
+
mid = "#{tag}_begin"
|
|
321
|
+
unless @strategy.respond_to?(mid)
|
|
322
|
+
error "strategy does not support tagged section: #{tag}"
|
|
323
|
+
headline level, label, caption
|
|
324
|
+
return
|
|
325
|
+
end
|
|
326
|
+
@tagged_section.push [tag, level]
|
|
327
|
+
@strategy.__send__ mid, level, label, caption
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def close_tagged_section(tag, level)
|
|
331
|
+
mid = "#{tag}_end"
|
|
332
|
+
if @strategy.respond_to?(mid)
|
|
333
|
+
@strategy.__send__ mid, level
|
|
334
|
+
else
|
|
335
|
+
error "strategy does not support block op: #{mid}"
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def close_all_tagged_section
|
|
340
|
+
until @tagged_section.empty?
|
|
341
|
+
close_tagged_section(* @tagged_section.pop)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def compile_ulist(f)
|
|
346
|
+
level = 0
|
|
347
|
+
f.while_match(/\A\s+\*|\A\#@/) do |line|
|
|
348
|
+
next if line =~ /\A\#@/
|
|
349
|
+
|
|
350
|
+
buf = [text(line.sub(/\*+/, '').strip)]
|
|
351
|
+
f.while_match(/\A\s+(?!\*)\S/) do |cont|
|
|
352
|
+
buf.push text(cont.strip)
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
line =~ /\A\s+(\*+)/
|
|
356
|
+
current_level = $1.size
|
|
357
|
+
if level == current_level
|
|
358
|
+
@strategy.ul_item_end
|
|
359
|
+
# body
|
|
360
|
+
@strategy.ul_item_begin buf
|
|
361
|
+
elsif level < current_level # down
|
|
362
|
+
level_diff = current_level - level
|
|
363
|
+
level = current_level
|
|
364
|
+
(1..(level_diff - 1)).to_a.reverse_each do |i|
|
|
365
|
+
@strategy.ul_begin { i }
|
|
366
|
+
@strategy.ul_item_begin []
|
|
367
|
+
end
|
|
368
|
+
@strategy.ul_begin { level }
|
|
369
|
+
@strategy.ul_item_begin buf
|
|
370
|
+
elsif level > current_level # up
|
|
371
|
+
level_diff = level - current_level
|
|
372
|
+
level = current_level
|
|
373
|
+
(1..level_diff).to_a.reverse_each do |i|
|
|
374
|
+
@strategy.ul_item_end
|
|
375
|
+
@strategy.ul_end { level + i }
|
|
376
|
+
end
|
|
377
|
+
@strategy.ul_item_end
|
|
378
|
+
# body
|
|
379
|
+
@strategy.ul_item_begin buf
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
(1..level).to_a.reverse_each do |i|
|
|
384
|
+
@strategy.ul_item_end
|
|
385
|
+
@strategy.ul_end { i }
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def compile_olist(f)
|
|
390
|
+
@strategy.ol_begin
|
|
391
|
+
f.while_match(/\A\s+\d+\.|\A\#@/) do |line|
|
|
392
|
+
next if line =~ /\A\#@/
|
|
393
|
+
|
|
394
|
+
num = line.match(/(\d+)\./)[1]
|
|
395
|
+
buf = [text(line.sub(/\d+\./, '').strip)]
|
|
396
|
+
f.while_match(/\A\s+(?!\d+\.)\S/) do |cont|
|
|
397
|
+
buf.push text(cont.strip)
|
|
398
|
+
end
|
|
399
|
+
@strategy.ol_item buf, num
|
|
400
|
+
end
|
|
401
|
+
@strategy.ol_end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def compile_dlist(f)
|
|
405
|
+
@strategy.dl_begin
|
|
406
|
+
while /\A\s*:/ =~ f.peek
|
|
407
|
+
@strategy.dt text(f.gets.sub(/\A\s*:/, '').strip)
|
|
408
|
+
desc = f.break(/\A(\S|\s*:|\s+\d+\.\s|\s+\*\s)/).map { |line| text(line.strip) }
|
|
409
|
+
@strategy.dd(desc)
|
|
410
|
+
f.skip_blank_lines
|
|
411
|
+
f.skip_comment_lines
|
|
412
|
+
end
|
|
413
|
+
@strategy.dl_end
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def compile_paragraph(f)
|
|
417
|
+
buf = []
|
|
418
|
+
f.until_match(%r{\A//|\A\#@}) do |line|
|
|
419
|
+
break if line.strip.empty?
|
|
420
|
+
buf.push text(line.sub(/^(\t+)\s*/) { |m| '<!ESCAPETAB!>' * m.size }.strip.gsub('<!ESCAPETAB!>', "\t"))
|
|
421
|
+
end
|
|
422
|
+
@strategy.paragraph buf
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
def read_command(f)
|
|
426
|
+
line = f.gets
|
|
427
|
+
name = line.slice(/[a-z]+/).to_sym
|
|
428
|
+
ignore_inline = (name == :embed)
|
|
429
|
+
args = parse_args(line.sub(%r{\A//[a-z]+}, '').rstrip.chomp('{'), name)
|
|
430
|
+
@strategy.doc_status[name] = true
|
|
431
|
+
lines = block_open?(line) ? read_block(f, ignore_inline) : nil
|
|
432
|
+
@strategy.doc_status[name] = nil
|
|
433
|
+
[name, args, lines]
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def block_open?(line)
|
|
437
|
+
line.rstrip[-1, 1] == '{'
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def read_block(f, ignore_inline)
|
|
441
|
+
head = f.lineno
|
|
442
|
+
buf = []
|
|
443
|
+
f.until_match(%r{\A//\}}) do |line|
|
|
444
|
+
if ignore_inline
|
|
445
|
+
buf.push line
|
|
446
|
+
elsif line !~ /\A\#@/
|
|
447
|
+
buf.push text(line.rstrip)
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
unless %r{\A//\}} =~ f.peek
|
|
451
|
+
error "unexpected EOF (block begins at: #{head})"
|
|
452
|
+
return buf
|
|
453
|
+
end
|
|
454
|
+
f.gets # discard terminator
|
|
455
|
+
buf
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
def parse_args(str, _name = nil)
|
|
459
|
+
return [] if str.empty?
|
|
460
|
+
scanner = StringScanner.new(str)
|
|
461
|
+
words = []
|
|
462
|
+
while word = scanner.scan(/(\[\]|\[.*?[^\\]\])/)
|
|
463
|
+
w2 = word[1..-2].gsub(/\\(.)/) do
|
|
464
|
+
ch = $1
|
|
465
|
+
(ch == ']' or ch == '\\') ? ch : '\\' + ch
|
|
466
|
+
end
|
|
467
|
+
words << w2
|
|
468
|
+
end
|
|
469
|
+
unless scanner.eos?
|
|
470
|
+
error "argument syntax error: #{scanner.rest} in #{str.inspect}"
|
|
471
|
+
return []
|
|
472
|
+
end
|
|
473
|
+
words
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def compile_command(syntax, args, lines)
|
|
477
|
+
unless @strategy.respond_to?(syntax.name)
|
|
478
|
+
error "strategy does not support command: //#{syntax.name}"
|
|
479
|
+
compile_unknown_command args, lines
|
|
480
|
+
return
|
|
481
|
+
end
|
|
482
|
+
begin
|
|
483
|
+
syntax.check_args args
|
|
484
|
+
rescue CompileError => err
|
|
485
|
+
error err.message
|
|
486
|
+
args = ['(NoArgument)'] * syntax.min_argc
|
|
487
|
+
end
|
|
488
|
+
if syntax.block_allowed?
|
|
489
|
+
compile_block syntax, args, lines
|
|
490
|
+
else
|
|
491
|
+
if lines
|
|
492
|
+
error "block is not allowed for command //#{syntax.name}; ignore"
|
|
493
|
+
end
|
|
494
|
+
compile_single syntax, args
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def compile_unknown_command(args, lines)
|
|
499
|
+
@strategy.unknown_command args, lines
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def compile_block(syntax, args, lines)
|
|
503
|
+
@strategy.__send__(syntax.name, (lines || default_block(syntax)), *args)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def default_block(syntax)
|
|
507
|
+
if syntax.block_required?
|
|
508
|
+
error "block is required for //#{syntax.name}; use empty block"
|
|
509
|
+
end
|
|
510
|
+
[]
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def compile_single(syntax, args)
|
|
514
|
+
@strategy.__send__(syntax.name, *args)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def replace_fence(str)
|
|
518
|
+
str.gsub(/@<(\w+)>([$|])(.+?)(\2)/) do
|
|
519
|
+
op = $1
|
|
520
|
+
arg = $3.gsub('@', "\x01").gsub('\\}') { '\\\\}' }.gsub('}') { '\}' }.sub(/(?:\\)+$/) { |m| '\\\\' * m.size }
|
|
521
|
+
"@<#{op}>{#{arg}}"
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def text(str)
|
|
526
|
+
return '' if str.empty?
|
|
527
|
+
words = replace_fence(str).split(/(@<\w+>\{(?:[^\}\\]|\\.)*?\})/, -1)
|
|
528
|
+
words.each do |w|
|
|
529
|
+
if w.scan(/@<\w+>/).size > 1 && !/\A@<raw>/.match(w)
|
|
530
|
+
error "`@<xxx>' seen but is not valid inline op: #{w}"
|
|
531
|
+
end
|
|
532
|
+
end
|
|
533
|
+
result = @strategy.nofunc_text(words.shift)
|
|
534
|
+
until words.empty?
|
|
535
|
+
result << compile_inline(words.shift.gsub(/\\\}/, '}').gsub(/\\\\/, '\\'))
|
|
536
|
+
result << @strategy.nofunc_text(words.shift)
|
|
537
|
+
end
|
|
538
|
+
result.gsub("\x01", '@')
|
|
539
|
+
rescue => err
|
|
540
|
+
error err.message
|
|
541
|
+
end
|
|
542
|
+
public :text # called from strategy
|
|
543
|
+
|
|
544
|
+
def compile_inline(str)
|
|
545
|
+
op, arg = /\A@<(\w+)>\{(.*?)\}\z/.match(str).captures
|
|
546
|
+
unless inline_defined?(op)
|
|
547
|
+
raise CompileError, "no such inline op: #{op}"
|
|
548
|
+
end
|
|
549
|
+
unless @strategy.respond_to?("inline_#{op}")
|
|
550
|
+
raise "strategy does not support inline op: @<#{op}>"
|
|
551
|
+
end
|
|
552
|
+
@strategy.__send__("inline_#{op}", arg)
|
|
553
|
+
rescue => err
|
|
554
|
+
error err.message
|
|
555
|
+
@strategy.nofunc_text(str)
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
def warn(msg)
|
|
559
|
+
@strategy.warn msg
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def error(msg)
|
|
563
|
+
@strategy.error msg
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
=end
|
|
567
|
+
|
|
568
|
+
#------------------------------
|
|
569
|
+
|
|
18
570
|
## ブロック命令
|
|
19
571
|
defblock :program, 0..3 ## プログラム
|
|
20
572
|
defblock :terminal, 0..3 ## ターミナル
|
|
573
|
+
defblock :output, 0..3 ## 出力結果
|
|
21
574
|
defblock :sideimage, 2..3 ## テキストの横に画像を表示
|
|
22
575
|
defblock :abstract, 0 ## 章の概要
|
|
576
|
+
defblock :chapterauthor, 1 ## 章の著者
|
|
577
|
+
defblock :talklist, 0..1 ## 会話リスト
|
|
578
|
+
defblock :talk, 1..3, true ## 会話項目
|
|
579
|
+
defblock :t, 1..3, true ## 会話項目(ショートカット用)
|
|
580
|
+
defblock :desclist, 0..1 ## キーと説明文のリスト
|
|
581
|
+
defblock :desc, 1..2, true ## キーと説明文のリスト
|
|
23
582
|
defblock :list, 0..3 ## (上書き)
|
|
24
583
|
defblock :listnum, 0..3 ## (上書き)
|
|
25
584
|
defblock :note, 0..2 ## (上書き)
|
|
26
585
|
defblock :texequation, 0..2 ## (上書き)
|
|
586
|
+
defblock :table, 0..3 ## (上書き)
|
|
587
|
+
defblock :imgtable, 0..3 ## (上書き)
|
|
27
588
|
|
|
28
589
|
defsingle :makechaptitlepage, 0..1 ## 章扉をつける
|
|
29
590
|
defsingle :needvspace, 2 ## 縦方向のスペースがなければ改ページ
|
|
30
591
|
defsingle :paragraphend, 0 ## 段の終わりにスペースを入れる
|
|
31
592
|
defsingle :subparagraphend, 0## 小段の終わりにスペースを入れる(あれば)
|
|
593
|
+
defsingle :vspace, 2 ## 縦方向の空きを入れる(\vspace)
|
|
594
|
+
defsingle :addvspace, 2 ## 縦方向の空きを入れる(\addvspace)
|
|
595
|
+
defsingle :tsize, 1..2 ## (上書き)
|
|
32
596
|
|
|
33
597
|
## インライン命令
|
|
598
|
+
definline :par ## 箇条書き内で改段落するのに使う
|
|
34
599
|
definline :balloon ## コード内でのふきだし説明(Re:VIEW3から追加)
|
|
35
600
|
definline :eq ## 数式を参照
|
|
36
601
|
definline :secref ## 節(Section)や項(Subsection)を参照
|
|
37
602
|
definline :noteref ## ノートを参照
|
|
38
603
|
definline :hlink ## @<href>{}の代わり
|
|
604
|
+
definline :term ## @<idx>{}かつゴシック体
|
|
605
|
+
definline :termnoidx ## ゴシック体にするだけで索引には登録しない
|
|
39
606
|
definline :file ## ファイル名
|
|
40
607
|
definline :userinput ## ユーザ入力
|
|
41
608
|
definline :nop ## 引数をそのまま表示 (No Operation)
|
|
42
609
|
definline :letitgo ## (nopのエイリアス名)
|
|
43
610
|
definline :foldhere ## 折り返し箇所を手動で指定
|
|
44
611
|
definline :cursor ## ターミナルでのカーソル
|
|
612
|
+
definline :qq ## 「``」と「''」で囲う
|
|
45
613
|
definline :weak ## 目立たせない(@<strong>{} の反対)
|
|
46
614
|
definline :small ## 文字サイズを小さく
|
|
47
615
|
definline :xsmall ## 文字サイズをもっと小さく
|
|
@@ -51,6 +619,9 @@ module ReVIEW
|
|
|
51
619
|
definline :xxlarge ## 文字サイズをもっともっと大きく
|
|
52
620
|
definline :xstrong ## 文字を大きくした@<strong>{}
|
|
53
621
|
definline :xxstrong ## 文字をもっと大きくした@<strong>{}
|
|
622
|
+
definline :w ## キーに対応した単語に展開
|
|
623
|
+
definline :wb ## キーに対応した単語に展開、かつ太字で表示
|
|
624
|
+
definline :W ## キーに対応した単語に展開、かつ強調表示
|
|
54
625
|
|
|
55
626
|
private
|
|
56
627
|
|
|
@@ -59,11 +630,13 @@ module ReVIEW
|
|
|
59
630
|
def do_compile
|
|
60
631
|
f = LineInput.new(StringIO.new(@chapter.content))
|
|
61
632
|
@strategy.bind self, @chapter, Location.new(@chapter.basename, f)
|
|
62
|
-
tagged_section_init
|
|
633
|
+
tagged_section_init()
|
|
63
634
|
parse_document(f, false)
|
|
64
|
-
close_all_tagged_section
|
|
635
|
+
close_all_tagged_section()
|
|
65
636
|
end
|
|
66
637
|
|
|
638
|
+
BLOCK_END_REXP = /\A\/\/\}\s*$/
|
|
639
|
+
|
|
67
640
|
def parse_document(f, block_cmd)
|
|
68
641
|
while f.next?
|
|
69
642
|
case f.peek
|
|
@@ -83,7 +656,8 @@ module ReVIEW
|
|
|
83
656
|
compile_olist f
|
|
84
657
|
when /\A\s*:\s/
|
|
85
658
|
compile_dlist f
|
|
86
|
-
when %r{\A//\}}
|
|
659
|
+
#when %r{\A//\}} #-
|
|
660
|
+
when BLOCK_END_REXP #+
|
|
87
661
|
return if block_cmd #+
|
|
88
662
|
f.gets
|
|
89
663
|
#error 'block end seen but not opened' #-
|
|
@@ -126,35 +700,59 @@ module ReVIEW
|
|
|
126
700
|
## ・'//table' と '//embed' ではタブ文字の展開は行わない。
|
|
127
701
|
def read_block_for(cmdname, f) # 追加
|
|
128
702
|
disable_comment = cmdname == :embed # '//embed' では行コメントを読み飛ばさない
|
|
129
|
-
ignore_inline = cmdname
|
|
703
|
+
ignore_inline = _ignore_inline?(cmdname) # '//embed' と '//table' ではインライン命令を解釈しない
|
|
130
704
|
enable_detab = cmdname !~ /\A(?:em)?table\z/ # '//table' ではタブ展開しない
|
|
131
705
|
f.enable_comment(false) if disable_comment
|
|
132
|
-
lines = read_block(f, ignore_inline, enable_detab)
|
|
706
|
+
lines = read_block(f, ignore_inline, enable_detab) { "//#{cmdname}" }
|
|
133
707
|
f.enable_comment(true) if disable_comment
|
|
134
708
|
return lines
|
|
135
709
|
end
|
|
710
|
+
def _ignore_inline?(cmdname)
|
|
711
|
+
return RAW_BLOCK_COMMANDS[cmdname]
|
|
712
|
+
end
|
|
136
713
|
def read_block(f, ignore_inline, enable_detab=true) # 上書き
|
|
137
714
|
head = f.lineno
|
|
138
715
|
buf = []
|
|
139
716
|
builder = @strategy #+
|
|
140
|
-
f.until_match(%r{\A//\}}) do |line|
|
|
717
|
+
#f.until_match(%r{\A//\}}) do |line| #-
|
|
718
|
+
f.until_match(BLOCK_END_REXP) do |line| #+
|
|
141
719
|
if ignore_inline
|
|
142
720
|
buf.push line
|
|
143
721
|
elsif line !~ /\A\#@/
|
|
144
722
|
#buf.push text(line.rstrip) #-
|
|
145
723
|
line = line.rstrip #+
|
|
146
724
|
line = builder.detab(line) if enable_detab #+
|
|
147
|
-
buf <<
|
|
725
|
+
buf << parse_text(line) #+
|
|
148
726
|
end
|
|
149
727
|
end
|
|
150
|
-
unless %r{\A//\}} =~ f.peek
|
|
151
|
-
|
|
728
|
+
#unless %r{\A//\}} =~ f.peek #-
|
|
729
|
+
unless f.peek() =~ BLOCK_END_REXP #+
|
|
730
|
+
if block_given? #+
|
|
731
|
+
error "#{yield} (at line #{head}): block command not closed." #+
|
|
732
|
+
else #+
|
|
733
|
+
error "unexpected EOF (block begins at: #{head})"
|
|
734
|
+
end #+
|
|
152
735
|
return buf
|
|
153
736
|
end
|
|
154
737
|
f.gets # discard terminator
|
|
155
738
|
buf
|
|
156
739
|
end
|
|
157
740
|
|
|
741
|
+
RAW_BLOCK_COMMANDS = {
|
|
742
|
+
embed: true,
|
|
743
|
+
raw: true,
|
|
744
|
+
table: true,
|
|
745
|
+
list: true,
|
|
746
|
+
emlist: true,
|
|
747
|
+
listnum: true,
|
|
748
|
+
emlistnum: true,
|
|
749
|
+
source: true,
|
|
750
|
+
program: true,
|
|
751
|
+
terminal: true,
|
|
752
|
+
cmd: true,
|
|
753
|
+
output: true,
|
|
754
|
+
}
|
|
755
|
+
|
|
158
756
|
## ブロック命令を入れ子可能に変更('//note' と '//quote')
|
|
159
757
|
|
|
160
758
|
def parse_block_command(f)
|
|
@@ -188,7 +786,7 @@ module ReVIEW
|
|
|
188
786
|
parse_document(f, cmdname)
|
|
189
787
|
end
|
|
190
788
|
s = f.peek()
|
|
191
|
-
f.peek() =~
|
|
789
|
+
f.peek() =~ BLOCK_END_REXP or
|
|
192
790
|
error "'//#{cmdname}': not closed (reached to EOF)"
|
|
193
791
|
f.gets() ## '//}' を読み捨てる
|
|
194
792
|
else
|
|
@@ -275,6 +873,57 @@ module ReVIEW
|
|
|
275
873
|
return line
|
|
276
874
|
end
|
|
277
875
|
|
|
876
|
+
def compile_dlist(f)
|
|
877
|
+
@strategy.dl_begin()
|
|
878
|
+
while /\A\s*:/ =~ f.peek()
|
|
879
|
+
dtext = f.gets.sub(/\A\s*:/, '').strip
|
|
880
|
+
@strategy.dt(parse_text(dtext))
|
|
881
|
+
buf = []
|
|
882
|
+
li_rexp = LIST_ITEM_REXP
|
|
883
|
+
indent = nil
|
|
884
|
+
first_p = true
|
|
885
|
+
@strategy.dl_dd_begin()
|
|
886
|
+
while (line = f.peek()) =~ /\A( [ \t]+|\t\s*)/ || line =~ /\A\s*$/
|
|
887
|
+
indent ||= $1
|
|
888
|
+
if indent
|
|
889
|
+
line =~ /\A\s*$/ || line.start_with?(indent) or
|
|
890
|
+
warn "` : #{dtext}': indent mismatched (maybe space and tab are mixed)"
|
|
891
|
+
if _dl_start_list?(line, indent, li_rexp)
|
|
892
|
+
buf, first_p = _dl_par(buf, first_p) unless buf.empty?
|
|
893
|
+
compile_list(f)
|
|
894
|
+
@strategy.noindent # disable indent just after ordered/unordered list
|
|
895
|
+
next
|
|
896
|
+
end
|
|
897
|
+
end
|
|
898
|
+
if line =~ /\A\s*$/
|
|
899
|
+
buf, first_p = _dl_par(buf, first_p) unless buf.empty?
|
|
900
|
+
else
|
|
901
|
+
buf << parse_text(line)
|
|
902
|
+
end
|
|
903
|
+
f.gets()
|
|
904
|
+
end
|
|
905
|
+
_, _ = _dl_par(buf, first_p) unless buf.empty?
|
|
906
|
+
@strategy.dl_dd_end()
|
|
907
|
+
f.skip_blank_lines()
|
|
908
|
+
f.skip_comment_lines()
|
|
909
|
+
end
|
|
910
|
+
@strategy.dl_end()
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def _dl_start_list?(line, indent, li_rexp)
|
|
914
|
+
return line.start_with?(indent) && line.sub(indent, '') =~ li_rexp && line =~ li_rexp
|
|
915
|
+
end
|
|
916
|
+
|
|
917
|
+
def _dl_par(buf, first_p)
|
|
918
|
+
return buf, first_p if buf.empty?
|
|
919
|
+
if first_p
|
|
920
|
+
@strategy.puts buf.join
|
|
921
|
+
else
|
|
922
|
+
@strategy.paragraph(buf)
|
|
923
|
+
end
|
|
924
|
+
return [], false
|
|
925
|
+
end
|
|
926
|
+
|
|
278
927
|
public
|
|
279
928
|
|
|
280
929
|
## 入れ子のインライン命令をパースできるよう上書き
|
|
@@ -366,10 +1015,9 @@ module ReVIEW
|
|
|
366
1015
|
elsif strategy.respond_to?("inline_#{op}")
|
|
367
1016
|
children.empty? || children.all? {|x| x.is_a?(String) } or
|
|
368
1017
|
error "'@<#{op}>' does not support nested inline commands."
|
|
369
|
-
|
|
370
1018
|
buf << strategy.__send__("inline_#{op}", children[0])
|
|
371
1019
|
else
|
|
372
|
-
error "strategy does not support inline op: @<#{op}>"
|
|
1020
|
+
error "strategy does not support inline op: @<#{op}> (strategy.class=#{strategy.class})"
|
|
373
1021
|
end
|
|
374
1022
|
else
|
|
375
1023
|
raise "internal error: x=#{x.inspect}"
|
|
@@ -382,7 +1030,7 @@ module ReVIEW
|
|
|
382
1030
|
return IGNORE_NESTED_INLINE_COMMANDS.include?(tag_name)
|
|
383
1031
|
end
|
|
384
1032
|
|
|
385
|
-
IGNORE_NESTED_INLINE_COMMANDS = Set.new(['m', 'raw', 'embed'])
|
|
1033
|
+
IGNORE_NESTED_INLINE_COMMANDS = Set.new(['m', 'raw', 'embed', 'idx', 'hidx', 'term'])
|
|
386
1034
|
|
|
387
1035
|
end
|
|
388
1036
|
|
|
@@ -424,22 +1072,22 @@ module ReVIEW
|
|
|
424
1072
|
|
|
425
1073
|
class Book::ListIndex
|
|
426
1074
|
|
|
427
|
-
## '//program' と '//terminal' をサポートするよう拡張
|
|
1075
|
+
## '//program' と '//terminal' と '//output' をサポートするよう拡張
|
|
428
1076
|
def self.item_type # override
|
|
429
1077
|
#'(list|listnum)' # original
|
|
430
|
-
'(list|listnum|program|terminal)'
|
|
1078
|
+
'(list|listnum|program|terminal|output)'
|
|
431
1079
|
end
|
|
432
1080
|
|
|
433
1081
|
## '//list' や '//terminal' のラベル(第1引数)を省略できるよう拡張
|
|
434
1082
|
def self.parse(src, *args) # override
|
|
435
1083
|
items = []
|
|
436
1084
|
seq = 1
|
|
437
|
-
src.grep(%r{\A//#{item_type}}) do |line|
|
|
1085
|
+
src.grep(%r{\A//#{item_type()}}) do |line|
|
|
438
1086
|
if id = line.slice(/\[(.*?)\]/, 1)
|
|
439
1087
|
next if id.empty? # 追加
|
|
440
|
-
items.push item_class.new(id, seq)
|
|
1088
|
+
items.push item_class().new(id, seq)
|
|
441
1089
|
seq += 1
|
|
442
|
-
ReVIEW.logger.warn "warning: no ID of #{item_type} in #{line}" if id.empty?
|
|
1090
|
+
ReVIEW.logger.warn "warning: no ID of #{item_type()} in #{line}" if id.empty?
|
|
443
1091
|
end
|
|
444
1092
|
end
|
|
445
1093
|
new(items, *args)
|
|
@@ -491,7 +1139,7 @@ module ReVIEW
|
|
|
491
1139
|
end
|
|
492
1140
|
|
|
493
1141
|
def equation_index
|
|
494
|
-
@equation_index ||= Book::EquationIndex.parse(lines)
|
|
1142
|
+
@equation_index ||= Book::EquationIndex.parse(lines())
|
|
495
1143
|
@equation_index
|
|
496
1144
|
end
|
|
497
1145
|
|
|
@@ -500,8 +1148,10 @@ module ReVIEW
|
|
|
500
1148
|
## こうすると、重複しないラベルをいちいち指定しなくても、ソースコードや
|
|
501
1149
|
## ターミナルにリスト番号がつく。ただし @<list>{} での参照はできない。
|
|
502
1150
|
unless @_done
|
|
503
|
-
|
|
504
|
-
|
|
1151
|
+
pat1 = Book::ListIndex.item_type # == '(list|listnum|program|terminal|output)'
|
|
1152
|
+
pat2 = Book::TableIndex.item_type # == '(table|imgtable)'
|
|
1153
|
+
pat = "#{pat1[1..-2]}|#{pat2[1..-2]}"
|
|
1154
|
+
@content = @content.gsub(/^\/\/(#{pat})\[\?\]/) { "//#{$1}[#{_random_label()}]" }
|
|
505
1155
|
## 改行コードを「\n」に統一する
|
|
506
1156
|
@content = @content.gsub(/\r\n/, "\n")
|
|
507
1157
|
## (experimental) 範囲コメント('#@+++' '#@---')を行コメント('#@#')に変換
|
|
@@ -514,9 +1164,12 @@ module ReVIEW
|
|
|
514
1164
|
module_function
|
|
515
1165
|
|
|
516
1166
|
def _random_label
|
|
517
|
-
"_" + rand().to_s[2..10]
|
|
1167
|
+
#"_" + rand().to_s[2..10]
|
|
1168
|
+
"_" + RANDOM.rand().to_s[2..10]
|
|
518
1169
|
end
|
|
519
1170
|
|
|
1171
|
+
RANDOM = Random.new(22360679)
|
|
1172
|
+
|
|
520
1173
|
end
|
|
521
1174
|
|
|
522
1175
|
|