cached_bitly 0.0.2
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 +17 -0
- data/.travis.yml +2 -0
- data/CHANGELOG.md +7 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +77 -0
- data/Rakefile +13 -0
- data/cached_bitly.gemspec +23 -0
- data/lib/cached_bitly.rb +145 -0
- data/lib/cached_bitly/version.rb +3 -0
- data/test/cached_bitly_test.rb +81 -0
- data/test/test_helper.rb +19 -0
- metadata +107 -0
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 dewski
|
2
|
+
|
3
|
+
MIT License
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
CachedBitly [](http://travis-ci.org/dewski/cached_bitly)
|
2
|
+
===========
|
3
|
+
|
4
|
+
An easy bit.ly toolkit with Redis as a caching layer.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'cached_bitly'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
```
|
17
|
+
$ bundle
|
18
|
+
```
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
```
|
23
|
+
$ gem install cached_bitly
|
24
|
+
```
|
25
|
+
|
26
|
+
## Usage
|
27
|
+
|
28
|
+
To communicate with bit.ly you'll need your login and API key which you can get from the [Advanced tab](https://bitly.com/a/settings/advanced) within your account settings.
|
29
|
+
|
30
|
+
If you set the bit.ly environment variables everything will just work:
|
31
|
+
|
32
|
+
```
|
33
|
+
BITLY_LOGIN=dewski
|
34
|
+
BITLY_API_KEY=Z_bf4b4fg16991dd72d276e7z9d94d1bc00b
|
35
|
+
```
|
36
|
+
|
37
|
+
You may also set the bit.ly client directly with your own configuration:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
CachedBitly.bitly_client = Bitly.new('username', 'password')
|
41
|
+
```
|
42
|
+
|
43
|
+
There are 2 main methods to interface with bit.ly, the first being a way to retreive already generated URLs:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# First lookup will generate the URL from bit.ly
|
47
|
+
CachedBitly.fetch('https://github.com') # => http://bit.ly/WuNWHc
|
48
|
+
|
49
|
+
# Hits redis with no additional HTTP request
|
50
|
+
CachedBitly.fetch('https://github.com') # => http://bit.ly/WuNWHc
|
51
|
+
```
|
52
|
+
|
53
|
+
If you'd like to just pass in a large block of HTML you can cache multiple links at once:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
content = "<a href='https://github.com'>GitHub</a> and <a href='https://github.com/dewski'>@dewski</a> join forces"
|
57
|
+
CachedBitly.clean(content) # => "<a href='http://bit.ly/WuNWHc'>GitHub</a> and <a href='http://bit.ly/10p297A'>@dewski</a> join forces"
|
58
|
+
```
|
59
|
+
|
60
|
+
## Configuring CachedBitly
|
61
|
+
|
62
|
+
If you don't want to shorten all links within your HTML you can bypass bit.ly by setting your allowed hostnames:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
CachedBitly.allowed_hostnames = ['github.com']
|
66
|
+
content = "<a href='https://github.com'>GitHub</a> and <a href='http://garrettbjerkhoel.com'>Garrett</a> join forces"
|
67
|
+
CachedBitly.clean(content) # => "<a href='https://github.com'>GitHub</a> and <a href='http://bit.ly/10p297A'>@dewski</a> join forces"
|
68
|
+
```
|
69
|
+
|
70
|
+
## Contributing
|
71
|
+
|
72
|
+
1. Fork it
|
73
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
74
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
75
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
76
|
+
5. Create new Pull Request
|
77
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
desc 'Default: run tests'
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
desc 'Run CachedBitly tests.'
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << 'lib'
|
10
|
+
t.libs << 'test'
|
11
|
+
t.test_files = FileList['test/*_test.rb']
|
12
|
+
t.verbose = true
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cached_bitly/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'cached_bitly'
|
8
|
+
gem.version = CachedBitly::VERSION
|
9
|
+
gem.authors = ['Garrett Bjerkhoel']
|
10
|
+
gem.email = ['me@garrettbjerkhoel.com']
|
11
|
+
gem.description = %q{An easy Bitly toolkit with Redis being the caching layer.}
|
12
|
+
gem.summary = %q{An easy Bitly toolkit with Redis being the caching layer.}
|
13
|
+
gem.homepage = 'https://github.com/dewski/cached_bitly'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
|
20
|
+
gem.add_dependency 'nokogiri'
|
21
|
+
gem.add_dependency 'redis'
|
22
|
+
gem.add_dependency 'bitly', '~> 0.8'
|
23
|
+
end
|
data/lib/cached_bitly.rb
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
require 'cached_bitly/version'
|
2
|
+
require 'redis'
|
3
|
+
require 'bitly'
|
4
|
+
require 'nokogiri'
|
5
|
+
|
6
|
+
module CachedBitly
|
7
|
+
extend self
|
8
|
+
|
9
|
+
def redis
|
10
|
+
@redis ||= Redis.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def redis=(redis)
|
14
|
+
@redis = redis
|
15
|
+
end
|
16
|
+
|
17
|
+
def redis_namespace
|
18
|
+
@redis_namespace ||= 'bitly'
|
19
|
+
end
|
20
|
+
|
21
|
+
def redis_namespace=(namespace)
|
22
|
+
@redis_namespace = namespace
|
23
|
+
end
|
24
|
+
|
25
|
+
def allowed_hostnames
|
26
|
+
@allowed_hostnames ||= []
|
27
|
+
end
|
28
|
+
|
29
|
+
def allowed_hostnames=(hostnames=[])
|
30
|
+
@allowed_hostnames = hostnames
|
31
|
+
end
|
32
|
+
|
33
|
+
def bitly_client
|
34
|
+
@bitly_client ||= begin
|
35
|
+
Bitly.use_api_version_3
|
36
|
+
Bitly.new(ENV['BITLY_LOGIN'], ENV['BITLY_API_KEY'])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def bitly_client=(client)
|
41
|
+
@bitly_client = client
|
42
|
+
end
|
43
|
+
|
44
|
+
def stats_enabled?
|
45
|
+
!!@stats_enabled
|
46
|
+
end
|
47
|
+
|
48
|
+
def stats_enabled
|
49
|
+
@stats_enabled ||= false
|
50
|
+
end
|
51
|
+
|
52
|
+
def stats_enabled=(enabled)
|
53
|
+
@stats_enabled = enabled
|
54
|
+
end
|
55
|
+
|
56
|
+
def url_scheme
|
57
|
+
@url_scheme ||= 'http'
|
58
|
+
end
|
59
|
+
|
60
|
+
def url_scheme=(scheme)
|
61
|
+
raise ArgumentError unless ['http', 'https'].include?(scheme)
|
62
|
+
@url_scheme = scheme
|
63
|
+
end
|
64
|
+
|
65
|
+
def clean(html)
|
66
|
+
clean_doc(html).css('body').inner_html
|
67
|
+
end
|
68
|
+
|
69
|
+
def clean_doc(doc)
|
70
|
+
doc = doc.is_a?(String) ? Nokogiri::HTML(doc) : doc
|
71
|
+
doc.css('a[href^=http]').each do |link|
|
72
|
+
url = link.attributes['href'].value
|
73
|
+
if !allowed_hostnames.empty? && url.match(Regexp.union(*allowed_hostnames))
|
74
|
+
next
|
75
|
+
end
|
76
|
+
link.attributes['href'].value = fetch(url)
|
77
|
+
end
|
78
|
+
doc
|
79
|
+
end
|
80
|
+
|
81
|
+
# Handles retreiving cached short urls and generating new
|
82
|
+
# ones if we don't have the short url for a particular url.
|
83
|
+
#
|
84
|
+
# Returns short url, default if save goes wrong.
|
85
|
+
def fetch(url, default=url)
|
86
|
+
short_url = shortened(url)
|
87
|
+
if short_url
|
88
|
+
hit!
|
89
|
+
short_url.gsub(/^http\:/, url_scheme + ':')
|
90
|
+
else
|
91
|
+
miss!
|
92
|
+
shorten(url) || default
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Handles generating the short url and storing it.
|
97
|
+
#
|
98
|
+
# Returns short url if stored, false if not.
|
99
|
+
def shorten(url)
|
100
|
+
url = bitly_client.shorten(url)
|
101
|
+
if save(url.long_url, url.short_url)
|
102
|
+
url.short_url
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Look to see if the url has already been shortened.
|
109
|
+
# If the url has been shortened, return the url.
|
110
|
+
#
|
111
|
+
# Returns short url if it has been shortened, nil if not
|
112
|
+
def shortened(url)
|
113
|
+
redis.hget("#{redis_namespace}:url", digest(url))
|
114
|
+
end
|
115
|
+
|
116
|
+
# Save the url along with it's associated short url
|
117
|
+
# for easy retrieval.
|
118
|
+
#
|
119
|
+
# Returns true if saved, false if not.
|
120
|
+
def save(long_url, short_url)
|
121
|
+
!!redis.hset("#{redis_namespace}:url", digest(long_url), short_url)
|
122
|
+
end
|
123
|
+
|
124
|
+
def totals
|
125
|
+
{ :hit => redis.get("#{redis_namespace}:url:hit").to_i,
|
126
|
+
:miss => redis.get("#{redis_namespace}:url:miss").to_i,
|
127
|
+
:total => redis.hlen("#{redis_namespace}:url") }
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def hit!
|
133
|
+
return unless stats_enabled?
|
134
|
+
redis.incr "#{redis_namespace}:url:hit"
|
135
|
+
end
|
136
|
+
|
137
|
+
def miss!
|
138
|
+
return unless stats_enabled?
|
139
|
+
redis.incr "#{redis_namespace}:url:miss"
|
140
|
+
end
|
141
|
+
|
142
|
+
def digest(object)
|
143
|
+
Digest::MD5.hexdigest object.to_s
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class TestCachedBitly < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
CachedBitly.redis.flushdb
|
6
|
+
CachedBitly.allowed_hostnames = []
|
7
|
+
CachedBitly.stats_enabled = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def stub_remote_bitly
|
11
|
+
response = Class.new
|
12
|
+
response.stubs(:short_url).returns('http://bit.ly/233')
|
13
|
+
response.stubs(:long_url).returns('https://garrettbjerkhoel.com')
|
14
|
+
CachedBitly.bitly_client.stubs(:shorten).returns(response)
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_clean_with_whitelisted_urls
|
18
|
+
stub_remote_bitly
|
19
|
+
CachedBitly.allowed_hostnames = ['garrettbjerkhoel.com']
|
20
|
+
content = "<p>Welcome <a href=\"http://garrettbjerkhoel.com\">@dewski</a>!</p>"
|
21
|
+
assert_equal content, CachedBitly.clean(content)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_clean_without_whitelisted_urls
|
25
|
+
stub_remote_bitly
|
26
|
+
content = "<p>Welcome <a href=\"http://garrettbjerkhoel.com\">@dewski</a>!</p>"
|
27
|
+
rendered_content = "<p>Welcome <a href=\"http://bit.ly/233\">@dewski</a>!</p>"
|
28
|
+
assert_equal rendered_content, CachedBitly.clean(content)
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_remote_shorten_url
|
32
|
+
stub_remote_bitly
|
33
|
+
assert_equal \
|
34
|
+
'http://bit.ly/233',
|
35
|
+
CachedBitly.shorten('https://garrettbjerkhoel.com'),
|
36
|
+
'should return the shortened url'
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_saving_long_and_short_url
|
40
|
+
assert CachedBitly.save('https://github.com/dewski', 'http://bit.ly/123df'), 'should save'
|
41
|
+
assert_equal 'http://bit.ly/123df', CachedBitly.fetch('https://github.com/dewski'), 'should have stored url'
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_failing_url_fetch_with_fallback
|
45
|
+
CachedBitly.stubs(:shortened).returns(false)
|
46
|
+
CachedBitly.stubs(:shorten).returns(false)
|
47
|
+
|
48
|
+
assert_equal \
|
49
|
+
'http://garrett.com',
|
50
|
+
CachedBitly.fetch('https://garrettbjerkhoel.com', 'http://garrett.com'),
|
51
|
+
'should return the fallback url'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Stats
|
55
|
+
def test_stats_enabled
|
56
|
+
stub_remote_bitly
|
57
|
+
CachedBitly.stats_enabled = true
|
58
|
+
CachedBitly.redis.expects(:incr).once
|
59
|
+
CachedBitly.fetch('https://garrettbjerkhoel.com')
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_stats_disabled
|
63
|
+
stub_remote_bitly
|
64
|
+
CachedBitly.stats_enabled = false
|
65
|
+
CachedBitly.redis.expects(:incr).never
|
66
|
+
CachedBitly.fetch('https://garrettbjerkhoel.com')
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_setting_url_scheme
|
70
|
+
stub_remote_bitly
|
71
|
+
CachedBitly.url_scheme = 'https'
|
72
|
+
assert CachedBitly.save('https://github.com/github', 'http://bit.ly/gzdf13'), 'should save'
|
73
|
+
assert_equal 'https://bit.ly/gzdf13', CachedBitly.fetch('https://github.com/github'), 'should return https version'
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_setting_invalid_url_scheme
|
77
|
+
assert_raises(ArgumentError) {
|
78
|
+
CachedBitly.url_scheme = 'ftp'
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup(:default, :test)
|
4
|
+
Bundler.require(:default, :test)
|
5
|
+
|
6
|
+
dir = File.dirname(File.expand_path(__FILE__))
|
7
|
+
$LOAD_PATH.unshift dir + '/../lib'
|
8
|
+
$TESTING = true
|
9
|
+
|
10
|
+
require 'test/unit'
|
11
|
+
require 'shoulda-context'
|
12
|
+
require 'mocha'
|
13
|
+
require 'cached_bitly'
|
14
|
+
|
15
|
+
if ENV.key?('GH_REDIS_URL')
|
16
|
+
uri = URI.parse(ENV['GH_REDIS_URL'])
|
17
|
+
redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password, :db => 1)
|
18
|
+
CachedBitly.redis = redis
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cached_bitly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Garrett Bjerkhoel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-25 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: nokogiri
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: redis
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bitly
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.8'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.8'
|
62
|
+
description: An easy Bitly toolkit with Redis being the caching layer.
|
63
|
+
email:
|
64
|
+
- me@garrettbjerkhoel.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- .travis.yml
|
71
|
+
- CHANGELOG.md
|
72
|
+
- Gemfile
|
73
|
+
- LICENSE.txt
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- cached_bitly.gemspec
|
77
|
+
- lib/cached_bitly.rb
|
78
|
+
- lib/cached_bitly/version.rb
|
79
|
+
- test/cached_bitly_test.rb
|
80
|
+
- test/test_helper.rb
|
81
|
+
homepage: https://github.com/dewski/cached_bitly
|
82
|
+
licenses: []
|
83
|
+
post_install_message:
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ! '>='
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
none: false
|
95
|
+
requirements:
|
96
|
+
- - ! '>='
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 1.8.23
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: An easy Bitly toolkit with Redis being the caching layer.
|
105
|
+
test_files:
|
106
|
+
- test/cached_bitly_test.rb
|
107
|
+
- test/test_helper.rb
|