typohero 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 28ea07741d2c4d9d7bbb1bec47acfd0b08ab08db
4
+ data.tar.gz: 91a85f687d7e5f32aea0873ffd50379dcca3f705
5
+ SHA512:
6
+ metadata.gz: 1ea735abd9566f3161a606b02cfa6845d9b766fc48ff522ba65b365549ac71e6ecadf24ebc8fe97041a926828364bed4a30fcc94ec7635861c235c3db6d95d72
7
+ data.tar.gz: 87e81cc088c613fb5336a4a4a30565d80a6f64ea572d50dcf44e9b68a77dad6a448949e07120173c7089c06cf550a1f803e5aa1c424c547ecf7d395953f3026b
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ *.swp
2
+ *.gem
3
+ Gemfile.lock
4
+ .bundle
5
+ .redcar
6
+ .rvmrc
7
+ .yardoc
8
+ coverage
9
+ pkg
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.0
5
+ - ruby-head
6
+ - jruby-19mode
7
+ - rbx-2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org/'
2
+ gemspec
3
+ gem 'rake'
4
+ gem 'minitest'
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # TypoHero
2
+
3
+ TypoHero enhances your typography. There are no options, there is no documentation.
4
+
5
+ ~~~
6
+ require 'typohero'
7
+ TypoHero.enhance('Some text...')
8
+ ~~~
9
+
10
+ ## Why?
11
+
12
+ There is already SmartyPants, RubyPants, Typogruby, Typogrify, Fast-Aleck? So why?!
13
+
14
+ * It is simpler, faster and more reliable than the others
15
+ * Has more features (Find out yourself...)
16
+ * And I like regular expressions :)
17
+
18
+ But why not improve the existing libraries?
19
+
20
+ * SmartyPants is Perl
21
+ * RubyPants is good but too much of a direct Perl port
22
+ * Typogruby has nice features but the implementation seems more like a hack
23
+ * Typogrify is Python
24
+ * Fast-Aleck is C, but I don't want to use C since Ruby is already a perfect text processing language!
25
+
26
+ ## License
27
+
28
+ ~~~
29
+ The MIT License
30
+
31
+ Copyright (c) 2014 Daniel Mendler
32
+
33
+ Permission is hereby granted, free of charge, to any person obtaining a copy
34
+ of this software and associated documentation files (the "Software"), to deal
35
+ in the Software without restriction, including without limitation the rights
36
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
37
+ copies of the Software, and to permit persons to whom the Software is
38
+ furnished to do so, subject to the following conditions:
39
+
40
+ The above copyright notice and this permission notice shall be included in
41
+ all copies or substantial portions of the Software.
42
+
43
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
46
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
48
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
49
+ THE SOFTWARE.
50
+ ~~~
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ Bundler::GemHelper.install_tasks
4
+ rescue Exception
5
+ end
6
+
7
+ require 'rake/testtask'
8
+
9
+ Rake::TestTask.new :test do |t|
10
+ t.libs << 'lib' << 'test'
11
+ t.test_files = FileList['test/*.rb']
12
+ t.verbose = true
13
+ end
14
+
15
+ task :default => :test
data/lib/typohero.rb ADDED
@@ -0,0 +1,246 @@
1
+ module TypoHero
2
+ VERSION = '0.0.1'
3
+
4
+ extend self
5
+
6
+ EXCLUDED_TAGS = %w(head pre code kbd math script textarea)
7
+ EXCLUDED_TAGS_RE = /\A<(\/)?(?:#{EXCLUDED_TAGS.join('|')})/im
8
+
9
+ TOKENIZER_RE = /<[^>]+>|[^<]+|\\[\(\[\)\]]|\$\$/im
10
+
11
+ ESCAPE = {
12
+ '\\\\' => '&#92;',
13
+ '\"' => '&#34;',
14
+ "\\\'" => '&#39;',
15
+ '\.' => '&#46;',
16
+ '\-' => '&#45;',
17
+ '\`' => '&#96;'
18
+ }
19
+ ESCAPE_RE = Regexp.union(*ESCAPE.keys)
20
+
21
+ EM_DASH = '&#8212;'
22
+ EN_DASH = '&#8211;'
23
+ ELLIPSIS = '&#8230;'
24
+ LEFT_DQUOTE = '&#8220;'
25
+ RIGHT_DQUOTE = '&#8221;'
26
+ LEFT_QUOTE = '&#8216;'
27
+ RIGHT_QUOTE = '&#8217;'
28
+
29
+ SPECIAL = {
30
+ # enhance!
31
+ '---' => EM_DASH,
32
+ '--' => EN_DASH,
33
+ '...' => ELLIPSIS,
34
+ '. . .' => ELLIPSIS,
35
+ '``' => LEFT_DQUOTE,
36
+ "''" => RIGHT_DQUOTE,
37
+ '`' => LEFT_QUOTE,
38
+ ',,' => '&#8222;',
39
+ '-&gt;' => '&rarr;',
40
+ '&lt;-' => '&larr;',
41
+ '=&gt;' => '&rArr;',
42
+ '&lt;=' => '&lArr;',
43
+ '&gt;&gt;' => '&raquo;',
44
+ '&lt;&lt;' => '&laquo;',
45
+ '(c)' => '&copy;',
46
+ '(C)' => '&copy;',
47
+ '(r)' => '&reg;',
48
+ '(R)' => '&reg;',
49
+ '(tm)' => '&trade;',
50
+ '(TM)' => '&trade;',
51
+ # normalize for further processing
52
+ '&ldquo;' => LEFT_DQUOTE,
53
+ '&lsquo;' => LEFT_QUOTE,
54
+ '&rdquo;' => RIGHT_DQUOTE,
55
+ '&rsquo;' => RIGHT_QUOTE,
56
+ '&#160;' => '&nbsp',
57
+ '&#xA0;' => '&nbsp',
58
+ '&#x2013;' => EN_DASH,
59
+ '&#x2014;' => EM_DASH,
60
+ '&mdash;' => EM_DASH,
61
+ '&ndash;' => EN_DASH,
62
+ '&#38;' => '&amp;',
63
+ '&#x26;' => '&amp;',
64
+ }
65
+ SPECIAL_RE = Regexp.union(*SPECIAL.keys)
66
+
67
+ SPACE_RE = '\s|&nbsp;|&#8201;'
68
+ DASH_RE = '&#821[12];'
69
+ AMP_RE = '&(?:amp;)?'
70
+
71
+ PRIME_RE = /(?<=\d)(''?)(?=#{SPACE_RE}|\d|$)/
72
+ PRIMES = {
73
+ "'" => '&prime;',
74
+ "''" => '&Prime;',
75
+ }
76
+ ORDINAL_RE = /(?<=\d)(st|nd|rd|th)(?=#{SPACE_RE}|$)/
77
+
78
+ EM_DASH_SPACE_RE = /\s*(#{EM_DASH})\s*/
79
+ EN_DASH_SPACE_RE = /\s*(#{EN_DASH})\s*/
80
+
81
+ REPLACE_AMP_RE = /(?<=#{SPACE_RE})#{AMP_RE}(?=#{SPACE_RE})/m
82
+
83
+ CAPS_BEGIN_RE = "(^|#{SPACE_RE}|#{LEFT_DQUOTE}|#{LEFT_QUOTE})"
84
+ CAPS_INNER_RE = "(?:#{AMP_RE}|[A-Z\\d\\.]|#{RIGHT_QUOTE})*" # right quote for posession (e.g. JIMMY'S)
85
+ REPLACE_CAPS_RE = /#{CAPS_BEGIN_RE}([A-Z\d]#{CAPS_INNER_RE}[A-Z]#{CAPS_INNER_RE}|[A-Z]#{CAPS_INNER_RE}[A-Z\d]#{CAPS_INNER_RE})/m
86
+
87
+ PUNCT_CLASS = '[!"#\$\%\'()*+,\-.\/:;<=>?\@\[\\\\\]\^_`{|}~]'
88
+ PUNCT_QUOTE_RE = /^['"](?=#{PUNCT_CLASS})\B/m
89
+ RIGHT_QUOTE_RE = /(?<!^|#{DASH_RE}|#{SPACE_RE}|[\[\{\(\-])['"]|['"](?=\s|s\b|$)|(?<=#{DASH_RE})['"](?=#{PUNCT_CLASS})/m
90
+
91
+ LEFT_QUOTES = {
92
+ "'" => LEFT_QUOTE,
93
+ '"' => LEFT_DQUOTE,
94
+ }
95
+
96
+ RIGHT_QUOTES = {
97
+ "'" => RIGHT_QUOTE,
98
+ '"' => RIGHT_DQUOTE,
99
+ }
100
+
101
+ TWO_QUOTES = {
102
+ '"\'' => LEFT_DQUOTE + LEFT_QUOTE,
103
+ '\'"' => LEFT_QUOTE + LEFT_DQUOTE
104
+ }
105
+
106
+ PARAGRAPH_RE = 'h[1-6]|p|li|dt|dd|div'
107
+ INLINE_RE = 'a|em|span|strong|i|b'
108
+
109
+ WIDONT_PARAGRAPH_RE = /\A<\/(?:#{PARAGRAPH_RE})>\Z/im
110
+ WIDONT_INLINE_RE = /\A<\/?(?:#{INLINE_RE})[^>]*>\Z/im
111
+
112
+ INITIAL_QUOTE_RE = /(?=(?:<(?:#{PARAGRAPH_RE})[^>]*>|^)(?:<(?:#{INLINE_RE})[^>]*>|\s)*)&#(8216|8220);/
113
+ INITIAL_QUOTES = {
114
+ LEFT_QUOTE => "<span class=\"quo\">#{LEFT_QUOTE}</span>",
115
+ LEFT_DQUOTE => "<span class=\"dquo\">#{LEFT_DQUOTE}</span>",
116
+ }
117
+
118
+ def tokenize(input)
119
+ excluded, latex, dollar = 0, 0, 0
120
+ input.scan TOKENIZER_RE do |s|
121
+ text = false
122
+ case s
123
+ when /\A</
124
+ excluded += ($1 ? -1 : 1) if s =~ EXCLUDED_TAGS_RE
125
+ when /\A\\[\(\[]\Z/
126
+ latex += 1
127
+ when /\A\\[\)\]]\Z/
128
+ latex -= 1
129
+ when '$$'
130
+ dollar += 1
131
+ else
132
+ text = true if latex == 0 && dollar.even? && excluded == 0
133
+ end
134
+ yield(s, text)
135
+ end
136
+ end
137
+
138
+ def enhance(input)
139
+ tokens, text, prev_last_char = [], []
140
+ tokenize(input) do |s, t|
141
+ if t
142
+ last_char = s[-1]
143
+ escape(s)
144
+ primes(s)
145
+ special(s)
146
+ quotes(s, prev_last_char)
147
+ dash_spaces(s)
148
+ prev_last_char = last_char
149
+ text << s
150
+ end
151
+ tokens << s
152
+ end
153
+ widont(tokens)
154
+ text.each do |s|
155
+ initial_quotes(s)
156
+ amp(s)
157
+ caps(s)
158
+ ordinals(s)
159
+ end
160
+ tokens.join
161
+ end
162
+
163
+ def widont(tokens)
164
+ state, i, widow = 1, tokens.size - 1, nil
165
+ while i >= 0
166
+ if tokens[i] =~ WIDONT_PARAGRAPH_RE
167
+ state = 1
168
+ elsif tokens[i] !~ WIDONT_INLINE_RE
169
+ if tokens[i] =~ /&nbsp;|<|>/
170
+ state = 0
171
+ elsif state == 1 || state == 3
172
+ if tokens[i] =~ (state == 1 ? /(\S+)?(\s+)?(\S+\s*)\Z/m : /(\S+)?(\s+)(\S*)\Z/m)
173
+ if $1 && $2
174
+ tokens[i].replace "#{$`}#{$1}&nbsp;#{$3}"
175
+ state = 0
176
+ elsif $2
177
+ state = 2
178
+ widow = tokens[i]
179
+ else
180
+ state = 3
181
+ end
182
+ end
183
+ elsif state == 2 && tokens[i] =~ /(\S+\s*)\Z/m
184
+ widow.sub!(/\A\s*/, '&nbsp;')
185
+ state = 0
186
+ end
187
+ end
188
+ i -= 1
189
+ end
190
+ end
191
+
192
+ def escape(s)
193
+ s.gsub!(ESCAPE_RE, ESCAPE)
194
+ end
195
+
196
+ def special(s)
197
+ s.gsub!(SPECIAL_RE, SPECIAL)
198
+ end
199
+
200
+ def dash_spaces(s)
201
+ s.gsub!(EM_DASH_SPACE_RE, '&#8201;\1&#8201;')
202
+ s.gsub!(EN_DASH_SPACE_RE, ' \1 ')
203
+ end
204
+
205
+ def amp(s)
206
+ s.gsub!(REPLACE_AMP_RE, '<span class="amp">&amp;</span>')
207
+ end
208
+
209
+ def caps(s)
210
+ s.gsub!(REPLACE_CAPS_RE, '\1<span class="caps">\2</span>')
211
+ end
212
+
213
+ def initial_quotes(s)
214
+ s.gsub!(INITIAL_QUOTE_RE, INITIAL_QUOTES)
215
+ end
216
+
217
+ def primes(s)
218
+ # Special case for inches and minutes, seconds
219
+ s.gsub!(PRIME_RE, PRIMES)
220
+ end
221
+
222
+ def ordinals(s)
223
+ s.gsub!(ORDINAL_RE, '<sup>\1</sup>')
224
+ end
225
+
226
+ def quotes(s, prev_last_char)
227
+ if s =~ /\A['"]\Z/
228
+ s.replace(prev_last_char =~ /\S/ ? RIGHT_QUOTES[s] : LEFT_QUOTES[s])
229
+ return
230
+ end
231
+
232
+ # Special case if the very first character is a closing
233
+ # quote followed by punctuation at a non-word-break
234
+ s.gsub!(PUNCT_QUOTE_RE, RIGHT_QUOTES)
235
+
236
+ # Special case for double sets of quotes, e.g.
237
+ # <p>He said, "'Quoted' words in a larger quote."</p>
238
+ s.gsub!(/(?:"'|'")(?=\p{Word})/, TWO_QUOTES)
239
+
240
+ # Special case for decade abbreviations (the '80s)
241
+ s.gsub!(/'(?=(\d{2}(?:s|\s|$)))/, RIGHT_QUOTES)
242
+
243
+ s.gsub!(RIGHT_QUOTE_RE, RIGHT_QUOTES)
244
+ s.gsub!(/['"]/, LEFT_QUOTES)
245
+ end
246
+ end
@@ -0,0 +1,240 @@
1
+ require 'minitest/autorun'
2
+ require 'typohero'
3
+
4
+ class TypoheroTest < Minitest::Test
5
+ def typo(str, orig)
6
+ # todo test recursive
7
+ a = TypoHero.enhance(str)
8
+ #b = TypoHero.enhance(a)
9
+ #assert_equal a, b
10
+ #c = Typogruby.improve(str)
11
+ #puts "\nInput: #{str}\nTypogruby: #{c}\nTypoHero: #{a}\n" if a != c
12
+ assert_equal orig, a
13
+ end
14
+
15
+ def test_verbatim
16
+ typo "foo!", "foo!"
17
+ typo "<div>This is html</div>", "<div>This is&nbsp;html</div>"
18
+ typo "<div>This is html with <crap </div> tags>", "<div>This is html with <crap </div> tags>"
19
+ typo %q{
20
+ multiline
21
+
22
+ <b>html</b>
23
+
24
+ code
25
+
26
+ }, %q{
27
+ multiline
28
+
29
+ <b>html</b>&nbsp;code
30
+
31
+ }
32
+ end
33
+
34
+ def test_quotes
35
+ typo '"A first example"', '<span class="dquo">&#8220;</span>A first&nbsp;example&#8221;'
36
+ typo '"A first "nested" example"',
37
+ '<span class="dquo">&#8220;</span>A first &#8220;nested&#8221;&nbsp;example&#8221;'
38
+
39
+ typo '".', '&#8221;.'
40
+ typo '"a', '<span class="dquo">&#8220;</span>a'
41
+
42
+ typo "'.", '&#8217;.'
43
+ typo "'a", '<span class="quo">&#8216;</span>a'
44
+
45
+ typo %{<p>He said, "'Quoted' words in a larger quote."</p>},
46
+ '<p>He said, &#8220;&#8216;Quoted&#8217; words in a larger&nbsp;quote.&#8221;</p>'
47
+
48
+ typo %{"I like the 70's"}, '<span class="dquo">&#8220;</span>I like the&nbsp;70&#8217;s&#8221;'
49
+ typo %{"I like the '70s"}, '<span class="dquo">&#8220;</span>I like the&nbsp;&#8217;70s&#8221;'
50
+ typo %{"I like the '70!"}, '<span class="dquo">&#8220;</span>I like the&nbsp;&#8216;70!&#8221;'
51
+
52
+ typo 'pre"post', 'pre&#8221;post'
53
+ typo 'pre "post', 'pre&nbsp;&#8220;post'
54
+ typo 'pre&nbsp;"post', 'pre&nbsp;&#8220;post'
55
+ typo 'pre--"post', 'pre &#8211;&nbsp;&#8220;post'
56
+ typo 'pre--"!', 'pre &#8211;&nbsp;&#8221;!'
57
+
58
+ typo "pre'post", 'pre&#8217;post'
59
+ typo "pre 'post", 'pre&nbsp;&#8216;post'
60
+ typo "pre&nbsp;'post", 'pre&nbsp;&#8216;post'
61
+ typo "pre--'post", 'pre &#8211;&nbsp;&#8216;post'
62
+ typo "pre--'!", 'pre &#8211;&nbsp;&#8217;!'
63
+
64
+ typo "<b>'</b>", '<b><span class="quo">&#8216;</span></b>'
65
+ typo "foo<b>'</b>", "foo<b>&#8217;</b>"
66
+
67
+ typo '<b>"</b>', '<b><span class="dquo">&#8220;</span></b>'
68
+ typo 'foo<b>"</b>', "foo<b>&#8221;</b>"
69
+ end
70
+
71
+ def test_dashes
72
+ typo "foo--bar", 'foo &#8211;&nbsp;bar'
73
+ typo "foo---bar", 'foo&#8201;&#8212;&#8201;bar'
74
+ end
75
+
76
+ def test_ellipses
77
+ typo "foo..bar", 'foo..bar'
78
+ typo "foo...bar", 'foo&#8230;bar'
79
+ typo "foo....bar", 'foo&#8230;.bar'
80
+
81
+ typo "foo. . ..bar", 'foo&#8230;.bar'
82
+ typo "foo. . ...bar", 'foo&#8230;..bar'
83
+ typo "foo. . ....bar", 'foo&#8230;&#8230;bar'
84
+ end
85
+
86
+ def test_backticks
87
+ typo "pre``post", 'pre&#8220;post'
88
+ typo "pre ``post", 'pre&nbsp;&#8220;post'
89
+ typo "pre&nbsp;``post", 'pre&nbsp;&#8220;post'
90
+ typo "pre--``post", 'pre &#8211;&nbsp;&#8220;post'
91
+ typo "pre--``!", 'pre &#8211;&nbsp;&#8220;!'
92
+
93
+ typo "pre''post", 'pre&#8221;post'
94
+ typo "pre ''post", 'pre&nbsp;&#8221;post'
95
+ typo "pre&nbsp;''post", 'pre&nbsp;&#8221;post'
96
+ typo "pre--''post", 'pre &#8211;&nbsp;&#8221;post'
97
+ typo "pre--''!", 'pre &#8211;&nbsp;&#8221;!'
98
+ end
99
+
100
+ def test_single_backticks
101
+ typo "`foo'", '<span class="quo">&#8216;</span>foo&#8217;'
102
+
103
+ typo "pre`post", 'pre&#8216;post'
104
+ typo "pre `post", 'pre&nbsp;&#8216;post'
105
+ typo "pre&nbsp;`post", 'pre&nbsp;&#8216;post'
106
+ typo "pre--`post", 'pre &#8211;&nbsp;&#8216;post'
107
+ typo "pre--`!", 'pre &#8211;&nbsp;&#8216;!'
108
+
109
+ typo "pre'post", 'pre&#8217;post'
110
+ typo "pre 'post", 'pre&nbsp;&#8216;post'
111
+ typo "pre&nbsp;'post", 'pre&nbsp;&#8216;post'
112
+ typo "pre--'post", 'pre &#8211;&nbsp;&#8216;post'
113
+ typo "pre--'!", 'pre &#8211;&nbsp;&#8217;!'
114
+ end
115
+
116
+ def test_process_escapes
117
+ typo %q{foo\bar}, "foo\\bar"
118
+ typo %q{foo\\\bar}, "foo&#92;bar"
119
+ typo %q{foo\\\\\bar}, "foo&#92;\\bar"
120
+ typo %q{foo\...bar}, "foo&#46;..bar"
121
+ typo %q{foo\.\.\.bar}, "foo&#46;&#46;&#46;bar"
122
+
123
+ typo %q{foo\'bar}, "foo&#39;bar"
124
+ typo %q{foo\"bar}, "foo&#34;bar"
125
+ typo %q{foo\-bar}, "foo&#45;bar"
126
+ typo %q{foo\`bar}, "foo&#96;bar"
127
+
128
+ typo %q{foo\#bar}, "foo\\#bar"
129
+ typo %q{foo\*bar}, "foo\\*bar"
130
+ typo %q{foo\&bar}, "foo\\&bar"
131
+ end
132
+
133
+ def test_should_replace_amps
134
+ typo 'One & two', 'One <span class="amp">&amp;</span>&nbsp;two'
135
+ typo 'One &amp; two', 'One <span class="amp">&amp;</span>&nbsp;two'
136
+ typo 'One &#38; two', 'One <span class="amp">&amp;</span>&nbsp;two'
137
+ typo 'One&nbsp;&amp;&nbsp;two', 'One&nbsp;<span class="amp">&amp;</span>&nbsp;two'
138
+ end
139
+
140
+ def test_should_ignore_special_amps
141
+ typo 'One <span class="amp">&amp;</span> two', 'One <span class="amp">&amp;</span>&nbsp;two'
142
+ typo '&ldquo;this&rdquo; & <a href="/?that&amp;test">that</a>', '<span class="dquo">&#8220;</span>this&#8221; <span class="amp">&amp;</span>&nbsp;<a href="/?that&amp;test">that</a>'
143
+ end
144
+
145
+ def test_should_replace_caps
146
+ typo "A message from KU", 'A message from&nbsp;<span class="caps">KU</span>'
147
+ typo 'Replace text <a href=".">IN</a> tags', 'Replace text <a href="."><span class="caps">IN</span></a>&nbsp;tags'
148
+ typo 'Replace text <i>IN</i> tags', 'Replace text <i><span class="caps">IN</span></i>&nbsp;tags'
149
+ end
150
+
151
+ def test_should_ignore_special_case_caps
152
+ typo 'It should ignore just numbers like 1234.', 'It should ignore just numbers like&nbsp;1234.'
153
+ typo "<pre>CAPS</pre> more CAPS", '<pre>CAPS</pre> more&nbsp;<span class="caps">CAPS</span>'
154
+ typo "<Pre>CAPS</PRE> with odd tag names CAPS", '<Pre>CAPS</PRE> with odd tag names&nbsp;<span class="caps">CAPS</span>'
155
+ typo "A message from 2KU2 with digits", 'A message from <span class="caps">2KU2</span> with&nbsp;digits'
156
+ typo "Dotted caps followed by spaces should never include them in the wrap D.O.T. like so.", 'Dotted caps followed by spaces should never include them in the wrap <span class="caps">D.O.T.</span> like&nbsp;so.'
157
+ typo 'Caps in attributes (<span title="Example CAPS">example</span>) should be ignored', 'Caps in attributes (<span title="Example CAPS">example</span>) should be&nbsp;ignored'
158
+ typo '<head><title>CAPS Example</title></head>', '<head><title>CAPS Example</title></head>'
159
+ end
160
+
161
+ def test_should_not_break_caps_with_apostrophes
162
+ typo "JIMMY'S", '<span class="caps">JIMMY&#8217;S</span>'
163
+ typo "<i>D.O.T.</i>HE34T<b>RFID</b>", '<i><span class="caps">D.O.T.</span></i><span class="caps">HE34T</span><b><span class="caps">RFID</span></b>'
164
+ end
165
+
166
+ def test_should_not_break_caps_with_ampersands
167
+ typo "AT&T", '<span class="caps">AT&T</span>'
168
+ typo "AT&amp;T", '<span class="caps">AT&amp;T</span>'
169
+ typo "AT&#38;T", '<span class="caps">AT&amp;T</span>'
170
+ end
171
+
172
+ def test_should_prevent_widows
173
+ typo 'A very simple test', 'A very simple&nbsp;test'
174
+ end
175
+
176
+ def test_should_not_change_single_word_items
177
+ typo 'Test', 'Test'
178
+ typo ' Test', ' Test'
179
+ typo '<ul><li>Test</p></li><ul>', '<ul><li>Test</p></li><ul>'
180
+ typo '<ul><li> Test</p></li><ul>', '<ul><li> Test</p></li><ul>'
181
+ typo '<p>In a couple of paragraphs</p><p>paragraph two</p>', '<p>In a couple of&nbsp;paragraphs</p><p>paragraph&nbsp;two</p>'
182
+ typo '<h1><a href="#">In a link inside a heading</i> </a></h1>', '<h1><a href="#">In a link inside a&nbsp;heading</i> </a></h1>'
183
+ typo '<h1><a href="#">In a link</a> followed by other text</h1>', '<h1><a href="#">In a link</a> followed by other&nbsp;text</h1>'
184
+ end
185
+
186
+ def test_should_not_add_nbsp_before_another
187
+ typo 'Sentence with one&nbsp;nbsp', 'Sentence with one&nbsp;nbsp'
188
+ end
189
+
190
+ def test_should_not_error_on_empty_html
191
+ typo '<h1><a href="#"></a></h1>', '<h1><a href="#"></a></h1>'
192
+ end
193
+
194
+ def test_should_ignore_widows_in_special_tags
195
+ typo '<div>Divs get love!</div>', '<div>Divs get&nbsp;love!</div>'
196
+ typo '<pre>Neither do PREs</pre>', '<pre>Neither do PREs</pre>'
197
+ typo '<textarea>nor text in textarea</textarea>', '<textarea>nor text in textarea</textarea>'
198
+ typo "<script>\nreturn window;\n</script>", "<script>\nreturn window;\n</script>"
199
+ typo '<div><p>But divs with paragraphs do!</p></div>', '<div><p>But divs with paragraphs&nbsp;do!</p></div>'
200
+ end
201
+
202
+ def test_widont
203
+ code = %q{
204
+ <ul>
205
+ <li>
206
+ <a href="/contact/">Contact</a>
207
+ </li>
208
+ </ul>}
209
+ typo code, code
210
+ end
211
+
212
+ def test_should_replace_quotes
213
+ typo '"With primes"', '<span class="dquo">&#8220;</span>With&nbsp;primes&#8221;'
214
+ typo "'With single primes'", '<span class="quo">&#8216;</span>With single&nbsp;primes&#8217;'
215
+ typo '<a href="#">"With primes and a link"</a>', '<a href="#"><span class="dquo">&#8220;</span>With primes and a&nbsp;link&#8221;</a>'
216
+ typo '&#8220;With smartypanted quotes&#8221;', '<span class="dquo">&#8220;</span>With smartypanted&nbsp;quotes&#8221;'
217
+ typo '&lsquo;With manual quotes&rsquo;', '<span class="quo">&#8216;</span>With manual&nbsp;quotes&#8217;'
218
+ end
219
+
220
+ def test_should_apply_all_filters
221
+ typo '<h2>"Jayhawks" & KU fans act extremely obnoxiously</h2>', '<h2><span class="dquo">&#8220;</span>Jayhawks&#8221; <span class="amp">&amp;</span> <span class="caps">KU</span> fans act extremely&nbsp;obnoxiously</h2>'
222
+ end
223
+
224
+ def test_other_special
225
+ typo ',,hello\'\'', '&#8222;hello&#8221;'
226
+ typo '&lt;&lt;', '&laquo;'
227
+ typo '&gt;&gt;', '&raquo;'
228
+ typo '-&gt;', '&rarr;'
229
+ typo '&lt;-', '&larr;'
230
+ typo '(tm)', '&trade;'
231
+ end
232
+
233
+ def test_primes
234
+ typo "She's 6'2''", 'She&#8217;s&nbsp;6&prime;2&Prime;'
235
+ end
236
+
237
+ def test_ordinals
238
+ typo 'I am the 1st', 'I am the&nbsp;1<sup>st</sup>'
239
+ end
240
+ end
data/typohero.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '/lib/typohero'
3
+ require 'date'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'typohero'
7
+ s.version = TypoHero::VERSION
8
+ s.date = Date.today.to_s
9
+ s.authors = ['Daniel Mendler']
10
+ s.email = ['mail@daniel-mendler.de']
11
+ s.summary = 'Typographic enhancer for HTML'
12
+ s.description = 'TypoHero improves web typography by applying various filters (similar to rubypants, typogruby, typogrify).'
13
+ s.homepage = 'https://github.com/minad/typohero/'
14
+ s.license = 'MIT'
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = %w(lib)
19
+ end
metadata ADDED
@@ -0,0 +1,54 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typohero
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Mendler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: TypoHero improves web typography by applying various filters (similar
14
+ to rubypants, typogruby, typogrify).
15
+ email:
16
+ - mail@daniel-mendler.de
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".gitignore"
22
+ - ".travis.yml"
23
+ - Gemfile
24
+ - README.md
25
+ - Rakefile
26
+ - lib/typohero.rb
27
+ - test/typohero_test.rb
28
+ - typohero.gemspec
29
+ homepage: https://github.com/minad/typohero/
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubyforge_project:
49
+ rubygems_version: 2.2.2
50
+ signing_key:
51
+ specification_version: 4
52
+ summary: Typographic enhancer for HTML
53
+ test_files: []
54
+ has_rdoc: