short 0.2.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.
- 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
|