typohero 0.0.1

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 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: