maruku 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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