maruku 0.6.1 → 0.7.0.beta1

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 (263) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/MIT-LICENSE.txt +20 -0
  5. data/bin/maruku +153 -152
  6. data/bin/marutex +2 -29
  7. data/data/entities.xml +261 -0
  8. data/docs/math.md +14 -18
  9. data/lib/maruku.rb +65 -77
  10. data/lib/maruku/attributes.rb +109 -214
  11. data/lib/maruku/defaults.rb +45 -67
  12. data/lib/maruku/document.rb +43 -0
  13. data/lib/maruku/element.rb +112 -0
  14. data/lib/maruku/errors.rb +71 -0
  15. data/lib/maruku/ext/div.rb +105 -113
  16. data/lib/maruku/ext/fenced_code.rb +97 -0
  17. data/lib/maruku/ext/math.rb +22 -26
  18. data/lib/maruku/ext/math/elements.rb +20 -26
  19. data/lib/maruku/ext/math/mathml_engines/blahtex.rb +92 -104
  20. data/lib/maruku/ext/math/mathml_engines/itex2mml.rb +33 -26
  21. data/lib/maruku/ext/math/mathml_engines/none.rb +11 -19
  22. data/lib/maruku/ext/math/mathml_engines/ritex.rb +2 -4
  23. data/lib/maruku/ext/math/parsing.rb +107 -113
  24. data/lib/maruku/ext/math/to_html.rb +184 -187
  25. data/lib/maruku/ext/math/to_latex.rb +30 -21
  26. data/lib/maruku/helpers.rb +158 -257
  27. data/lib/maruku/html.rb +254 -0
  28. data/lib/maruku/input/charsource.rb +272 -319
  29. data/lib/maruku/input/extensions.rb +62 -63
  30. data/lib/maruku/input/html_helper.rb +220 -189
  31. data/lib/maruku/input/linesource.rb +90 -110
  32. data/lib/maruku/input/mdline.rb +129 -0
  33. data/lib/maruku/input/parse_block.rb +618 -612
  34. data/lib/maruku/input/parse_doc.rb +145 -215
  35. data/lib/maruku/input/parse_span.rb +658 -0
  36. data/lib/maruku/input/rubypants.rb +200 -128
  37. data/lib/maruku/inspect_element.rb +60 -0
  38. data/lib/maruku/maruku.rb +10 -31
  39. data/lib/maruku/output/entity_table.rb +33 -0
  40. data/lib/maruku/output/s5/fancy.rb +462 -462
  41. data/lib/maruku/output/s5/to_s5.rb +115 -135
  42. data/lib/maruku/output/to_html.rb +898 -983
  43. data/lib/maruku/output/to_latex.rb +561 -560
  44. data/lib/maruku/output/to_markdown.rb +207 -162
  45. data/lib/maruku/output/to_s.rb +11 -52
  46. data/lib/maruku/string_utils.rb +129 -179
  47. data/lib/maruku/toc.rb +185 -196
  48. data/lib/maruku/version.rb +33 -38
  49. data/spec/block_docs/abbrev.md +776 -0
  50. data/{tests/unittest → spec/block_docs}/abbreviations.md +11 -20
  51. data/{tests/unittest → spec/block_docs}/alt.md +2 -14
  52. data/{tests/unittest/pending → spec/block_docs}/amps.md +1 -13
  53. data/{tests/unittest → spec/block_docs}/attributes/att2.md +0 -12
  54. data/{tests/unittest → spec/block_docs}/attributes/att3.md +2 -14
  55. data/{tests/unittest → spec/block_docs}/attributes/attributes.md +12 -16
  56. data/{tests/unittest → spec/block_docs}/attributes/circular.md +0 -12
  57. data/{tests/unittest → spec/block_docs}/attributes/default.md +1 -13
  58. data/{tests/unittest → spec/block_docs}/blank.md +0 -12
  59. data/{tests/unittest → spec/block_docs}/blanks_in_code.md +16 -15
  60. data/{tests/unittest/loss.md → spec/block_docs/bug_def.md} +6 -18
  61. data/{tests/unittest → spec/block_docs}/bug_table.md +3 -15
  62. data/{tests/unittest → spec/block_docs}/code.md +7 -14
  63. data/{tests/unittest → spec/block_docs}/code2.md +4 -14
  64. data/{tests/unittest → spec/block_docs}/code3.md +12 -16
  65. data/{tests/unittest → spec/block_docs}/data_loss.md +2 -14
  66. data/{tests/unittest → spec/block_docs}/divs/div1.md +0 -12
  67. data/{tests/unittest → spec/block_docs}/divs/div2.md +0 -12
  68. data/{tests/unittest → spec/block_docs}/divs/div3_nest.md +3 -15
  69. data/{tests/unittest → spec/block_docs}/easy.md +1 -13
  70. data/spec/block_docs/email.md +29 -0
  71. data/{tests/unittest/pending → spec/block_docs}/empty_cells.md +3 -15
  72. data/{tests/unittest → spec/block_docs}/encoding/iso-8859-1.md +1 -14
  73. data/{tests/unittest → spec/block_docs}/encoding/utf-8.md +0 -12
  74. data/{tests/unittest → spec/block_docs}/entities.md +27 -29
  75. data/{tests/unittest/notyet → spec/block_docs}/escape.md +2 -14
  76. data/{tests/unittest → spec/block_docs}/escaping.md +11 -22
  77. data/{tests/unittest → spec/block_docs}/extra_dl.md +2 -13
  78. data/{tests/unittest → spec/block_docs}/extra_header_id.md +14 -20
  79. data/{tests/unittest → spec/block_docs}/extra_table1.md +3 -15
  80. data/spec/block_docs/fenced_code_blocks.md +66 -0
  81. data/spec/block_docs/fenced_code_blocks_highlighted.md +18 -0
  82. data/{tests/unittest → spec/block_docs}/footnotes.md +12 -24
  83. data/spec/block_docs/footnotes2.md +78 -0
  84. data/spec/block_docs/hard.md +25 -0
  85. data/spec/block_docs/header_after_par.md +62 -0
  86. data/{tests/unittest → spec/block_docs}/headers.md +10 -18
  87. data/{tests/unittest → spec/block_docs}/hex_entities.md +7 -18
  88. data/{tests/unittest → spec/block_docs}/hrule.md +5 -12
  89. data/{tests/unittest → spec/block_docs}/html3.md +1 -13
  90. data/{tests/unittest → spec/block_docs}/html4.md +2 -14
  91. data/{tests/unittest → spec/block_docs}/html5.md +2 -14
  92. data/spec/block_docs/html_block_in_para.md +22 -0
  93. data/spec/block_docs/html_inline.md +25 -0
  94. data/spec/block_docs/html_trailing.md +31 -0
  95. data/spec/block_docs/ie.md +62 -0
  96. data/spec/block_docs/iframe.md +29 -0
  97. data/{tests/unittest → spec/block_docs}/images.md +22 -28
  98. data/{tests/unittest → spec/block_docs}/images2.md +7 -17
  99. data/{tests/unittest → spec/block_docs}/inline_html.md +37 -67
  100. data/{tests/unittest → spec/block_docs}/inline_html2.md +1 -13
  101. data/spec/block_docs/inline_html_beginning.md +10 -0
  102. data/spec/block_docs/issue20.md +9 -0
  103. data/spec/block_docs/issue26.md +22 -0
  104. data/spec/block_docs/issue29.md +9 -0
  105. data/spec/block_docs/issue30.md +30 -0
  106. data/spec/block_docs/issue31.md +25 -0
  107. data/spec/block_docs/issue40.md +40 -0
  108. data/spec/block_docs/issue64.md +55 -0
  109. data/spec/block_docs/issue67.md +19 -0
  110. data/spec/block_docs/issue70.md +11 -0
  111. data/spec/block_docs/issue72.md +17 -0
  112. data/spec/block_docs/issue74.md +38 -0
  113. data/spec/block_docs/issue79.md +15 -0
  114. data/spec/block_docs/issue83.md +13 -0
  115. data/spec/block_docs/issue85.md +25 -0
  116. data/spec/block_docs/issue88.md +19 -0
  117. data/spec/block_docs/issue89.md +12 -0
  118. data/spec/block_docs/issue90.md +38 -0
  119. data/{tests/unittest/pending → spec/block_docs}/link.md +21 -18
  120. data/{tests/unittest → spec/block_docs}/links.md +33 -32
  121. data/spec/block_docs/links2.md +21 -0
  122. data/{tests/unittest → spec/block_docs}/list1.md +0 -12
  123. data/{tests/unittest → spec/block_docs}/list12.md +2 -14
  124. data/{tests/unittest → spec/block_docs}/list2.md +2 -14
  125. data/spec/block_docs/list_multipara.md +42 -0
  126. data/{tests/unittest → spec/block_docs}/lists.md +28 -29
  127. data/{tests/unittest → spec/block_docs}/lists10.md +2 -14
  128. data/spec/block_docs/lists11.md +23 -0
  129. data/spec/block_docs/lists12.md +43 -0
  130. data/spec/block_docs/lists13.md +55 -0
  131. data/spec/block_docs/lists14.md +61 -0
  132. data/spec/block_docs/lists15.md +36 -0
  133. data/spec/block_docs/lists6.md +88 -0
  134. data/spec/block_docs/lists7b.md +58 -0
  135. data/spec/block_docs/lists9.md +53 -0
  136. data/{tests/unittest → spec/block_docs}/lists_after_paragraph.md +19 -25
  137. data/spec/block_docs/lists_blank.md +35 -0
  138. data/{tests/unittest/list3.md → spec/block_docs/lists_blockquote_code.md} +2 -14
  139. data/{tests/unittest/list4.md → spec/block_docs/lists_need_blank_line.md} +50 -21
  140. data/spec/block_docs/lists_nested.md +44 -0
  141. data/spec/block_docs/lists_nested_blankline.md +28 -0
  142. data/spec/block_docs/lists_nested_deep.md +43 -0
  143. data/{tests/unittest → spec/block_docs}/lists_ol.md +37 -54
  144. data/spec/block_docs/lists_paraindent.md +47 -0
  145. data/spec/block_docs/lists_tab.md +54 -0
  146. data/spec/block_docs/loss.md +17 -0
  147. data/spec/block_docs/math-blahtex/equations.md +30 -0
  148. data/spec/block_docs/math-blahtex/inline.md +48 -0
  149. data/spec/block_docs/math-blahtex/math2.md +45 -0
  150. data/spec/block_docs/math-blahtex/table.md +25 -0
  151. data/spec/block_docs/math/embedded_invalid_svg.md +79 -0
  152. data/spec/block_docs/math/embedded_svg.md +97 -0
  153. data/spec/block_docs/math/equations.md +44 -0
  154. data/{tests/unittest → spec/block_docs}/math/inline.md +7 -19
  155. data/spec/block_docs/math/math2.md +45 -0
  156. data/{tests/unittest → spec/block_docs}/math/notmath.md +0 -12
  157. data/spec/block_docs/math/raw_mathml.md +87 -0
  158. data/spec/block_docs/math/table.md +25 -0
  159. data/{tests/unittest → spec/block_docs}/math/table2.md +5 -17
  160. data/{tests/unittest → spec/block_docs}/misc_sw.md +181 -118
  161. data/{tests/unittest → spec/block_docs}/olist.md +6 -18
  162. data/{tests/unittest → spec/block_docs}/one.md +0 -12
  163. data/{tests/unittest → spec/block_docs}/paragraph.md +0 -12
  164. data/{tests/unittest → spec/block_docs}/paragraph_rules/dont_merge_ref.md +4 -12
  165. data/{tests/unittest → spec/block_docs}/paragraph_rules/tab_is_blank.md +0 -12
  166. data/{tests/unittest → spec/block_docs}/paragraphs.md +1 -13
  167. data/{tests/unittest → spec/block_docs}/recover/recover_links.md +4 -16
  168. data/{tests/unittest/pending/ref.md → spec/block_docs/ref_with_period.md} +7 -16
  169. data/spec/block_docs/ref_with_title.md +22 -0
  170. data/{tests/unittest → spec/block_docs}/references/long_example.md +16 -23
  171. data/{tests/unittest → spec/block_docs}/references/spaces_and_numbers.md +0 -12
  172. data/{tests/unittest → spec/block_docs}/smartypants.md +24 -31
  173. data/{tests/unittest → spec/block_docs}/syntax_hl.md +13 -17
  174. data/{tests/unittest → spec/block_docs}/table_attributes.md +2 -14
  175. data/spec/block_docs/tables.md +58 -0
  176. data/{tests/unittest → spec/block_docs}/test.md +1 -13
  177. data/{tests/unittest/notyet → spec/block_docs}/ticks.md +1 -13
  178. data/spec/block_docs/toc.md +87 -0
  179. data/{tests/unittest/notyet → spec/block_docs}/triggering.md +14 -25
  180. data/{tests/unittest → spec/block_docs}/underscore_in_words.md +0 -12
  181. data/{tests/unittest → spec/block_docs}/wrapping.md +4 -16
  182. data/spec/block_docs/xml.md +33 -0
  183. data/{tests/unittest → spec/block_docs}/xml2.md +0 -12
  184. data/spec/block_docs/xml3.md +24 -0
  185. data/{tests/unittest → spec/block_docs}/xml_instruction.md +9 -20
  186. data/spec/block_spec.rb +110 -0
  187. data/spec/cli_spec.rb +8 -0
  188. data/spec/span_spec.rb +256 -0
  189. data/spec/spec_helper.rb +2 -0
  190. data/spec/to_html_utf8_spec.rb +13 -0
  191. metadata +205 -243
  192. metadata.gz.sig +3 -0
  193. data/Rakefile +0 -48
  194. data/bin/marudown +0 -29
  195. data/bin/marutest +0 -345
  196. data/docs/changelog.md +0 -334
  197. data/lib/maruku/errors_management.rb +0 -92
  198. data/lib/maruku/ext/math/latex_fix.rb +0 -12
  199. data/lib/maruku/input/parse_span_better.rb +0 -746
  200. data/lib/maruku/input/type_detection.rb +0 -147
  201. data/lib/maruku/output/to_latex_entities.rb +0 -367
  202. data/lib/maruku/output/to_latex_strings.rb +0 -64
  203. data/lib/maruku/structures.rb +0 -167
  204. data/lib/maruku/structures_inspect.rb +0 -87
  205. data/lib/maruku/structures_iterators.rb +0 -61
  206. data/lib/maruku/tests/benchmark.rb +0 -82
  207. data/lib/maruku/tests/new_parser.rb +0 -373
  208. data/lib/maruku/tests/tests.rb +0 -136
  209. data/lib/maruku/usage/example1.rb +0 -33
  210. data/tests/bugs/code_in_links.md +0 -101
  211. data/tests/bugs/complex_escaping.md +0 -38
  212. data/tests/math/syntax.md +0 -46
  213. data/tests/math_usage/document.md +0 -13
  214. data/tests/others/abbreviations.md +0 -11
  215. data/tests/others/blank.md +0 -4
  216. data/tests/others/code.md +0 -5
  217. data/tests/others/code2.md +0 -8
  218. data/tests/others/code3.md +0 -16
  219. data/tests/others/email.md +0 -4
  220. data/tests/others/entities.md +0 -19
  221. data/tests/others/escaping.md +0 -16
  222. data/tests/others/extra_dl.md +0 -101
  223. data/tests/others/extra_header_id.md +0 -13
  224. data/tests/others/extra_table1.md +0 -40
  225. data/tests/others/footnotes.md +0 -17
  226. data/tests/others/headers.md +0 -10
  227. data/tests/others/hrule.md +0 -10
  228. data/tests/others/images.md +0 -20
  229. data/tests/others/inline_html.md +0 -42
  230. data/tests/others/links.md +0 -38
  231. data/tests/others/list1.md +0 -4
  232. data/tests/others/list2.md +0 -5
  233. data/tests/others/list3.md +0 -8
  234. data/tests/others/lists.md +0 -32
  235. data/tests/others/lists_after_paragraph.md +0 -44
  236. data/tests/others/lists_ol.md +0 -39
  237. data/tests/others/misc_sw.md +0 -105
  238. data/tests/others/one.md +0 -1
  239. data/tests/others/paragraphs.md +0 -13
  240. data/tests/others/sss06.md +0 -352
  241. data/tests/others/test.md +0 -4
  242. data/tests/s5/s5profiling.md +0 -48
  243. data/tests/unittest/bug_def.md +0 -28
  244. data/tests/unittest/email.md +0 -32
  245. data/tests/unittest/html2.md +0 -34
  246. data/tests/unittest/ie.md +0 -61
  247. data/tests/unittest/links2.md +0 -34
  248. data/tests/unittest/lists11.md +0 -28
  249. data/tests/unittest/lists6.md +0 -53
  250. data/tests/unittest/lists9.md +0 -76
  251. data/tests/unittest/math/equations.md +0 -86
  252. data/tests/unittest/math/math2.md +0 -57
  253. data/tests/unittest/math/table.md +0 -37
  254. data/tests/unittest/notyet/header_after_par.md +0 -70
  255. data/tests/unittest/red_tests/abbrev.md +0 -1388
  256. data/tests/unittest/red_tests/lists7.md +0 -68
  257. data/tests/unittest/red_tests/lists7b.md +0 -128
  258. data/tests/unittest/red_tests/lists8.md +0 -76
  259. data/tests/unittest/red_tests/xml.md +0 -70
  260. data/tests/unittest/xml3.md +0 -38
  261. data/tests/utf8-files/simple.md +0 -1
  262. data/unit_test_block.sh +0 -5
  263. data/unit_test_span.sh +0 -3
@@ -1,33 +1,16 @@
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
- module MaRuKu; module In; module Markdown; module BlockLevelParser
22
-
23
- def parse_doc(s)
24
- # FIXME \r\n => \n
25
- meta2 = parse_email_headers(s)
26
- data = meta2[:data]
27
- meta2.delete :data
28
-
29
- self.attributes.merge! meta2
30
-
1
+ require 'strscan'
2
+ require 'cgi'
3
+
4
+ module MaRuKu::In::Markdown::BlockLevelParser
5
+
6
+ def parse_doc(s)
7
+ # Remove BOM if it is present
8
+ s = s.sub(/^\xEF\xBB\xBF/u, '')
9
+ meta2 = parse_email_headers(s)
10
+ data = meta2.delete :data
11
+
12
+ self.attributes.merge! meta2
13
+
31
14
  =begin maruku_doc
32
15
  Attribute: encoding
33
16
  Scope: document
@@ -37,50 +20,40 @@ If the `encoding` attribute is specified, then the content
37
20
  will be converted from the specified encoding to UTF-8.
38
21
  =end
39
22
 
40
- enc = self.attributes[:encoding]
41
- self.attributes.delete :encoding
42
- if enc && enc.downcase != 'utf-8'
43
-
44
- # Switch to ruby 1.9 String#encode
45
- # with backward 1.8 compatibility
46
- if data.respond_to?(:encode!)
47
- data.encode!('UTF-8', enc)
48
- else
49
- require 'iconv'
50
- data = Iconv.new('utf-8', enc).iconv(data)
51
- end
52
-
53
- end
54
-
55
- @children = parse_text_as_markdown(data)
56
-
57
- if true #markdown_extra?
58
- self.search_abbreviations
59
- self.substitute_markdown_inside_raw_html
60
- end
61
-
62
- toc = create_toc
63
-
64
- # use title if not set
65
- if not self.attributes[:title] and toc.header_element
66
- title = toc.header_element.to_s
67
- self.attributes[:title] = title
68
- # puts "Set document title to #{title}"
69
- end
70
-
71
- # save for later use
72
- self.toc = toc
73
-
74
- # Now do the attributes magic
75
- each_element do |e|
76
- # default attribute list
77
- if default = self.ald[e.node_type.to_s]
78
- expand_attribute_list(default, e.attributes)
79
- end
80
- expand_attribute_list(e.al, e.attributes)
81
- # puts "#{e.node_type}: #{e.attributes.inspect}"
82
- end
83
-
23
+ enc = self.attributes.delete(:encoding) || 'utf-8'
24
+ if enc.downcase != 'utf-8'
25
+ # Switch to ruby 1.9 String#encode
26
+ # with backward 1.8 compatibility
27
+ if data.respond_to?(:encode!)
28
+ data.encode!('UTF-8', enc)
29
+ else
30
+ require 'iconv'
31
+ data = Iconv.new('utf-8', enc).iconv(data)
32
+ end
33
+ end
34
+
35
+ @children = parse_text_as_markdown(data)
36
+
37
+ if markdown_extra?
38
+ self.search_abbreviations
39
+ self.substitute_markdown_inside_raw_html
40
+ end
41
+
42
+ self.toc = create_toc
43
+
44
+ # use title if not set
45
+ self.attributes[:title] ||= toc.header_element.children.join if toc.header_element
46
+
47
+ # Now do the attributes magic
48
+ each_element do |e|
49
+ # default attribute list
50
+ if default = self.ald[e.node_type.to_s]
51
+ expand_attribute_list(default, e.attributes)
52
+ end
53
+ expand_attribute_list(e.al, e.attributes)
54
+ # puts "#{e.node_type}: #{e.attributes.inspect}"
55
+ end
56
+
84
57
  =begin maruku_doc
85
58
  Attribute: unsafe_features
86
59
  Scope: global
@@ -89,144 +62,101 @@ Summary: Enables execution of XML instructions.
89
62
  Disabled by default because of security concerns.
90
63
  =end
91
64
 
92
- if Maruku::Globals[:unsafe_features]
93
- self.execute_code_blocks
94
- # TODO: remove executed code blocks
95
- end
96
- end
97
-
98
- # Expands an attribute list in an Hash
99
- def expand_attribute_list(al, result)
100
- al.each do |k, v|
101
- case k
102
- when :class
103
- if not result[:class]
104
- result[:class] = v
105
- else
106
- result[:class] += " " + v
107
- end
108
- when :id; result[:id] = v
109
- when :ref;
110
- if self.ald[v]
111
- already = (result[:expanded_references] ||= [])
112
- if not already.include?(v)
113
- already.push v
114
- expand_attribute_list(self.ald[v], result)
115
- else
116
- already.push v
117
- maruku_error "Circular reference between labels.\n\n"+
118
- "Label #{v.inspect} calls itself via recursion.\nThe recursion is "+
119
- (already.map{|x| x.inspect}.join(' => '))
120
- end
121
- else
122
- if not result[:unresolved_references]
123
- result[:unresolved_references] = v
124
- else
125
- result[:unresolved_references] << " #{v}"
126
- end
127
-
128
- # $stderr.puts "Unresolved reference #{v.inspect} (avail: #{self.ald.keys.inspect})"
129
- result[v.to_sym] = true
130
- end
131
- else
132
- result[k.to_sym]=v
133
- end
134
- end
135
- end
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
-
159
- def execute_code_blocks
160
- self.each_element(:xml_instr) do |e|
161
- if e.target == 'maruku'
162
- result = safe_execute_code(e, e.code)
163
- if result.kind_of?(String)
164
- puts "Result is : #{result.inspect}"
165
- end
166
- end
167
- end
168
- end
169
-
170
- def search_abbreviations
171
- self.abbreviations.each do |abbrev, title|
172
- reg = Regexp.new(Regexp.escape(abbrev))
173
- self.replace_each_string do |s|
174
- # bug if many abbreviations are present (agorf)
175
- if m = reg.match(s)
176
- e = md_abbr(abbrev.dup, title ? title.dup : nil)
177
- [m.pre_match, e, m.post_match]
178
- else
179
- s
180
- end
181
- end
182
- end
183
- end
184
-
185
- include REXML
186
- # (PHP Markdown extra) Search for elements that have
187
- # markdown=1 or markdown=block defined
188
- def substitute_markdown_inside_raw_html
189
- self.each_element(:raw_html) do |e|
190
- doc = e.instance_variable_get :@parsed_html
191
- if doc # valid html
192
- # parse block-level markdown elements in these HTML tags
193
- block_tags = ['div']
194
-
195
- # use xpath to find elements with 'markdown' attribute
196
- XPath.match(doc, "//*[attribute::markdown]" ).each do |e|
197
- # puts "Found #{e}"
198
- # should we parse block-level or span-level?
199
-
200
- how = e.attributes['markdown']
201
- parse_blocks = (how == 'block') || block_tags.include?(e.name)
202
-
203
- # Select all text elements of e
204
- XPath.match(e, "//text()" ).each { |original_text|
205
- s = original_text.value.strip
206
- if s.size > 0
207
-
208
- # puts "Parsing #{s.inspect} as blocks: #{parse_blocks} (#{e.name}, #{e.attributes['markdown']}) "
209
-
210
- el = md_el(:dummy,
211
- parse_blocks ? parse_text_as_markdown(s) :
212
- parse_lines_as_span([s]) )
213
- p = original_text.parent
214
- el.children_to_html.each do |x|
215
- p.insert_before(original_text, x)
216
- end
217
- p.delete(original_text)
218
-
219
- end
220
- }
221
-
222
-
223
- # remove 'markdown' attribute
224
- e.delete_attribute 'markdown'
225
-
226
- end
227
-
228
- end
229
- end
230
- end
231
-
232
- end end end end
65
+ if Maruku::Globals[:unsafe_features]
66
+ self.execute_code_blocks
67
+ # TODO: remove executed code blocks
68
+ end
69
+ end
70
+
71
+ # Expands an attribute list in an Hash
72
+ def expand_attribute_list(al, result)
73
+ al.each do |k, v|
74
+ case k
75
+ when :class
76
+ if result[:class]
77
+ result[:class] << " " << v
78
+ else
79
+ result[:class] = v
80
+ end
81
+ when :id
82
+ result[:id] = v
83
+ when :ref
84
+ if self.ald[v]
85
+ already = (result[:expanded_references] ||= [])
86
+ if !already.include?(v)
87
+ already << v
88
+ expand_attribute_list(self.ald[v], result)
89
+ else
90
+ already << v
91
+ maruku_error "Circular reference between labels.\n\n" +
92
+ "Label #{v.inspect} calls itself via recursion.\nThe recursion is " +
93
+ already.map(&:inspect).join(' => ')
94
+ end
95
+ else
96
+ if result[:unresolved_references]
97
+ result[:unresolved_references] << " " << v
98
+ else
99
+ result[:unresolved_references] = v
100
+ end
101
+
102
+ # $stderr.puts "Unresolved reference #{v.inspect} (avail: #{self.ald.keys.inspect})"
103
+ result[v.to_sym] = true
104
+ end
105
+ else
106
+ result[k.to_sym] = v
107
+ end
108
+ end
109
+ end
110
+
111
+ def safe_execute_code(object, code)
112
+ begin
113
+ object.instance_eval(code)
114
+ rescue StandardError, ScriptError => e
115
+ maruku_error "Exception while executing this:\n" +
116
+ code.gsub(/^/, ">") +
117
+ "\nThe error was:\n" +
118
+ (e.inspect + "\n" + e.caller.join("\n")).gsub(/^/, "|")
119
+ nil
120
+ end
121
+ end
122
+
123
+ def execute_code_blocks
124
+ each_element(:xml_instr) do |e|
125
+ if e.target == 'maruku'
126
+ result = safe_execute_code(e, e.code)
127
+ if result.kind_of?(String)
128
+ puts "Result is : #{result.inspect}"
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+ def search_abbreviations
135
+ abbreviations.each do |abbrev, title|
136
+ reg = Regexp.new(Regexp.escape(abbrev))
137
+ replace_each_string do |s|
138
+ # bug if many abbreviations are present (agorf)
139
+ p = StringScanner.new(s)
140
+ a = []
141
+ until p.eos?
142
+ o = ''
143
+ o << p.getch until p.scan(reg) or p.eos?
144
+ a << o unless o.empty?
145
+ a << md_abbr(abbrev.dup, title ? title.dup : nil) if p.matched == abbrev
146
+ end
147
+ a
148
+ end
149
+ end
150
+ end
151
+
152
+ # (PHP Markdown extra) Search for elements that have
153
+ # markdown=1 or markdown=block defined
154
+ def substitute_markdown_inside_raw_html
155
+ each_element(:raw_html) do |e|
156
+ html = e.parsed_html
157
+ next unless html
158
+
159
+ html.process_markdown_inside_elements(self)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,658 @@
1
+ module MaRuKu::In::Markdown::SpanLevelParser
2
+ include MaRuKu::Helpers
3
+
4
+ EscapedCharInText = '\\`*_{}[]()#.!|:+->'.split(//)
5
+ EscapedCharInQuotes = EscapedCharInText + ["'", '"']
6
+
7
+ EscapedCharInInlineCode = ['\\', '`']
8
+
9
+ IgnoreWikiLinks = MaRuKu::Globals[:ignore_wikilinks]
10
+
11
+ def parse_span(string, parent=nil)
12
+ string = Array(string).join("\n") unless string.kind_of? String
13
+ src = MaRuKu::In::Markdown::SpanLevelParser::CharSource.new(string, parent)
14
+ read_span(src, EscapedCharInText, [nil])
15
+ end
16
+
17
+ # This is the main loop for reading span elements
18
+ #
19
+ # It's long, but not *complex* or difficult to understand.
20
+ #
21
+ #
22
+ def read_span(src, escaped, exit_on_chars=nil, exit_on_strings=nil)
23
+ escaped = Array(escaped)
24
+ con = SpanContext.new
25
+ c = d = nil
26
+ while true
27
+ c = src.cur_char
28
+
29
+ # This is only an optimization which cuts 50% of the time used.
30
+ # (but you can't use a-zA-z in exit_on_chars)
31
+ if c && c =~ /a-zA-Z0-9/
32
+ con.push_char src.shift_char
33
+ next
34
+ end
35
+
36
+ break if Array(exit_on_chars).include?(c)
37
+ if Array(exit_on_strings).any? {|x| src.cur_chars_are x }
38
+ # Special case: bold nested in italic
39
+ break unless !(['*', '_'] & Array(exit_on_strings)).empty? &&
40
+ ['**', '__'].include?(src.cur_chars(2)) &&
41
+ !['***', '___'].include?(src.cur_chars(3))
42
+ end
43
+
44
+ # check if there are extensions
45
+ next if check_span_extensions(src, con)
46
+
47
+ case c = src.cur_char
48
+ when ' '
49
+ if src.cur_chars_are " \n"
50
+ src.ignore_chars(3)
51
+ con.push_element md_br
52
+ next
53
+ else
54
+ src.ignore_char
55
+ con.push_space
56
+ end
57
+ when "\n", "\t"
58
+ src.ignore_char
59
+ con.push_space
60
+ when '`'
61
+ read_inline_code(src, con)
62
+ when '<'
63
+ # It could be:
64
+ # 1) HTML "<div ..."
65
+ # 2) HTML "<!-- ..."
66
+ # 3) url "<http:// ", "<ftp:// ..."
67
+ # 4) email "<andrea@... ", "<mailto:andrea@..."
68
+ # 5) on itself! "a < b "
69
+ # 6) Start of <<guillemettes>>
70
+
71
+ case d = src.next_char
72
+ when '<' # guillemettes
73
+ src.ignore_chars(2)
74
+ con.push_char '<'
75
+ con.push_char '<'
76
+ when '!'
77
+ if src.cur_chars_are '<!--'
78
+ read_inline_html(src, con)
79
+ else
80
+ con.push_char src.shift_char
81
+ end
82
+ when '?'
83
+ read_xml_instr_span(src, con)
84
+ when ' ', "\t"
85
+ con.push_char src.shift_char
86
+ else
87
+ if src.next_matches(/<mailto:/) ||
88
+ src.next_matches(/<[\w\.]+\@/)
89
+ read_email_el(src, con)
90
+ elsif src.next_matches(/<\w+:/)
91
+ read_url_el(src, con)
92
+ elsif src.next_matches(/<\w/)
93
+ #puts "This is HTML: #{src.cur_chars(20)}"
94
+ read_inline_html(src, con)
95
+ else
96
+ #puts "This is NOT HTML: #{src.cur_chars(20)}"
97
+ con.push_char src.shift_char
98
+ end
99
+ end
100
+ when "\\"
101
+ d = src.next_char
102
+ if d == "'"
103
+ src.ignore_chars(2)
104
+ con.push_element md_entity('apos')
105
+ elsif d == '"'
106
+ src.ignore_chars(2)
107
+ con.push_element md_entity('quot')
108
+ elsif escaped.include? d
109
+ src.ignore_chars(2)
110
+ con.push_char d
111
+ else
112
+ con.push_char src.shift_char
113
+ end
114
+ when '['
115
+ if markdown_extra? && src.next_char == '^'
116
+ read_footnote_ref(src,con)
117
+ elsif IgnoreWikiLinks && src.next_char == '['
118
+ con.push_char src.shift_char
119
+ con.push_char src.shift_char
120
+ else
121
+ read_link(src, con)
122
+ end
123
+ when '!'
124
+ if src.next_char == '['
125
+ read_image(src, con)
126
+ else
127
+ con.push_char src.shift_char
128
+ end
129
+ when '&'
130
+ # named references
131
+ if m = src.read_regexp(/\&(\w+);/)
132
+ con.push_element md_entity(m[1])
133
+ # numeric
134
+ elsif m = src.read_regexp(/\&\#(x)?(\w+);/)
135
+ num = m[1] ? m[2].hex : m[2].to_i
136
+ con.push_element md_entity(num)
137
+ else
138
+ con.push_char src.shift_char
139
+ end
140
+ when '*'
141
+ if !src.next_char
142
+ maruku_error "Opening * as last char.", src, con, 'Treating as literal'
143
+ con.push_char src.shift_char
144
+ else
145
+ follows = src.cur_chars(4)
146
+ if follows =~ /^\*\*\*[^\s\*]/
147
+ con.push_element read_emstrong(src, '***')
148
+ elsif follows =~ /^\*\*[^\s\*]/
149
+ con.push_element read_strong(src, '**')
150
+ elsif follows =~ /^\*[^\s\*]/
151
+ con.push_element read_em(src, '*')
152
+ else # * is just a normal char
153
+ con.push_char src.shift_char
154
+ end
155
+ end
156
+ when '_'
157
+ if !src.next_char
158
+ maruku_error "Opening _ as last char", src, con, 'Treating as literal'
159
+ con.push_char src.shift_char
160
+ else
161
+ # we don't want "mod_ruby" to start an emphasis
162
+ # so we start one only if
163
+ # 1) there's nothing else in the span (first char)
164
+ # or 2) the last char was a space
165
+ # or 3) the current string is empty
166
+ #if con.elements.empty? ||
167
+ if con.is_end?
168
+ # also, we check the next characters
169
+ follows = src.cur_chars(4)
170
+ if follows =~ /^\_\_\_[^\s\_]/
171
+ con.push_element read_emstrong(src, '___')
172
+ elsif follows =~ /^\_\_[^\s\_]/
173
+ con.push_element read_strong(src, '__')
174
+ elsif follows =~ /^\_[^\s\_]/
175
+ con.push_element read_em(src, '_')
176
+ else # _ is just a normal char
177
+ con.push_char src.shift_char
178
+ end
179
+ else
180
+ # _ is just a normal char
181
+ con.push_char src.shift_char
182
+ end
183
+ end
184
+ when '{' # extension
185
+ if ['#', '.', ':'].include? src.next_char
186
+ src.ignore_char # {
187
+ interpret_extension(src, con, '}')
188
+ src.ignore_char # }
189
+ else
190
+ con.push_char src.shift_char
191
+ end
192
+ when nil
193
+ maruku_error( ("Unclosed span (waiting for %s" +
194
+ "#{exit_on_strings.inspect})") %
195
+ [ exit_on_chars ? "#{exit_on_chars.inspect} or" : "" ],
196
+ src, con)
197
+ break
198
+ else # normal text
199
+ con.push_char src.shift_char
200
+ end # end case
201
+ end # end while true
202
+
203
+ con.push_string_if_present
204
+
205
+ # Assign IAL to elements
206
+ merge_ial(con.elements, src, con)
207
+
208
+ # Remove leading space
209
+ if (s = con.elements.first).kind_of? String
210
+ if s[0, 1] == ' '
211
+ con.elements[0] = s[1..-1]
212
+ end
213
+ con.elements.shift if s.empty?
214
+ end
215
+
216
+ # Remove final spaces
217
+ if (s = con.elements.last).kind_of? String
218
+ s.chop! if s[-1, 1] == ' '
219
+ con.elements.pop if s.empty?
220
+ end
221
+
222
+ educate(con.elements)
223
+ end
224
+
225
+
226
+ def read_xml_instr_span(src, con)
227
+ src.ignore_chars(2) # starting <?
228
+
229
+ # read target <?target code... ?>
230
+ target = if m = src.read_regexp(/^(\w+)/)
231
+ m[1]
232
+ else
233
+ # XML instructions are invalid without a target
234
+ ''
235
+ end
236
+
237
+ delim = "?>"
238
+
239
+ code = read_simple(src, nil, nil, delim)
240
+
241
+ src.ignore_chars delim.size
242
+
243
+ code = (code || "").strip
244
+ con.push_element md_xml_instr(target, code)
245
+ end
246
+
247
+ # Start: cursor on character **after** '{'
248
+ # End: curson on '}' or EOF
249
+ def interpret_extension(src, con, break_on_chars=nil)
250
+ case src.cur_char
251
+ when ':'
252
+ src.ignore_char # :
253
+ extension_meta(src, con, break_on_chars)
254
+ when '#', '.'
255
+ extension_meta(src, con, break_on_chars)
256
+ else
257
+ stuff = read_simple(src, '}', break_on_chars)
258
+ if stuff =~ /^(\w+\s|[^\w])/
259
+ extension_id = $1.strip
260
+
261
+ maruku_recover "I don't know what to do with extension '#{extension_id}'\n" +
262
+ "I will treat this:\n\t{#{stuff}} \n as meta-data.\n", src, con
263
+ else
264
+ maruku_recover "I will treat this:\n\t{#{stuff}} \n as meta-data.\n", src, con
265
+ end
266
+ extension_meta(src, con, break_on_chars)
267
+ end
268
+ end
269
+
270
+ def extension_meta(src, con, break_on_chars=nil)
271
+ if m = src.read_regexp(/([^\s\:\"\'}]+?):/)
272
+ name = m[1]
273
+ al = read_attribute_list(src, con, break_on_chars)
274
+ self.doc.ald[name] = al
275
+ con.push md_ald(name, al)
276
+ else
277
+ al = read_attribute_list(src, con, break_on_chars)
278
+ con.push md_ial(al)
279
+ end
280
+ end
281
+
282
+ def read_url_el(src,con)
283
+ src.ignore_char # leading <
284
+ url = read_simple(src, nil, '>')
285
+ src.ignore_char # closing >
286
+
287
+ con.push_element md_url(url)
288
+ end
289
+
290
+ def read_email_el(src,con)
291
+ src.ignore_char # leading <
292
+ mail = read_simple(src, nil, '>')
293
+ src.ignore_char # closing >
294
+
295
+ address = mail.gsub(/^mailto:/, '')
296
+ con.push_element md_email(address)
297
+ end
298
+
299
+ def read_url(src, break_on)
300
+ if ["'", '"'].include? src.cur_char
301
+ maruku_error 'Invalid char for url', src
302
+ end
303
+
304
+ url = read_simple(src, nil, break_on) || ''
305
+
306
+ if url[0, 1] == '<' && url[-1, 1] == '>'
307
+ url = url[1, url.size-2]
308
+ end
309
+
310
+ return nil if url.empty?
311
+ url
312
+ end
313
+
314
+
315
+ def read_quoted_or_unquoted(src, con, escaped, exit_on_chars)
316
+ case src.cur_char
317
+ when "'", '"'
318
+ read_quoted(src, con)
319
+ else
320
+ read_simple(src, escaped, exit_on_chars, nil, false)
321
+ end
322
+ end
323
+
324
+ # Tries to read a quoted value. If stream does not
325
+ # start with ' or ", returns nil.
326
+ def read_quoted(src, con)
327
+ case src.cur_char
328
+ when "'", '"'
329
+ quote_char = src.shift_char # opening quote
330
+ string = read_simple(src, EscapedCharInQuotes, quote_char)
331
+ src.ignore_char # closing quote
332
+ string
333
+ else
334
+ nil
335
+ end
336
+ end
337
+
338
+ # Reads a simple string (no formatting) until one of break_on_chars,
339
+ # while escaping the escaped.
340
+ # If the string is empty, it returns nil.
341
+ # By default, raises on error if the string terminates unexpectedly. This can be
342
+ # by setting the last argument to false.
343
+ def read_simple(src, escaped, exit_on_chars=nil, exit_on_strings=nil, warn=true)
344
+ text = ""
345
+ escaped = Array(escaped)
346
+ exit_on_chars = Array(exit_on_chars)
347
+ exit_on_strings = Array(exit_on_strings)
348
+ while true
349
+ c = src.cur_char
350
+
351
+ break if exit_on_chars.include?(c)
352
+ break if exit_on_strings.any? {|x| src.cur_chars_are x }
353
+
354
+ case c
355
+ when nil
356
+ if warn
357
+ maruku_error "String finished while reading (break on " +
358
+ "#{exit_on_chars.inspect})" +
359
+ " already read: #{text.inspect}", src
360
+ end
361
+ break
362
+ when "\\"
363
+ d = src.next_char
364
+ if escaped.include? d
365
+ src.ignore_chars(2)
366
+ text << d
367
+ else
368
+ text << src.shift_char
369
+ end
370
+ else
371
+ text << src.shift_char
372
+ end
373
+ end
374
+
375
+ text.empty? ? nil : text
376
+ end
377
+
378
+ def read_em(src, delim)
379
+ src.ignore_char
380
+ children = read_span(src, EscapedCharInText, nil, delim)
381
+ src.ignore_char
382
+ md_em(children)
383
+ end
384
+
385
+ def read_strong(src, delim)
386
+ src.ignore_chars(2)
387
+ children = read_span(src, EscapedCharInText, nil, delim)
388
+ src.ignore_chars(2)
389
+ md_strong(children)
390
+ end
391
+
392
+ def read_emstrong(src, delim)
393
+ src.ignore_chars(3)
394
+ children = read_span(src, EscapedCharInText, nil, delim)
395
+ src.ignore_chars(3)
396
+ md_emstrong(children)
397
+ end
398
+
399
+ # Reads a bracketed id "[refid]". Consumes also both brackets.
400
+ def read_ref_id(src, con)
401
+ src.ignore_char # [
402
+ if m = src.read_regexp(/([^\]]*?)\]/)
403
+ m[1]
404
+ else
405
+ nil
406
+ end
407
+ end
408
+
409
+ def read_footnote_ref(src,con)
410
+ ref = read_ref_id(src,con)
411
+ con.push_element md_foot_ref(ref)
412
+ end
413
+
414
+ def read_inline_html(src, con)
415
+ h = HTMLHelper.new
416
+ begin
417
+ # This is our current buffer in the context
418
+ next_stuff = src.current_remaining_buffer
419
+
420
+ consumed = 0
421
+ while true
422
+ if consumed >= next_stuff.size
423
+ maruku_error "Malformed HTML starting at #{next_stuff.inspect}", src, con
424
+ break
425
+ end
426
+
427
+ h.eat_this next_stuff[consumed].chr
428
+ consumed += 1
429
+ break if h.is_finished?
430
+ end
431
+ src.ignore_chars(consumed)
432
+ con.push_element md_html(h.stuff_you_read)
433
+ rescue => e
434
+ maruku_error "Bad html: \n" +
435
+ e.inspect.gsub(/^/, '>'), src, con, "I will try to continue after bad HTML."
436
+ con.push_char src.shift_char
437
+ end
438
+ end
439
+
440
+ def read_inline_code(src, con)
441
+ # Count the number of ticks
442
+ num_ticks = 0
443
+ while src.cur_char == '`'
444
+ num_ticks += 1
445
+ src.ignore_char
446
+ end
447
+ # We will read until this string
448
+ end_string = "`" * num_ticks
449
+
450
+ code = read_simple(src, nil, nil, end_string)
451
+
452
+ # We didn't find a closing batch!
453
+ if !code || src.cur_char != '`'
454
+ con.push_element(end_string + (code || '')) and return
455
+ end
456
+
457
+ # We didn't find a closing batch!
458
+ if !code || src.cur_char != '`'
459
+ con.push_element(end_string + (code || ''))
460
+ return
461
+ end
462
+
463
+ # puts "Now I expects #{num_ticks} ticks: #{src.cur_chars(10).inspect}"
464
+ src.ignore_chars num_ticks
465
+
466
+ # Ignore at most one space
467
+ if num_ticks > 1 && code[0, 1] == ' '
468
+ code = code[1..-1]
469
+ end
470
+
471
+ # drop last space
472
+ if num_ticks > 1 && code[-1, 1] == ' '
473
+ code = code[0..-2]
474
+ end
475
+
476
+ # puts "Read `` code: #{code.inspect}; after: #{src.cur_chars(10).inspect} "
477
+ con.push_element md_code(code)
478
+ end
479
+
480
+ def read_link(src, con)
481
+ # we read the string and see what happens
482
+ src.ignore_char # opening bracket
483
+ children = read_span(src, EscapedCharInText, ']')
484
+ src.ignore_char # closing bracket
485
+
486
+ # ignore space
487
+ if src.cur_char == ' ' && ['[', '('].include?(src.next_char)
488
+ src.shift_char
489
+ end
490
+
491
+ case src.cur_char
492
+ when '('
493
+ src.ignore_char # opening (
494
+ src.consume_whitespace
495
+ url = read_url(src, [' ', "\t", ")"]) || ''
496
+
497
+ src.consume_whitespace
498
+ title = nil
499
+ if src.cur_char != ')' # we have a title
500
+ quote_char = src.cur_char
501
+ title = read_quoted(src, con)
502
+
503
+ if not title
504
+ maruku_error 'Must quote title', src, con
505
+ else
506
+ # Tries to read a title with quotes: ![a](url "ti"tle")
507
+ # this is the most ugly thing in Markdown
508
+ unless src.next_matches(/\s*\)/)
509
+ # if there is not a closing par ), then read
510
+ # the rest and guess it's title with quotes
511
+ rest = read_simple(src, nil, ')', nil)
512
+ # chop the closing char
513
+ rest.chop!
514
+ title << quote_char << rest
515
+ end
516
+ end
517
+ end
518
+ src.consume_whitespace
519
+ closing = src.shift_char # closing )
520
+ if closing != ')'
521
+ maruku_error 'Unclosed link', src, con, "No closing ): I will not create" +
522
+ " the link for #{children.inspect}"
523
+ con.push_elements children
524
+ return
525
+ end
526
+ con.push_element md_im_link(children, url, title)
527
+ when '[' # link ref
528
+ ref_id = read_ref_id(src, con)
529
+ if ref_id
530
+ con.push_element md_link(children, ref_id)
531
+ else
532
+ maruku_error "Could not read ref_id", src, con, "I will not create the link for " +
533
+ "#{children.inspect}"
534
+ con.push_elements children
535
+ return
536
+ end
537
+ else # empty [link]
538
+ con.push_element md_link(children, nil)
539
+ end
540
+ end # read link
541
+
542
+ def read_image(src, con)
543
+ src.ignore_chars(2) # opening "!["
544
+ alt_text = read_span(src, EscapedCharInText, ']')
545
+ src.ignore_char # closing bracket
546
+ # ignore space
547
+ if src.cur_char == ' ' && ['[', '('].include?(src.next_char)
548
+ src.ignore_char
549
+ end
550
+ case src.cur_char
551
+ when '('
552
+ src.ignore_char # opening (
553
+ src.consume_whitespace
554
+ url = read_url(src, [' ', "\t", ')'])
555
+ unless url
556
+ maruku_error "Could not read url from #{src.cur_chars(10).inspect}", src, con
557
+ end
558
+ src.consume_whitespace
559
+ title = nil
560
+ if src.cur_char != ')' # we have a title
561
+ quote_char = src.cur_char
562
+ title = read_quoted(src, con)
563
+ if !title
564
+ maruku_error 'Must quote title', src, con
565
+ else
566
+ # Tries to read a title with quotes: ![a](url "ti"tle")
567
+ # this is the most ugly thing in Markdown
568
+ if !src.next_matches(/\s*\)/)
569
+ # if there is not a closing par ), then read
570
+ # the rest and guess it's title with quotes
571
+ rest = read_simple(src, nil, ')', nil)
572
+ # chop the closing char
573
+ rest.chop!
574
+ title << quote_char << rest
575
+ end
576
+ end
577
+ end
578
+ src.consume_whitespace
579
+ closing = src.shift_char # closing )
580
+ if closing != ')'
581
+ maruku_error "Unclosed link: '#{closing}'" +
582
+ " Read url=#{url.inspect} title=#{title.inspect}", src, con
583
+ end
584
+ con.push_element md_im_image(alt_text, url, title)
585
+ when '[' # link ref
586
+ ref_id = read_ref_id(src, con)
587
+ if !ref_id # TODO: check around
588
+ maruku_error 'Reference not closed.', src, con
589
+ ref_id = ""
590
+ end
591
+
592
+ con.push_element md_image(alt_text, ref_id)
593
+ else # no stuff
594
+ ref_id = alt_text.join
595
+ con.push_element md_image(alt_text, ref_id)
596
+ end
597
+ end # read link
598
+
599
+ class SpanContext
600
+ # Read elements
601
+ attr_accessor :elements
602
+
603
+ def initialize
604
+ @elements = []
605
+ @cur_string = ''
606
+ end
607
+
608
+ def push_element(e)
609
+ raise "Only MDElement and String, please. You pushed #{e.class}: #{e.inspect} " unless
610
+ e.kind_of?(String) || e.kind_of?(MaRuKu::MDElement)
611
+
612
+ push_string_if_present
613
+
614
+ @elements << e
615
+ end
616
+ alias push push_element
617
+
618
+ def push_elements(a)
619
+ a.each do |e|
620
+ if e.kind_of? String
621
+ @cur_string << e
622
+ else
623
+ push_element e
624
+ end
625
+ end
626
+ end
627
+
628
+ def is_end?
629
+ @cur_string.empty? || @cur_string =~ /\s\z/
630
+ end
631
+
632
+ def push_string_if_present
633
+ unless @cur_string.empty?
634
+ @elements << @cur_string
635
+ @cur_string = ''
636
+ end
637
+ end
638
+
639
+ def push_char(c)
640
+ @cur_string << c
641
+ end
642
+
643
+ # push space into current string if
644
+ # there isn't one
645
+ def push_space
646
+ @cur_string << ' ' unless @cur_string[-1, 1] == ' '
647
+ end
648
+
649
+ def describe
650
+ lines = @elements.map{|x| x.inspect }.join("\n")
651
+ s = "Elements read in span: \n" +
652
+ lines.gsub(/^/, ' -') + "\n"
653
+
654
+ s += "Current string: \n #{@cur_string.inspect}\n" unless @cur_string.empty?
655
+ s
656
+ end
657
+ end
658
+ end