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