typohero 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/README.md +50 -0
- data/Rakefile +15 -0
- data/lib/typohero.rb +246 -0
- data/test/typohero_test.rb +240 -0
- data/typohero.gemspec +19 -0
- metadata +54 -0
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
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
|
+
'\\\\' => '\',
|
13
|
+
'\"' => '"',
|
14
|
+
"\\\'" => ''',
|
15
|
+
'\.' => '.',
|
16
|
+
'\-' => '-',
|
17
|
+
'\`' => '`'
|
18
|
+
}
|
19
|
+
ESCAPE_RE = Regexp.union(*ESCAPE.keys)
|
20
|
+
|
21
|
+
EM_DASH = '—'
|
22
|
+
EN_DASH = '–'
|
23
|
+
ELLIPSIS = '…'
|
24
|
+
LEFT_DQUOTE = '“'
|
25
|
+
RIGHT_DQUOTE = '”'
|
26
|
+
LEFT_QUOTE = '‘'
|
27
|
+
RIGHT_QUOTE = '’'
|
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
|
+
',,' => '„',
|
39
|
+
'->' => '→',
|
40
|
+
'<-' => '←',
|
41
|
+
'=>' => '⇒',
|
42
|
+
'<=' => '⇐',
|
43
|
+
'>>' => '»',
|
44
|
+
'<<' => '«',
|
45
|
+
'(c)' => '©',
|
46
|
+
'(C)' => '©',
|
47
|
+
'(r)' => '®',
|
48
|
+
'(R)' => '®',
|
49
|
+
'(tm)' => '™',
|
50
|
+
'(TM)' => '™',
|
51
|
+
# normalize for further processing
|
52
|
+
'“' => LEFT_DQUOTE,
|
53
|
+
'‘' => LEFT_QUOTE,
|
54
|
+
'”' => RIGHT_DQUOTE,
|
55
|
+
'’' => RIGHT_QUOTE,
|
56
|
+
' ' => ' ',
|
57
|
+
' ' => ' ',
|
58
|
+
'–' => EN_DASH,
|
59
|
+
'—' => EM_DASH,
|
60
|
+
'—' => EM_DASH,
|
61
|
+
'–' => EN_DASH,
|
62
|
+
'&' => '&',
|
63
|
+
'&' => '&',
|
64
|
+
}
|
65
|
+
SPECIAL_RE = Regexp.union(*SPECIAL.keys)
|
66
|
+
|
67
|
+
SPACE_RE = '\s| | '
|
68
|
+
DASH_RE = '̵[12];'
|
69
|
+
AMP_RE = '&(?:amp;)?'
|
70
|
+
|
71
|
+
PRIME_RE = /(?<=\d)(''?)(?=#{SPACE_RE}|\d|$)/
|
72
|
+
PRIMES = {
|
73
|
+
"'" => '′',
|
74
|
+
"''" => '″',
|
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] =~ / |<|>/
|
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} #{$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*/, ' ')
|
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, ' \1 ')
|
202
|
+
s.gsub!(EN_DASH_SPACE_RE, ' \1 ')
|
203
|
+
end
|
204
|
+
|
205
|
+
def amp(s)
|
206
|
+
s.gsub!(REPLACE_AMP_RE, '<span class="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 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> code
|
30
|
+
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_quotes
|
35
|
+
typo '"A first example"', '<span class="dquo">“</span>A first example”'
|
36
|
+
typo '"A first "nested" example"',
|
37
|
+
'<span class="dquo">“</span>A first “nested” example”'
|
38
|
+
|
39
|
+
typo '".', '”.'
|
40
|
+
typo '"a', '<span class="dquo">“</span>a'
|
41
|
+
|
42
|
+
typo "'.", '’.'
|
43
|
+
typo "'a", '<span class="quo">‘</span>a'
|
44
|
+
|
45
|
+
typo %{<p>He said, "'Quoted' words in a larger quote."</p>},
|
46
|
+
'<p>He said, “‘Quoted’ words in a larger quote.”</p>'
|
47
|
+
|
48
|
+
typo %{"I like the 70's"}, '<span class="dquo">“</span>I like the 70’s”'
|
49
|
+
typo %{"I like the '70s"}, '<span class="dquo">“</span>I like the ’70s”'
|
50
|
+
typo %{"I like the '70!"}, '<span class="dquo">“</span>I like the ‘70!”'
|
51
|
+
|
52
|
+
typo 'pre"post', 'pre”post'
|
53
|
+
typo 'pre "post', 'pre “post'
|
54
|
+
typo 'pre "post', 'pre “post'
|
55
|
+
typo 'pre--"post', 'pre – “post'
|
56
|
+
typo 'pre--"!', 'pre – ”!'
|
57
|
+
|
58
|
+
typo "pre'post", 'pre’post'
|
59
|
+
typo "pre 'post", 'pre ‘post'
|
60
|
+
typo "pre 'post", 'pre ‘post'
|
61
|
+
typo "pre--'post", 'pre – ‘post'
|
62
|
+
typo "pre--'!", 'pre – ’!'
|
63
|
+
|
64
|
+
typo "<b>'</b>", '<b><span class="quo">‘</span></b>'
|
65
|
+
typo "foo<b>'</b>", "foo<b>’</b>"
|
66
|
+
|
67
|
+
typo '<b>"</b>', '<b><span class="dquo">“</span></b>'
|
68
|
+
typo 'foo<b>"</b>', "foo<b>”</b>"
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_dashes
|
72
|
+
typo "foo--bar", 'foo – bar'
|
73
|
+
typo "foo---bar", 'foo — bar'
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_ellipses
|
77
|
+
typo "foo..bar", 'foo..bar'
|
78
|
+
typo "foo...bar", 'foo…bar'
|
79
|
+
typo "foo....bar", 'foo….bar'
|
80
|
+
|
81
|
+
typo "foo. . ..bar", 'foo….bar'
|
82
|
+
typo "foo. . ...bar", 'foo…..bar'
|
83
|
+
typo "foo. . ....bar", 'foo……bar'
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_backticks
|
87
|
+
typo "pre``post", 'pre“post'
|
88
|
+
typo "pre ``post", 'pre “post'
|
89
|
+
typo "pre ``post", 'pre “post'
|
90
|
+
typo "pre--``post", 'pre – “post'
|
91
|
+
typo "pre--``!", 'pre – “!'
|
92
|
+
|
93
|
+
typo "pre''post", 'pre”post'
|
94
|
+
typo "pre ''post", 'pre ”post'
|
95
|
+
typo "pre ''post", 'pre ”post'
|
96
|
+
typo "pre--''post", 'pre – ”post'
|
97
|
+
typo "pre--''!", 'pre – ”!'
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_single_backticks
|
101
|
+
typo "`foo'", '<span class="quo">‘</span>foo’'
|
102
|
+
|
103
|
+
typo "pre`post", 'pre‘post'
|
104
|
+
typo "pre `post", 'pre ‘post'
|
105
|
+
typo "pre `post", 'pre ‘post'
|
106
|
+
typo "pre--`post", 'pre – ‘post'
|
107
|
+
typo "pre--`!", 'pre – ‘!'
|
108
|
+
|
109
|
+
typo "pre'post", 'pre’post'
|
110
|
+
typo "pre 'post", 'pre ‘post'
|
111
|
+
typo "pre 'post", 'pre ‘post'
|
112
|
+
typo "pre--'post", 'pre – ‘post'
|
113
|
+
typo "pre--'!", 'pre – ’!'
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_process_escapes
|
117
|
+
typo %q{foo\bar}, "foo\\bar"
|
118
|
+
typo %q{foo\\\bar}, "foo\bar"
|
119
|
+
typo %q{foo\\\\\bar}, "foo\\\bar"
|
120
|
+
typo %q{foo\...bar}, "foo...bar"
|
121
|
+
typo %q{foo\.\.\.bar}, "foo...bar"
|
122
|
+
|
123
|
+
typo %q{foo\'bar}, "foo'bar"
|
124
|
+
typo %q{foo\"bar}, "foo"bar"
|
125
|
+
typo %q{foo\-bar}, "foo-bar"
|
126
|
+
typo %q{foo\`bar}, "foo`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">&</span> two'
|
135
|
+
typo 'One & two', 'One <span class="amp">&</span> two'
|
136
|
+
typo 'One & two', 'One <span class="amp">&</span> two'
|
137
|
+
typo 'One & two', 'One <span class="amp">&</span> two'
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_should_ignore_special_amps
|
141
|
+
typo 'One <span class="amp">&</span> two', 'One <span class="amp">&</span> two'
|
142
|
+
typo '“this” & <a href="/?that&test">that</a>', '<span class="dquo">“</span>this” <span class="amp">&</span> <a href="/?that&test">that</a>'
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_should_replace_caps
|
146
|
+
typo "A message from KU", 'A message from <span class="caps">KU</span>'
|
147
|
+
typo 'Replace text <a href=".">IN</a> tags', 'Replace text <a href="."><span class="caps">IN</span></a> tags'
|
148
|
+
typo 'Replace text <i>IN</i> tags', 'Replace text <i><span class="caps">IN</span></i> 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 1234.'
|
153
|
+
typo "<pre>CAPS</pre> more CAPS", '<pre>CAPS</pre> more <span class="caps">CAPS</span>'
|
154
|
+
typo "<Pre>CAPS</PRE> with odd tag names CAPS", '<Pre>CAPS</PRE> with odd tag names <span class="caps">CAPS</span>'
|
155
|
+
typo "A message from 2KU2 with digits", 'A message from <span class="caps">2KU2</span> with 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 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 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’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&T", '<span class="caps">AT&T</span>'
|
169
|
+
typo "AT&T", '<span class="caps">AT&T</span>'
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_should_prevent_widows
|
173
|
+
typo 'A very simple test', 'A very simple 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 paragraphs</p><p>paragraph two</p>'
|
182
|
+
typo '<h1><a href="#">In a link inside a heading</i> </a></h1>', '<h1><a href="#">In a link inside a 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 text</h1>'
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_should_not_add_nbsp_before_another
|
187
|
+
typo 'Sentence with one nbsp', 'Sentence with one 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 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 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">“</span>With primes”'
|
214
|
+
typo "'With single primes'", '<span class="quo">‘</span>With single primes’'
|
215
|
+
typo '<a href="#">"With primes and a link"</a>', '<a href="#"><span class="dquo">“</span>With primes and a link”</a>'
|
216
|
+
typo '“With smartypanted quotes”', '<span class="dquo">“</span>With smartypanted quotes”'
|
217
|
+
typo '‘With manual quotes’', '<span class="quo">‘</span>With manual quotes’'
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_should_apply_all_filters
|
221
|
+
typo '<h2>"Jayhawks" & KU fans act extremely obnoxiously</h2>', '<h2><span class="dquo">“</span>Jayhawks” <span class="amp">&</span> <span class="caps">KU</span> fans act extremely obnoxiously</h2>'
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_other_special
|
225
|
+
typo ',,hello\'\'', '„hello”'
|
226
|
+
typo '<<', '«'
|
227
|
+
typo '>>', '»'
|
228
|
+
typo '->', '→'
|
229
|
+
typo '<-', '←'
|
230
|
+
typo '(tm)', '™'
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_primes
|
234
|
+
typo "She's 6'2''", 'She’s 6′2″'
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_ordinals
|
238
|
+
typo 'I am the 1st', 'I am the 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:
|