sterile 1.0.0

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.
@@ -0,0 +1,259 @@
1
+ # encoding: UTF-8
2
+
3
+ module Sterile
4
+ HTML_ENTITIES = {
5
+ "quot" => 34,
6
+ "amp" => 38,
7
+ "apos" => 39,
8
+ "lt" => 60,
9
+ "gt" => 62,
10
+ "nbsp" => 160,
11
+ "iexcl" => 161,
12
+ "cent" => 162,
13
+ "pound" => 163,
14
+ "curren" => 164,
15
+ "yen" => 165,
16
+ "brvbar" => 166,
17
+ "sect" => 167,
18
+ "uml" => 168,
19
+ "copy" => 169,
20
+ "ordf" => 170,
21
+ "laquo" => 171,
22
+ "not" => 172,
23
+ "shy" => 173,
24
+ "reg" => 174,
25
+ "macr" => 175,
26
+ "deg" => 176,
27
+ "plusmn" => 177,
28
+ "sup2" => 178,
29
+ "sup3" => 179,
30
+ "acute" => 180,
31
+ "micro" => 181,
32
+ "para" => 182,
33
+ "middot" => 183,
34
+ "cedil" => 184,
35
+ "sup1" => 185,
36
+ "ordm" => 186,
37
+ "raquo" => 187,
38
+ "frac14" => 188,
39
+ "frac12" => 189,
40
+ "frac34" => 190,
41
+ "iquest" => 191,
42
+ "Agrave" => 192,
43
+ "Aacute" => 193,
44
+ "Acirc" => 194,
45
+ "Atilde" => 195,
46
+ "Auml" => 196,
47
+ "Aring" => 197,
48
+ "AElig" => 198,
49
+ "Ccedil" => 199,
50
+ "Egrave" => 200,
51
+ "Eacute" => 201,
52
+ "Ecirc" => 202,
53
+ "Euml" => 203,
54
+ "Igrave" => 204,
55
+ "Iacute" => 205,
56
+ "Icirc" => 206,
57
+ "Iuml" => 207,
58
+ "ETH" => 208,
59
+ "Ntilde" => 209,
60
+ "Ograve" => 210,
61
+ "Oacute" => 211,
62
+ "Ocirc" => 212,
63
+ "Otilde" => 213,
64
+ "Ouml" => 214,
65
+ "times" => 215,
66
+ "Oslash" => 216,
67
+ "Ugrave" => 217,
68
+ "Uacute" => 218,
69
+ "Ucirc" => 219,
70
+ "Uuml" => 220,
71
+ "Yacute" => 221,
72
+ "THORN" => 222,
73
+ "szlig" => 223,
74
+ "agrave" => 224,
75
+ "aacute" => 225,
76
+ "acirc" => 226,
77
+ "atilde" => 227,
78
+ "auml" => 228,
79
+ "aring" => 229,
80
+ "aelig" => 230,
81
+ "ccedil" => 231,
82
+ "egrave" => 232,
83
+ "eacute" => 233,
84
+ "ecirc" => 234,
85
+ "euml" => 235,
86
+ "igrave" => 236,
87
+ "iacute" => 237,
88
+ "icirc" => 238,
89
+ "iuml" => 239,
90
+ "eth" => 240,
91
+ "ntilde" => 241,
92
+ "ograve" => 242,
93
+ "oacute" => 243,
94
+ "ocirc" => 244,
95
+ "otilde" => 245,
96
+ "ouml" => 246,
97
+ "divide" => 247,
98
+ "oslash" => 248,
99
+ "ugrave" => 249,
100
+ "uacute" => 250,
101
+ "ucirc" => 251,
102
+ "uuml" => 252,
103
+ "yacute" => 253,
104
+ "thorn" => 254,
105
+ "yuml" => 255,
106
+ "OElig" => 338,
107
+ "oelig" => 339,
108
+ "Scaron" => 352,
109
+ "scaron" => 353,
110
+ "Yuml" => 376,
111
+ "fnof" => 402,
112
+ "circ" => 710,
113
+ "tilde" => 732,
114
+ "Alpha" => 913,
115
+ "Beta" => 914,
116
+ "Gamma" => 915,
117
+ "Delta" => 916,
118
+ "Epsilon" => 917,
119
+ "Zeta" => 918,
120
+ "Eta" => 919,
121
+ "Theta" => 920,
122
+ "Iota" => 921,
123
+ "Kappa" => 922,
124
+ "Lambda" => 923,
125
+ "Mu" => 924,
126
+ "Nu" => 925,
127
+ "Xi" => 926,
128
+ "Omicron" => 927,
129
+ "Pi" => 928,
130
+ "Rho" => 929,
131
+ "Sigma" => 931,
132
+ "Tau" => 932,
133
+ "Upsilon" => 933,
134
+ "Phi" => 934,
135
+ "Chi" => 935,
136
+ "Psi" => 936,
137
+ "Omega" => 937,
138
+ "alpha" => 945,
139
+ "beta" => 946,
140
+ "gamma" => 947,
141
+ "delta" => 948,
142
+ "epsilon" => 949,
143
+ "zeta" => 950,
144
+ "eta" => 951,
145
+ "theta" => 952,
146
+ "iota" => 953,
147
+ "kappa" => 954,
148
+ "lambda" => 955,
149
+ "mu" => 956,
150
+ "nu" => 957,
151
+ "xi" => 958,
152
+ "omicron" => 959,
153
+ "pi" => 960,
154
+ "rho" => 961,
155
+ "sigmaf" => 962,
156
+ "sigma" => 963,
157
+ "tau" => 964,
158
+ "upsilon" => 965,
159
+ "phi" => 966,
160
+ "chi" => 967,
161
+ "psi" => 968,
162
+ "omega" => 969,
163
+ "thetasym" => 977,
164
+ "upsih" => 978,
165
+ "piv" => 982,
166
+ "ensp" => 8194,
167
+ "emsp" => 8195,
168
+ "thinsp" => 8201,
169
+ "zwnj" => 8204,
170
+ "zwj" => 8205,
171
+ "lrm" => 8206,
172
+ "rlm" => 8207,
173
+ "ndash" => 8211,
174
+ "mdash" => 8212,
175
+ "lsquo" => 8216,
176
+ "rsquo" => 8217,
177
+ "sbquo" => 8218,
178
+ "ldquo" => 8220,
179
+ "rdquo" => 8221,
180
+ "bdquo" => 8222,
181
+ "dagger" => 8224,
182
+ "Dagger" => 8225,
183
+ "bull" => 8226,
184
+ "hellip" => 8230,
185
+ "permil" => 8240,
186
+ "prime" => 8242,
187
+ "Prime" => 8243,
188
+ "lsaquo" => 8249,
189
+ "rsaquo" => 8250,
190
+ "oline" => 8254,
191
+ "frasl" => 8260,
192
+ "euro" => 8364,
193
+ "image" => 8465,
194
+ "weierp" => 8472,
195
+ "real" => 8476,
196
+ "trade" => 8482,
197
+ "alefsym" => 8501,
198
+ "larr" => 8592,
199
+ "uarr" => 8593,
200
+ "rarr" => 8594,
201
+ "darr" => 8595,
202
+ "harr" => 8596,
203
+ "crarr" => 8629,
204
+ "lArr" => 8656,
205
+ "uArr" => 8657,
206
+ "rArr" => 8658,
207
+ "dArr" => 8659,
208
+ "hArr" => 8660,
209
+ "forall" => 8704,
210
+ "part" => 8706,
211
+ "exist" => 8707,
212
+ "empty" => 8709,
213
+ "nabla" => 8711,
214
+ "isin" => 8712,
215
+ "notin" => 8713,
216
+ "ni" => 8715,
217
+ "prod" => 8719,
218
+ "sum" => 8721,
219
+ "minus" => 8722,
220
+ "lowast" => 8727,
221
+ "radic" => 8730,
222
+ "prop" => 8733,
223
+ "infin" => 8734,
224
+ "ang" => 8736,
225
+ "and" => 8743,
226
+ "or" => 8744,
227
+ "cap" => 8745,
228
+ "cup" => 8746,
229
+ "int" => 8747,
230
+ "there4" => 8756,
231
+ "sim" => 8764,
232
+ "cong" => 8773,
233
+ "asymp" => 8776,
234
+ "ne" => 8800,
235
+ "equiv" => 8801,
236
+ "le" => 8804,
237
+ "ge" => 8805,
238
+ "sub" => 8834,
239
+ "sup" => 8835,
240
+ "nsub" => 8836,
241
+ "sube" => 8838,
242
+ "supe" => 8839,
243
+ "oplus" => 8853,
244
+ "otimes" => 8855,
245
+ "perp" => 8869,
246
+ "sdot" => 8901,
247
+ "lceil" => 8968,
248
+ "rceil" => 8969,
249
+ "lfloor" => 8970,
250
+ "rfloor" => 8971,
251
+ "lang" => 10216,
252
+ "rang" => 10217,
253
+ "loz" => 9674,
254
+ "spades" => 9824,
255
+ "clubs" => 9827,
256
+ "hearts" => 9829,
257
+ "diams" => 9830
258
+ }
259
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: UTF-8
2
+
3
+ module Sterile
4
+ SMART_FORMAT_RULES = [
5
+ ["'tain't", "’tain’t"],
6
+ ["'twere", "’twere"],
7
+ ["'twas", "’twas"],
8
+ ["'tis", "’tis"],
9
+ ["'twill", "’twill"],
10
+ ["'til", "’til"],
11
+ ["'bout", "’bout"],
12
+ ["'nuff", "’nuff"],
13
+ ["'round", "’round"],
14
+ ["'cause", "’cause"],
15
+ ["'cos", "’cos"],
16
+ ["i'm", "i’m"],
17
+ ['--"', "—”"],
18
+ ["--'", "—’"],
19
+ ["--", "—"],
20
+ ["...", "…"],
21
+ ["(tm)", "™"],
22
+ ["(TM)", "™"],
23
+ ["(c)", "©"],
24
+ ["(r)", "®"],
25
+ ["(R)", "®"],
26
+ [/s\'([^a-zA-Z0-9])/, "s’\\1"],
27
+ [/"([:;])/, "”\\1"],
28
+ [/\'s$/, "’s"],
29
+ [/\'(\d\d(?:’|\')?s)/, "’\\1"],
30
+ [/(\s|\A|"|\(|\[)\'/, "\\1‘"],
31
+ [/(\d+)"/, "\\1′"],
32
+ [/(\d+)\'/, "\\1″"],
33
+ [/(\S)\'([^\'\s])/, "\\1’\\2"],
34
+ [/(\s|\A|\(|\[)"(?!\s)/, "\\1“\\2"],
35
+ [/"(\s|\S|\Z)/, "”\\1"],
36
+ [/\'([\s.]|\Z)/, "’\\1"],
37
+ [/(\d+)x(\d+)/, "\\1×\\2"],
38
+ [/([a-z])'(t|d|s|ll|re|ve)(\b)/i, "\\1’\\2\\3"]
39
+ ]
40
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: UTF-8
2
+
3
+ module Sterile
4
+ VERSION = "1.0.0"
5
+ end
data/lib/sterile.rb ADDED
@@ -0,0 +1,339 @@
1
+ # encoding: UTF-8
2
+
3
+ # Copyright (c) 2011 Patrick Hogan
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require "sterile/codepoints"
25
+ require "sterile/html_entities"
26
+ require "sterile/smart_format_rules"
27
+
28
+
29
+ module Sterile
30
+
31
+ class << self
32
+
33
+ def transmogrify(string, &block)
34
+ raise "No block given" unless block_given?
35
+
36
+ result = ""
37
+ string.unpack("U*").each do |codepoint|
38
+ cg = codepoint >> 8
39
+ cp = codepoint & 0xFF
40
+ begin
41
+ mapping = CODEPOINTS[cg][cp]
42
+ result << yield(mapping, codepoint)
43
+ rescue
44
+ end
45
+ end
46
+
47
+ result
48
+ end
49
+
50
+ # Transliterate Unicode [and accented ASCII] characters to their plain-text
51
+ # ASCII equivalents. This is based on data from the stringex gem (https://github.com/rsl/stringex)
52
+ # which is in turn a port of Perl's Unidecode and ostensibly provides
53
+ # superior results to iconv. The optical conversion data is based on work
54
+ # by Eric Boehs at https://github.com/ericboehs/to_slug
55
+ # Passing an option of :optical => true will prefer optical mapping instead
56
+ # of more pedantic matches.
57
+ #
58
+ # "ýůçký".transliterate # => "yucky"
59
+ #
60
+ def transliterate(string, options = {})
61
+ options = {
62
+ :optical => false
63
+ }.merge!(options)
64
+
65
+ if options[:optical]
66
+ transmogrify(string) do |mapping, codepoint|
67
+ mapping[1] || mapping[0] || ""
68
+ end
69
+ else
70
+ transmogrify(string) do |mapping, codepoint|
71
+ mapping[0] || mapping[1] || ""
72
+ end
73
+ end
74
+ end
75
+ alias_method :to_ascii, :transliterate
76
+
77
+
78
+ # Trim whitespace from start and end of string and remove any redundant
79
+ # whitespace in between.
80
+ #
81
+ # " Hello world! ".transliterate # => "Hello world!"
82
+ #
83
+ def trim_whitespace(string)
84
+ string.gsub(/\s+/, " ").strip
85
+ end
86
+
87
+
88
+ # Transliterate to ASCII and strip out any HTML/XML tags.
89
+ #
90
+ # "<b>nåsty</b>".sterilize # => "nasty"
91
+ #
92
+ def sterilize(string)
93
+ strip_tags(transliterate(string))
94
+ end
95
+
96
+
97
+ # Transliterate to ASCII, downcase and format for URL permalink/slug
98
+ # by stripping out all non-alphanumeric characters and replacing spaces
99
+ # with a delimiter (defaults to '-').
100
+ #
101
+ # "Hello World!".sluggerize # => "hello-world"
102
+ #
103
+ def sluggerize(string, options = {})
104
+ options = {
105
+ :delimiter => "-"
106
+ }.merge!(options)
107
+
108
+ sterilize(string).strip.gsub(/\s+/, "-").gsub(/[^a-zA-Z0-9\-]/, "").gsub(/-+/, options[:delimiter]).downcase
109
+ end
110
+ alias_method :to_slug, :sluggerize
111
+
112
+
113
+ # Format text with proper "curly" quotes, m-dashes, copyright, trademark, etc.
114
+ #
115
+ # q{"He said, 'Away with you, Drake!'"}.smart_format # => “He said, ‘Away with you, Drake!’”
116
+ #
117
+ def smart_format(string)
118
+ SMART_FORMAT_RULES.each do |rule|
119
+ string.gsub!(rule[0], rule[1])
120
+ end
121
+ string
122
+ end
123
+
124
+
125
+ # Turn Unicode characters into their HTML equivilents.
126
+ # If a valid HTML entity is not possible, it will create a numeric entity.
127
+ #
128
+ # q{“Economy Hits Bottom,” ran the headline}.smart_format # => &ldquo;Economy Hits Bottom,&rdquo; ran the headline
129
+ #
130
+ def encode_entities(string)
131
+ transmogrify(string) do |mapping, codepoint|
132
+ if (32..126).include?(codepoint)
133
+ mapping[0]
134
+ else
135
+ "&" + (mapping[2] || "#" + codepoint.to_s) + ";"
136
+ end
137
+ end
138
+ end
139
+
140
+
141
+ # The reverse of +encode_entities+. Turns HTML or numeric entities into
142
+ # their Unicode counterparts.
143
+ #
144
+ def decode_entities(string)
145
+ string.gsub!(/&#(\d{1,4});/) { [$1.to_i].pack("U") }
146
+ string.gsub(/&([a-zA-Z0-9]+);/) do
147
+ codepoint = HTML_ENTITIES[$1]
148
+ codepoint ? [codepoint].pack("U") : $&
149
+ end
150
+ end
151
+
152
+
153
+ # Remove HTML/XML tags from text. Also strips out comments, PHP and ERB style tags.
154
+ # CDATA is considered text unless :keep_cdata => false is specified.
155
+ # Redundant whitespace will be removed unless :keep_whitespace => true is specified.
156
+ #
157
+ def strip_tags(string, options = {})
158
+ options = {
159
+ :keep_whitespace => false,
160
+ :keep_cdata => true
161
+ }.merge!(options)
162
+
163
+ string.gsub!(/<[%?](php)?[^>]*>/, '') # strip php, erb et al
164
+ string.gsub!(/<!--[^-]*-->/, '') # strip comments
165
+
166
+ string.gsub!(
167
+ /
168
+ <!\[CDATA\[
169
+ ([^\]]*)
170
+ \]\]>
171
+ /xi,
172
+ options[:keep_cdata] ? '\\1' : ''
173
+ )
174
+
175
+ html_name = /[\w:-]+/
176
+ html_data = /([A-Za-z0-9]+|('[^']*?'|"[^"]*?"))/
177
+ html_attr = /(#{html_name}(\s*=\s*#{html_data})?)/
178
+
179
+ string.gsub!(
180
+ /
181
+ <
182
+ [\/]?
183
+ #{html_name}
184
+ (\s+(#{html_attr}(\s+#{html_attr})*))?
185
+ \s*
186
+ [\/]?
187
+ >
188
+ /xi,
189
+ ''
190
+ )
191
+
192
+ options[:keep_whitespace] ? string : trim_whitespace(string)
193
+ end
194
+
195
+
196
+ # Similar to +gsub+, except it works in between HTML/XML tags and
197
+ # yields text to a block. Text will be replaced by what the block
198
+ # returns.
199
+ # Warning: does not work in some degenerate cases.
200
+ #
201
+ def gsub_tags(string, &block)
202
+ raise "No block given" unless block_given?
203
+
204
+ string.gsub!(/(<[^>]*>)|([^<]+)/) do |match|
205
+ $2 ? yield($2) : $1
206
+ end
207
+ end
208
+
209
+
210
+ # Iterates over all text in between HTML/XML tags and yields
211
+ # it to a block.
212
+ # Warning: does not work in some degenerate cases.
213
+ #
214
+ def scan_tags(string, &block)
215
+ raise "No block given" unless block_given?
216
+
217
+ string.scan(/(<[^>]*>)|([^<]+)/) do |match|
218
+ yield($2) unless $2.nil?
219
+ end
220
+ end
221
+
222
+
223
+ # Like +smart_format+, but works with HTML/XML (somewhat).
224
+ #
225
+ def smart_format_tags(string)
226
+ string.gsub_tags do |text|
227
+ text.smart_format.encode_entities
228
+ end
229
+ end
230
+
231
+
232
+ # Format text appropriately for titles. This method is much smarter
233
+ # than ActiveSupport's +titlecase+. The algorithm is based on work done
234
+ # by John Gruber et al (http://daringfireball.net/2008/08/title_case_update)
235
+ #
236
+ def titlecase(string)
237
+ string.strip!
238
+ string.gsub!(/\s+/, " ")
239
+ string.downcase! unless string =~ /[[:lower:]]/
240
+
241
+ small_words = %w{ a an and as at(?!&t) but by en for if in nor of on or the to v[.]? via vs[.]? }.join("|")
242
+ apos = / (?: ['’] [[:lower:]]* )? /xu
243
+
244
+ string.gsub!(
245
+ /
246
+ \b
247
+ ([_\*]*)
248
+ (?:
249
+ ( [-\+\w]+ [@.\:\/] [-\w@.\:\/]+ #{apos} ) # URL, domain, or email
250
+ |
251
+ ( (?i: #{small_words} ) #{apos} ) # or small word, case-insensitive
252
+ |
253
+ ( [[:alpha:]] [[:lower:]'’()\[\]{}]* #{apos} ) # or word without internal caps
254
+ |
255
+ ( [[:alpha:]] [[:alpha:]'’()\[\]{}]* #{apos} ) # or some other word
256
+ )
257
+ ([_\*]*)
258
+ \b
259
+ /xu
260
+ ) do
261
+ ($1 ? $1 : "") +
262
+ ($2 ? $2 : ($3 ? $3.downcase : ($4 ? $4.downcase.capitalize : $5))) +
263
+ ($6 ? $6 : "")
264
+ end
265
+
266
+ if RUBY_VERSION < "1.9.0"
267
+ string.gsub!(
268
+ /
269
+ \b
270
+ ([:alpha:]+)
271
+ (‑)
272
+ ([:alpha:]+)
273
+ \b
274
+ /xu
275
+ ) do
276
+ $1.downcase.capitalize + $2 + $1.downcase.capitalize
277
+ end
278
+ end
279
+
280
+ string.gsub!(
281
+ /
282
+ (
283
+ \A [[:punct:]]* # start of title
284
+ | [:.;?!][ ]+ # or of subsentence
285
+ | [ ]['"“‘(\[][ ]* # or of inserted subphrase
286
+ )
287
+ ( #{small_words} ) # followed by a small-word
288
+ \b
289
+ /xiu
290
+ ) do
291
+ $1 + $2.downcase.capitalize
292
+ end
293
+
294
+ string.gsub!(
295
+ /
296
+ \b
297
+ ( #{small_words} ) # small-word
298
+ (?=
299
+ [[:punct:]]* \Z # at the end of the title
300
+ |
301
+ ['"’”)\]] [ ] # or of an inserted subphrase
302
+ )
303
+ /xu
304
+ ) do
305
+ $1.downcase.capitalize
306
+ end
307
+
308
+ string.gsub!(
309
+ /
310
+ (
311
+ \b
312
+ [[:alpha:]] # single first letter
313
+ [\-‑] # followed by a dash
314
+ )
315
+ ( [[:alpha:]] ) # followed by a letter
316
+ /xu
317
+ ) do
318
+ $1 + $2.downcase
319
+ end
320
+
321
+ string.gsub!(/q&a/i, 'Q&A')
322
+
323
+ string
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+
330
+
331
+ # Add extensions to String
332
+ #
333
+ class String
334
+ Sterile.methods(false).each do |method|
335
+ eval("def #{method}(*args, &block); Sterile.#{method}(self, *args, &block); end")
336
+ eval("def #{method}!(*args, &block); replace Sterile.#{method}(self, *args, &block); end")
337
+ end
338
+ end
339
+
data/sterilize.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ $:.push File.expand_path("../lib", __FILE__)
4
+ require "sterile/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "sterile"
8
+ s.version = Sterile::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = ["Patrick Hogan"]
11
+ s.email = ["pbhogan@gmail.com"]
12
+ s.homepage = "https://github.com/pbhogan/sterile"
13
+ s.summary = %q{Sterilize your strings! Transliterate, generate slugs, smart format, strip tags, encode/decode entities and more.}
14
+ s.description = s.summary
15
+
16
+ s.rubyforge_project = "sterile"
17
+
18
+ # s.add_dependency("nokogiri")
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+ end