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.
@@ -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