short 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.md +9 -0
- data/Rakefile +3 -0
- data/bin/shortener +115 -0
- data/config.ru +2 -0
- data/lib/shortener/client.rb +69 -0
- data/lib/shortener/configuration.rb +76 -0
- data/lib/shortener/server/Gemfile +6 -0
- data/lib/shortener/server/Gemfile.lock +23 -0
- data/lib/shortener/server/config.ru.template +2 -0
- data/lib/shortener/server/public/delete-icon.png +0 -0
- data/lib/shortener/server/public/flash/Jplayer.swf +0 -0
- data/lib/shortener/server/public/flash/clippy.swf +0 -0
- data/lib/shortener/server/public/flash/swfupload.swf +0 -0
- data/lib/shortener/server/public/images/XPButtonUploadText_61x22.png +0 -0
- data/lib/shortener/server/public/jquery-swfupload-min.js +1 -0
- data/lib/shortener/server/public/jquery-swfupload.js +64 -0
- data/lib/shortener/server/public/jquery.jplayer.min.js +97 -0
- data/lib/shortener/server/public/jquery.min.js +18 -0
- data/lib/shortener/server/public/patched.bootstrap.min.css +370 -0
- data/lib/shortener/server/public/site.js +36 -0
- data/lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.css +623 -0
- data/lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.jpg +0 -0
- data/lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.seeking.gif +0 -0
- data/lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.video.play.png +0 -0
- data/lib/shortener/server/public/swfupload.js +980 -0
- data/lib/shortener/server/views/add.haml +22 -0
- data/lib/shortener/server/views/display.haml +5 -0
- data/lib/shortener/server/views/index.haml +25 -0
- data/lib/shortener/server/views/layout.haml +21 -0
- data/lib/shortener/server/views/s3/audio.haml +75 -0
- data/lib/shortener/server/views/s3/image.haml +27 -0
- data/lib/shortener/server/views/s3/layout.haml +23 -0
- data/lib/shortener/server/views/s3/video.haml +71 -0
- data/lib/shortener/server/views/upload.haml +130 -0
- data/lib/shortener/server.rb +346 -0
- data/lib/shortener/version.rb +6 -0
- data/lib/shortener.rb +9 -0
- data/tasks/heroku.rake +46 -0
- data/test/test_client.rb +26 -0
- data/test/test_configuration.rb +23 -0
- data/test/test_server.rb +0 -0
- metadata +102 -0
@@ -0,0 +1,346 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'redis-namespace'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
require 'haml'
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'base64'
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
class Shortener
|
12
|
+
class Server < Sinatra::Base
|
13
|
+
dir = File.expand_path(File.dirname(__FILE__))
|
14
|
+
set :root, File.join(dir, 'server')
|
15
|
+
set :public_folder, File.join(dir, 'server', 'public')
|
16
|
+
|
17
|
+
configure do
|
18
|
+
uri = URI.parse(ENV["REDISTOGO_URL"])
|
19
|
+
_redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
|
20
|
+
$redis = Redis::Namespace.new(:shortener, redis: _redis)
|
21
|
+
$default_url = ENV['DEFAULT_URL'] || '/index'
|
22
|
+
$s3_config = {
|
23
|
+
bucket: ENV['S3_BUCKET'],
|
24
|
+
key_prefix: ENV['S3_KEY_PREFIX'],
|
25
|
+
default_acl: ENV['S3_DEFAULT_ACL'],
|
26
|
+
access_key_id: ENV['S3_ACCESS_KEY_ID'],
|
27
|
+
secret_access_key: ENV['S3_SECRET_ACCESS_KEY']
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
helpers do
|
32
|
+
|
33
|
+
def bad! message
|
34
|
+
halt 412, {}, message
|
35
|
+
end
|
36
|
+
|
37
|
+
def nope!
|
38
|
+
halt 404, {}, "No luck."
|
39
|
+
end
|
40
|
+
|
41
|
+
def base_url
|
42
|
+
@base_url ||= "#{request.env['rack.url_scheme']}://#{request.env['HTTP_HOST']}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def clippy(text, bgcolor='#FFFFFF')
|
46
|
+
html = <<-EOF
|
47
|
+
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
|
48
|
+
width="110"
|
49
|
+
height="25"
|
50
|
+
id="clippy" >
|
51
|
+
<param name="movie" value="/flash/clippy.swf"/>
|
52
|
+
<param name="allowScriptAccess" value="always" />
|
53
|
+
<param name="quality" value="high" />
|
54
|
+
<param name="scale" value="noscale" />
|
55
|
+
<param NAME="FlashVars" value="text=#{text}">
|
56
|
+
<param name="bgcolor" value="#{bgcolor}">
|
57
|
+
<embed src="/flash/clippy.swf"
|
58
|
+
width="110"
|
59
|
+
height="14"
|
60
|
+
name="clippy"
|
61
|
+
quality="high"
|
62
|
+
allowScriptAccess="always"
|
63
|
+
type="application/x-shockwave-flash"
|
64
|
+
pluginspage="http://www.macromedia.com/go/getflashplayer"
|
65
|
+
FlashVars="text=#{text}"
|
66
|
+
bgcolor="#{bgcolor}"
|
67
|
+
/>
|
68
|
+
</object>
|
69
|
+
EOF
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_or_fetch_url(params)
|
73
|
+
bad! 'Missing url.' unless url = params['url']
|
74
|
+
url = "http://#{url}" unless /^http/i =~ url
|
75
|
+
bad! 'Malformed url.' unless (url = URI.parse(url)) && /^http/ =~ url.scheme
|
76
|
+
|
77
|
+
%w(max-clicks expire desired-short allow-override).each do |k|
|
78
|
+
params[k] = false if params[k].nil? || params[k].empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
unless params['max-clicks'] || params['expire'] || params['desired-short']
|
82
|
+
id = check_cache(url)
|
83
|
+
end
|
84
|
+
id ||= shorten(url, params)
|
85
|
+
|
86
|
+
return "#{base_url}/#{id}", id
|
87
|
+
end
|
88
|
+
|
89
|
+
def set_upload_short(params)
|
90
|
+
bad! 'Missing content type.' unless type = params['type']
|
91
|
+
key = generate_short
|
92
|
+
fname = params['file_name'].gsub(' ', '+')
|
93
|
+
sha = Digest::SHA1.hexdigest(fname)
|
94
|
+
hash_key = "data:#{sha}:#{key}"
|
95
|
+
url = "https://s3.amazonaws.com/#{$s3_config[:bucket]}/#{$s3_config[:key_prefix]}/#{fname}"
|
96
|
+
ext = File.extname(fname)[1..-1]
|
97
|
+
extras = ['url', url, 's3', true, 'shortened', key, 'extension', ext, 'set-count', 1]
|
98
|
+
data = params.keys.map {|k| [k, params[k]] }.flatten.concat(extras)
|
99
|
+
|
100
|
+
$redis.set(key, sha)
|
101
|
+
$redis.hmset(hash_key, *data)
|
102
|
+
|
103
|
+
"#{base_url}/#{key}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def shorten(url, options = {})
|
107
|
+
|
108
|
+
unless options['desired-short']
|
109
|
+
key = generate_short
|
110
|
+
else
|
111
|
+
check = $redis.get(options['desired-short'])
|
112
|
+
if check # it's already taken
|
113
|
+
check_key = "data:#{check}:#{options['desired-short']}"
|
114
|
+
prev_set = $redis.hgetall(check_key)
|
115
|
+
|
116
|
+
# if we don't expire or have max clicks and previously set key
|
117
|
+
# doesn't expire or have max clicks we can go ahead and use it
|
118
|
+
# without any further setup.
|
119
|
+
unless options['expire'] || options['max-clicks']
|
120
|
+
if (!prev_set['max-clicks'] && !prev_set['expire'] &&
|
121
|
+
(prev_set['url'] == url.to_s))
|
122
|
+
$redis.hincrby(check_key, 'set-count', 1)
|
123
|
+
return options['desired-short']
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
if prev_set['max-clicks'].to_i < prev_set['clicks'].to_i
|
128
|
+
# previous key is no longer valid, we can assign to it
|
129
|
+
key = options['desired-short']
|
130
|
+
else
|
131
|
+
bad! 'Name is already taken. Use Allow override' unless options['allow-override'] == 'true'
|
132
|
+
key = generate_short
|
133
|
+
end
|
134
|
+
|
135
|
+
else
|
136
|
+
key = options['desired-short']
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
sha = Digest::SHA1.hexdigest(url.to_s)
|
141
|
+
$redis.set(key, sha)
|
142
|
+
|
143
|
+
hsh_data = ['shortened', key, 'url', url, 'set-count', 1]
|
144
|
+
hsh_data.concat(['max-clicks', options['max-clicks'].to_i]) if options['max-clicks']
|
145
|
+
|
146
|
+
if options['expire'] # set expire time if specified
|
147
|
+
ttl = options['expire'].to_i
|
148
|
+
ttl_key = "expire:#{sha}:#{key}"
|
149
|
+
$redis.set(ttl_key, "#{sha}:#{key}")
|
150
|
+
$redis.expire(ttl_key, ttl)
|
151
|
+
hsh_data.concat(['expire', ttl_key])
|
152
|
+
end
|
153
|
+
$redis.hmset("data:#{sha}:#{key}", *hsh_data)
|
154
|
+
|
155
|
+
key
|
156
|
+
end
|
157
|
+
|
158
|
+
def check_cache(url)
|
159
|
+
sha = Digest::SHA1.hexdigest(url.to_s)
|
160
|
+
|
161
|
+
$redis.keys("data:#{sha}:*").each do |key|
|
162
|
+
short = $redis.hgetall(key)
|
163
|
+
unless short == {} || short['expire'] || short['max-clicks']
|
164
|
+
$redis.hincrby(key, 'set-count', 1)
|
165
|
+
return short['shortened']
|
166
|
+
end
|
167
|
+
end
|
168
|
+
nil
|
169
|
+
end
|
170
|
+
|
171
|
+
def delete_short(id)
|
172
|
+
puts "deleting #{id}"
|
173
|
+
puts sha = $redis.get(id)
|
174
|
+
$redis.multi do
|
175
|
+
$redis.del "data:#{sha}:#{id}"
|
176
|
+
$redis.del "expire:#{sha}:#{id}"
|
177
|
+
$redis.del id
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def generate_short
|
182
|
+
begin
|
183
|
+
o = [('a'..'z'),('A'..'Z'),(0..9)].map{|i| i.to_a}.flatten;
|
184
|
+
key = (0..4).map{ o[rand(o.length)] }.join;
|
185
|
+
puts "testing #{key}"
|
186
|
+
end while !$redis.get(key).nil?
|
187
|
+
key
|
188
|
+
end
|
189
|
+
|
190
|
+
def ttl_display(ttl)
|
191
|
+
if ttl == -1
|
192
|
+
ret = 'expired'
|
193
|
+
elsif ttl == nil
|
194
|
+
ret = '∞'
|
195
|
+
else
|
196
|
+
ret = ttl
|
197
|
+
end
|
198
|
+
ret
|
199
|
+
end
|
200
|
+
|
201
|
+
def s3_policy
|
202
|
+
expiration_date = (Time.now + 36000).utc.strftime('%Y-%m-%dT%H:%M:%S.000Z') # 10.hours.from_now
|
203
|
+
max_filesize = 2147483648 # 2.gigabyte
|
204
|
+
policy = Base64.encode64(
|
205
|
+
"{'expiration': '#{expiration_date}',
|
206
|
+
'conditions': [
|
207
|
+
{'bucket': '#{$s3_config[:bucket]}'},
|
208
|
+
['starts-with', '$key', '#{$s3_config[:key_prefix]}'],
|
209
|
+
{'acl': '#{$s3_config[:default_acl]}'},
|
210
|
+
{'success_action_status': '201'},
|
211
|
+
['starts-with', '$Filename', ''],
|
212
|
+
['content-length-range', 0, #{max_filesize}]
|
213
|
+
]
|
214
|
+
}"
|
215
|
+
).gsub(/\n|\r/, '')
|
216
|
+
end
|
217
|
+
|
218
|
+
def s3_signature(policy)
|
219
|
+
signature = Base64.encode64(OpenSSL::HMAC.digest(
|
220
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
221
|
+
$s3_config[:secret_access_key], policy)
|
222
|
+
).gsub("\n","")
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
|
227
|
+
before do
|
228
|
+
params
|
229
|
+
end
|
230
|
+
|
231
|
+
get '/' do
|
232
|
+
redirect $default_url
|
233
|
+
end
|
234
|
+
|
235
|
+
get '/add' do
|
236
|
+
haml :add
|
237
|
+
end
|
238
|
+
|
239
|
+
get '/index.?:format?' do
|
240
|
+
@shortens = Array.new
|
241
|
+
$redis.keys('data:*').each do |key|
|
242
|
+
short = $redis.hgetall(key)
|
243
|
+
short['expire'] = $redis.ttl(short['expire']) if short.has_key?('expire')
|
244
|
+
@shortens << short
|
245
|
+
puts " url for #{key[-5..-1]} => #{short['url']}"
|
246
|
+
end
|
247
|
+
if params[:format] == 'json'
|
248
|
+
content_type :json
|
249
|
+
return @shortens.to_json
|
250
|
+
end
|
251
|
+
haml :index
|
252
|
+
end
|
253
|
+
|
254
|
+
get '/delete/:id.:format' do |id, format|
|
255
|
+
puts "#{id}, #{format}"
|
256
|
+
delete_short(id)
|
257
|
+
if format == 'json'
|
258
|
+
content_type :json
|
259
|
+
{success: true, short: id}.to_json
|
260
|
+
else
|
261
|
+
redirect :index
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
get '/upload' do
|
266
|
+
policy = s3_policy
|
267
|
+
signature = s3_signature(policy)
|
268
|
+
|
269
|
+
@post = {
|
270
|
+
"key" => "#{$s3_config[:key_prefix]}/${filename}",
|
271
|
+
"AWSAccessKeyId" => "#{$s3_config[:access_key_id]}",
|
272
|
+
"acl" => "#{$s3_config[:default_acl]}",
|
273
|
+
"policy" => "#{policy}",
|
274
|
+
"signature" => "#{signature}",
|
275
|
+
"success_action_status" => "201"
|
276
|
+
}
|
277
|
+
|
278
|
+
@upload_url = "http://#{$s3_config[:bucket]}.s3.amazonaws.com/"
|
279
|
+
haml :upload
|
280
|
+
end
|
281
|
+
|
282
|
+
#get '/:id.?:format?' do
|
283
|
+
get %r{\/([a-z0-9]{5})(\.[a-z]{3,}){0,1}}i do
|
284
|
+
id = params[:captures].first
|
285
|
+
sha = $redis.get(id)
|
286
|
+
unless sha.nil?
|
287
|
+
key = "data:#{sha}:#{id}"
|
288
|
+
short = $redis.hgetall(key)
|
289
|
+
not_expired = short.has_key?('expire') ? $redis.get(short['expire']) : true
|
290
|
+
not_maxed = !(short['click-count'].to_i >= short['max-clicks'].to_i)
|
291
|
+
short.has_key?('max-clicks') ? not_maxed : not_maxed = true
|
292
|
+
$redis.hincrby(key, 'click-count', 1) if not_expired && not_maxed
|
293
|
+
if params[:captures].last == '.json'
|
294
|
+
ret = short.merge({expired: not_expired.nil? , maxed: !not_maxed})
|
295
|
+
content_type :json
|
296
|
+
return ret.to_json
|
297
|
+
else
|
298
|
+
if not_expired
|
299
|
+
unless short['s3'] == 'true' && !(short['type'] == 'download')
|
300
|
+
if not_maxed
|
301
|
+
puts "redirecting #{id} to #{url}"
|
302
|
+
redirect short['url']
|
303
|
+
end # => max clicks check
|
304
|
+
else
|
305
|
+
@short = short
|
306
|
+
puts "rendering view for s3 content. #{id} => #{short['url']}"
|
307
|
+
return haml(:"s3/#{short['type']}", layout: :'s3/layout')
|
308
|
+
end # => it's S3 and needs displaying.
|
309
|
+
end # => expired check
|
310
|
+
end # => format
|
311
|
+
end
|
312
|
+
puts "redirecting to default url"
|
313
|
+
redirect $default_url
|
314
|
+
end
|
315
|
+
|
316
|
+
post '/upload.?:format?' do |format|
|
317
|
+
content_type :json
|
318
|
+
short = set_upload_short(params['shortener'])
|
319
|
+
puts "set #{short} to #{params['shortener']['file_name']}"
|
320
|
+
if format == 'json'
|
321
|
+
{url: short}.to_json
|
322
|
+
else
|
323
|
+
redirect :index
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
post '/add.?:format?' do |format|
|
328
|
+
begin
|
329
|
+
# TODO figure out why the fuck these are parsing from Net::HTTP
|
330
|
+
params['shortener'] = JSON.parse(params['shortener']) if params['shortener'].is_a?(String)
|
331
|
+
rescue Exception => boom
|
332
|
+
# essentally, params = params
|
333
|
+
end
|
334
|
+
|
335
|
+
@url, id = set_or_fetch_url(params["shortener"])
|
336
|
+
puts "set #{@url} to #{params['shortener']['url']}"
|
337
|
+
if format == 'json'
|
338
|
+
content_type :json
|
339
|
+
{success: true, short: id, url: @url, html: haml(:display, :layout => false)}.to_json
|
340
|
+
else
|
341
|
+
haml :display
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
end # => Server
|
346
|
+
end # => Shortener
|
data/lib/shortener.rb
ADDED
data/tasks/heroku.rake
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
$gem_dir = File.expand_path(File.dirname(File.dirname(__FILE__)))
|
2
|
+
def gem_file(*args)
|
3
|
+
ret = args.map {|f| File.join($gem_dir, f)}.join(" ")
|
4
|
+
end
|
5
|
+
namespace :heroku do
|
6
|
+
|
7
|
+
desc "Build a Heroku Ready Git repo"
|
8
|
+
task :build do
|
9
|
+
cmd = "mkdir heroku"
|
10
|
+
cmd += " && mkdir heroku/server"
|
11
|
+
cmd += " && cp -r #{gem_file("lib/shortener/server/*")} ./heroku/server/"
|
12
|
+
cmd += " && cp #{gem_file('lib/shortener/server.rb')} ./heroku/main.rb"
|
13
|
+
cmd += " && mv ./heroku/server/config.ru.template ./heroku/config.ru"
|
14
|
+
cmd += " && mv ./heroku/server/Gemfile ./heroku/server/Gemfile.lock ./heroku"
|
15
|
+
cmd += " && git init heroku && cd heroku && git add . && git commit -m initial"
|
16
|
+
sh cmd
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "config a Heroku app the way we need it. Optionally set APPNAME to set heroku app name"
|
20
|
+
task :config do
|
21
|
+
require_relative '../lib/shortener'
|
22
|
+
$name = ENV['APPNAME'] || "shner-#{`whoami`.chomp}"
|
23
|
+
cmd = Dir.pwd =~ /heroku$/ ? "" : "cd heroku && "
|
24
|
+
cmd += "heroku create #{name}"
|
25
|
+
cmd += " && heroku addons:add redistogo:nano"
|
26
|
+
cmd += " && heroku config:add #{Shortener::Configuration.new.to_params}"
|
27
|
+
cmd += " && heroku addons:add custom_domains:basic"
|
28
|
+
sh cmd
|
29
|
+
end
|
30
|
+
|
31
|
+
desc "Push to Heroku"
|
32
|
+
task :push do
|
33
|
+
cmd = Dir.pwd =~ /heroku$/ ? "" : "cd heroku && "
|
34
|
+
cmd += "git push heroku master"
|
35
|
+
sh cmd
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Build, configure and push a shortener app to Heroku"
|
39
|
+
task :setup => [:build, :config, :push] do
|
40
|
+
puts "\nYour app has (hopefully) been created and pushed and available @" +
|
41
|
+
" http://#{$name}.heroku.com\n\n" +
|
42
|
+
"the Custom Domain Addon has been added, but still needs configuring, for" +
|
43
|
+
" steps see\n http://devcenter.heroku.com/articles/custom-domains"
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/test/test_client.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative '../lib/shortener/client'
|
3
|
+
|
4
|
+
class TestShortenerClient < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@client = Shortener::Client.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_add
|
11
|
+
short = @client.shorten('www.google.com')
|
12
|
+
assert short['success']
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_index
|
16
|
+
ind = @client.index
|
17
|
+
assert ind.is_a?(Array)
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_delete
|
21
|
+
add = @client.shorten('www.google.com')
|
22
|
+
del = @client.delete(add['short'])
|
23
|
+
assert del['success']
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative '../lib/shortener/configuration'
|
3
|
+
|
4
|
+
class TestShortenerConfiguration < MiniTest::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@conf = Shortener::Configuration.new(:SHORTENER_URL => 'localhost:4567')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_pass_options
|
11
|
+
@conf = Shortener::Configuration.new(:SHORTENER_URL => 'fuckoff')
|
12
|
+
assert @conf.is_a?(Shortener::Configuration)
|
13
|
+
assert @conf.shortener_url == 'fuckoff'
|
14
|
+
p @conf.default_url
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_uri_for
|
18
|
+
test = URI.parse("#{@conf.shortener_url}/add.json")
|
19
|
+
p @conf, @conf.shortener_url, @conf.uri_for(:add)
|
20
|
+
assert @conf.uri_for(:add) == test
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/test/test_server.rb
ADDED
File without changes
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: short
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- jake
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: turn
|
16
|
+
requirement: &70302203079100 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70302203079100
|
25
|
+
description: A (hopefully) easy and handy way to shorten links.
|
26
|
+
email:
|
27
|
+
- jake.wilkins@adfitech.com
|
28
|
+
executables:
|
29
|
+
- shortener
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- README.md
|
35
|
+
- Rakefile
|
36
|
+
- bin/shortener
|
37
|
+
- config.ru
|
38
|
+
- lib/shortener.rb
|
39
|
+
- lib/shortener/client.rb
|
40
|
+
- lib/shortener/configuration.rb
|
41
|
+
- lib/shortener/server.rb
|
42
|
+
- lib/shortener/server/Gemfile
|
43
|
+
- lib/shortener/server/Gemfile.lock
|
44
|
+
- lib/shortener/server/config.ru.template
|
45
|
+
- lib/shortener/server/public/delete-icon.png
|
46
|
+
- lib/shortener/server/public/flash/Jplayer.swf
|
47
|
+
- lib/shortener/server/public/flash/clippy.swf
|
48
|
+
- lib/shortener/server/public/flash/swfupload.swf
|
49
|
+
- lib/shortener/server/public/images/XPButtonUploadText_61x22.png
|
50
|
+
- lib/shortener/server/public/jquery-swfupload-min.js
|
51
|
+
- lib/shortener/server/public/jquery-swfupload.js
|
52
|
+
- lib/shortener/server/public/jquery.jplayer.min.js
|
53
|
+
- lib/shortener/server/public/jquery.min.js
|
54
|
+
- lib/shortener/server/public/patched.bootstrap.min.css
|
55
|
+
- lib/shortener/server/public/site.js
|
56
|
+
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.css
|
57
|
+
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.jpg
|
58
|
+
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.seeking.gif
|
59
|
+
- lib/shortener/server/public/skin/blue.monday/jplayer.blue.monday.video.play.png
|
60
|
+
- lib/shortener/server/public/swfupload.js
|
61
|
+
- lib/shortener/server/views/add.haml
|
62
|
+
- lib/shortener/server/views/display.haml
|
63
|
+
- lib/shortener/server/views/index.haml
|
64
|
+
- lib/shortener/server/views/layout.haml
|
65
|
+
- lib/shortener/server/views/s3/audio.haml
|
66
|
+
- lib/shortener/server/views/s3/image.haml
|
67
|
+
- lib/shortener/server/views/s3/layout.haml
|
68
|
+
- lib/shortener/server/views/s3/video.haml
|
69
|
+
- lib/shortener/server/views/upload.haml
|
70
|
+
- lib/shortener/version.rb
|
71
|
+
- tasks/heroku.rake
|
72
|
+
- test/test_client.rb
|
73
|
+
- test/test_configuration.rb
|
74
|
+
- test/test_server.rb
|
75
|
+
homepage: ''
|
76
|
+
licenses: []
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 1.8.10
|
96
|
+
signing_key:
|
97
|
+
specification_version: 3
|
98
|
+
summary: Link Shortener
|
99
|
+
test_files:
|
100
|
+
- test/test_client.rb
|
101
|
+
- test/test_configuration.rb
|
102
|
+
- test/test_server.rb
|