cached_bitly 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,2 @@
1
+ services:
2
+ - redis-server
@@ -0,0 +1,7 @@
1
+ # 0.0.1
2
+
3
+ - Initial release.
4
+
5
+ # 0.0.2
6
+
7
+ - Allow you to set the returned short url's scheme to either https or http [#1](https://github.com/dewski/cached_bitly/issues/1)
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cached_bitly.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
7
+
8
+ group :test do
9
+ gem 'shoulda-context', '1.0.0', :require => false
10
+ gem 'mocha', '0.11.4', :require => false
11
+ end
@@ -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.
@@ -0,0 +1,77 @@
1
+ CachedBitly [![Build Status](https://secure.travis-ci.org/dewski/cached_bitly.png)](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
+
@@ -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
@@ -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,3 @@
1
+ module CachedBitly
2
+ VERSION = '0.0.2'
3
+ 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
@@ -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