maruku 0.7.1 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 683162dc0e147b79e1df24f79949ba8521f4ca68
4
- data.tar.gz: 6fa761f0e28fa67213236f05c07a97937cd98101
3
+ metadata.gz: 708a01507319b4ce86bcd733bb4767009aea05c5
4
+ data.tar.gz: 34c0dd886c8f800e2746dec49d9e2b2d71823270
5
5
  SHA512:
6
- metadata.gz: f8d53ba730189a09d616f91ed91635701334454ff114737769f2e5189db2e52c647ca5815bce72c6faa43f83c9c1daf0a1a115cc0a00916c152004c537c8fbeb
7
- data.tar.gz: 8330490c4b4787cc1bbc27f470f21c985df7f1847db2238732cb48a73e60374a52910e1a4c49c66b6edb995087cf08080cf41b2b2e1a0f2cf740631983edf6e6
6
+ metadata.gz: faafb8314908e9c5edecc191e06ece6ad7fac71f6f840091c27d20bbbee0f1017f8f3dbeaa21e385561b60e049fb6d50c75c52e886b26695e896da76bb9ac523
7
+ data.tar.gz: dfde96cf9d35055eb1dbece3753ddc57d6b12db104b00fed643b57994a59ee6744524232fc0cf13d3db9976821566ba02ab2ede77a9f04cd4e459a3de308c86b
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -89,7 +89,6 @@ require 'maruku/input/parse_block'
89
89
  # Code for parsing Markdown span-level elements
90
90
  require 'maruku/input/charsource'
91
91
  require 'maruku/input/parse_span'
92
- require 'maruku/input/rubypants'
93
92
 
94
93
  require 'maruku/input/extensions'
95
94
 
@@ -35,8 +35,17 @@ module MaRuKu
35
35
  end
36
36
  end
37
37
 
38
+ # This is like {#maruku_error} but will never raise.
38
39
  def maruku_recover(s, src=nil, con=nil, recover=nil)
39
- tell_user create_frame(describe_error(s, src, con, recover))
40
+ policy = get_setting(:on_error)
41
+
42
+ case policy
43
+ when :ignore
44
+ when :raise, :warning
45
+ tell_user create_frame(describe_error(s, src, con, recover))
46
+ else
47
+ raise "Unknown on_error policy: #{policy.inspect}"
48
+ end
40
49
  end
41
50
 
42
51
  def raise_error(s)
@@ -41,7 +41,7 @@ module MaRuKu
41
41
  # I had a bug with emails and urls at the beginning of the
42
42
  # line that were mistaken for raw_html
43
43
  return :text if self =~ /\A[ ]{0,3}#{EMailAddress}/
44
- return :text if self =~ /\A[ ]{0,3}<http:/
44
+ return :text if self =~ /\A[ ]{0,3}<\w+:\/\//
45
45
  # raw html is like PHP Markdown Extra: at most three spaces before
46
46
  return :xml_instr if self =~ /\A\s*<\?/
47
47
  return :raw_html if self =~ %r{\A[ ]{0,3}</?\s*\w+}
@@ -22,14 +22,16 @@ module MaRuKu::In::Markdown::SpanLevelParser
22
22
  def read_span(src, escaped, exit_on_chars=nil, exit_on_strings=nil)
23
23
  escaped = Array(escaped)
24
24
  con = SpanContext.new
25
- c = d = nil
25
+ dquote_state = squote_state = :closed
26
+ c = d = prev_char = nil
26
27
  while true
27
28
  c = src.cur_char
28
29
 
29
30
  # This is only an optimization which cuts 50% of the time used.
30
31
  # (but you can't use a-zA-z in exit_on_chars)
31
- if c && c =~ /a-zA-Z0-9/
32
+ if c && c =~ /[[:alnum:]]/
32
33
  con.push_char src.shift_char
34
+ prev_char = c
33
35
  next
34
36
  end
35
37
 
@@ -49,7 +51,21 @@ module MaRuKu::In::Markdown::SpanLevelParser
49
51
  if src.cur_chars_are " \n"
50
52
  src.ignore_chars(3)
51
53
  con.push_element md_br
54
+ prev_char = ' '
52
55
  next
56
+ elsif src.cur_chars_are ' >>' # closing guillemettes
57
+ src.ignore_chars(3)
58
+ con.push_element md_entity('nbsp')
59
+ con.push_element md_entity('raquo')
60
+ elsif src.cur_chars(5) =~ / '\d\ds/ # special case: '80s
61
+ src.ignore_chars(2)
62
+ con.push_space
63
+ con.push_element md_entity('rsquo')
64
+ elsif src.cur_chars_are " '" # opening single-quote
65
+ src.ignore_chars(2)
66
+ con.push_space
67
+ con.push_element md_entity('lsquo')
68
+ squote_state = :open
53
69
  else
54
70
  src.ignore_char
55
71
  con.push_space
@@ -70,9 +86,14 @@ module MaRuKu::In::Markdown::SpanLevelParser
70
86
 
71
87
  case d = src.next_char
72
88
  when '<' # guillemettes
73
- src.ignore_chars(2)
74
- con.push_char '<'
75
- con.push_char '<'
89
+ if src.cur_chars_are '<< '
90
+ src.ignore_chars(3)
91
+ con.push_element md_entity('laquo')
92
+ con.push_element md_entity('nbsp')
93
+ else
94
+ src.ignore_chars(2)
95
+ con.push_element md_entity('laquo')
96
+ end
76
97
  when '!'
77
98
  if src.cur_chars_are '<!--'
78
99
  read_inline_html(src, con)
@@ -97,6 +118,13 @@ module MaRuKu::In::Markdown::SpanLevelParser
97
118
  con.push_char src.shift_char
98
119
  end
99
120
  end
121
+ when '>'
122
+ if src.next_char == '>'
123
+ src.ignore_chars(2)
124
+ con.push_element md_entity('raquo')
125
+ else
126
+ con.push_char src.shift_char
127
+ end
100
128
  when "\\"
101
129
  d = src.next_char
102
130
  if d == "'"
@@ -195,9 +223,60 @@ module MaRuKu::In::Markdown::SpanLevelParser
195
223
  [ exit_on_chars ? "#{exit_on_chars.inspect} or" : "" ],
196
224
  src, con)
197
225
  break
226
+ when '-' # dashes
227
+ if src.next_char == '-'
228
+ if src.cur_chars_are '---'
229
+ src.ignore_chars(3)
230
+ con.push_element md_entity('mdash')
231
+ else
232
+ src.ignore_chars(2)
233
+ con.push_element md_entity('ndash')
234
+ end
235
+ else
236
+ con.push_char src.shift_char
237
+ end
238
+ when '.' # ellipses
239
+ if src.cur_chars_are '...'
240
+ src.ignore_chars(3)
241
+ con.push_element md_entity('hellip')
242
+ elsif src.cur_chars_are '. . .'
243
+ src.ignore_chars(5)
244
+ con.push_element md_entity('hellip')
245
+ else
246
+ con.push_char src.shift_char
247
+ end
248
+ when '"'
249
+ if dquote_state == :closed
250
+ dquote_state = :open
251
+ src.ignore_char
252
+ con.push_element md_entity('ldquo')
253
+ else
254
+ dquote_state = :closed
255
+ src.ignore_char
256
+ con.push_element md_entity('rdquo')
257
+ end
258
+ when "'"
259
+ if src.cur_chars(4) =~ /'\d\ds/ # special case: '80s
260
+ src.ignore_char
261
+ con.push_element md_entity('rsquo')
262
+ elsif squote_state == :open
263
+ squote_state = :closed unless src.next_char =~ /[[:alpha:]]/
264
+ src.ignore_char
265
+ con.push_element md_entity('rsquo')
266
+ else
267
+ if prev_char =~ /[[:alpha:]]/
268
+ src.ignore_char
269
+ con.push_element md_entity('rsquo')
270
+ else
271
+ src.ignore_char
272
+ con.push_element md_entity('lsquo')
273
+ squote_state = :open
274
+ end
275
+ end
198
276
  else # normal text
199
277
  con.push_char src.shift_char
200
278
  end # end case
279
+ prev_char = c
201
280
  end # end while true
202
281
 
203
282
  con.push_string_if_present
@@ -212,6 +291,8 @@ module MaRuKu::In::Markdown::SpanLevelParser
212
291
  end
213
292
  con.elements.shift if s.empty?
214
293
  end
294
+
295
+ con.elements.shift if (con.elements.first.kind_of?(String) && con.elements.first.empty?)
215
296
 
216
297
  # Remove final spaces
217
298
  if (s = con.elements.last).kind_of? String
@@ -219,7 +300,7 @@ module MaRuKu::In::Markdown::SpanLevelParser
219
300
  con.elements.pop if s.empty?
220
301
  end
221
302
 
222
- educate(con.elements)
303
+ con.elements
223
304
  end
224
305
 
225
306
 
@@ -449,7 +530,9 @@ module MaRuKu::In::Markdown::SpanLevelParser
449
530
 
450
531
  # Try to handle empty single-ticks
451
532
  if num_ticks > 1 && !src.next_matches(/.*#{Regexp.escape(end_string)}/)
452
- con.push_element(end_string) and return
533
+ con.push_element md_entity('ldquo')
534
+ src.ignore_chars(2)
535
+ return
453
536
  end
454
537
 
455
538
  code = read_simple(src, nil, nil, end_string)
@@ -216,7 +216,7 @@ module MaRuKu::Out::HTML
216
216
 
217
217
  # render footnotes
218
218
  unless @doc.footnotes_order.empty?
219
- body << render_footnotes(@doc)
219
+ body << render_footnotes
220
220
  end
221
221
 
222
222
  # When we are rendering a whole document, we add a signature
@@ -1,6 +1,6 @@
1
1
  module MaRuKu
2
2
  # The Maruku version.
3
- VERSION = '0.7.1'
3
+ VERSION = '0.7.2'
4
4
 
5
5
  # @deprecated Exists for backwards compatibility. Use {VERSION}
6
6
  # @private
@@ -0,0 +1,9 @@
1
+ Handle HTTPS links at the start of a line. https://github.com/bhollis/maruku/issues/126
2
+ *** Parameters: ***
3
+ { }
4
+ *** Markdown input: ***
5
+ <https://google.com>
6
+ *** Output of inspect ***
7
+
8
+ *** Output of to_html ***
9
+ <p><a href="https://google.com">https://google.com</a></p>
@@ -0,0 +1,11 @@
1
+ Handle ellipsis at the end of a line. https://github.com/bhollis/maruku/issues/130
2
+ *** Parameters: ***
3
+ { }
4
+ *** Markdown input: ***
5
+ A paragraph... continued...
6
+ *** Output of inspect ***
7
+ md_el(:document, md_par(["A paragraph", md_entity("hellip"), " continued", md_entity("hellip")]))
8
+ *** Output of to_html ***
9
+ <p>A paragraph… continued…</p>
10
+ *** Output of to_latex ***
11
+ A paragraph\ldots{} continued\ldots{}
@@ -162,6 +162,11 @@ EXPECTATIONS = Maruku.new.instance_eval do
162
162
  ["[a](#url)", [md_im_link(['a'],'#url')]],
163
163
  ["[a](</script?foo=1&bar=2>)", [md_im_link(['a'],'/script?foo=1&bar=2')]],
164
164
 
165
+ # Links to URLs that contain closing parentheses. #128
166
+ ['[a](url())', [md_im_link(['a'],'url()')], 'Link with parentheses 1', true], # PENDING
167
+ ['[a](url\(\))', [md_im_link(['a'],'url()')], 'Link with parentheses 2', true], # PENDING
168
+ ['[a](url()foo)', [md_im_link(['a'],'url()foo')], 'Link with parentheses 3', true], # PENDING
169
+ ['[a](url(foo))', [md_im_link(['a'],'url(foo)')], 'Link with parentheses 4', true], # PENDING
165
170
 
166
171
  # Images
167
172
  ["\\![a](url)", ['!', md_im_link(['a'],'url') ], 'Escaping images'],
@@ -186,6 +191,8 @@ EXPECTATIONS = Maruku.new.instance_eval do
186
191
 
187
192
  ['<http://example.com/?foo=1&bar=2>',
188
193
  [md_url('http://example.com/?foo=1&bar=2')], 'Immediate link'],
194
+ ['<https://example.com/?foo=1&bar=2>',
195
+ [md_url('https://example.com/?foo=1&bar=2')], 'Immediate link https'],
189
196
  ['a<http://example.com/?foo=1&bar=2>b',
190
197
  ['a',md_url('http://example.com/?foo=1&bar=2'),'b'] ],
191
198
  ['<andrea@censi.org>',
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: maruku
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Censi
@@ -14,25 +14,25 @@ cert_chain:
14
14
  -----BEGIN CERTIFICATE-----
15
15
  MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQwwCgYDVQQDDANiZW4x
16
16
  GTAXBgoJkiaJk/IsZAEZFgliZW5ob2xsaXMxEzARBgoJkiaJk/IsZAEZFgNuZXQw
17
- HhcNMTMwMzExMDI1NTUzWhcNMTQwMzExMDI1NTUzWjA+MQwwCgYDVQQDDANiZW4x
17
+ HhcNMTQwNTI2MjExNjI2WhcNMTUwNTI2MjExNjI2WjA+MQwwCgYDVQQDDANiZW4x
18
18
  GTAXBgoJkiaJk/IsZAEZFgliZW5ob2xsaXMxEzARBgoJkiaJk/IsZAEZFgNuZXQw
19
- ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5zj8rp/43ROFWTrOmaXFI
20
- saYRkgYgv5y0+10/MRZDPYT3i42GRmCPRd+ZWAVPKGJCRgSizgAfPSYh8+CFceFm
21
- ZVzeTd5haVWgsSkLpDHc3CuIUnanEOhlLcNt7bjofoxiZaqMm8ntLGAPARunOn4H
22
- whN7a82KEMRYCqC0H1TvTR15K7Fb40U6UgrqwiaQtDvTyIALakzICl2mBk5we3La
23
- OAgoKVT0SDAUa8E4BSitpOpukNTPPy7r0WKb8yO69ON19LGxg17tDJjolKYsScOt
24
- +nXZj7HJwfBkn9m6zxDqCk7mvAvybH7oMOCzrxLB9kxSOjNOCVnOV26Bj5xPUY8p
25
- AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBTXpLoI
26
- loLWXfgQPVg/t4cAuHJZeDAcBgNVHREEFTATgRFiZW5AYmVuaG9sbGlzLm5ldDAc
19
+ ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYZvEbHldtIRP8nQNyM6SQ
20
+ oqi6pF1VznSiOzVIZi84MnTwab4jl4pEso/tnjpm+jpHoFc1RhJMwwaO9v0ih5/e
21
+ VRZFZQyoKrPaP9Dq3q1iO9SXwoucGfoVCcvtfF2DGnFlkDZlswPWNdL/GTxuiM6X
22
+ dzQ36hzSSZrgTms1AdPSuCHt3LTNRDSkpRGqDWdsPbKLi7eLSkGbUO1Ibe8JAtdE
23
+ ueSaVAksFJvNgUxZHMSBkSrd33PRpiSi2g6IvogoeWGj+4vxJgGbPGGWHmsfVT28
24
+ ggEdA8Ix8y223pCovPUJtCEoEdoNxTq7NUA9lUL2rZ0URc2jCiEKzQmtRAf8ZWSn
25
+ AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBSL7ijr
26
+ kd/8I5COGX6gWvHNSCPyTDAcBgNVHREEFTATgRFiZW5AYmVuaG9sbGlzLm5ldDAc
27
27
  BgNVHRIEFTATgRFiZW5AYmVuaG9sbGlzLm5ldDANBgkqhkiG9w0BAQUFAAOCAQEA
28
- CYVy5TtuDMQz7ddO+bc+Jve9V6o1Fpu6bXTlwsXafXtfOeJ3/+0g+WboTCbCWjIR
29
- JnPcE2vCVrLF48PpR3YWVRGZai4YxRXdmZEcVDnJReymUHVu0hZdcxV6+LecoZ5a
30
- X06W7WI+JhEKb5zIgGFGuW074n0el0il85NI/frb9D3dOKqU5+NK7hrRO8rjWLXo
31
- U+KBnqpu0lI1TIAshS1R8JYVLIixEg6sl3+QljvpDQW+P4D1qg6wspdjKj91/geA
32
- KwEyUbH6HjXBdbfv95jHa8ncuWqIryt/ywGZg3sbPVsscZA9RcyDtUQRnlHSGRWH
33
- ZSsIzVZvxIwTYa0z3d+JWQ==
28
+ PHS/jYQNXccqlZVJUVz5hlkXCslHZs8OqfzOIPRQ4VZZY/USERfDCHou8eQRxeOG
29
+ Ux9/jfp35TQRC/1OEx/7mDhixRo3vYCdHHUUUKOdko9VWSyTrTmVLq6V1Iwu7TCe
30
+ K+yasGZ4CfUqUaK+MgFLEEI8k0q2TmRd534a3C6nGS69x9HmIqJISlpvwNYr1YCX
31
+ mk4SFXYX0a5PWeGRiIKg4vPQy4PG1oFAN7+mAgSGNRtMG3Sx4qkMaYLfW0wd7zZ9
32
+ IjHSEqEpekJnAXUJNPdgIBHUVUMNfcnULDPNzaBckgjGm0PqFMlknEOk+NxoXt7m
33
+ ouF3Zkp3xx1U+2uMJ1SVRg==
34
34
  -----END CERTIFICATE-----
35
- date: 2014-01-15 00:00:00.000000000 Z
35
+ date: 2014-05-26 00:00:00.000000000 Z
36
36
  dependencies: []
37
37
  description: "Maruku is a Markdown interpreter in Ruby.\n\tIt features native export
38
38
  to HTML and PDF (via Latex). The\n\toutput is really beautiful!"
@@ -81,7 +81,6 @@ files:
81
81
  - lib/maruku/input/parse_block.rb
82
82
  - lib/maruku/input/parse_doc.rb
83
83
  - lib/maruku/input/parse_span.rb
84
- - lib/maruku/input/rubypants.rb
85
84
  - lib/maruku/input_textile2/t2_parser.rb
86
85
  - lib/maruku/inspect_element.rb
87
86
  - lib/maruku/maruku.rb
@@ -160,6 +159,8 @@ files:
160
159
  - spec/block_docs/issue120.md
161
160
  - spec/block_docs/issue123.md
162
161
  - spec/block_docs/issue124.md
162
+ - spec/block_docs/issue126.md
163
+ - spec/block_docs/issue130.md
163
164
  - spec/block_docs/issue20.md
164
165
  - spec/block_docs/issue26.md
165
166
  - spec/block_docs/issue29.md
@@ -272,7 +273,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
272
273
  version: '0'
273
274
  requirements: []
274
275
  rubyforge_project:
275
- rubygems_version: 2.2.0.rc.1
276
+ rubygems_version: 2.2.2
276
277
  signing_key:
277
278
  specification_version: 4
278
279
  summary: Maruku is a Markdown-superset interpreter written in Ruby.
metadata.gz.sig CHANGED
Binary file
@@ -1,310 +0,0 @@
1
- #
2
- # NOTA BENE:
3
- #
4
- # The following algorithm is a rip-off of RubyPants written by
5
- # Christian Neukirchen.
6
- #
7
- # RubyPants is a Ruby port of SmartyPants written by John Gruber.
8
- #
9
- # This file is distributed under the MIT license, which is compatible
10
- # with the terms of the RubyPants license (3-clause BSD).
11
- #
12
- # -- Andrea Censi
13
-
14
-
15
- # = RubyPants -- SmartyPants ported to Ruby
16
- #
17
- # Ported by Christian Neukirchen <mailto:chneukirchen@gmail.com>
18
- # Copyright (C) 2004 Christian Neukirchen
19
- #
20
- # Incooporates ideas, comments and documentation by Chad Miller
21
- # Copyright (C) 2004 Chad Miller
22
- #
23
- # Original SmartyPants by John Gruber
24
- # Copyright (C) 2003 John Gruber
25
- #
26
-
27
- #
28
- # = RubyPants -- SmartyPants ported to Ruby
29
- #
30
- #
31
- # [snip]
32
- #
33
- # == Authors
34
- #
35
- # John Gruber did all of the hard work of writing this software in
36
- # Perl for Movable Type and almost all of this useful documentation.
37
- # Chad Miller ported it to Python to use with Pyblosxom.
38
- #
39
- # Christian Neukirchen provided the Ruby port, as a general-purpose
40
- # library that follows the *Cloth API.
41
- #
42
- #
43
- # == Copyright and License
44
- #
45
- # === SmartyPants license:
46
- #
47
- # Copyright (c) 2003 John Gruber
48
- # (http://daringfireball.net)
49
- # All rights reserved.
50
- #
51
- # Redistribution and use in source and binary forms, with or without
52
- # modification, are permitted provided that the following conditions
53
- # are met:
54
- #
55
- # * Redistributions of source code must retain the above copyright
56
- # notice, this list of conditions and the following disclaimer.
57
- #
58
- # * Redistributions in binary form must reproduce the above copyright
59
- # notice, this list of conditions and the following disclaimer in
60
- # the documentation and/or other materials provided with the
61
- # distribution.
62
- #
63
- # * Neither the name "SmartyPants" nor the names of its contributors
64
- # may be used to endorse or promote products derived from this
65
- # software without specific prior written permission.
66
- #
67
- # This software is provided by the copyright holders and contributors
68
- # "as is" and any express or implied warranties, including, but not
69
- # limited to, the implied warranties of merchantability and fitness
70
- # for a particular purpose are disclaimed. In no event shall the
71
- # copyright owner or contributors be liable for any direct, indirect,
72
- # incidental, special, exemplary, or consequential damages (including,
73
- # but not limited to, procurement of substitute goods or services;
74
- # loss of use, data, or profits; or business interruption) however
75
- # caused and on any theory of liability, whether in contract, strict
76
- # liability, or tort (including negligence or otherwise) arising in
77
- # any way out of the use of this software, even if advised of the
78
- # possibility of such damage.
79
- #
80
- # === RubyPants license
81
- #
82
- # RubyPants is a derivative work of SmartyPants and smartypants.py.
83
- #
84
- # Redistribution and use in source and binary forms, with or without
85
- # modification, are permitted provided that the following conditions
86
- # are met:
87
- #
88
- # * Redistributions of source code must retain the above copyright
89
- # notice, this list of conditions and the following disclaimer.
90
- #
91
- # * Redistributions in binary form must reproduce the above copyright
92
- # notice, this list of conditions and the following disclaimer in
93
- # the documentation and/or other materials provided with the
94
- # distribution.
95
- #
96
- # This software is provided by the copyright holders and contributors
97
- # "as is" and any express or implied warranties, including, but not
98
- # limited to, the implied warranties of merchantability and fitness
99
- # for a particular purpose are disclaimed. In no event shall the
100
- # copyright owner or contributors be liable for any direct, indirect,
101
- # incidental, special, exemplary, or consequential damages (including,
102
- # but not limited to, procurement of substitute goods or services;
103
- # loss of use, data, or profits; or business interruption) however
104
- # caused and on any theory of liability, whether in contract, strict
105
- # liability, or tort (including negligence or otherwise) arising in
106
- # any way out of the use of this software, even if advised of the
107
- # possibility of such damage.
108
- #
109
- #
110
- # == Links
111
- #
112
- # John Gruber:: http://daringfireball.net
113
- # SmartyPants:: http://daringfireball.net/projects/smartypants
114
- #
115
- # Chad Miller:: http://web.chad.org
116
- #
117
- # Christian Neukirchen:: http://kronavita.de/chris
118
-
119
-
120
- module MaRuKu::In::Markdown::SpanLevelParser
121
- Punct_class = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
122
- Close_class = "[^\ \t\r\n\\[\{\(\-]"
123
-
124
- # A rule to apply a particular pattern (like double quotes)
125
- # against a list of strings and MDElements, replacing that
126
- # pattern with the smartypants version.
127
- class Rule
128
- # The pattern to search for
129
- attr_accessor :pattern
130
-
131
- # The replacement tokens (entities or other instructions)
132
- attr_accessor :replacement
133
-
134
- # This is a hack to allow us to build entities.
135
- attr_accessor :doc
136
-
137
- def initialize(pattern, replacement)
138
- @pattern = pattern
139
- @replacement = replacement
140
- end
141
-
142
- private
143
-
144
- # Add something to the output array. If it's
145
- # not a string (like an MDElement) just add it direcltly,
146
- # otherwise attempt to add on to the last element in the
147
- # output if it's a string.
148
- def append_to_output(output, str)
149
- if !str.kind_of?(String)
150
- output << str
151
- return
152
- end
153
- return if str.empty?
154
- if output.last.kind_of?(String)
155
- output.last << str
156
- else
157
- output << str
158
- end
159
- end
160
- end
161
-
162
- # Simple rule that says "Replace this pattern with these entities"
163
- class ReplaceRule < Rule
164
- # Replace all matches in the input at once with the
165
- # same elements from "replacement".
166
- def apply(first, input, output)
167
- split = first.split(pattern)
168
- if split.empty?
169
- first.scan(pattern).size.times do
170
- clone_elems(replacement).each do |x|
171
- append_to_output(output, x)
172
- end
173
- end
174
- else
175
- intersperse(first.split(pattern), replacement).each do |x|
176
- append_to_output(output, x)
177
- end
178
- end
179
- end
180
-
181
- private
182
-
183
- # Sort of like "join" - places the elements in "elem"
184
- # between each adjacent element in the array.
185
- def intersperse(ary, elem)
186
- return clone_elems(elem) if ary.empty?
187
- return ary if ary.length == 1
188
- h, *t = ary
189
- t.inject([h]) do |r, e|
190
- r.concat clone_elems(elem)
191
- r << e
192
- end
193
- end
194
-
195
- def clone_elems(elems)
196
- elems.map do |el|
197
- en = el.clone
198
- en.doc = doc
199
- en
200
- end
201
- end
202
- end
203
-
204
- # A more complex rule that uses a capture group from the
205
- # pattern in its replacement.
206
- class CaptureRule < Rule
207
- # One at a time, replace each match, including
208
- # some part of the match, and put the rest back into
209
- # input to be processed next.
210
- def apply(first, input, output)
211
- if pattern =~ first
212
- m = Regexp.last_match
213
- append_to_output(output, m.pre_match)
214
- input.unshift m.post_match unless m.post_match.empty?
215
- replacement.reverse_each do |sub|
216
- if sub == :one
217
- input.unshift m[1]
218
- else
219
- entity = sub.clone
220
- sub.doc = doc
221
- input.unshift entity
222
- end
223
- end
224
- else
225
- append_to_output(output, first)
226
- end
227
- end
228
- end
229
-
230
- # All the rules that will be applied (in order) to smarten the document.
231
- Rules =
232
- [
233
- ['---', :mdash ],
234
- ['--', :ndash ],
235
- ['...', :hellip ],
236
- ['. . .', :hellip ],
237
- ["``", :ldquo ],
238
- ["''", :rdquo ],
239
- [/<<\s/, [:laquo, :nbsp] ],
240
- [/\s>>/, [:nbsp, :raquo] ],
241
- ['<<', :laquo ],
242
- ['>>', :raquo ],
243
-
244
- # Special case if the very first character is a quote followed by
245
- # punctuation at a non-word-break. Close the quotes by brute
246
- # force:
247
- [/\A'(?=#{Punct_class}\B)/, :rsquo],
248
- [/\A"(?=#{Punct_class}\B)/, :rdquo],
249
- # Special case for double sets of quotes, e.g.:
250
- # <p>He said, "'Quoted' words in a larger quote."</p>
251
- [/"'(?=\w)/, [:ldquo, :lsquo] ],
252
- [/'"(?=\w)/, [:lsquo, :ldquo] ],
253
- # Special case for decade abbreviations (the '80s):
254
- [/'(?=\d\ds)/, :rsquo ],
255
- # Get most opening single quotes:
256
- [/(\s)'(?=\w)/, [:one, :lsquo] ],
257
- # Single closing quotes:
258
- [/(#{Close_class})'/, [:one, :rsquo]],
259
- [/'(\s|s\b|$)/, [:rsquo, :one]],
260
- # Any remaining single quotes should be opening ones:
261
- ["'", :lsquo],
262
- # Get most opening double quotes:
263
- [/(\s)"(?=\w)/, [:one, :ldquo]],
264
- # Double closing quotes:
265
- [/(#{Close_class})"/, [:one, :rdquo]],
266
- [/"(\s|s\b|$)/, [:rdquo, :one]],
267
- # Any remaining quotes should be opening ones:
268
- ['"', :ldquo]
269
- ].
270
- map do |reg, subst| # People should do the thinking, machines should do the work.
271
- captures = false
272
- subst = Array(subst).map do |s|
273
- if s == :one
274
- captures = true
275
- s
276
- else
277
- MaRuKu::MDElement.new(:entity, [], { :entity_name => s.to_s.freeze }, nil)
278
- end
279
- end.freeze
280
-
281
- if captures
282
- CaptureRule.new reg, subst
283
- else
284
- ReplaceRule.new reg, subst
285
- end
286
- end
287
-
288
- # Fully apply a single rule to an entire array
289
- # of elements.
290
- # note: input will be modified in place
291
- def apply_one_rule!(rule, input)
292
- output = []
293
- while first = input.shift
294
- if first.kind_of?(String)
295
- rule.doc = @doc
296
- rule.apply(first, input, output)
297
- else
298
- output << first
299
- end
300
- end
301
- output
302
- end
303
-
304
- # Transform elements to have SmartyPants punctuation.
305
- def educate(elements)
306
- Rules.inject(elements) do |elems, rule|
307
- apply_one_rule!(rule, elems)
308
- end
309
- end
310
- end