short 0.3.3 → 0.4.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.
data/Guardfile ADDED
@@ -0,0 +1,14 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'minitest' do
5
+ # with Minitest::Unit
6
+ watch(%r|^test/test_(.*)\.rb|)
7
+ watch(%r|^lib/shortener/(.*)\.rb|) { |m| "test/test_#{m[1]}.rb" }
8
+ watch(%r|^test/test_helper\.rb|) { "test" }
9
+
10
+ # with Minitest::Spec
11
+ # watch(%r|^spec/(.*)_spec\.rb|)
12
+ # watch(%r|^lib/(.*)\.rb|) { |m| "spec/#{m[1]}_spec.rb" }
13
+ # watch(%r|^spec/spec_helper\.rb|) { "spec" }
14
+ end
data/README.md CHANGED
@@ -5,24 +5,89 @@ A super simple, Sinatra based, Redis backed URL shortener designed to be deploye
5
5
 
6
6
  Check it out [here](http://shortener1.heroku.com), but be aware that the css on the add page that displays the shortened link assumes you're using a short url, so it kind of looks like shit when it displays `http://shortener1.heroku.com/whatev`
7
7
 
8
- :edit: the version ^ there is an oldy but a goody. There's been a lot of feature creep since then which would muck up a demo. Very soon there will be a way to disable said feature creep and we'll get a new demo.
8
+ for obvious reasons, the demo is configured with S3 disabled. However, if you are just looking
9
+ to play around with `short` follow the instructions below and use `http://shortener1.heroku.com`
10
+ as your the url you want to use.
9
11
 
10
- ### Configuration
12
+ ### Installation
11
13
 
12
- Currently, short uses either environment variables or settings from a config file, ~/.shortener.
14
+ is now as easy as
15
+
16
+ `gem install short`
17
+
18
+ and
19
+
20
+ `short`
21
+
22
+ which will then prompt you to supply the config vars necessary to use the short
23
+ executable or the short server. You can check out `/lib/shortener/configuration.rb`
24
+ to see a list of available configuration variables.
25
+
26
+ configuration variables are parsed by default on-load and cached for each use,
27
+ but all client methods allow override.
28
+
29
+ ### Server
30
+
31
+ Shortener server provides a primitive API for interacting with shorts. You get the following.
13
32
 
14
- for the `short` client to work, you only need something like
15
33
  <pre>
16
- ---
17
- :SHORTENER_URL: http://< your shortener url>
34
+ get '/index.json' => info on all shorts.
35
+
36
+ get '/delete/:id.json' => delete short @ id, returns {success: true, shortened: id}
37
+
38
+ get '/:id.json' => data hash for short @ id
39
+
40
+ post '/add.json' => data hash for new short
41
+ can accept the following options:
42
+ url: the url to shorten
43
+ expire: time in seconds that this short should live
44
+ max-clicks: the maximum number of clicks this short should accept.
45
+ desired-short: a short that should be set for this url.
46
+ allow-override: if desired-short is passed, whether or not to allow
47
+ a random short override.
48
+
49
+ post '/upload.json' => data hash for new short,
50
+
51
+ a data hash will contain the following keys:
52
+
53
+ shortened => id of this short. i.e. 'xZ147'
54
+ url => url of this short. i.e. 'www.google.com'
55
+ set-count => number of times this url has been shortened.
56
+ click-count => number of times this short has been resolved.
57
+
58
+ a data hash might contain the following keys:
59
+
60
+ expire => expire key that will be checked to see if this key will expire
61
+ max-clicks => maximum number of clicks this short will resolve for
62
+
63
+ if it's an endpoint that performs an action it will have a success key set to true or false.
64
+ (right now this is stupid and is set to true always, unless it errors, in which case you
65
+ get a 500 server error. hopefully that changes with some better error messages.)
66
+
67
+
68
+ S3 Keys
69
+
70
+ S3 => true if this is S3 content
71
+ extension => the file extension of S3 content. i.e. 'm4v'
72
+ file\_name => the name of the file. i.e. '1234.m4v'
73
+ name => the descriptive name. i.e. 'Pandas'
74
+ description => the description. i.e. 'A panda sneezes'
18
75
  </pre>
19
76
 
20
- checkout `lib/shortener/configuration.rb` for more options that you can set.
77
+ ### Client
78
+
79
+ `Shortener::Short` provides methods to access the server. You can access it through
80
+ the `Shortener` class or directly through the `Shortener::Short` class. You get:
21
81
 
22
- I'm going to put together a better way of handling this soon, because right now
23
- it's kind of a mess.
82
+ * shorten
83
+ * index
84
+ * fetch
85
+ * delete
24
86
 
25
- ### Usage
87
+ methods, each of which will return a/n istance of the `Short` class which will
88
+ parse the data and provide some defaults and access to said data.
89
+
90
+ ### Executable
26
91
 
27
92
  Use the `short` executable to
28
93
 
@@ -44,12 +109,15 @@ Run `short rake -T` to see more info.
44
109
 
45
110
  ### License
46
111
 
112
+ Short makes use of a number of libraries, each of which has its own license.
113
+
114
+ Short uses the DWTFYWPL.
47
115
 
48
116
  <pre>
49
117
  DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
50
118
  Version 2, December 2004
51
119
 
52
- Copyright (C) 20011 Jake Wilkins <jake AT jakewilkins DOT com>
120
+ Copyright (C) 20011 Jake Wilkins \<jake AT jakewilkins DOT com\>
53
121
 
54
122
  Everyone is permitted to copy and distribute verbatim or modified
55
123
  copies of this license document, and changing it is allowed as long
data/bin/short CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
  $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
3
3
 
4
- require 'shortener/client'
4
+ require 'shortener'
5
5
 
6
6
  def start_web
7
7
  begin
@@ -27,18 +27,19 @@ def start_web
27
27
  end
28
28
 
29
29
  def shorten(url)
30
- puts Shortener::Client.new.shorten(url)['url']
30
+ puts Shortener.shorten(url).short_url
31
31
  end
32
32
 
33
33
  def fetch(url)
34
34
  if url =~ /http:/
35
35
  url = url[-6..-1]
36
36
  end
37
- fetched = Shortener::Client.new.fetch(url)
37
+ fetched = Shortener.fetch(url)
38
38
  unless fetched['success'] == false
39
39
  puts <<-EOF
40
40
  short => #{fetched['shortened']}
41
41
  url => #{fetched['url']}
42
+ short-url => #{fetched.short_url}
42
43
  set-count => #{fetched['set-count']}
43
44
  click-count => #{fetched['click-count']}
44
45
  expired => #{fetched['expired']}
@@ -50,12 +51,12 @@ def fetch(url)
50
51
  end
51
52
 
52
53
  def delete(short)
53
- del = Shortener::Client.new.delete(short)
54
+ del = Shortener.delete(short)
54
55
  puts "#{short} deleted" if del['success']
55
56
  end
56
57
 
57
58
  def show_index
58
- index = Shortener::Client.new.index
59
+ index = Shortener.index
59
60
  short_summary = index.map do |v|
60
61
  url = v['url'].length > 38 ? "#{v['url'][0..25]}...#{v['url'][-10..-1]}" : "#{v['url']}"
61
62
  "#{v['shortened']} : #{url} #{v['type'].nil? ? '' : ('type: ' + v['type'])}"
@@ -151,6 +152,8 @@ when 'rake'
151
152
  do_action(:rake, ARGV[1])
152
153
  when 'delete'
153
154
  do_action(:delete, ARGV[1])
155
+ when '-v', '--version'
156
+ puts "short version #{Shortener::VERSION}"
154
157
  else
155
158
  do_action(:shorten, ARGV[0])
156
159
  end
@@ -5,6 +5,14 @@ class Shortener
5
5
  # The class for storing Configuration Information
6
6
  class Configuration
7
7
 
8
+ class << self
9
+ def current
10
+ @current_configuration ||= Configuration.new(conf_current_placeholder: nil)
11
+ end
12
+ end
13
+
14
+ attr_accessor :options
15
+
8
16
  OPTIONS = [:SHORTENER_URL, :DEFAULT_URL, :REDISTOGO_URL, :S3_KEY_PREFIX,
9
17
  :S3_ACCESS_KEY_ID, :S3_SECRET_ACCESS_KEY, :S3_DEFAULT_ACL, :S3_BUCKET,
10
18
  :DOTFILE_PATH, :S3_ENABLED]
@@ -17,13 +25,19 @@ class Shortener
17
25
  # priority goes dotfile < env < passed option
18
26
  def initialize(opts = Hash.new)
19
27
  # TODO check keys by calls opts.delete {|k| !OPTIONS.include?(k)
20
- @options = Hash.new
21
- check_dotfile
22
- check_env
23
- @options = @options.merge!(opts)
24
- @options[:DEFAULT_URL] ||= '/index'
28
+ unless opts.empty?
29
+ opts.delete(:conf_current_placeholder)
30
+ @options = Hash.new
31
+ check_dotfile
32
+ check_env
33
+ @options = @options.merge!(opts)
34
+ @options[:DEFAULT_URL] ||= '/index'
35
+ else
36
+ @options = Configuration.current.options
37
+ end
25
38
  end
26
39
 
40
+ # return a URI for an endpoint based on this configuration
27
41
  def uri_for(end_point, opts = nil)
28
42
  if END_POINTS.include?(end_point.to_sym)
29
43
  path = path_for(end_point, opts)
@@ -33,6 +47,7 @@ class Shortener
33
47
  end
34
48
  end
35
49
 
50
+ # return a string ENV's for command line use.
36
51
  def to_params
37
52
  ret = Array.new
38
53
  @options.each {|k,v| ret << "#{k}=#{v}" unless HEROKU_IGNORE.include?(k)}
@@ -47,6 +62,7 @@ class Shortener
47
62
  end
48
63
  end
49
64
 
65
+ # return the URI for the redistogo url
50
66
  def redistogo_url
51
67
  begin
52
68
  URI.parse(@options[:REDISTOGO_URL])
@@ -58,10 +74,27 @@ class Shortener
58
74
  end
59
75
  end
60
76
 
77
+ # return a boolean of the S3_ENABLED option
61
78
  def s3_enabled
62
79
  @options[:S3_ENABLED].to_s == 'true'
63
80
  end
64
81
 
82
+ # are the necessary options present for S3 to work?
83
+ def s3_configured
84
+ ret = true
85
+ [:S3_KEY_PREFIX, :S3_ACCESS_KEY_ID, :S3_SECRET_ACCESS_KEY,
86
+ :S3_DEFAULT_ACL, :S3_BUCKET ].each do |k|
87
+ ret = !@options[k].nil? unless ret == false
88
+ end
89
+ ret
90
+ end
91
+
92
+ # is S3 enabled and configured?
93
+ def s3_available
94
+ s3_enabled && s3_configured
95
+ end
96
+
97
+ # build an S3 policy.
65
98
  def s3_policy
66
99
  expiration_date = (Time.now + 36000).utc.strftime('%Y-%m-%dT%H:%M:%S.000Z') # 10.hours.from_now
67
100
  max_filesize = 2147483648 # 2.gigabyte
@@ -79,6 +112,7 @@ class Shortener
79
112
  ).gsub(/\n|\r/, '')
80
113
  end
81
114
 
115
+ # Sign an S3 policy
82
116
  def s3_signature(policy)
83
117
  signature = Base64.encode64(OpenSSL::HMAC.digest(
84
118
  OpenSSL::Digest::Digest.new('sha1'), s3_secret_access_key, policy)).gsub("\n","")
@@ -86,6 +120,7 @@ class Shortener
86
120
 
87
121
  private
88
122
 
123
+ # Parse the YAML dotfile if one exists
89
124
  def check_dotfile
90
125
  dotfile = @options[:DOTFILE_PATH] || File.join(ENV['HOME'], ".shortener")
91
126
  if File.exists?(dotfile)
@@ -93,6 +128,7 @@ class Shortener
93
128
  end
94
129
  end
95
130
 
131
+ # Check our environment for any overrides.
96
132
  def check_env
97
133
  OPTIONS.each do |opt|
98
134
  @options[opt] = ENV[opt.to_s] unless ENV[opt.to_s].nil?
@@ -1,5 +1,6 @@
1
1
  #link_display.row
2
- %div.offset5.span4
2
+ - classs = @data['s3'].nil? ? 'offset5' : ''
3
+ %div.span4{class: classs}
3
4
  %h4= @url
4
5
  %div.span2
5
6
  = clippy(@url)
@@ -16,7 +16,7 @@
16
16
  %a{href: '/index'} Index
17
17
  %li
18
18
  %a{href: '/add'} Add
19
- - if $conf.s3_enabled
19
+ - if $conf.s3_available
20
20
  %li
21
21
  %a{href: '/upload'} Upload
22
22
 
@@ -63,7 +63,7 @@
63
63
  // async: false,
64
64
  // });
65
65
  $.post('/upload.json', values, function(data){
66
- $('#shortener-display').text(data.url)
66
+ $('#shortener-display').html(data.html)
67
67
  .removeClass('hide');
68
68
  });
69
69
 
@@ -102,7 +102,7 @@
102
102
  %li.radio
103
103
  %input{name: 'shortener[type]', type: 'radio', value: 'download'} Download
104
104
  .row
105
- %label.offset8.hide{id: 'shortener-display'}
105
+ #shortener-display.offset8.hide
106
106
  .field
107
107
  %label{for: 'shorterner[name]'} Name
108
108
  %input{type: 'text', name: 'shortener[name]'}
@@ -22,7 +22,7 @@ class Shortener
22
22
  $redis = Redis::Namespace.new(:shortener, redis: _redis)
23
23
  end
24
24
 
25
- set(:s3_enabled) {|v| condition {$conf.s3_enabled == v}}
25
+ set(:s3_available) {|v| condition {$conf.s3_available == v}}
26
26
 
27
27
  helpers do
28
28
 
@@ -75,11 +75,11 @@ class Shortener
75
75
  end
76
76
 
77
77
  unless params['max-clicks'] || params['expire'] || params['desired-short']
78
- id = check_cache(url)
78
+ data = check_cache(url)
79
79
  end
80
- id ||= shorten(url, params)
80
+ data ||= shorten(url, params)
81
81
 
82
- return "#{base_url}/#{id}", id
82
+ data
83
83
  end
84
84
 
85
85
  def set_upload_short(params)
@@ -90,13 +90,14 @@ class Shortener
90
90
  hash_key = "data:#{sha}:#{key}"
91
91
  url = "https://s3.amazonaws.com/#{$conf.s3_bucket}/#{$conf.s3_key_prefix}/#{fname}"
92
92
  ext = File.extname(fname)[1..-1]
93
- extras = ['url', url, 's3', true, 'shortened', key, 'extension', ext, 'set-count', 1]
94
- data = params.keys.map {|k| [k, params[k]] }.flatten.concat(extras)
93
+ data = {'url' => url, 's3' => true, 'shortened' => key,
94
+ 'extension' => ext, 'set-count' => 1}
95
+ data = params.merge(data)
95
96
 
96
97
  $redis.set(key, sha)
97
- $redis.hmset(hash_key, *data)
98
+ $redis.hmset(hash_key, *arrayify_hash(data))
98
99
 
99
- "#{base_url}/#{key}"
100
+ data
100
101
  end
101
102
 
102
103
  def shorten(url, options = {})
@@ -116,7 +117,7 @@ class Shortener
116
117
  if (!prev_set['max-clicks'] && !prev_set['expire'] &&
117
118
  (prev_set['url'] == url.to_s))
118
119
  $redis.hincrby(check_key, 'set-count', 1)
119
- return options['desired-short']
120
+ return prev_set
120
121
  end
121
122
  end
122
123
 
@@ -136,19 +137,19 @@ class Shortener
136
137
  sha = Digest::SHA1.hexdigest(url.to_s)
137
138
  $redis.set(key, sha)
138
139
 
139
- hsh_data = ['shortened', key, 'url', url, 'set-count', 1]
140
- hsh_data.concat(['max-clicks', options['max-clicks'].to_i]) if options['max-clicks']
140
+ hsh_data = {'shortened' => key, 'url' => url, 'set-count' => 1}
141
+ hsh_data['max-clicks'] = options['max-clicks'].to_i if options['max-clicks']
141
142
 
142
143
  if options['expire'] # set expire time if specified
143
144
  ttl = options['expire'].to_i
144
145
  ttl_key = "expire:#{sha}:#{key}"
145
146
  $redis.set(ttl_key, "#{sha}:#{key}")
146
147
  $redis.expire(ttl_key, ttl)
147
- hsh_data.concat(['expire', ttl_key])
148
+ hsh_data[:expire] = ttl_key
148
149
  end
149
- $redis.hmset("data:#{sha}:#{key}", *hsh_data)
150
+ $redis.hmset("data:#{sha}:#{key}", *arrayify_hash(hsh_data))
150
151
 
151
- key
152
+ hsh_data
152
153
  end
153
154
 
154
155
  def check_cache(url)
@@ -158,7 +159,7 @@ class Shortener
158
159
  short = $redis.hgetall(key)
159
160
  unless short == {} || short['expire'] || short['max-clicks']
160
161
  $redis.hincrby(key, 'set-count', 1)
161
- return short['shortened']
162
+ return short
162
163
  end
163
164
  end
164
165
  nil
@@ -194,6 +195,10 @@ class Shortener
194
195
  ret
195
196
  end
196
197
 
198
+ def arrayify_hash(hsh)
199
+ hsh.keys.map {|k| [k, hsh[k]] }.flatten
200
+ end
201
+
197
202
  end
198
203
 
199
204
  before do
@@ -224,17 +229,16 @@ class Shortener
224
229
  end
225
230
 
226
231
  get '/delete/:id.:format' do |id, format|
227
- puts "#{id}, #{format}"
228
232
  delete_short(id)
229
233
  if format == 'json'
230
234
  content_type :json
231
- {success: true, short: id}.to_json
235
+ {success: true, shortened: id}.to_json
232
236
  else
233
237
  redirect :index
234
238
  end
235
239
  end
236
240
 
237
- get '/upload', s3_enabled: true do
241
+ get '/upload', s3_available: true do
238
242
  policy = $conf.s3_policy
239
243
  signature = $conf.s3_signature(policy)
240
244
 
@@ -294,11 +298,12 @@ class Shortener
294
298
  end
295
299
 
296
300
  post '/upload.?:format?' do |format|
297
- short = set_upload_short(params['shortener'])
298
- puts "set #{short} to #{params['shortener']['file_name']}"
301
+ @data = set_upload_short(params['shortener'])
302
+ puts "set #{@data['shortened']} to #{params['shortener']['file_name']}"
303
+ @url = "#{base_url}/#{@data['shortened']}"
299
304
  if format == 'json'
300
305
  content_type :json
301
- {url: short}.to_json
306
+ @data.merge({html: haml(:display, layout: false)}).to_json
302
307
  else
303
308
  redirect :index
304
309
  end
@@ -312,11 +317,12 @@ class Shortener
312
317
  # essentally, params = params
313
318
  end
314
319
 
315
- @url, id = set_or_fetch_url(params["shortener"])
320
+ @data = set_or_fetch_url(params["shortener"])
321
+ @url = "#{base_url}/#{@data['shortened']}"
316
322
  puts "set #{@url} to #{params['shortener']['url']}"
317
323
  if format == 'json'
318
324
  content_type :json
319
- {success: true, short: id, url: @url, html: haml(:display, :layout => false)}.to_json
325
+ @data.merge({success: true, html: haml(:display, :layout => false)}).to_json
320
326
  else
321
327
  haml :display
322
328
  end
@@ -0,0 +1,147 @@
1
+
2
+ class Shortener
3
+ class Short
4
+
5
+ SHORT_KEYS = [:url, :shortened, :type, :ext, :s3, :'click-count', :'max-count',
6
+ :'set-count', :'expire-time', :sha]
7
+
8
+ attr_reader :data
9
+
10
+ module ClassMethods
11
+
12
+ # set a shortened url
13
+ def shorten(url, conf = nil)
14
+ opts = {'shortener' => {'url' => url}.to_json}
15
+ response = request(:post, :add, conf, opts)
16
+ if response.is_a?(Net::HTTPOK)
17
+ return Short.new(response.body, conf)
18
+ else
19
+ raise "OH SHIT! #{response}"
20
+ end
21
+ end
22
+
23
+ # get data for a short, including full url.
24
+ def fetch(short, conf = nil)
25
+ response = request(:get, :fetch, conf, short)
26
+ Short.new(response.body, conf)
27
+ end
28
+
29
+ # post a file to the configured s3 bucket and set a short.
30
+ def upload(file)
31
+ end
32
+
33
+ # fetch data on multiple shorts
34
+ def index(start = 0, stop = nil, conf = nil)
35
+ response = request(:get, :index, conf)
36
+ data = JSON.parse(response.body)
37
+ shorts = data.map {|sh| Short.new(sh, conf)}
38
+ shorts
39
+ end
40
+
41
+ # delete a short
42
+ def delete(short, conf = nil)
43
+ response = request(:get, :delete, conf, short)
44
+ Short.new(response.body, conf)
45
+ end
46
+
47
+ # build a request based on configurations
48
+ def request(type, end_point, conf = nil, args = nil)
49
+ config = conf || Shortener::Configuration.current
50
+ case type
51
+ when :post
52
+ Net::HTTP.post_form(config.uri_for(end_point), args)
53
+ when :get
54
+ Net::HTTP.get_response(config.uri_for(end_point, args))
55
+ end
56
+ end
57
+
58
+ end # => ClassMethods
59
+
60
+ class << self; include ClassMethods; end
61
+
62
+ # Set up this short. Will:
63
+ # * store the configuration if necessary
64
+ # * parse JSON if short is JSON
65
+ # * symbolize the shorts keys
66
+ # * turn string numbers in to actual numbers.
67
+ def initialize(short, conf = nil)
68
+ @configuration = conf unless conf.nil?
69
+ short = parse_return(short) unless short.is_a?(Hash)
70
+ @data = symbolize_keys(short)
71
+ normalize_data
72
+ end
73
+
74
+ # the configuration that this short will use.
75
+ def configuration
76
+ @configuation.nil? ? Shortener::Configuration.current : @configuration
77
+ end
78
+
79
+ # an alternative way to fetch stuff from @data
80
+ def [](key)
81
+ @data[key.to_sym]
82
+ end
83
+
84
+ # return a URI of the URL
85
+ def uri
86
+ URI.parse(url)
87
+ end
88
+
89
+ # shortened combined with config#shortener_url
90
+ def short_url
91
+ "#{configuration.shortener_url}/#{shortened}"
92
+ end
93
+
94
+ # a URI of short url.
95
+ def short_uri
96
+ URI.parse(short_url)
97
+ end
98
+
99
+ # allow the updating of a field. considering an arity like:
100
+ # update(hsh_of_fields_with_values)
101
+ # and it will create a POST request to send server side.
102
+ def update
103
+ #TODO
104
+ end
105
+
106
+ SHORT_KEYS.each do |key|
107
+ name = key.to_s.include?('-') ? key.to_s.gsub('-', '_').to_sym : key
108
+ define_method name do
109
+ @data[key]
110
+ end
111
+ end
112
+
113
+ def pretty_print
114
+
115
+ end
116
+
117
+ private
118
+
119
+ # used to turn the hash keys of the data hash in to symbols.
120
+ def symbolize_keys(hash)
121
+ ret = Hash.new
122
+ hash.each do |k,v|
123
+ ret[k.to_sym] = v
124
+ end
125
+ ret
126
+ end
127
+
128
+ # turn string numbers in to actual numbers.
129
+ def normalize_data
130
+ [:'click-count', :'max-count', :'set-count'].each do |k|
131
+ @data[k] = @data[k].to_i
132
+ end
133
+ @data[:'click-count'] ||= 0
134
+ end
135
+
136
+ # parse JSON safely.
137
+ def parse_return(json)
138
+ begin
139
+ return JSON.parse(json)
140
+ rescue Exception => boom
141
+ raise "OH SHIT! #{boom}\n\n #{json}"
142
+ end
143
+ end
144
+
145
+ end
146
+ end
147
+ require_relative '../shortener'
@@ -1,6 +1,6 @@
1
1
 
2
2
  class Shortener
3
3
 
4
- VERSION = '0.3.3'
4
+ VERSION = '0.4.0'
5
5
 
6
6
  end
data/lib/shortener.rb CHANGED
@@ -1,9 +1,12 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require 'net/http'
1
4
  dir = File.expand_path(File.dirname(__FILE__))
2
5
 
3
6
  require File.join(dir, 'shortener', 'version')
4
- require File.join(dir, 'shortener', 'client')
5
7
  require File.join(dir, 'shortener', 'configuration')
8
+ require File.join(dir, 'shortener', 'short')
6
9
 
7
10
  class Shortener
8
-
11
+ class << self; include Shortener::Short::ClassMethods; end
9
12
  end
data/tasks/heroku.rake CHANGED
@@ -22,7 +22,7 @@ namespace :heroku do
22
22
  require_relative '../lib/shortener'
23
23
  $name = ENV['APPNAME'] || "shner-#{`whoami`.chomp}"
24
24
  cmd = Dir.pwd =~ /heroku$/ ? "" : "cd heroku && "
25
- cmd += "heroku create #{name}"
25
+ cmd += "heroku create #{$name}"
26
26
  cmd += " && heroku addons:add redistogo:nano"
27
27
  cmd += " && heroku config:add #{Shortener::Configuration.new.to_params}"
28
28
  cmd += " && heroku addons:add custom_domains:basic"
@@ -0,0 +1,68 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../lib/shortener/short'
3
+
4
+ class TestShortenerShort < MiniTest::Unit::TestCase
5
+
6
+ def setup
7
+ @short = Shortener::Short.new('url' => 'http://google.com', 'shortened' => '12345',
8
+ 'set-count' => '12')
9
+ end
10
+
11
+ def test_instance_methods
12
+ assert @short.shortened == '12345'
13
+ assert @short.url == 'http://google.com'
14
+ assert @short.set_count == 12
15
+ end
16
+
17
+ def test_brackets
18
+ assert @short['shortened'] == '12345'
19
+ assert @short[:shortened] == '12345'
20
+ assert @short['set-count'] == 12
21
+ end
22
+
23
+ def test_defaults_ensure_keys
24
+ short = Shortener.shorten('www.google.com')
25
+ assert !short.shortened.nil?
26
+ assert !short.url.nil?
27
+ assert !short.set_count.nil?
28
+ assert !short.click_count.nil?
29
+ end
30
+
31
+ def test_short_url
32
+ assert @short.short_url == "#{@short.configuration.shortener_url}/#{@short.shortened}"
33
+ end
34
+
35
+ def test_uri
36
+ assert @short.uri == URI.parse(@short.url)
37
+ end
38
+
39
+ def test_add
40
+ short = Shortener::Short.shorten('www.google.com')
41
+ assert short.is_a?(Shortener::Short)
42
+ assert short.shortened.nil? == false
43
+ assert short.url == 'http://www.google.com'
44
+ assert short['success']
45
+ end
46
+
47
+ def test_index
48
+ ind = Shortener::Short.index
49
+ assert ind.is_a?(Array)
50
+ assert ind.length >= 1
51
+ assert ind.first.is_a?(Shortener::Short)
52
+ end
53
+
54
+ def test_fetch
55
+ short = Shortener.shorten('www.google.com')
56
+ short2 = Shortener::Short.fetch(short.shortened)
57
+ assert short.shortened == short2.shortened
58
+ short = Shortener.fetch('nope')
59
+ assert short[:success] == false
60
+ end
61
+
62
+ def test_delete
63
+ add = Shortener.shorten('www.google.com')
64
+ del = Shortener.delete(add['shortened'])
65
+ assert del['success']
66
+ end
67
+
68
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: short
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-30 00:00:00.000000000 Z
12
+ date: 2012-01-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
16
- requirement: &70233398587560 !ruby/object:Gem::Requirement
16
+ requirement: &70301329586160 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70233398587560
24
+ version_requirements: *70301329586160
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: redis-namespace
27
- requirement: &70233398586580 !ruby/object:Gem::Requirement
27
+ requirement: &70301329585240 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70233398586580
35
+ version_requirements: *70301329585240
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: haml
38
- requirement: &70233398584580 !ruby/object:Gem::Requirement
38
+ requirement: &70301329583820 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70233398584580
46
+ version_requirements: *70301329583820
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: turn
49
- requirement: &70233398580400 !ruby/object:Gem::Requirement
49
+ requirement: &70301329582280 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70233398580400
57
+ version_requirements: *70301329582280
58
58
  description: A (hopefully) easy and handy deployable APIable way to shorten links.
59
59
  email:
60
60
  - jake@jakewilkins.com
@@ -64,12 +64,12 @@ extensions: []
64
64
  extra_rdoc_files: []
65
65
  files:
66
66
  - .gitignore
67
+ - Guardfile
67
68
  - README.md
68
69
  - Rakefile
69
70
  - bin/short
70
71
  - config.ru
71
72
  - lib/shortener.rb
72
- - lib/shortener/client.rb
73
73
  - lib/shortener/configuration.rb
74
74
  - lib/shortener/server.rb
75
75
  - lib/shortener/server/Gemfile
@@ -100,12 +100,13 @@ files:
100
100
  - lib/shortener/server/views/s3/layout.haml
101
101
  - lib/shortener/server/views/s3/video.haml
102
102
  - lib/shortener/server/views/upload.haml
103
+ - lib/shortener/short.rb
103
104
  - lib/shortener/version.rb
104
105
  - short.gemspec
105
106
  - tasks/heroku.rake
106
- - test/test_client.rb
107
107
  - test/test_configuration.rb
108
108
  - test/test_server.rb
109
+ - test/test_short.rb
109
110
  homepage: ''
110
111
  licenses: []
111
112
  post_install_message:
@@ -131,6 +132,6 @@ signing_key:
131
132
  specification_version: 3
132
133
  summary: A Link Shortener
133
134
  test_files:
134
- - test/test_client.rb
135
135
  - test/test_configuration.rb
136
136
  - test/test_server.rb
137
+ - test/test_short.rb
@@ -1,69 +0,0 @@
1
- dir = File.expand_path(File.dirname(__FILE__))
2
- require 'net/http'
3
- require 'json'
4
- require File.join(dir, 'configuration')
5
-
6
- class Shortener
7
- class Client
8
-
9
- attr_accessor :configuration
10
-
11
- def initialize(options = Hash.new)
12
- @configuration = Configuration.new(options)
13
- end
14
-
15
- # set a shortened url
16
- def shorten(url)
17
- opts = {'shortener' => {'url' => url}.to_json}
18
- response= request(:post, :add, opts)
19
- if response.is_a?(Net::HTTPOK)
20
- return parse_return(response.body)
21
- else
22
- raise "OH SHIT! #{response}"
23
- end
24
- end
25
-
26
- # get data for a short, including full url.
27
- def fetch(short)
28
- response = request(:get, :fetch, short)
29
- parse_return(response)
30
- end
31
-
32
- # post a file to the configured s3 bucket and set a short.
33
- def upload(file)
34
- end
35
-
36
- # fetch data on multiple shorts
37
- def index(start = 0, stop = nil)
38
- response = request(:get, :index)
39
- parse_return(response)
40
- end
41
-
42
- # delete a short
43
- def delete(short)
44
- response = request(:get, :delete, short)
45
- parse_return(response)
46
- end
47
-
48
- private
49
-
50
- # build a request based on configurations
51
- def request(type, end_point, args = nil)
52
- case type
53
- when :post
54
- Net::HTTP.post_form(@configuration.uri_for(end_point), args)
55
- when :get
56
- Net::HTTP.get(@configuration.uri_for(end_point, args))
57
- end
58
- end
59
-
60
- def parse_return(json)
61
- begin
62
- return JSON.parse(json)
63
- rescue Exception => boom
64
- raise "OH SHIT! #{boom}\n\n #{json}"
65
- end
66
- end
67
-
68
- end # => Client
69
- end # => Shortener
data/test/test_client.rb DELETED
@@ -1,26 +0,0 @@
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