maruku 0.4.1 → 0.4.2
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.
- data/bin/maruku +41 -25
- data/docs/char.html +1924 -0
- data/docs/entity_test.html +2 -2
- data/docs/exd.html +92 -0
- data/docs/index.html +77 -23
- data/docs/markdown_syntax.html +6 -6
- data/docs/maruku.html +75 -21
- data/docs/maruku.md +60 -26
- data/docs/proposal.html +105 -123
- data/docs/proposal.md +121 -109
- data/lib/maruku/attributes.rb +33 -1
- data/lib/maruku/defaults.rb +8 -0
- data/lib/maruku/input/charsource.rb +8 -2
- data/lib/maruku/input/parse_block.rb +44 -18
- data/lib/maruku/input/parse_doc.rb +28 -26
- data/lib/maruku/input/parse_span_better.rb +57 -41
- data/lib/maruku/input/type_detection.rb +3 -2
- data/lib/maruku/output/to_html.rb +6 -6
- data/lib/maruku/output/to_latex.rb +21 -5
- data/lib/maruku/structures.rb +0 -4
- data/lib/maruku/tests/new_parser.rb +6 -1
- data/lib/maruku/version.rb +1 -1
- data/tests/unittest/attributes/att2.md +36 -0
- data/tests/unittest/attributes/att3.md +55 -0
- data/tests/unittest/attributes/attributes.md +23 -18
- data/tests/unittest/attributes/circular.md +9 -9
- data/tests/unittest/email.md +2 -2
- data/tests/unittest/links.md +2 -2
- data/tests/unittest/misc_sw.md +1 -1
- data/tests/unittest/syntax_hl.md +3 -5
- data/tests/unittest/xml_instruction.md +1 -2
- metadata +6 -2
@@ -41,9 +41,11 @@ CharSource = CharSourceManual # faster! 58ms vs. 65ms
|
|
41
41
|
class CharSourceManual
|
42
42
|
include MaRuKu::Strings
|
43
43
|
|
44
|
-
def initialize(s)
|
44
|
+
def initialize(s, parent=nil)
|
45
|
+
raise "Passed #{s.class}" if not s.kind_of? String
|
45
46
|
@buffer = s
|
46
47
|
@buffer_index = 0
|
48
|
+
@parent = parent
|
47
49
|
end
|
48
50
|
|
49
51
|
# Return current char as a FixNum (or nil).
|
@@ -140,7 +142,11 @@ class CharSourceManual
|
|
140
142
|
end
|
141
143
|
|
142
144
|
def describe
|
143
|
-
describe_pos(@buffer, @buffer_index)
|
145
|
+
s = describe_pos(@buffer, @buffer_index)
|
146
|
+
if @parent
|
147
|
+
s += "\n\n" + @parent.describe
|
148
|
+
end
|
149
|
+
s
|
144
150
|
end
|
145
151
|
include SpanLevelParser
|
146
152
|
end
|
@@ -24,6 +24,15 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
24
24
|
include Helpers
|
25
25
|
include MaRuKu::Strings
|
26
26
|
include MaRuKu::In::Markdown::SpanLevelParser
|
27
|
+
|
28
|
+
class BlockContext < Array
|
29
|
+
def describe
|
30
|
+
n = 5
|
31
|
+
desc = size > n ? self[-n,n] : self
|
32
|
+
"Last #{n} elements: "+
|
33
|
+
desc.map{|x| "\n -" + x.inspect}.join
|
34
|
+
end
|
35
|
+
end
|
27
36
|
|
28
37
|
# Splits the string and calls parse_lines_as_markdown
|
29
38
|
def parse_text_as_markdown(text)
|
@@ -32,25 +41,23 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
32
41
|
return parse_blocks(src)
|
33
42
|
end
|
34
43
|
|
44
|
+
# Input is a LineSource
|
35
45
|
def parse_blocks(src)
|
36
|
-
output =
|
46
|
+
output = BlockContext.new
|
37
47
|
|
38
48
|
# run state machine
|
39
49
|
while src.cur_line
|
40
50
|
# Prints detected type (useful for debugging)
|
41
|
-
|
51
|
+
# puts "#{src.cur_line.md_type}|#{src.cur_line}"
|
42
52
|
case src.cur_line.md_type
|
43
53
|
when :empty;
|
54
|
+
output.push :empty
|
44
55
|
src.ignore_line
|
45
56
|
when :ial
|
46
|
-
m =
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
else
|
51
|
-
maruku_error "An attribute list at beginning of context {#{al.to_md}}", src
|
52
|
-
maruku_recover "I will ignore this AL: {#{al.to_md}}", src
|
53
|
-
end
|
57
|
+
m = InlineAttributeList.match src.shift_line
|
58
|
+
content = m[1] || ""
|
59
|
+
src2 = CharSource.new(content, src)
|
60
|
+
interpret_extension(src2, output, [nil])
|
54
61
|
when :ald
|
55
62
|
output.push read_ald(src)
|
56
63
|
when :text
|
@@ -61,7 +68,8 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
61
68
|
output.push read_header12(src)
|
62
69
|
elsif eventually_comes_a_def_list(src)
|
63
70
|
definition = read_definition(src)
|
64
|
-
if output.last &&
|
71
|
+
if output.last.kind_of?(MDElement) &&
|
72
|
+
output.last.node_type == :definition_list then
|
65
73
|
output.last.children << definition
|
66
74
|
else
|
67
75
|
output.push md_el(:definition_list, [definition])
|
@@ -79,7 +87,8 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
79
87
|
list_type = src.cur_line.md_type == :ulist ? :ul : :ol
|
80
88
|
li = read_list_item(src)
|
81
89
|
# append to current list if we have one
|
82
|
-
if output.last &&
|
90
|
+
if output.last.kind_of?(MDElement) &&
|
91
|
+
output.last.node_type == list_type then
|
83
92
|
output.last.children << li
|
84
93
|
else
|
85
94
|
output.push md_el(list_type, [li])
|
@@ -91,7 +100,7 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
91
100
|
when :footnote_text; output.push read_footnote_text(src)
|
92
101
|
when :ref_definition; output.push read_ref_definition(src)
|
93
102
|
when :abbreviation; output.push read_abbreviation(src)
|
94
|
-
when :xml_instr;
|
103
|
+
when :xml_instr; read_xml_instruction(src, output)
|
95
104
|
# # these do not produce output
|
96
105
|
when :metadata;
|
97
106
|
maruku_error "Please use the new meta-data syntax: \n"+
|
@@ -104,7 +113,13 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
104
113
|
src.shift_line
|
105
114
|
end
|
106
115
|
end
|
116
|
+
|
117
|
+
merge_ial(output, src, output)
|
118
|
+
output.delete_if {|x| x.kind_of?(MDElement) &&
|
119
|
+
x.node_type == :ial}
|
107
120
|
|
121
|
+
# get rid of empty line markers
|
122
|
+
output.delete_if {|x| x == :empty}
|
108
123
|
# See for each list if we can omit the paragraphs and use li_span
|
109
124
|
# TODO: do this after
|
110
125
|
output.each do |c|
|
@@ -135,7 +150,7 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
135
150
|
def read_ald(src)
|
136
151
|
if (l=src.shift_line) =~ AttributeDefinitionList
|
137
152
|
id = $1; al=$2;
|
138
|
-
al = read_attribute_list(CharSource.new(al), context=nil, break_on=[nil])
|
153
|
+
al = read_attribute_list(CharSource.new(al,src), context=nil, break_on=[nil])
|
139
154
|
self.ald[id] = al;
|
140
155
|
return md_ald(id, al)
|
141
156
|
else
|
@@ -152,7 +167,7 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
152
167
|
if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
|
153
168
|
line = $1.strip
|
154
169
|
ial = $2
|
155
|
-
al = read_attribute_list(CharSource.new(ial), context=nil, break_on=[nil])
|
170
|
+
al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
|
156
171
|
end
|
157
172
|
text = parse_lines_as_span [ line ]
|
158
173
|
level = src.cur_line.md_type == :header2 ? 2 : 1;
|
@@ -168,14 +183,14 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
168
183
|
if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
|
169
184
|
line = $1.strip
|
170
185
|
ial = $2
|
171
|
-
al = read_attribute_list(CharSource.new(ial), context=nil, break_on=[nil])
|
186
|
+
al = read_attribute_list(CharSource.new(ial,src), context=nil, break_on=[nil])
|
172
187
|
end
|
173
188
|
level = num_leading_hashes(line)
|
174
189
|
text = parse_lines_as_span [strip_hashes(line)]
|
175
190
|
return md_header(level, text, al)
|
176
191
|
end
|
177
192
|
|
178
|
-
def read_xml_instruction(src)
|
193
|
+
def read_xml_instruction(src, output)
|
179
194
|
m = /^\s*<\?((\w+)\s*)?(.*)$/.match src.shift_line
|
180
195
|
raise "BugBug" if not m
|
181
196
|
target = m[2] || ''
|
@@ -190,7 +205,18 @@ module MaRuKu; module In; module Markdown; module BlockLevelParser
|
|
190
205
|
end
|
191
206
|
code.gsub!(/\?>\s*$/, '')
|
192
207
|
|
193
|
-
|
208
|
+
if target == 'mrk' && MaRuKu::Globals[:unsafe_features]
|
209
|
+
result = safe_execute_code(self, code)
|
210
|
+
if result
|
211
|
+
if result.kind_of? String
|
212
|
+
raise "Not expected"
|
213
|
+
else
|
214
|
+
output.push *result
|
215
|
+
end
|
216
|
+
end
|
217
|
+
else
|
218
|
+
output.push md_xml_instr(target, code)
|
219
|
+
end
|
194
220
|
end
|
195
221
|
|
196
222
|
def read_raw_html(src)
|
@@ -84,8 +84,7 @@ Conversion happens using the `iconv` library.
|
|
84
84
|
end
|
85
85
|
|
86
86
|
=begin maruku_doc
|
87
|
-
|
88
|
-
Scope: document
|
87
|
+
Variable: Maruku::Globals[:unsafe_features]
|
89
88
|
Summary: Enables execution of XML instructions.
|
90
89
|
|
91
90
|
Disabled by default because of security concerns.
|
@@ -115,8 +114,10 @@ Disabled by default because of security concerns.
|
|
115
114
|
already.push v
|
116
115
|
expand_attribute_list(self.ald[v], result)
|
117
116
|
else
|
118
|
-
|
119
|
-
|
117
|
+
already.push v
|
118
|
+
maruku_error "Circular reference between labels.\n\n"+
|
119
|
+
"Label #{v.inspect} calls itself via recursion.\nThe recursion is "+
|
120
|
+
(already.map{|x| x.inspect}.join(' => '))
|
120
121
|
end
|
121
122
|
else
|
122
123
|
if not result[:unresolved_references]
|
@@ -133,31 +134,32 @@ Disabled by default because of security concerns.
|
|
133
134
|
end
|
134
135
|
end
|
135
136
|
|
137
|
+
def safe_execute_code(object, code)
|
138
|
+
begin
|
139
|
+
return object.instance_eval(code)
|
140
|
+
rescue Exception => e
|
141
|
+
maruku_error "Exception while executing this:\n"+
|
142
|
+
add_tabs(code, 1, ">")+
|
143
|
+
"\nThe error was:\n"+
|
144
|
+
add_tabs(e.inspect+"\n"+e.caller.join("\n"), 1, "|")
|
145
|
+
rescue RuntimeError => e
|
146
|
+
maruku_error "2: Exception while executing this:\n"+
|
147
|
+
add_tabs(code, 1, ">")+
|
148
|
+
"\nThe error was:\n"+
|
149
|
+
add_tabs(e.inspect, 1, "|")
|
150
|
+
rescue SyntaxError => e
|
151
|
+
maruku_error "2: Exception while executing this:\n"+
|
152
|
+
add_tabs(code, 1, ">")+
|
153
|
+
"\nThe error was:\n"+
|
154
|
+
add_tabs(e.inspect, 1, "|")
|
155
|
+
end
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
|
136
159
|
def execute_code_blocks
|
137
160
|
self.each_element(:xml_instr) do |e|
|
138
161
|
if e.target == 'maruku'
|
139
|
-
|
140
|
-
code = e.code
|
141
|
-
result = nil
|
142
|
-
begin
|
143
|
-
e.instance_eval(code)
|
144
|
-
rescue Exception => e
|
145
|
-
maruku_error "Exception while executing this:\n"+
|
146
|
-
add_tabs(code, 1, ">")+
|
147
|
-
"\nThe error was:\n"+
|
148
|
-
add_tabs(e.inspect+"\n"+e.caller.join("\n"), 1, "|")
|
149
|
-
next
|
150
|
-
rescue RuntimeError => e
|
151
|
-
maruku_error "2: Exception while executing this:\n"+
|
152
|
-
add_tabs(code, 1, ">")+
|
153
|
-
"\nThe error was:\n"+
|
154
|
-
add_tabs(e.inspect, 1, "|")
|
155
|
-
rescue SyntaxError => e
|
156
|
-
maruku_error "2: Exception while executing this:\n"+
|
157
|
-
add_tabs(code, 1, ">")+
|
158
|
-
"\nThe error was:\n"+
|
159
|
-
add_tabs(e.inspect, 1, "|")
|
160
|
-
end
|
162
|
+
result = safe_execute_code(e, e.code)
|
161
163
|
if result.kind_of?(String)
|
162
164
|
puts "Result is : #{result.inspect}"
|
163
165
|
end
|
@@ -186,54 +186,26 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
|
|
186
186
|
con.push_char src.shift_char
|
187
187
|
end
|
188
188
|
end
|
189
|
-
when ?{ #
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
con.push_element ial
|
196
|
-
else # normal text
|
197
|
-
con.push_char src.shift_char
|
198
|
-
end
|
189
|
+
when ?{ # extension
|
190
|
+
src.ignore_char # {
|
191
|
+
interpret_extension(src, con, [?}])
|
192
|
+
src.ignore_char # }
|
193
|
+
|
199
194
|
when nil
|
200
195
|
maruku_error ("Unclosed span (waiting for %s"+
|
201
196
|
"#{exit_on_strings.inspect})") % [
|
202
197
|
exit_on_chars ? "#{exit_on_chars.inspect} or" : ""],
|
203
198
|
src,con
|
204
|
-
|
205
199
|
break
|
206
200
|
else # normal text
|
207
201
|
con.push_char src.shift_char
|
208
202
|
end # end case
|
209
203
|
end # end while true
|
210
204
|
con.push_string_if_present
|
211
|
-
|
212
|
-
# Now we handle the IAL stuff
|
213
|
-
# We need a helper
|
214
|
-
def is_ial(e); e.kind_of? MDElement and e.node_type == :ial end
|
215
|
-
# A IAL at the beginning is strange and we ignore it
|
216
|
-
if false && is_ial(e=con.elements[0])
|
217
|
-
"Attribute list at the beginning of span {#{e.to_md}}"
|
218
|
-
tell_user "Ignoring {#{e.to_md}}"
|
219
|
-
con.elements.shift
|
220
|
-
end
|
221
|
-
# Apply each IAL to the element before
|
222
|
-
con.elements.each_with_index do |e, i| if is_ial(e) && i>= 1 then
|
223
|
-
before = con.elements[i-1]
|
224
|
-
if before.kind_of? MDElement
|
225
|
-
#puts "Assigning #{e.ial} to #{before}"
|
226
|
-
before.al = e.ial
|
227
|
-
else
|
228
|
-
maruku_error "This IAL: {#{e.ial.to_md}} seems to"+
|
229
|
-
" refer to a string:\n"+
|
230
|
-
before.inspect, src, con
|
231
|
-
maruku_recover "Ignoring IAL: {#{e.ial.to_md}}", src, con
|
232
|
-
end
|
233
|
-
end end
|
234
205
|
|
235
|
-
#
|
236
|
-
|
206
|
+
# Assign IAL to elements
|
207
|
+
merge_ial(con.elements, src, con)
|
208
|
+
|
237
209
|
|
238
210
|
# Remove leading space
|
239
211
|
if (s = con.elements.first).kind_of? String
|
@@ -251,7 +223,8 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
|
|
251
223
|
|
252
224
|
educated
|
253
225
|
end
|
254
|
-
|
226
|
+
|
227
|
+
|
255
228
|
def read_xml_instr_span(src, con)
|
256
229
|
src.ignore_chars(2) # starting <?
|
257
230
|
|
@@ -274,6 +247,46 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
|
|
274
247
|
con.push_element md_xml_instr(target, code)
|
275
248
|
end
|
276
249
|
|
250
|
+
# Start: cursor on character **after** '{'
|
251
|
+
# End: curson on '}' or EOF
|
252
|
+
def interpret_extension(src, con, break_on_chars)
|
253
|
+
case src.cur_char
|
254
|
+
when ?:
|
255
|
+
src.ignore_char # :
|
256
|
+
extension_meta(src, con, break_on_chars)
|
257
|
+
when ?#, ?.
|
258
|
+
extension_meta(src, con, break_on_chars)
|
259
|
+
else
|
260
|
+
stuff = read_simple(src, escaped=[?}], break_on_chars, [])
|
261
|
+
if stuff =~ /^(\w+\s|[^\w])/
|
262
|
+
extension_id = $1.strip
|
263
|
+
if false
|
264
|
+
else
|
265
|
+
maruku_recover "I don't know what to do with extension '#{extension_id}'\n"+
|
266
|
+
"I will threat this:\n\t{#{stuff}} \n as meta-data.\n", src, con
|
267
|
+
extension_meta(src, con, break_on_chars)
|
268
|
+
end
|
269
|
+
else
|
270
|
+
maruku_recover "I will threat this:\n\t{#{stuff}} \n as meta-data.\n", src, con
|
271
|
+
extension_meta(src, con, break_on_chars)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def extension_meta(src, con, break_on_chars)
|
277
|
+
if m = src.read_regexp(/(\w)+\:/)
|
278
|
+
name = m[1]
|
279
|
+
content = m[2]
|
280
|
+
al = read_attribute_list(src, con, break_on_chars)
|
281
|
+
self.doc.ald[name] = al
|
282
|
+
con.push md_ald(name, al)
|
283
|
+
else
|
284
|
+
al = read_attribute_list(src, con, break_on_chars)
|
285
|
+
self.doc.ald[name] = al
|
286
|
+
con.push md_ial(al)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
277
290
|
def read_url_el(src,con)
|
278
291
|
src.ignore_char # leading <
|
279
292
|
url = read_simple(src, [], [?>])
|
@@ -340,14 +353,16 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
|
|
340
353
|
# Reads a simple string (no formatting) until one of break_on_chars,
|
341
354
|
# while escaping the escaped.
|
342
355
|
# If the string is empty, it returns nil.
|
343
|
-
# Raises on error.
|
344
|
-
|
356
|
+
# Raises on error if the string terminates unexpectedly.
|
357
|
+
# # If eat_delim is true, and if the delim is not the EOF, then the delim
|
358
|
+
# # gets eaten from the stream.
|
359
|
+
def read_simple(src, escaped, exit_on_chars, exit_on_strings=nil)
|
345
360
|
text = ""
|
346
361
|
while true
|
347
362
|
# puts "Reading simple #{text.inspect}"
|
348
363
|
c = src.cur_char
|
349
364
|
if exit_on_chars && exit_on_chars.include?(c)
|
350
|
-
#
|
365
|
+
# src.ignore_char if eat_delim
|
351
366
|
break
|
352
367
|
end
|
353
368
|
|
@@ -625,7 +640,8 @@ module MaRuKu; module In; module Markdown; module SpanLevelParser
|
|
625
640
|
@elements << e
|
626
641
|
nil
|
627
642
|
end
|
628
|
-
|
643
|
+
alias push push_element
|
644
|
+
|
629
645
|
def push_elements(a)
|
630
646
|
for e in a
|
631
647
|
if e.kind_of? String
|
@@ -62,14 +62,15 @@ module MaRuKu; module Strings
|
|
62
62
|
return :metadata if l =~ /^@/
|
63
63
|
# if @@new_meta_data?
|
64
64
|
return :ald if l =~ AttributeDefinitionList
|
65
|
-
return :ial if l =~
|
65
|
+
return :ial if l =~ InlineAttributeList
|
66
66
|
# end
|
67
67
|
return :text # else, it's just text
|
68
68
|
end
|
69
69
|
|
70
70
|
# $1 = id $2 = attribute list
|
71
71
|
AttributeDefinitionList = /^\s{0,3}\{([\w\d\s]+)\}:\s*(.*)\s*$/
|
72
|
-
|
72
|
+
#
|
73
|
+
InlineAttributeList = /^\s{0,3}\{(.*)\}\s*$/
|
73
74
|
# Example:
|
74
75
|
# ^:blah blah
|
75
76
|
# ^: blah blah
|
@@ -346,7 +346,7 @@ module MaRuKu; module Out; module HTML
|
|
346
346
|
end
|
347
347
|
|
348
348
|
def to_html_code_using_pre(source)
|
349
|
-
pre =
|
349
|
+
pre = create_html_element 'pre'
|
350
350
|
code = Element.new 'code', pre
|
351
351
|
s = source
|
352
352
|
|
@@ -368,7 +368,7 @@ module MaRuKu; module Out; module HTML
|
|
368
368
|
end
|
369
369
|
|
370
370
|
def to_html_inline_code;
|
371
|
-
pre =
|
371
|
+
pre = create_html_element 'code'
|
372
372
|
source = self.raw_code
|
373
373
|
pre << source2html(source)
|
374
374
|
|
@@ -381,7 +381,7 @@ module MaRuKu; module Out; module HTML
|
|
381
381
|
end
|
382
382
|
|
383
383
|
def to_html_immediate_link
|
384
|
-
a =
|
384
|
+
a = create_html_element 'a'
|
385
385
|
url = self.url
|
386
386
|
text = url.gsub(/^mailto:/,'') # don't show mailto
|
387
387
|
a << Text.new(text)
|
@@ -439,7 +439,7 @@ module MaRuKu; module Out; module HTML
|
|
439
439
|
|
440
440
|
def to_html_email_address
|
441
441
|
email = self.email
|
442
|
-
a =
|
442
|
+
a = create_html_element 'a'
|
443
443
|
#a.attributes['href'] = Text.new("mailto:"+obfuscate(email),false,nil,true)
|
444
444
|
#a.attributes.add Attribute.new('href',Text.new(
|
445
445
|
#"mailto:"+obfuscate(email),false,nil,true))
|
@@ -453,7 +453,7 @@ module MaRuKu; module Out; module HTML
|
|
453
453
|
##### Images
|
454
454
|
|
455
455
|
def to_html_image
|
456
|
-
a =
|
456
|
+
a = create_html_element 'img'
|
457
457
|
id = self.ref_id
|
458
458
|
if ref = @doc.refs[id]
|
459
459
|
url = ref[:url]
|
@@ -478,7 +478,7 @@ module MaRuKu; module Out; module HTML
|
|
478
478
|
return wrap_as_element('span')
|
479
479
|
end
|
480
480
|
title = self.title
|
481
|
-
a =
|
481
|
+
a = create_html_element 'img'
|
482
482
|
a.attributes['src'] = url
|
483
483
|
a.attributes['title'] = title if title
|
484
484
|
return a
|