maruku 0.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 +25 -0
- data/bin/marutex +29 -0
- data/docs/Makefile +25 -0
- data/docs/char_codes.xml +884 -0
- data/docs/color-package-demo.aux +1 -0
- data/docs/color-package-demo.log +127 -0
- data/docs/color-package-demo.tex +149 -0
- data/docs/index.html +74 -0
- data/docs/markdown_syntax.aux +13 -0
- data/docs/markdown_syntax.html +266 -0
- data/docs/markdown_syntax.log +287 -0
- data/docs/markdown_syntax.md +920 -0
- data/docs/markdown_syntax.out +0 -0
- data/docs/markdown_syntax.pdf +0 -0
- data/docs/markdown_syntax.tex +1203 -0
- data/docs/maruku.aux +13 -0
- data/docs/maruku.html +74 -0
- data/docs/maruku.log +294 -0
- data/docs/maruku.md +394 -0
- data/docs/maruku.out +0 -0
- data/docs/maruku.pdf +0 -0
- data/docs/maruku.tex +548 -0
- data/docs/style.css +65 -0
- data/docs/todo.md +12 -0
- data/lib/maruku.rb +20 -0
- data/lib/maruku/parse_block.rb +577 -0
- data/lib/maruku/parse_span.rb +336 -0
- data/lib/maruku/string_utils.rb +270 -0
- data/lib/maruku/structures.rb +31 -0
- data/lib/maruku/to_html.rb +430 -0
- data/lib/maruku/to_latex.rb +345 -0
- data/lib/maruku/to_latex_strings.rb +330 -0
- data/tests/abbreviations.md +11 -0
- data/tests/blank.md +4 -0
- data/tests/code.md +5 -0
- data/tests/code2.md +8 -0
- data/tests/code3.md +16 -0
- data/tests/email.md +4 -0
- data/tests/entities.md +19 -0
- data/tests/escaping.md +14 -0
- data/tests/extra_dl.md +101 -0
- data/tests/extra_header_id.md +13 -0
- data/tests/extra_table1.md +40 -0
- data/tests/footnotes.md +17 -0
- data/tests/headers.md +10 -0
- data/tests/hrule.md +10 -0
- data/tests/images.md +20 -0
- data/tests/inline_html.md +35 -0
- data/tests/links.md +31 -0
- data/tests/list1.md +4 -0
- data/tests/list2.md +5 -0
- data/tests/list3.md +8 -0
- data/tests/lists.md +32 -0
- data/tests/lists_ol.md +39 -0
- data/tests/misc_sw.md +105 -0
- data/tests/one.md +1 -0
- data/tests/paragraphs.md +13 -0
- data/tests/sss06.md +352 -0
- data/tests/test.md +4 -0
- metadata +113 -0
data/docs/style.css
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
|
|
2
|
+
BODY {
|
|
3
|
+
font-family: Georgia;
|
|
4
|
+
max-width: 40em;
|
|
5
|
+
padding-left: 3em;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
H1 { margin-left: -1em;}
|
|
9
|
+
H2 { margin-left: -1em;}
|
|
10
|
+
|
|
11
|
+
PRE {
|
|
12
|
+
margin-left: 2em;
|
|
13
|
+
background-color: #eff;
|
|
14
|
+
border: solid 1px #bdd;
|
|
15
|
+
padding: 4px;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
H1 STRONG { color: red;}
|
|
19
|
+
|
|
20
|
+
TABLE.example TD,
|
|
21
|
+
TABLE.example TR,
|
|
22
|
+
TABLE.example TH {
|
|
23
|
+
border: solid 1px #555;
|
|
24
|
+
padding: 4px;
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
/*pre SPAN { border: solid 1px black; }*/
|
|
28
|
+
|
|
29
|
+
.ruby .normal {}
|
|
30
|
+
.ruby .comment { color: #005; font-style: italic; }
|
|
31
|
+
.ruby .keyword { color: #A00; font-weight: bold; }
|
|
32
|
+
.ruby .method { color: #077; }
|
|
33
|
+
.ruby .class { color: #074; }
|
|
34
|
+
.ruby .module { color: #050; }
|
|
35
|
+
.ruby .punct { color: #447; font-weight: bold; }
|
|
36
|
+
.ruby .symbol { color: #099; }
|
|
37
|
+
.ruby .string { color: #944; background: #FFE; }
|
|
38
|
+
.ruby .char { color: #F07; }
|
|
39
|
+
.ruby .ident { color: #004; }
|
|
40
|
+
.ruby .constant { color: #07F; }
|
|
41
|
+
.ruby .regex { color: #B66; background: #FEF; }
|
|
42
|
+
.ruby .number { color: #F99; }
|
|
43
|
+
.ruby .attribute { color: #7BB; }
|
|
44
|
+
.ruby .global { color: #7FB; }
|
|
45
|
+
.ruby .expr { color: #227; }
|
|
46
|
+
.ruby .escape { color: #277; }
|
|
47
|
+
|
|
48
|
+
.xml .normal {}
|
|
49
|
+
.xml .namespace { color: #B66; font-weight: bold; }
|
|
50
|
+
.xml .tag { color: #F88; }
|
|
51
|
+
.xml .comment { color: #005; font-style: italic; }
|
|
52
|
+
.xml .punct { color: #447; font-weight: bold; }
|
|
53
|
+
.xml .string { color: #944; }
|
|
54
|
+
.xml .number { color: #F99; }
|
|
55
|
+
.xml .attribute { color: #BB7; }
|
|
56
|
+
|
|
57
|
+
.html .normal {}
|
|
58
|
+
.html .namespace { color: #B66; font-weight: bold; }
|
|
59
|
+
.html .tag { color: #F88; }
|
|
60
|
+
.html .comment { color: #005; font-style: italic; }
|
|
61
|
+
.html .punct { color: #447; font-weight: bold; }
|
|
62
|
+
.html .string { color: #944; }
|
|
63
|
+
.html .number { color: #F99; }
|
|
64
|
+
.html .attribute { color: #BB7; }
|
|
65
|
+
|
data/docs/todo.md
ADDED
data/lib/maruku.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
# Structures definition
|
|
3
|
+
require 'maruku/structures'
|
|
4
|
+
|
|
5
|
+
# Code for parsing block-level elements
|
|
6
|
+
require 'maruku/parse_block'
|
|
7
|
+
|
|
8
|
+
# Code for parsing span-level elements
|
|
9
|
+
require 'maruku/parse_span'
|
|
10
|
+
|
|
11
|
+
# Ugly things kept in a closet
|
|
12
|
+
require 'maruku/string_utils'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Exporting to html
|
|
16
|
+
require 'maruku/to_html'
|
|
17
|
+
|
|
18
|
+
# Exporting to latex
|
|
19
|
+
require 'maruku/to_latex'
|
|
20
|
+
require 'maruku/to_latex_strings'
|
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
|
|
2
|
+
class Maruku
|
|
3
|
+
def initialize(s=nil)
|
|
4
|
+
@node_type = :document
|
|
5
|
+
@doc = self
|
|
6
|
+
|
|
7
|
+
@refs = {}
|
|
8
|
+
@footnotes = {}
|
|
9
|
+
@abbreviations = {}
|
|
10
|
+
|
|
11
|
+
parse_doc(s) if s
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse_doc(s)
|
|
15
|
+
# setup initial stack
|
|
16
|
+
@stack = []
|
|
17
|
+
|
|
18
|
+
@meta = parse_email_headers(s)
|
|
19
|
+
lines = split_lines(@meta[:data])
|
|
20
|
+
@children = parse_lines_as_markdown(lines)
|
|
21
|
+
|
|
22
|
+
self.search_abbreviations
|
|
23
|
+
self.substitute_markdown_inside_raw_html
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def search_abbreviations
|
|
27
|
+
@abbreviations.each do |abbrev, title|
|
|
28
|
+
# puts "#{abbrev} => #{title}"
|
|
29
|
+
self.map_match(Regexp.new(Regexp.escape(abbrev))) {
|
|
30
|
+
e = create_md_element(:abbreviation)
|
|
31
|
+
e.children = [abbrev.dup]
|
|
32
|
+
e.meta[:title] = title.dup if title
|
|
33
|
+
e
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# (PHP Markdown extra) Search for elements that have
|
|
39
|
+
# markdown=1 or markdown=block defined
|
|
40
|
+
def substitute_markdown_inside_raw_html
|
|
41
|
+
self.each_element(:raw_html) do |e|
|
|
42
|
+
doc = e.meta[:parsed_html]
|
|
43
|
+
if doc # valid html
|
|
44
|
+
# parse block-level markdown elements in these HTML tags
|
|
45
|
+
block_tags = ['div']
|
|
46
|
+
# use xpath to find elements with 'markdown' attribute
|
|
47
|
+
doc.elements.to_a( "//*[attribute::markdown]" ).each do |e|
|
|
48
|
+
# should we parse block-level or span-level?
|
|
49
|
+
parse_blocks = (e.attributes['markdown'] == 'block') ||
|
|
50
|
+
block_tags.include?(e.name)
|
|
51
|
+
# remove 'markdown' attribute
|
|
52
|
+
e.delete_attribute 'markdown'
|
|
53
|
+
# Select all text elements of e
|
|
54
|
+
e.texts.each do |original_text|
|
|
55
|
+
# puts "parse_blocks = #{parse_blocks} found = #{original_text} "
|
|
56
|
+
s = original_text.to_s.strip # XXX
|
|
57
|
+
el = create_md_element(:dummy,
|
|
58
|
+
parse_blocks ? parse_text_as_markdown(s) :
|
|
59
|
+
parse_lines_as_span(s) )
|
|
60
|
+
el.children_to_html.each do |x|
|
|
61
|
+
e.insert_before(original_text, x)
|
|
62
|
+
end
|
|
63
|
+
e.delete(original_text)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Splits the string and calls parse_lines_as_markdown
|
|
73
|
+
def parse_text_as_markdown(text)
|
|
74
|
+
lines = split_lines(text)
|
|
75
|
+
parse_lines_as_markdown(lines)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_lines_as_markdown(lines)
|
|
79
|
+
@stack.push lines
|
|
80
|
+
output = []; current_metadata = just_read_metadata = nil
|
|
81
|
+
# run state machine
|
|
82
|
+
while cur_line
|
|
83
|
+
# puts "#{cur_line_node_type}|#{cur_line}"
|
|
84
|
+
case cur_line_node_type
|
|
85
|
+
when :empty;
|
|
86
|
+
shift_line;
|
|
87
|
+
when :text
|
|
88
|
+
if cur_line =~ MightBeTableHeader and
|
|
89
|
+
(next_line && next_line =~ TableSeparator)
|
|
90
|
+
output << read_table
|
|
91
|
+
elsif [:header1,:header2].include? next_line_node_type
|
|
92
|
+
e = create_md_element(:header)
|
|
93
|
+
line = shift_line.strip
|
|
94
|
+
if line =~ HeaderWithId
|
|
95
|
+
line = $1.strip
|
|
96
|
+
e.meta[:id] = $2
|
|
97
|
+
end
|
|
98
|
+
e.children = parse_lines_as_span [ line ]
|
|
99
|
+
|
|
100
|
+
e.meta[:level] = cur_line_node_type == :header2 ? 2 : 1
|
|
101
|
+
shift_line
|
|
102
|
+
|
|
103
|
+
output << e
|
|
104
|
+
elsif eventually_comes_a_def_list
|
|
105
|
+
definition = read_definition
|
|
106
|
+
if output.last && output.last.node_type == :definition_list
|
|
107
|
+
output.last.children << definition
|
|
108
|
+
else
|
|
109
|
+
output << create_md_element(:definition_list, [definition])
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
else # Start of a paragraph
|
|
113
|
+
output << read_paragraph
|
|
114
|
+
end
|
|
115
|
+
when :header2, :hrule
|
|
116
|
+
# hrule
|
|
117
|
+
shift_line
|
|
118
|
+
output << create_md_element(:hrule)
|
|
119
|
+
when :header3
|
|
120
|
+
e = create_md_element(:header)
|
|
121
|
+
line = shift_line.strip
|
|
122
|
+
if line =~ HeaderWithId
|
|
123
|
+
line = $1.strip
|
|
124
|
+
e.meta[:id] = $2
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
e.meta[:level] = num_leading_hashes(line)
|
|
128
|
+
e.children = parse_lines_as_span [strip_hashes(line)]
|
|
129
|
+
output << e
|
|
130
|
+
when :ulist, :olist
|
|
131
|
+
list_type = cur_line_node_type == :ulist ? :ul : :ol
|
|
132
|
+
li = read_list_item
|
|
133
|
+
# append to current list if we have one
|
|
134
|
+
if output.last && output.last.node_type == list_type
|
|
135
|
+
output.last.children << li
|
|
136
|
+
else
|
|
137
|
+
output << create_md_element(list_type, [li])
|
|
138
|
+
end
|
|
139
|
+
when :quote; output << read_quote
|
|
140
|
+
when :code; e = read_code; output << e if e
|
|
141
|
+
when :raw_html; output << read_raw_html
|
|
142
|
+
|
|
143
|
+
# these do not produce output
|
|
144
|
+
when :footnote_text; read_footnote_text
|
|
145
|
+
when :ref; read_ref
|
|
146
|
+
when :abbreviation; read_abbreviation
|
|
147
|
+
when :metadata; just_read_metadata = read_metadata
|
|
148
|
+
|
|
149
|
+
# warn if we forgot something
|
|
150
|
+
else
|
|
151
|
+
node_type = cur_line_node_type
|
|
152
|
+
$stderr.puts "Ignoring line '#{shift_line}' type = #{node_type}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if current_metadata and output.last
|
|
156
|
+
output.last.meta.merge! current_metadata
|
|
157
|
+
current_metadata = nil
|
|
158
|
+
# puts "meta for #{output.last.node_type}\n #{output.last.meta.inspect}"
|
|
159
|
+
end
|
|
160
|
+
current_metadata = just_read_metadata
|
|
161
|
+
just_read_metadata = nil
|
|
162
|
+
end
|
|
163
|
+
# pop the stack
|
|
164
|
+
@stack.pop
|
|
165
|
+
|
|
166
|
+
# See for each list if we can omit the paragraphs and use li_span
|
|
167
|
+
output.each do |c|
|
|
168
|
+
# Remove paragraphs that we can get rid of
|
|
169
|
+
if [:ul,:ol].include? c.node_type
|
|
170
|
+
if c.children.all? {|li| !li.meta[:want_my_paragraph]} then
|
|
171
|
+
c.children.each do |d|
|
|
172
|
+
d.node_type = :li_span
|
|
173
|
+
d.children = d.children[0].children
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
if c.node_type == :definition_list
|
|
178
|
+
if c.children.all?{|defi| !defi.meta[:want_my_paragraph]} then
|
|
179
|
+
c.children.each do |definition|
|
|
180
|
+
dds = definition.meta[:definitions]
|
|
181
|
+
dds.each do |dd|
|
|
182
|
+
dd.children = dd.children[0].children
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
output
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def create_md_element(node_type, children=[])
|
|
193
|
+
e = MDElement.new
|
|
194
|
+
e.node_type = node_type
|
|
195
|
+
e.children = children
|
|
196
|
+
e.doc = self
|
|
197
|
+
e
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def top; @stack.last end
|
|
201
|
+
def cur_line_node_type; line_node_type top.first end
|
|
202
|
+
def cur_line; top.empty? ? nil : top.first end
|
|
203
|
+
def next_line; top.empty? ? nil : top[1] end
|
|
204
|
+
def next_line_node_type; (top.size >= 2) ? line_node_type(top[1]) : nil end
|
|
205
|
+
def shift_line; top.shift; end
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def read_raw_html
|
|
209
|
+
lines = []
|
|
210
|
+
|
|
211
|
+
cur_line =~ %r{^<(\w+)}
|
|
212
|
+
tag = $1
|
|
213
|
+
# puts "Start tag = #{tag} "
|
|
214
|
+
|
|
215
|
+
while cur_line
|
|
216
|
+
break if (number_of_leading_spaces(cur_line) == 0) &&
|
|
217
|
+
(not [:raw_html, :empty].include? cur_line_node_type)
|
|
218
|
+
|
|
219
|
+
lines << shift_line
|
|
220
|
+
# check for a closing tag
|
|
221
|
+
if (lines.last =~ %r{^</(\w+)}||
|
|
222
|
+
lines.last =~ %r{</(\w+)>\s*$}) && $1 == tag
|
|
223
|
+
break
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# dbg_describe_ary(lines, 'HTML')
|
|
228
|
+
|
|
229
|
+
raw_html = lines.join("\n")
|
|
230
|
+
|
|
231
|
+
e = create_md_element(:raw_html)
|
|
232
|
+
|
|
233
|
+
begin
|
|
234
|
+
e.meta[:parsed_html] = Document.new(raw_html)
|
|
235
|
+
rescue
|
|
236
|
+
$stderr.puts "Malformed HTML:\n#{raw_html}"
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
e.meta[:raw_html] = raw_html
|
|
240
|
+
e
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def read_paragraph
|
|
244
|
+
lines = []
|
|
245
|
+
while cur_line && cur_line_node_type == :text
|
|
246
|
+
lines << shift_line
|
|
247
|
+
end
|
|
248
|
+
# dbg_describe_ary(lines, 'PAR')
|
|
249
|
+
children = parse_lines_as_span(lines)
|
|
250
|
+
|
|
251
|
+
e = create_md_element(:paragraph, children)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# Reads one list item, either ordered or unordered.
|
|
257
|
+
def read_list_item
|
|
258
|
+
item_type = cur_line_node_type
|
|
259
|
+
first = shift_line
|
|
260
|
+
|
|
261
|
+
# Ugly things going on inside `read_indented_content`
|
|
262
|
+
indentation = spaces_before_first_char(first)
|
|
263
|
+
break_list = [:ulist, :olist]
|
|
264
|
+
lines, want_my_paragraph =
|
|
265
|
+
read_indented_content(indentation, break_list, item_type)
|
|
266
|
+
|
|
267
|
+
# add first line
|
|
268
|
+
# Strip first '*' or '-' from first line
|
|
269
|
+
stripped = first[indentation, first.size-1]
|
|
270
|
+
lines.unshift stripped
|
|
271
|
+
|
|
272
|
+
e = create_md_element(:li)
|
|
273
|
+
e.children = parse_lines_as_markdown(lines)
|
|
274
|
+
e.meta[:want_my_paragraph] = want_my_paragraph|| (e.children.size>1)
|
|
275
|
+
e
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
def read_abbreviation
|
|
279
|
+
shift_line =~ Abbreviation
|
|
280
|
+
abbrev = $1
|
|
281
|
+
description = $2
|
|
282
|
+
|
|
283
|
+
@abbreviations[abbrev] = description
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def read_footnote_text
|
|
287
|
+
first = shift_line
|
|
288
|
+
|
|
289
|
+
first =~ FootnoteText
|
|
290
|
+
id = $1
|
|
291
|
+
text = $2
|
|
292
|
+
|
|
293
|
+
# Ugly things going on inside `read_indented_content`
|
|
294
|
+
indentation = 4 #first.size-text.size
|
|
295
|
+
|
|
296
|
+
# puts "id =_#{id}_; text=_#{text}_ indent=#{indentation}"
|
|
297
|
+
|
|
298
|
+
break_list = [:footnote_text]
|
|
299
|
+
item_type = :footnote_text
|
|
300
|
+
lines, want_my_paragraph =
|
|
301
|
+
read_indented_content(indentation, break_list, item_type)
|
|
302
|
+
|
|
303
|
+
# add first line
|
|
304
|
+
if text && text.strip != "" then lines.unshift text end
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# dbg_describe_ary(lines, 'FOOTNOTE')
|
|
308
|
+
children = parse_lines_as_markdown(lines)
|
|
309
|
+
@footnotes[id] = create_md_element(:footnote, children)
|
|
310
|
+
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# This is the only ugly function in the code base.
|
|
315
|
+
# It is used to read list items, descriptions, footnote text
|
|
316
|
+
def read_indented_content(indentation, break_list, item_type)
|
|
317
|
+
lines =[]
|
|
318
|
+
# collect all indented lines
|
|
319
|
+
saw_empty = false; saw_anything_after = false
|
|
320
|
+
while cur_line
|
|
321
|
+
if cur_line_node_type == :empty
|
|
322
|
+
saw_empty = true
|
|
323
|
+
lines << shift_line
|
|
324
|
+
next
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
# after a white line
|
|
328
|
+
if saw_empty
|
|
329
|
+
# we expect things to be properly aligned
|
|
330
|
+
if number_of_leading_spaces(cur_line) < indentation
|
|
331
|
+
# debug "breaking for spaces: #{cur_line}"
|
|
332
|
+
break
|
|
333
|
+
end
|
|
334
|
+
saw_anything_after = true
|
|
335
|
+
else
|
|
336
|
+
break if break_list.include? cur_line_node_type
|
|
337
|
+
# break if cur_line_node_type != :text
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# debug "Accepted '#{cur_line}'"
|
|
341
|
+
|
|
342
|
+
stripped = strip_indent(shift_line, indentation)
|
|
343
|
+
lines << stripped
|
|
344
|
+
|
|
345
|
+
# You are only required to indent the first line of
|
|
346
|
+
# a child paragraph.
|
|
347
|
+
if line_node_type(stripped) == :text
|
|
348
|
+
while cur_line && (cur_line_node_type == :text)
|
|
349
|
+
lines << strip_indent(shift_line, indentation)
|
|
350
|
+
end
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
want_my_paragraph = saw_anything_after ||
|
|
355
|
+
(saw_empty && (cur_line && (cur_line_node_type == item_type)))
|
|
356
|
+
|
|
357
|
+
# dbg_describe_ary(lines, 'LI')
|
|
358
|
+
# create a new context
|
|
359
|
+
|
|
360
|
+
while lines.last && (line_node_type(lines.last) == :empty)
|
|
361
|
+
lines.pop
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
return lines, want_my_paragraph
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def read_quote
|
|
369
|
+
lines = []
|
|
370
|
+
# collect all indented lines
|
|
371
|
+
while cur_line && line_node_type(cur_line) == :quote
|
|
372
|
+
lines << unquote(shift_line)
|
|
373
|
+
end
|
|
374
|
+
# dbg_describe_ary(lines, 'QUOTE')
|
|
375
|
+
|
|
376
|
+
e = create_md_element(:quote)
|
|
377
|
+
e.children = parse_lines_as_markdown(lines)
|
|
378
|
+
e
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def read_code
|
|
382
|
+
e = create_md_element(:code)
|
|
383
|
+
# collect all indented lines
|
|
384
|
+
lines = []
|
|
385
|
+
while cur_line && ([:code, :empty].include? cur_line_node_type)
|
|
386
|
+
lines << strip_indent(shift_line, 4)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
while lines.last && (line_node_type(lines.last) == :empty )
|
|
390
|
+
lines.pop
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
return nil if lines.empty?
|
|
394
|
+
|
|
395
|
+
# dbg_describe_ary(lines, 'CODE')
|
|
396
|
+
e.meta[:raw_code] = lines.join("\n")
|
|
397
|
+
e
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# Reads a series of metadata lines with empty lines in between
|
|
401
|
+
def read_metadata
|
|
402
|
+
hash = {}
|
|
403
|
+
while cur_line
|
|
404
|
+
case cur_line_node_type
|
|
405
|
+
when :empty; shift_line
|
|
406
|
+
when :metadata; hash.merge! parse_metadata(shift_line)
|
|
407
|
+
else break
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
hash
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
# parse one metadata line
|
|
414
|
+
def parse_metadata(l)
|
|
415
|
+
hash = {}
|
|
416
|
+
# remove leading '@'
|
|
417
|
+
l = l[1, l.size].strip
|
|
418
|
+
l.split(';').each do |kv|
|
|
419
|
+
k, v = kv.split(':')
|
|
420
|
+
v ||= 'true'
|
|
421
|
+
k = k.strip.to_sym
|
|
422
|
+
hash[k] = v.strip
|
|
423
|
+
end
|
|
424
|
+
hash
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def read_ref
|
|
428
|
+
line = shift_line
|
|
429
|
+
|
|
430
|
+
# if link is incomplete, shift next line
|
|
431
|
+
while cur_line && (cur_line_node_type != :ref) &&
|
|
432
|
+
([1,2,3].include? number_of_leading_spaces(cur_line) )
|
|
433
|
+
line += " "+ shift_line
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
# puts "total= #{line}"
|
|
437
|
+
|
|
438
|
+
if match = LinkRegex.match(line)
|
|
439
|
+
id = match[1]; url = match[2]; title = match[3];
|
|
440
|
+
id = id.strip.downcase
|
|
441
|
+
|
|
442
|
+
hash = self.refs[id] = {:url=>url,:title=>title}
|
|
443
|
+
|
|
444
|
+
stuff=match[4]
|
|
445
|
+
|
|
446
|
+
if stuff
|
|
447
|
+
stuff.split.each do |couple|
|
|
448
|
+
# puts "found #{couple}"
|
|
449
|
+
k, v = couple.split('=')
|
|
450
|
+
if v[0,1]=='"' then v = v[1, v.size-2] end
|
|
451
|
+
# puts "key:_#{k}_ value=_#{v}_"
|
|
452
|
+
hash[k.to_sym] = v
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
# puts hash.inspect
|
|
456
|
+
|
|
457
|
+
else
|
|
458
|
+
raise "Link does not respect format: '#{line}'"
|
|
459
|
+
end
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def read_table
|
|
463
|
+
|
|
464
|
+
def split_cells(s)
|
|
465
|
+
s.strip.split('|').select{|x|x.strip.size>0}.map{|x|x.strip}
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
head = split_cells(shift_line).map{|s|
|
|
469
|
+
create_md_element(:head_cell, parse_lines_as_span([s]))}
|
|
470
|
+
|
|
471
|
+
separator=split_cells(shift_line)
|
|
472
|
+
|
|
473
|
+
align = separator.map { |s| s =~ Sep
|
|
474
|
+
if $1 and $2 then :center elsif $2 then :right else :left end }
|
|
475
|
+
|
|
476
|
+
num_columns = align.size
|
|
477
|
+
|
|
478
|
+
if head.size != num_columns
|
|
479
|
+
$stderr.puts "Head does not have #{num_columns} columns: \n#{head.inspect}"
|
|
480
|
+
return create_md_element(:linebreak)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
rows = []
|
|
484
|
+
|
|
485
|
+
while cur_line && cur_line =~ /\|/
|
|
486
|
+
row = split_cells(shift_line).map{|s|
|
|
487
|
+
create_md_element(:cell, parse_lines_as_span([s]))}
|
|
488
|
+
if head.size != num_columns
|
|
489
|
+
$stderr.puts "Row does not have #{num_columns} columns: \n#{row.inspect}"
|
|
490
|
+
return create_md_element(:linebreak)
|
|
491
|
+
end
|
|
492
|
+
rows << row
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
e = create_md_element(:table)
|
|
496
|
+
e.meta[:align] = align
|
|
497
|
+
e.children = (head+rows).flatten
|
|
498
|
+
e
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# If current line is text, a definition list is coming
|
|
502
|
+
# if 1) text,empty,[text,empty]*,definition
|
|
503
|
+
|
|
504
|
+
def eventually_comes_a_def_list
|
|
505
|
+
future = create_next_string
|
|
506
|
+
ok = future =~ %r{^t+e?d}x
|
|
507
|
+
# puts "future: #{future} - #{ok}"
|
|
508
|
+
ok
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Returns the type of next line as a string
|
|
512
|
+
# breaks at first :definition
|
|
513
|
+
def create_next_string
|
|
514
|
+
s = ""; num_e = 0;
|
|
515
|
+
for line in top
|
|
516
|
+
c = case line_node_type(line)
|
|
517
|
+
when :text; "t"
|
|
518
|
+
when :empty; num_e+=1; "e"
|
|
519
|
+
when :definition; "d"
|
|
520
|
+
else "o"
|
|
521
|
+
end
|
|
522
|
+
s += c
|
|
523
|
+
break if c == "d" or num_e>1
|
|
524
|
+
end
|
|
525
|
+
s
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def read_definition
|
|
529
|
+
# Read one or more terms
|
|
530
|
+
terms = []
|
|
531
|
+
while cur_line && cur_line_node_type == :text
|
|
532
|
+
terms << create_md_element(:definition_term, parse_lines_as_span([shift_line]))
|
|
533
|
+
end
|
|
534
|
+
# dbg_describe_ary(terms, 'DT')
|
|
535
|
+
|
|
536
|
+
want_paragraph = false
|
|
537
|
+
|
|
538
|
+
raise "Bug!Bug" if not cur_line
|
|
539
|
+
|
|
540
|
+
# one optional empty
|
|
541
|
+
if cur_line_node_type == :empty
|
|
542
|
+
want_my_paragraph = true
|
|
543
|
+
shift_line
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
raise "Bug!Bug" if cur_line_node_type != :definition
|
|
547
|
+
|
|
548
|
+
# Read one or more definitions
|
|
549
|
+
definitions = []
|
|
550
|
+
while cur_line && cur_line_node_type == :definition
|
|
551
|
+
first = shift_line
|
|
552
|
+
first =~ Definition
|
|
553
|
+
first = $1
|
|
554
|
+
|
|
555
|
+
# I know, it's ugly!!!
|
|
556
|
+
|
|
557
|
+
lines, w_m_p =
|
|
558
|
+
read_indented_content(4, [:definition], :definition)
|
|
559
|
+
want_my_paragraph ||= w_m_p
|
|
560
|
+
|
|
561
|
+
lines.unshift first
|
|
562
|
+
|
|
563
|
+
# dbg_describe_ary(lines, 'DD')
|
|
564
|
+
|
|
565
|
+
children = parse_lines_as_markdown(lines)
|
|
566
|
+
definitions << create_md_element(:definition_data, children)
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
definition = create_md_element(:definition)
|
|
570
|
+
definition.meta[:terms] = terms
|
|
571
|
+
definition.meta[:definitions] = definitions
|
|
572
|
+
definition.children = terms + definitions
|
|
573
|
+
definition.meta[:want_my_paragraph] = want_my_paragraph
|
|
574
|
+
definition
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|