maruku 0.3.0 → 0.4.0
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/{maruku0.3 → marudown} +6 -14
- data/bin/maruku +1 -1
- data/bin/marutest +37 -9
- data/docs/TOFIX.html +22 -0
- data/docs/TOFIX.md +3 -0
- data/docs/changelog-0.2.13.html +30 -0
- data/docs/changelog-0.2.13.md +6 -0
- data/docs/changelog-0.3.html +19 -5
- data/docs/faq.html +51 -40
- data/docs/faq.md +3 -3
- data/docs/hidden_o_n_squared.md +10 -0
- data/docs/index.html +84 -396
- data/docs/markdown_syntax.html +139 -330
- data/docs/markdown_syntax.md +80 -93
- data/docs/maruku.html +84 -396
- data/docs/maruku.md +88 -158
- data/docs/proposal.html +13 -106
- data/docs/proposal.md +3 -3
- data/docs/todo.html +38 -28
- data/lib/maruku.rb +77 -11
- data/lib/maruku/attributes.rb +186 -0
- data/lib/maruku/defaults.rb +40 -0
- data/lib/maruku/errors_management.rb +55 -39
- data/lib/maruku/helpers.rb +156 -72
- data/lib/maruku/input/charsource.rb +319 -0
- data/lib/maruku/{html_helper.rb → input/html_helper.rb} +30 -9
- data/lib/maruku/input/linesource.rb +111 -0
- data/lib/maruku/input/parse_block.rb +562 -0
- data/lib/maruku/{parse_doc.rb → input/parse_doc.rb} +60 -28
- data/lib/maruku/{parse_span_better.rb → input/parse_span_better.rb} +226 -256
- data/lib/maruku/input/type_detection.rb +137 -0
- data/lib/maruku/maruku.rb +33 -0
- data/lib/maruku/{to_html.rb → output/to_html.rb} +151 -132
- data/lib/maruku/{to_latex.rb → output/to_latex.rb} +31 -35
- data/lib/maruku/{to_latex_entities.rb → output/to_latex_entities.rb} +25 -3
- data/lib/maruku/output/to_latex_strings.rb +64 -0
- data/lib/maruku/output/to_markdown.rb +164 -0
- data/lib/maruku/{to_s.rb → output/to_s.rb} +6 -0
- data/lib/maruku/string_utils.rb +12 -181
- data/lib/maruku/structures.rb +91 -67
- data/lib/maruku/structures_inspect.rb +78 -0
- data/lib/maruku/structures_iterators.rb +24 -2
- data/lib/maruku/tests/benchmark.rb +41 -9
- data/lib/maruku/tests/new_parser.rb +317 -286
- data/lib/maruku/tests/tests.rb +20 -0
- data/lib/maruku/toc.rb +64 -64
- data/lib/maruku/usage/example1.rb +33 -0
- data/lib/maruku/version.rb +8 -2
- data/tests/unittest/abbreviations.md +27 -16
- data/tests/unittest/attributes/attributes.md +89 -0
- data/tests/unittest/attributes/circular.md +51 -0
- data/tests/unittest/attributes/default.md +47 -0
- data/tests/unittest/blank.md +10 -6
- data/tests/unittest/blanks_in_code.md +26 -26
- data/tests/unittest/code.md +9 -9
- data/tests/unittest/code2.md +12 -13
- data/tests/unittest/code3.md +34 -34
- data/tests/unittest/easy.md +9 -7
- data/tests/unittest/email.md +9 -7
- data/tests/unittest/encoding/iso-8859-1.md +41 -4
- data/tests/unittest/encoding/utf-8.md +6 -5
- data/tests/unittest/entities.md +52 -80
- data/tests/unittest/escaping.md +47 -35
- data/tests/unittest/extra_dl.md +19 -29
- data/tests/unittest/extra_header_id.md +31 -24
- data/tests/unittest/extra_table1.md +14 -32
- data/tests/unittest/footnotes.md +58 -42
- data/tests/unittest/headers.md +11 -11
- data/tests/unittest/hrule.md +14 -24
- data/tests/unittest/images.md +41 -26
- data/tests/unittest/inline_html.md +104 -56
- data/tests/unittest/inline_html2.md +38 -0
- data/tests/unittest/links.md +74 -33
- data/tests/unittest/list1.md +18 -15
- data/tests/unittest/list2.md +31 -13
- data/tests/unittest/list3.md +29 -28
- data/tests/unittest/list4.md +103 -12
- data/tests/unittest/lists.md +86 -53
- data/tests/unittest/lists6.md +53 -0
- data/tests/unittest/lists7.md +31 -0
- data/tests/unittest/lists_after_paragraph.md +105 -71
- data/tests/unittest/lists_ol.md +149 -73
- data/tests/unittest/misc_sw.md +366 -326
- data/tests/unittest/notyet/escape.md +10 -10
- data/tests/unittest/notyet/header_after_par.md +20 -14
- data/tests/unittest/notyet/ticks.md +8 -35
- data/tests/unittest/notyet/triggering.md +72 -45
- data/tests/unittest/olist.md +78 -0
- data/tests/unittest/one.md +5 -3
- data/tests/unittest/paragraph.md +5 -3
- data/tests/unittest/paragraph_rules/dont_merge_ref.md +15 -9
- data/tests/unittest/paragraph_rules/tab_is_blank.md +9 -5
- data/tests/unittest/paragraphs.md +21 -26
- data/tests/unittest/recover/recover_links.md +6 -5
- data/tests/unittest/references/long_example.md +39 -30
- data/tests/unittest/references/spaces_and_numbers.md +2 -2
- data/tests/unittest/syntax_hl.md +33 -31
- data/tests/unittest/test.md +4 -6
- data/tests/unittest/wrapping.md +43 -26
- metadata +160 -139
- data/docs/markdown_extra2.html +0 -87
- data/docs/markdown_extra2.md +0 -83
- data/docs/markdown_syntax_2.html +0 -152
- data/lib/maruku/parse_block.rb +0 -564
- data/lib/maruku/parse_span.rb +0 -451
- data/lib/maruku/to_latex_strings.rb +0 -59
- data/lib/maruku/to_markdown.rb +0 -110
- data/lib/test.rb +0 -29
@@ -1,12 +1,32 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
|
3
|
+
#
|
4
|
+
# This file is part of Maruku.
|
5
|
+
#
|
6
|
+
# Maruku is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Maruku is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Maruku; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
19
|
+
#++
|
1
20
|
|
2
|
-
|
21
|
+
|
22
|
+
module MaRuKu; module In; module Markdown; module SpanLevelParser
|
3
23
|
|
4
24
|
# This class helps me read and sanitize HTML blocks
|
5
25
|
|
6
26
|
# I tried to do this with REXML, but wasn't able to. (suggestions?)
|
7
27
|
|
8
28
|
class HTMLHelper
|
9
|
-
include
|
29
|
+
include MaRuKu::Strings
|
10
30
|
|
11
31
|
Tag = %r{^<(/)?(\w+)\s*([^>]*)>}m
|
12
32
|
EverythingElse = %r{^[^<]+}m
|
@@ -69,14 +89,14 @@ class Maruku
|
|
69
89
|
end
|
70
90
|
elsif is_closing
|
71
91
|
@already += @m.to_s
|
72
|
-
if @tag_stack.last != tag
|
73
|
-
error "Malformed: tag <#{tag}> "+
|
74
|
-
"closes <#{@tag_stack.last}>"
|
75
|
-
end
|
76
92
|
if @tag_stack.empty?
|
77
93
|
error "Malformed: closing tag #{tag.inspect} "+
|
78
94
|
"in empty list"
|
79
95
|
end
|
96
|
+
if @tag_stack.last != tag
|
97
|
+
error "Malformed: tag <#{tag}> "+
|
98
|
+
"closes <#{@tag_stack.last}>"
|
99
|
+
end
|
80
100
|
@tag_stack.pop
|
81
101
|
elsif not is_single
|
82
102
|
@tag_stack.push tag
|
@@ -98,7 +118,7 @@ class Maruku
|
|
98
118
|
|
99
119
|
|
100
120
|
def error(s)
|
101
|
-
raise
|
121
|
+
raise Exception, "Error: #{s} \n"+ inspect, caller
|
102
122
|
end
|
103
123
|
|
104
124
|
def inspect; "HTML READER\n comment=#{@inside_comment} "+
|
@@ -119,5 +139,6 @@ class Maruku
|
|
119
139
|
def is_finished?
|
120
140
|
not @inside_comment and @tag_stack.empty?
|
121
141
|
end
|
122
|
-
end
|
123
|
-
|
142
|
+
end # html helper
|
143
|
+
|
144
|
+
end end end end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
|
3
|
+
#
|
4
|
+
# This file is part of Maruku.
|
5
|
+
#
|
6
|
+
# Maruku is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Maruku is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Maruku; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
19
|
+
#++
|
20
|
+
|
21
|
+
|
22
|
+
module MaRuKu; module In; module Markdown; module BlockLevelParser
|
23
|
+
|
24
|
+
# This represents a source of lines that can be consumed.
|
25
|
+
#
|
26
|
+
# It is the twin of CharSource.
|
27
|
+
#
|
28
|
+
|
29
|
+
class LineSource
|
30
|
+
include MaRuKu::Strings
|
31
|
+
|
32
|
+
def initialize(lines, parent=nil, parent_offset=nil)
|
33
|
+
raise "NIL lines? " if not lines
|
34
|
+
@lines = lines
|
35
|
+
@lines_index = 0
|
36
|
+
@parent = parent
|
37
|
+
@parent_offset = parent_offset
|
38
|
+
end
|
39
|
+
|
40
|
+
def cur_line() @lines[@lines_index] end
|
41
|
+
def next_line() @lines[@lines_index+1] end
|
42
|
+
|
43
|
+
def shift_line()
|
44
|
+
raise "Over the rainbow" if @lines_index >= @lines.size
|
45
|
+
l = @lines[@lines_index]
|
46
|
+
@lines_index += 1
|
47
|
+
return l
|
48
|
+
end
|
49
|
+
|
50
|
+
def ignore_line
|
51
|
+
raise "Over the rainbow" if @lines_index >= @lines.size
|
52
|
+
@lines_index += 1
|
53
|
+
end
|
54
|
+
|
55
|
+
def describe
|
56
|
+
#s = "At line ##{@lines_index} of #{@lines.size}:\n"
|
57
|
+
s = "At line #{original_line_number(@lines_index)}\n"
|
58
|
+
|
59
|
+
context = 3 # lines
|
60
|
+
from = [@lines_index-context, 0].max
|
61
|
+
to = [@lines_index+context, @lines.size-1].min
|
62
|
+
|
63
|
+
for i in from..to
|
64
|
+
prefix = (i == @lines_index) ? '--> ' : ' ';
|
65
|
+
l = @lines[i]
|
66
|
+
s += "%10s %4s|#{l}" %
|
67
|
+
[@lines[i].md_type.to_s, prefix]
|
68
|
+
|
69
|
+
s += "|\n"
|
70
|
+
end
|
71
|
+
|
72
|
+
# if @parent
|
73
|
+
# s << "Parent context is: \n"
|
74
|
+
# s << add_tabs(@parent.describe,1,'|')
|
75
|
+
# end
|
76
|
+
s
|
77
|
+
end
|
78
|
+
|
79
|
+
def original_line_number(index)
|
80
|
+
if @parent
|
81
|
+
return index + @parent.original_line_number(@parent_offset)
|
82
|
+
else
|
83
|
+
1 + index
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def cur_index
|
88
|
+
@lines_index
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the type of next line as a string
|
92
|
+
# breaks at first :definition
|
93
|
+
def tell_me_the_future
|
94
|
+
s = ""; num_e = 0;
|
95
|
+
for i in @lines_index..@lines.size-1
|
96
|
+
c = case @lines[i].md_type
|
97
|
+
when :text; "t"
|
98
|
+
when :empty; num_e+=1; "e"
|
99
|
+
when :definition; "d"
|
100
|
+
else "o"
|
101
|
+
end
|
102
|
+
s += c
|
103
|
+
break if c == "d" or num_e>1
|
104
|
+
end
|
105
|
+
s
|
106
|
+
end
|
107
|
+
|
108
|
+
end # linesource
|
109
|
+
|
110
|
+
end end end end # block
|
111
|
+
|
@@ -0,0 +1,562 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C) 2006 Andrea Censi <andrea (at) rubyforge.org>
|
3
|
+
#
|
4
|
+
# This file is part of Maruku.
|
5
|
+
#
|
6
|
+
# Maruku is free software; you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation; either version 2 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# Maruku is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with Maruku; if not, write to the Free Software
|
18
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
19
|
+
#++
|
20
|
+
|
21
|
+
|
22
|
+
module MaRuKu; module In; module Markdown; module BlockLevelParser
|
23
|
+
|
24
|
+
include Helpers
|
25
|
+
include MaRuKu::Strings
|
26
|
+
include MaRuKu::In::Markdown::SpanLevelParser
|
27
|
+
|
28
|
+
# Splits the string and calls parse_lines_as_markdown
|
29
|
+
def parse_text_as_markdown(text)
|
30
|
+
lines = split_lines(text)
|
31
|
+
src = LineSource.new(lines)
|
32
|
+
return parse_blocks(src)
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse_blocks(src)
|
36
|
+
output = [];
|
37
|
+
|
38
|
+
# run state machine
|
39
|
+
while src.cur_line
|
40
|
+
# Prints detected type (useful for debugging)
|
41
|
+
#puts "#{src.cur_line.md_type}|#{src.cur_line}"
|
42
|
+
case src.cur_line.md_type
|
43
|
+
when :empty;
|
44
|
+
src.ignore_line
|
45
|
+
when :ial
|
46
|
+
src.shift_line =~ /\s*\{([^\}]*)\}\s*/
|
47
|
+
al = $1
|
48
|
+
al = read_attribute_list(CharSource.new(al), context=nil, break_on=[nil])
|
49
|
+
if not output.empty?
|
50
|
+
output.last.al = al
|
51
|
+
else
|
52
|
+
maruku_error "An attribute list at beginning of context {#{al.to_md}}"
|
53
|
+
tell_user "I will ignore this AL: {#{al.to_md}}"
|
54
|
+
end
|
55
|
+
when :ald
|
56
|
+
output << read_ald(src)
|
57
|
+
when :text
|
58
|
+
if src.cur_line =~ MightBeTableHeader and
|
59
|
+
(src.next_line && src.next_line =~ TableSeparator)
|
60
|
+
output << read_table(src)
|
61
|
+
elsif [:header1,:header2].include? src.next_line.md_type
|
62
|
+
output << read_header12(src)
|
63
|
+
elsif eventually_comes_a_def_list(src)
|
64
|
+
definition = read_definition(src)
|
65
|
+
if output.last && output.last.node_type == :definition_list
|
66
|
+
output.last.children << definition
|
67
|
+
else
|
68
|
+
output << md_el(:definition_list, [definition])
|
69
|
+
end
|
70
|
+
else # Start of a paragraph
|
71
|
+
output << read_paragraph(src)
|
72
|
+
end
|
73
|
+
when :header2, :hrule
|
74
|
+
# hrule
|
75
|
+
src.shift_line
|
76
|
+
output << md_hrule()
|
77
|
+
when :header3
|
78
|
+
output << read_header3(src)
|
79
|
+
when :ulist, :olist
|
80
|
+
list_type = src.cur_line.md_type == :ulist ? :ul : :ol
|
81
|
+
li = read_list_item(src)
|
82
|
+
# append to current list if we have one
|
83
|
+
if output.last && output.last.node_type == list_type
|
84
|
+
output.last.children << li
|
85
|
+
else
|
86
|
+
output << md_el(list_type, [li])
|
87
|
+
end
|
88
|
+
when :quote; output << read_quote(src)
|
89
|
+
when :code; e = read_code(src); output << e if e
|
90
|
+
when :raw_html; e = read_raw_html(src); output << e if e
|
91
|
+
|
92
|
+
when :footnote_text; output << read_footnote_text(src)
|
93
|
+
when :ref_definition; output << read_ref_definition(src)
|
94
|
+
when :abbreviation; output << read_abbreviation(src)
|
95
|
+
|
96
|
+
# # these do not produce output
|
97
|
+
when :metadata;
|
98
|
+
maruku_error "Please use the new meta-data syntax: \n"+
|
99
|
+
" http://maruku.rubyforge.org/proposal.html\n", src
|
100
|
+
src.ignore_line
|
101
|
+
# warn if we forgot something
|
102
|
+
else
|
103
|
+
md_type = src.cur_line.md_type
|
104
|
+
line = src.cur_line
|
105
|
+
maruku_error "Ignoring line '#{line}' type = #{md_type}", src
|
106
|
+
src.shift_line
|
107
|
+
end
|
108
|
+
|
109
|
+
# FIXME
|
110
|
+
# if current_metadata and output.last
|
111
|
+
# output.last.meta.merge! current_metadata
|
112
|
+
# current_metadata = nil
|
113
|
+
# puts "meta for #{output.last.node_type}\n #{output.last.meta.inspect}"
|
114
|
+
# end
|
115
|
+
# current_metadata = just_read_metadata
|
116
|
+
# just_read_metadata = nil
|
117
|
+
end
|
118
|
+
|
119
|
+
# See for each list if we can omit the paragraphs and use li_span
|
120
|
+
# TODO: do this after
|
121
|
+
output.each do |c|
|
122
|
+
# Remove paragraphs that we can get rid of
|
123
|
+
if [:ul,:ol].include? c.node_type
|
124
|
+
if c.children.all? {|li| !li.want_my_paragraph} then
|
125
|
+
c.children.each do |d|
|
126
|
+
d.node_type = :li_span
|
127
|
+
d.children = d.children[0].children
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
if c.node_type == :definition_list
|
132
|
+
if c.children.all?{|defi| !defi.want_my_paragraph} then
|
133
|
+
c.children.each do |definition|
|
134
|
+
definition.definitions.each do |dd|
|
135
|
+
dd.children = dd.children[0].children
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
output
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def read_ald(src)
|
147
|
+
if (l=src.shift_line) =~ AttributeDefinitionList
|
148
|
+
id = $1; al=$2;
|
149
|
+
al = read_attribute_list(CharSource.new(al), context=nil, break_on=[nil])
|
150
|
+
self.ald[id] = al;
|
151
|
+
return md_ald(id, al)
|
152
|
+
else
|
153
|
+
maruku_error "Bug Bug:\n#{l.inspect}"
|
154
|
+
return nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# reads a header (with ----- or ========)
|
159
|
+
def read_header12(src)
|
160
|
+
line = src.shift_line.strip
|
161
|
+
al = nil
|
162
|
+
# Check if there is an IAL
|
163
|
+
if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
|
164
|
+
line = $1.strip
|
165
|
+
ial = $2
|
166
|
+
al = read_attribute_list(CharSource.new(ial), context=nil, break_on=[nil])
|
167
|
+
end
|
168
|
+
text = parse_lines_as_span [ line ]
|
169
|
+
level = src.cur_line.md_type == :header2 ? 2 : 1;
|
170
|
+
src.shift_line
|
171
|
+
return md_header(level, text, al)
|
172
|
+
end
|
173
|
+
|
174
|
+
# reads a header like '#### header ####'
|
175
|
+
def read_header3(src)
|
176
|
+
line = src.shift_line.strip
|
177
|
+
al = nil
|
178
|
+
# Check if there is an IAL
|
179
|
+
if new_meta_data? and line =~ /^(.*)\{(.*)\}\s*$/
|
180
|
+
line = $1.strip
|
181
|
+
ial = $2
|
182
|
+
al = read_attribute_list(CharSource.new(ial), context=nil, break_on=[nil])
|
183
|
+
end
|
184
|
+
level = num_leading_hashes(line)
|
185
|
+
text = parse_lines_as_span [strip_hashes(line)]
|
186
|
+
return md_header(level, text, al)
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def read_raw_html(src)
|
191
|
+
h = HTMLHelper.new
|
192
|
+
begin
|
193
|
+
h.eat_this(l=src.shift_line)
|
194
|
+
# puts "\nBLOCK:\nhtml -> #{l.inspect}"
|
195
|
+
while src.cur_line and not h.is_finished?
|
196
|
+
l=src.shift_line
|
197
|
+
# puts "html -> #{l.inspect}"
|
198
|
+
h.eat_this "\n"+l
|
199
|
+
end
|
200
|
+
rescue Exception => e
|
201
|
+
ex = e.inspect + e.backtrace.join("\n")
|
202
|
+
maruku_error "Bad block-level HTML:\n#{add_tabs(ex,1,'|')}\n", src
|
203
|
+
end
|
204
|
+
raw_html = h.stuff_you_read
|
205
|
+
return md_html(raw_html)
|
206
|
+
end
|
207
|
+
|
208
|
+
def read_paragraph(src)
|
209
|
+
lines = []
|
210
|
+
while src.cur_line
|
211
|
+
# :olist does not break
|
212
|
+
case t = src.cur_line.md_type
|
213
|
+
when :quote,:header3,:empty,:raw_html,:ref_definition,:ial
|
214
|
+
break
|
215
|
+
when :olist,:ulist
|
216
|
+
break if src.next_line.md_type == t
|
217
|
+
else
|
218
|
+
true
|
219
|
+
end
|
220
|
+
|
221
|
+
break if src.cur_line.strip.size == 0
|
222
|
+
|
223
|
+
break if [:header1,:header2].include? src.next_line.md_type
|
224
|
+
|
225
|
+
lines << src.shift_line
|
226
|
+
end
|
227
|
+
# dbg_describe_ary(lines, 'PAR')
|
228
|
+
children = parse_lines_as_span(lines)
|
229
|
+
|
230
|
+
return md_par(children)
|
231
|
+
end
|
232
|
+
|
233
|
+
# Reads one list item, either ordered or unordered.
|
234
|
+
def read_list_item(src)
|
235
|
+
parent_offset = src.cur_index
|
236
|
+
|
237
|
+
item_type = src.cur_line.md_type
|
238
|
+
first = src.shift_line
|
239
|
+
|
240
|
+
# Ugly things going on inside `read_indented_content`
|
241
|
+
indentation = spaces_before_first_char(first)
|
242
|
+
break_list = [:ulist, :olist, :ial]
|
243
|
+
lines, want_my_paragraph =
|
244
|
+
read_indented_content(src,indentation, break_list, item_type)
|
245
|
+
|
246
|
+
# add first line
|
247
|
+
# Strip first '*', '-', '+' from first line
|
248
|
+
stripped = first[indentation, first.size-1]
|
249
|
+
lines.unshift stripped
|
250
|
+
|
251
|
+
#dbg_describe_ary(lines, 'LIST ITEM ')
|
252
|
+
|
253
|
+
src2 = LineSource.new(lines, src, parent_offset)
|
254
|
+
children = parse_blocks(src2)
|
255
|
+
with_par = want_my_paragraph || (children.size>1)
|
256
|
+
|
257
|
+
return md_li(children, with_par)
|
258
|
+
end
|
259
|
+
|
260
|
+
def read_abbreviation(src)
|
261
|
+
if not (l=src.shift_line) =~ Abbreviation
|
262
|
+
maruku_error "Bug: it's Andrea's fault. Tell him.\n#{l.inspect}"
|
263
|
+
end
|
264
|
+
|
265
|
+
abbr = $1
|
266
|
+
desc = $2
|
267
|
+
|
268
|
+
if (not abbr) or (abbr.size==0)
|
269
|
+
maruku_error "Bad abbrev. abbr=#{abbr.inspect} desc=#{desc.inspect}"
|
270
|
+
end
|
271
|
+
|
272
|
+
self.abbreviations[abbr] = desc
|
273
|
+
|
274
|
+
return md_abbr_def(abbr, desc)
|
275
|
+
end
|
276
|
+
|
277
|
+
def read_footnote_text(src)
|
278
|
+
parent_offset = src.cur_index
|
279
|
+
|
280
|
+
first = src.shift_line
|
281
|
+
|
282
|
+
if not first =~ FootnoteText
|
283
|
+
maruku_error "Bug (it's Andrea's fault)"
|
284
|
+
end
|
285
|
+
|
286
|
+
id = $1
|
287
|
+
text = $2
|
288
|
+
|
289
|
+
# Ugly things going on inside `read_indented_content`
|
290
|
+
indentation = 4 #first.size-text.size
|
291
|
+
|
292
|
+
# puts "id =_#{id}_; text=_#{text}_ indent=#{indentation}"
|
293
|
+
|
294
|
+
break_list = [:footnote_text]
|
295
|
+
item_type = :footnote_text
|
296
|
+
lines, want_my_paragraph =
|
297
|
+
read_indented_content(src,indentation, break_list, item_type)
|
298
|
+
|
299
|
+
# add first line
|
300
|
+
if text && text.strip != "" then lines.unshift text end
|
301
|
+
|
302
|
+
# dbg_describe_ary(lines, 'FOOTNOTE')
|
303
|
+
src2 = LineSource.new(lines, src, parent_offset)
|
304
|
+
children = parse_blocks(src2)
|
305
|
+
|
306
|
+
e = md_footnote(id, children)
|
307
|
+
self.footnotes[id] = e
|
308
|
+
return e
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
# This is the only ugly function in the code base.
|
313
|
+
# It is used to read list items, descriptions, footnote text
|
314
|
+
def read_indented_content(src, indentation, break_list, item_type)
|
315
|
+
lines =[]
|
316
|
+
# collect all indented lines
|
317
|
+
saw_empty = false; saw_anything_after = false
|
318
|
+
while src.cur_line
|
319
|
+
#puts "#{src.cur_line.md_type} #{src.cur_line.inspect}"
|
320
|
+
if src.cur_line.md_type == :empty
|
321
|
+
saw_empty = true
|
322
|
+
lines << src.shift_line
|
323
|
+
next
|
324
|
+
end
|
325
|
+
|
326
|
+
# after a white line
|
327
|
+
if saw_empty
|
328
|
+
# we expect things to be properly aligned
|
329
|
+
if (ns=number_of_leading_spaces(src.cur_line)) < indentation
|
330
|
+
#puts "breaking for spaces, only #{ns}: #{src.cur_line}"
|
331
|
+
break
|
332
|
+
end
|
333
|
+
saw_anything_after = true
|
334
|
+
else
|
335
|
+
break if break_list.include? src.cur_line.md_type
|
336
|
+
# break if src.cur_line.md_type != :text
|
337
|
+
end
|
338
|
+
|
339
|
+
|
340
|
+
stripped = strip_indent(src.shift_line, indentation)
|
341
|
+
lines << stripped
|
342
|
+
|
343
|
+
#puts "Accepted as #{stripped.inspect}"
|
344
|
+
|
345
|
+
# You are only required to indent the first line of
|
346
|
+
# a child paragraph.
|
347
|
+
if stripped.md_type == :text
|
348
|
+
while src.cur_line && (src.cur_line.md_type == :text)
|
349
|
+
lines << strip_indent(src.shift_line, indentation)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
want_my_paragraph = saw_anything_after ||
|
355
|
+
(saw_empty && (src.cur_line && (src.cur_line.md_type == item_type)))
|
356
|
+
|
357
|
+
# dbg_describe_ary(lines, 'LI')
|
358
|
+
# create a new context
|
359
|
+
|
360
|
+
while lines.last && (lines.last.md_type == :empty)
|
361
|
+
lines.pop
|
362
|
+
end
|
363
|
+
|
364
|
+
return lines, want_my_paragraph
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
def read_quote(src)
|
369
|
+
parent_offset = src.cur_index
|
370
|
+
|
371
|
+
lines = []
|
372
|
+
# collect all indented lines
|
373
|
+
while src.cur_line && src.cur_line.md_type == :quote
|
374
|
+
lines << unquote(src.shift_line)
|
375
|
+
end
|
376
|
+
# dbg_describe_ary(lines, 'QUOTE')
|
377
|
+
|
378
|
+
src2 = LineSource.new(lines, src, parent_offset)
|
379
|
+
children = parse_blocks(src2)
|
380
|
+
return md_quote(children)
|
381
|
+
end
|
382
|
+
|
383
|
+
def read_code(src)
|
384
|
+
# collect all indented lines
|
385
|
+
lines = []
|
386
|
+
while src.cur_line && ([:code, :empty].include? src.cur_line.md_type)
|
387
|
+
lines << strip_indent(src.shift_line, 4)
|
388
|
+
end
|
389
|
+
|
390
|
+
#while lines.last && (lines.last.md_type == :empty )
|
391
|
+
while lines.last && lines.last.strip.size == 0
|
392
|
+
lines.pop
|
393
|
+
end
|
394
|
+
|
395
|
+
while lines.first && lines.first.strip.size == 0
|
396
|
+
lines.shift
|
397
|
+
end
|
398
|
+
|
399
|
+
return nil if lines.empty?
|
400
|
+
|
401
|
+
source = lines.join("\n")
|
402
|
+
|
403
|
+
# dbg_describe_ary(lines, 'CODE')
|
404
|
+
|
405
|
+
return md_codeblock(source)
|
406
|
+
end
|
407
|
+
|
408
|
+
# Reads a series of metadata lines with empty lines in between
|
409
|
+
def read_metadata(src)
|
410
|
+
hash = {}
|
411
|
+
while src.cur_line
|
412
|
+
case src.cur_line.md_type
|
413
|
+
when :empty; src.shift_line
|
414
|
+
when :metadata; hash.merge! parse_metadata(src.shift_line)
|
415
|
+
else break
|
416
|
+
end
|
417
|
+
end
|
418
|
+
hash
|
419
|
+
end
|
420
|
+
|
421
|
+
|
422
|
+
def read_ref_definition(src)
|
423
|
+
line = src.shift_line
|
424
|
+
|
425
|
+
# if link is incomplete, shift next line
|
426
|
+
if src.cur_line && (src.cur_line.md_type != :ref_definition) &&
|
427
|
+
([1,2,3].include? number_of_leading_spaces(src.cur_line) )
|
428
|
+
line += " "+ src.shift_line
|
429
|
+
end
|
430
|
+
|
431
|
+
# puts "total= #{line}"
|
432
|
+
|
433
|
+
match = LinkRegex.match(line)
|
434
|
+
if not match
|
435
|
+
error "Link does not respect format: '#{line}'"
|
436
|
+
end
|
437
|
+
|
438
|
+
id = match[1]; url = match[2]; title = match[3];
|
439
|
+
id = id.strip.downcase
|
440
|
+
|
441
|
+
hash = self.refs[id] = {:url=>url,:title=>title}
|
442
|
+
|
443
|
+
stuff=match[4]
|
444
|
+
|
445
|
+
if stuff
|
446
|
+
stuff.split.each do |couple|
|
447
|
+
# puts "found #{couple}"
|
448
|
+
k, v = couple.split('=')
|
449
|
+
v ||= ""
|
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
|
+
return md_ref_def(id, url, meta={:title=>title})
|
458
|
+
end
|
459
|
+
|
460
|
+
def read_table(src)
|
461
|
+
|
462
|
+
def split_cells(s)
|
463
|
+
s.strip.split('|').select{|x|x.strip.size>0}.map{|x|x.strip}
|
464
|
+
end
|
465
|
+
|
466
|
+
head = split_cells(src.shift_line).map{|s| md_el(:head_cell, parse_lines_as_span([s])) }
|
467
|
+
|
468
|
+
separator=split_cells(src.shift_line)
|
469
|
+
|
470
|
+
align = separator.map { |s| s =~ Sep
|
471
|
+
if $1 and $2 then :center elsif $2 then :right else :left end }
|
472
|
+
|
473
|
+
num_columns = align.size
|
474
|
+
|
475
|
+
if head.size != num_columns
|
476
|
+
maruku_error "Table head does not have #{num_columns} columns: \n#{head.inspect}"
|
477
|
+
tell_user "I will ignore this table."
|
478
|
+
# XXX try to recover
|
479
|
+
return md_br()
|
480
|
+
end
|
481
|
+
|
482
|
+
rows = []
|
483
|
+
|
484
|
+
while src.cur_line && src.cur_line =~ /\|/
|
485
|
+
row = split_cells(src.shift_line).map{|s|
|
486
|
+
md_el(:cell, parse_lines_as_span([s]))}
|
487
|
+
if head.size != num_columns
|
488
|
+
maruku_error "Row does not have #{num_columns} columns: \n#{row.inspect}"
|
489
|
+
tell_user "I will ignore this table."
|
490
|
+
# XXX try to recover
|
491
|
+
return md_br()
|
492
|
+
end
|
493
|
+
rows << row
|
494
|
+
end
|
495
|
+
|
496
|
+
children = (head+rows).flatten
|
497
|
+
return md_el(:table, children, {:align => align})
|
498
|
+
end
|
499
|
+
|
500
|
+
# If current line is text, a definition list is coming
|
501
|
+
# if 1) text,empty,[text,empty]*,definition
|
502
|
+
|
503
|
+
def eventually_comes_a_def_list(src)
|
504
|
+
future = src.tell_me_the_future
|
505
|
+
ok = future =~ %r{^t+e?d}x
|
506
|
+
# puts "future: #{future} - #{ok}"
|
507
|
+
ok
|
508
|
+
end
|
509
|
+
|
510
|
+
|
511
|
+
def read_definition(src)
|
512
|
+
# Read one or more terms
|
513
|
+
terms = []
|
514
|
+
while src.cur_line && src.cur_line.md_type == :text
|
515
|
+
terms << md_el(:definition_term, parse_lines_as_span([src.shift_line]))
|
516
|
+
end
|
517
|
+
# dbg_describe_ary(terms, 'DT')
|
518
|
+
|
519
|
+
want_my_paragraph = false
|
520
|
+
|
521
|
+
raise "Chunky Bacon!" if not src.cur_line
|
522
|
+
|
523
|
+
# one optional empty
|
524
|
+
if src.cur_line.md_type == :empty
|
525
|
+
want_my_paragraph = true
|
526
|
+
src.shift_line
|
527
|
+
end
|
528
|
+
|
529
|
+
raise "Chunky Bacon!" if src.cur_line.md_type != :definition
|
530
|
+
|
531
|
+
# Read one or more definitions
|
532
|
+
definitions = []
|
533
|
+
while src.cur_line && src.cur_line.md_type == :definition
|
534
|
+
parent_offset = src.cur_index
|
535
|
+
|
536
|
+
first = src.shift_line
|
537
|
+
first =~ Definition
|
538
|
+
first = $1
|
539
|
+
|
540
|
+
# I know, it's ugly!!!
|
541
|
+
|
542
|
+
lines, w_m_p =
|
543
|
+
read_indented_content(src,4, [:definition], :definition)
|
544
|
+
want_my_paragraph ||= w_m_p
|
545
|
+
|
546
|
+
lines.unshift first
|
547
|
+
|
548
|
+
# dbg_describe_ary(lines, 'DD')
|
549
|
+
src2 = LineSource.new(lines, src, parent_offset)
|
550
|
+
children = parse_blocks(src2)
|
551
|
+
definitions << md_el(:definition_data, children)
|
552
|
+
end
|
553
|
+
|
554
|
+
return md_el(:definition, terms+definitions, {
|
555
|
+
:terms => terms,
|
556
|
+
:definitions => definitions,
|
557
|
+
:want_my_paragraph => want_my_paragraph})
|
558
|
+
end
|
559
|
+
end # BlockLevelParser
|
560
|
+
end # MaRuKu
|
561
|
+
end
|
562
|
+
end
|