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.
Files changed (108) hide show
  1. data/bin/{maruku0.3 → marudown} +6 -14
  2. data/bin/maruku +1 -1
  3. data/bin/marutest +37 -9
  4. data/docs/TOFIX.html +22 -0
  5. data/docs/TOFIX.md +3 -0
  6. data/docs/changelog-0.2.13.html +30 -0
  7. data/docs/changelog-0.2.13.md +6 -0
  8. data/docs/changelog-0.3.html +19 -5
  9. data/docs/faq.html +51 -40
  10. data/docs/faq.md +3 -3
  11. data/docs/hidden_o_n_squared.md +10 -0
  12. data/docs/index.html +84 -396
  13. data/docs/markdown_syntax.html +139 -330
  14. data/docs/markdown_syntax.md +80 -93
  15. data/docs/maruku.html +84 -396
  16. data/docs/maruku.md +88 -158
  17. data/docs/proposal.html +13 -106
  18. data/docs/proposal.md +3 -3
  19. data/docs/todo.html +38 -28
  20. data/lib/maruku.rb +77 -11
  21. data/lib/maruku/attributes.rb +186 -0
  22. data/lib/maruku/defaults.rb +40 -0
  23. data/lib/maruku/errors_management.rb +55 -39
  24. data/lib/maruku/helpers.rb +156 -72
  25. data/lib/maruku/input/charsource.rb +319 -0
  26. data/lib/maruku/{html_helper.rb → input/html_helper.rb} +30 -9
  27. data/lib/maruku/input/linesource.rb +111 -0
  28. data/lib/maruku/input/parse_block.rb +562 -0
  29. data/lib/maruku/{parse_doc.rb → input/parse_doc.rb} +60 -28
  30. data/lib/maruku/{parse_span_better.rb → input/parse_span_better.rb} +226 -256
  31. data/lib/maruku/input/type_detection.rb +137 -0
  32. data/lib/maruku/maruku.rb +33 -0
  33. data/lib/maruku/{to_html.rb → output/to_html.rb} +151 -132
  34. data/lib/maruku/{to_latex.rb → output/to_latex.rb} +31 -35
  35. data/lib/maruku/{to_latex_entities.rb → output/to_latex_entities.rb} +25 -3
  36. data/lib/maruku/output/to_latex_strings.rb +64 -0
  37. data/lib/maruku/output/to_markdown.rb +164 -0
  38. data/lib/maruku/{to_s.rb → output/to_s.rb} +6 -0
  39. data/lib/maruku/string_utils.rb +12 -181
  40. data/lib/maruku/structures.rb +91 -67
  41. data/lib/maruku/structures_inspect.rb +78 -0
  42. data/lib/maruku/structures_iterators.rb +24 -2
  43. data/lib/maruku/tests/benchmark.rb +41 -9
  44. data/lib/maruku/tests/new_parser.rb +317 -286
  45. data/lib/maruku/tests/tests.rb +20 -0
  46. data/lib/maruku/toc.rb +64 -64
  47. data/lib/maruku/usage/example1.rb +33 -0
  48. data/lib/maruku/version.rb +8 -2
  49. data/tests/unittest/abbreviations.md +27 -16
  50. data/tests/unittest/attributes/attributes.md +89 -0
  51. data/tests/unittest/attributes/circular.md +51 -0
  52. data/tests/unittest/attributes/default.md +47 -0
  53. data/tests/unittest/blank.md +10 -6
  54. data/tests/unittest/blanks_in_code.md +26 -26
  55. data/tests/unittest/code.md +9 -9
  56. data/tests/unittest/code2.md +12 -13
  57. data/tests/unittest/code3.md +34 -34
  58. data/tests/unittest/easy.md +9 -7
  59. data/tests/unittest/email.md +9 -7
  60. data/tests/unittest/encoding/iso-8859-1.md +41 -4
  61. data/tests/unittest/encoding/utf-8.md +6 -5
  62. data/tests/unittest/entities.md +52 -80
  63. data/tests/unittest/escaping.md +47 -35
  64. data/tests/unittest/extra_dl.md +19 -29
  65. data/tests/unittest/extra_header_id.md +31 -24
  66. data/tests/unittest/extra_table1.md +14 -32
  67. data/tests/unittest/footnotes.md +58 -42
  68. data/tests/unittest/headers.md +11 -11
  69. data/tests/unittest/hrule.md +14 -24
  70. data/tests/unittest/images.md +41 -26
  71. data/tests/unittest/inline_html.md +104 -56
  72. data/tests/unittest/inline_html2.md +38 -0
  73. data/tests/unittest/links.md +74 -33
  74. data/tests/unittest/list1.md +18 -15
  75. data/tests/unittest/list2.md +31 -13
  76. data/tests/unittest/list3.md +29 -28
  77. data/tests/unittest/list4.md +103 -12
  78. data/tests/unittest/lists.md +86 -53
  79. data/tests/unittest/lists6.md +53 -0
  80. data/tests/unittest/lists7.md +31 -0
  81. data/tests/unittest/lists_after_paragraph.md +105 -71
  82. data/tests/unittest/lists_ol.md +149 -73
  83. data/tests/unittest/misc_sw.md +366 -326
  84. data/tests/unittest/notyet/escape.md +10 -10
  85. data/tests/unittest/notyet/header_after_par.md +20 -14
  86. data/tests/unittest/notyet/ticks.md +8 -35
  87. data/tests/unittest/notyet/triggering.md +72 -45
  88. data/tests/unittest/olist.md +78 -0
  89. data/tests/unittest/one.md +5 -3
  90. data/tests/unittest/paragraph.md +5 -3
  91. data/tests/unittest/paragraph_rules/dont_merge_ref.md +15 -9
  92. data/tests/unittest/paragraph_rules/tab_is_blank.md +9 -5
  93. data/tests/unittest/paragraphs.md +21 -26
  94. data/tests/unittest/recover/recover_links.md +6 -5
  95. data/tests/unittest/references/long_example.md +39 -30
  96. data/tests/unittest/references/spaces_and_numbers.md +2 -2
  97. data/tests/unittest/syntax_hl.md +33 -31
  98. data/tests/unittest/test.md +4 -6
  99. data/tests/unittest/wrapping.md +43 -26
  100. metadata +160 -139
  101. data/docs/markdown_extra2.html +0 -87
  102. data/docs/markdown_extra2.md +0 -83
  103. data/docs/markdown_syntax_2.html +0 -152
  104. data/lib/maruku/parse_block.rb +0 -564
  105. data/lib/maruku/parse_span.rb +0 -451
  106. data/lib/maruku/to_latex_strings.rb +0 -59
  107. data/lib/maruku/to_markdown.rb +0 -110
  108. 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
- class Maruku
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 MarukuStrings
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 RuntimeError, "Error: #{s} "+ inspect, caller
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
- end
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