ivey-longurl 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ doc
3
+ pkg
4
+ rdoc
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009, Fabien Jakimowicz <fabien@jakimowicz.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/Manifest ADDED
@@ -0,0 +1,13 @@
1
+ bin/longurl
2
+ ChangeLog
3
+ lib/longurl/constants.rb
4
+ lib/longurl/direct.rb
5
+ lib/longurl/exceptions.rb
6
+ lib/longurl/service.rb
7
+ lib/longurl.rb
8
+ LICENSE
9
+ longurl.gemspec
10
+ Manifest
11
+ Rakefile
12
+ README
13
+ TODO
data/README.rdoc ADDED
@@ -0,0 +1,73 @@
1
+ = LongURL
2
+
3
+ == DESCRIPTION
4
+ LongURL expands short urls (tinyurl, is.gd, ...) to original ones, using on LongURL.org, internal resolution or direct resolution
5
+ First, expand will try to expand url using longurl.org service.
6
+ Then, it will try to direct follow redirections on the given url and returns final one.
7
+
8
+ == SYNOPSIS
9
+ === Options
10
+ * <tt>:cache</tt> : cache object to use, must implement [] and []= functions.
11
+
12
+ === Types
13
+ <tt>url</tt> is expected to be a String and returns a String with the url.
14
+
15
+ === Examples
16
+ # simple expands
17
+ LongURL.expand("http://tinyurl.com/1c2") # => "http://www.google.com"
18
+ LongURL.expand("http://tinyurl.com/blnhsg") # => "http://www.google.com/search?q=number+of+horns+on+a+unicorn&ie=UTF-8"
19
+ LongURL.expand("http://is.gd/iUKg") # => "http://fabien.jakimowicz.com"
20
+
21
+ # not expandable urls, without any http call
22
+ LongURL.expand("http://www.linuxfr.org") # => "http://www.linuxfr.org"
23
+
24
+ # Use MemCache
25
+ LongURL.expand("http://is.gd/iUKg", :cache => MemCache.new("localhost:11211", :namespace => "LongURL"))
26
+ # => "http://fabien.jakimowicz.com"
27
+
28
+ # Expander class
29
+ expander = LongURL::Expander.new
30
+ expander.expand("http://tinyurl.com/1c2") # => "http://www.google.com"
31
+ # not expandable urls, direct resolution only
32
+ expander.direct_resolution("http://www.linuxfr.org") # => "http://www.linuxfr.org/pub"
33
+ # not expandable urls, calling longurl.org only
34
+ expander.expand_with_service_only("http://www.linuxfr.org") # => "http://www.linuxfr.org/pub"
35
+ # ... with MemCache
36
+ expander = LongURL::Expander.new(:cache => MemCache.new("localhost:11211", :namespace => "LongURL"))
37
+ expander.expand("http://tinyurl.com/1c2") # => "http://www.google.com"
38
+
39
+ === Exceptions
40
+ * LongURL::InvalidURL : will occurs if given url is nil, empty or invalid
41
+ * LongURL::NetworkError : a network (timeout, host could be reached, ...) error occurs
42
+ * LongURL::UnknownError : an unknown error occurs
43
+
44
+ == REQUIREMENTS
45
+
46
+ * json gem
47
+
48
+ == INSTALL
49
+ gem install longurl
50
+
51
+ == LICENSE
52
+
53
+ (The MIT License)
54
+
55
+ Copyright (c) 2009, Fabien Jakimowicz <fabien@jakimowicz.com>
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
58
+ this software and associated documentation files (the "Software"), to deal in
59
+ the Software without restriction, including without limitation the rights to
60
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
61
+ of the Software, and to permit persons to whom the Software is furnished to do
62
+ so, subject to the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be included in all
65
+ copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
73
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "ivey-longurl"
7
+ s.summary = %q{LongURL expands shorten urls (tinyurl, is.gd, ...)}
8
+ s.homepage = "http://github.com/ivey/longurl"
9
+ s.description = %q{LongURL expands short urls (tinyurl, is.gd, ...) to original ones, using on LongURL.org, internal resolution or direct resolution}
10
+ s.authors = ["Fabien Jakimowicz", "Michael D. Ivey"]
11
+ s.email = "ivey@gweezlebur.com"
12
+ s.rubyforge_project = 'longurl'
13
+
14
+ s.add_dependency 'json'
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
18
+ end
19
+
20
+ desc "build rdoc using hanna theme"
21
+ task :rdoc do
22
+ `rm -rf rdoc && rdoc --op=rdoc --title=LongURL --inline-source --format=darkfish LICENSE README* lib/**/*.rb`
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib' << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |t|
35
+ t.libs << 'test'
36
+ t.test_files = FileList['test/**/*_test.rb']
37
+ t.verbose = true
38
+ end
39
+ rescue LoadError
40
+ puts "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+
43
+ # Rubyforge publishing
44
+ begin
45
+ require 'rake/contrib/sshpublisher'
46
+ namespace :rubyforge do
47
+
48
+ desc "Release gem and RDoc documentation to RubyForge"
49
+ task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
50
+
51
+ namespace :release do
52
+ desc "Publish RDoc to RubyForge."
53
+ task :docs => [:rdoc] do
54
+ config = YAML.load(
55
+ File.read(File.expand_path('~/.rubyforge/user-config.yml'))
56
+ )
57
+
58
+ host = "#{config['username']}@rubyforge.org"
59
+ remote_dir = "/var/www/gforge-projects/longurl/"
60
+ local_dir = 'rdoc'
61
+
62
+ Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
63
+ end
64
+ end
65
+ end
66
+ rescue LoadError
67
+ puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
68
+ end
69
+
70
+ task :default => :test
data/TODO ADDED
@@ -0,0 +1,6 @@
1
+ * add a cache to avoid multiple calls for same url
2
+ * cache could be : hash, memcache, ...
3
+ * handle http errors
4
+ * handle json parsing errors
5
+ * handle ServiceUnknown exception
6
+ * possible infinite loop in Direct::follow_redirections
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 7
3
+ :major: 0
4
+ :minor: 1
data/bin/longurl ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rubygems"
4
+ require "longurl"
5
+
6
+ def usage
7
+ puts "Usage: #$0 <url>"
8
+ exit(-1)
9
+ end
10
+
11
+ if ARGV.size < 1
12
+ usage
13
+ end
14
+
15
+ puts LongURL.expand(ARGV[0])
data/lib/longurl.rb ADDED
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2009 by Fabien Jakimowicz (fabien@jakimowicz.com)
2
+ #
3
+ # Please see the LICENSE file for licensing.
4
+
5
+ require 'longurl/constants'
6
+ require 'longurl/exceptions'
7
+ require 'longurl/url'
8
+ require 'longurl/service'
9
+ require 'longurl/direct'
10
+ require 'longurl/expander'
11
+ require 'longurl/expand'
@@ -0,0 +1,9 @@
1
+ require "uri"
2
+
3
+ module LongURL
4
+ ShortURLMatchRegexp = /http:\/\/[\/\-_.a-z0-9]+/im
5
+
6
+ # Urls for longurl
7
+ EndPoint = URI.parse("http://api.longurl.org/v2/expand")
8
+ ServiceEndPoint = URI.parse("http://api.longurl.org/v2/services")
9
+ end
@@ -0,0 +1,26 @@
1
+ require "net/http"
2
+ require "longurl/url"
3
+ require "longurl/exceptions"
4
+
5
+ module LongURL
6
+ module Direct
7
+ # Will follow redirections given url <tt>orig</tt>.
8
+ # === Exceptions
9
+ # * LongURL::NetworkError in case of a network error (timeout, socket error, ...)
10
+ # * LongURL::InvalidURL in case of a bad url (nil, empty, not http scheme ...)
11
+ # * LongURL::TooManyRedirections if there are too many redirection for destination
12
+ def self.follow_redirections(orig, limit = 5)
13
+ raise LongURL::TooManyRedirections if limit == 0
14
+ uri = LongURL::URL.check(orig)
15
+ Net::HTTP.start(uri.host, uri.port) do |http|
16
+ answer = http.get(uri.path.empty? ? '/' : uri.path)
17
+ dest = answer['Location']
18
+ (dest && dest[0, 7] == 'http://' && follow_redirections(dest, limit - 1)) || orig
19
+ end
20
+ rescue Timeout::Error, Errno::ENETUNREACH, Errno::ETIMEDOUT, SocketError
21
+ raise LongURL::NetworkError
22
+ rescue
23
+ raise LongURL::UnknownError
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ module LongURL
2
+ # Raised by LongURL::Service class if longurl.org service returns unsupported service error.
3
+ class UnsupportedService < StandardError
4
+ end
5
+
6
+ # Raised by LongURL::Service class if longurl.org service returns a not supported answer.
7
+ class UnknownError < StandardError
8
+ end
9
+
10
+ # Raised by LongURL::Service if supplied url is invalid (nil, empty, ...)
11
+ class InvalidURL < StandardError
12
+ end
13
+
14
+ # Raised if a network error occurs : timeout, unreachable network, ...
15
+ class NetworkError < StandardError
16
+ end
17
+
18
+ # Raised if there are too many redirection in a direct resolution.
19
+ class TooManyRedirections < StandardError
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ require 'longurl/expander'
2
+
3
+ module LongURL
4
+
5
+ class << self
6
+
7
+ # Expand given <tt>:url</tt> to a longest one.
8
+ # First, expand will try to expand url using longurl.org service.
9
+ # Then, it will try to direct follow redirections on the given url and returns final one.
10
+ # === Options
11
+ # * <tt>:cache</tt> : cache object to use, must implement [] and []= functions.
12
+ # * <tt>:first_only</tt> : if true, users all-redirects option to the API to fetch only the first redirect
13
+ # instead of following all of them. Useful to identify the original URL that was shortened, not the
14
+ # destination.
15
+ # === Types
16
+ # <tt>url</tt> is expected to be a String and returns a String with the url.
17
+ # === Examples
18
+ # # simple expands
19
+ # LongURL.expand("http://tinyurl.com/1c2") # => "http://www.google.com"
20
+ # LongURL.expand("http://tinyurl.com/blnhsg") # => "http://www.google.com/search?q=number+of+horns+on+a+unicorn&ie=UTF-8"
21
+ # LongURL.expand("http://is.gd/iUKg") # => "http://fabien.jakimowicz.com"
22
+ #
23
+ # # not expandable urls
24
+ # LongURL.expand("http://www.linuxfr.org") # => "http://www.linuxfr.org"
25
+ #
26
+ # === Exceptions
27
+ # * LongURL::InvalidURL : will occurs if given url is nil, empty or invalid
28
+ # * LongURL::NetworkError : a network (timeout, host could be reached, ...) error occurs
29
+ # * LongURL::UnknownError : an unknown error occurs
30
+ def expand(url, options = {})
31
+ @@expander ||= Expander.new(
32
+ :cache => options[:cache],
33
+ :first_only => options[:first_only]
34
+ )
35
+ @@expander.expand(url)
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,95 @@
1
+ require "longurl/exceptions"
2
+ require "longurl/service"
3
+ require "longurl/direct"
4
+
5
+ module LongURL
6
+
7
+ # == URL Expander class.
8
+ # Will use Service and Direct classes to expand an url.
9
+ # Each call to external services is cached to save time and cache object is customizable.
10
+ # You can for example use MemCache for the cache. it will allow different instances of Expander and Service to share the same cache.
11
+ # === Examples
12
+ # # Simple usage
13
+ # e = LongURL::Expander.new
14
+ # e.expand("http://tinyurl.com/1c2") # => "http://www.google.com"
15
+ # e.expand("http://tinyurl.com/blnhsg") # => "http://www.google.com/search?q=number+of+horns+on+a+unicorn&ie=UTF-8"
16
+ # e.expand("http://is.gd/iUKg") # => "http://fabien.jakimowicz.com"
17
+ #
18
+ # # not expandable urls
19
+ # e.expand("http://www.linuxfr.org") # => "http://www.linuxfr.org"
20
+ #
21
+ # # not expandable urls, calling longurl.org only
22
+ # e.expand("http://www.linuxfr.org", :direct_resolution => true) # => "http://www.linuxfr.org/pub"
23
+ #
24
+ # # not expandable urls, direct resolution only
25
+ # e.direct_resolution("http://www.linuxfr.org") # => "http://www.linuxfr.org/pub"
26
+ #
27
+ # # MemCache as cache
28
+ # e = LongURL::Expander.new(:cache => MemCache.new("localhost:11211", :namespace => "LongURL"))
29
+ # e.expand("http://is.gd/iUKg") # => "http://fabien.jakimowicz.com"
30
+ # === Exceptions
31
+ # * LongURL::InvalidURL : will occurs if given url is nil, empty or invalid
32
+ # * LongURL::NetworkError : a network (timeout, host could be reached, ...) error occurs
33
+ # * LongURL::UnknownError : an unknown error occurs
34
+ class Expander
35
+
36
+ attr_accessor :first_only
37
+
38
+ # Initialize a new Expander.
39
+ # === Options
40
+ # * <tt>:cache</tt>: define a cache which Expander can use.
41
+ # It must implements [] and []= methods. It can be disabled using false.
42
+ # * <tt>:first_only</tt> : if true, users all-redirects option to the API to fetch only the first redirect
43
+ # instead of following all of them. Useful to identify the original URL that was shortened, not the
44
+ # destination.
45
+ def initialize(options = {})
46
+ # OPTIMIZE : This code is a complete duplicate of cache handling in service.
47
+ if options[:cache].nil?
48
+ @@cache = Hash.new
49
+ elsif options[:cache] == false
50
+ @@cache = nil
51
+ else
52
+ @@cache = options[:cache]
53
+ end
54
+ @@service = Service.new(:cache => @@cache)
55
+ @first_only = options[:first_only]
56
+ end
57
+
58
+ # Expand given url using LongURL::Service class first and then try a direct_resolution,
59
+ # unless :direct_resolution is set to false in options hash.
60
+ def expand(url, options={ })
61
+ @@service.query_supported_service_only url, :first_only => first_only
62
+ rescue UnsupportedService
63
+ options[:direct_resolution] == false ? raise(UnsupportedService) : direct_resolution(url)
64
+ end
65
+
66
+ # Try to directly resolve url using LongURL::Direct to get final redirection.
67
+ # This call is cached.
68
+ def direct_resolution(url)
69
+ # OPTIMIZE : this code is almost identical as the one in service for handling service retrieval.
70
+ if @@cache
71
+ @@cache[url] ||= Direct.follow_redirections(url)
72
+ else
73
+ Direct.follow_redirections(url)
74
+ end
75
+ end
76
+
77
+ # Expand all url in the given string, if an error occurs while expanding url, then the original url is used.
78
+ # <tt>options</tt> accepts same options as expand, see expand for more details.
79
+ def expand_each_in(text, options = {})
80
+ text.gsub(ShortURLMatchRegexp) do |shorturl|
81
+ begin
82
+ expand shorturl
83
+ rescue InvalidURL,
84
+ NetworkError,
85
+ TooManyRedirections,
86
+ UnknownError,
87
+ UnsupportedService,
88
+ JSON::ParserError
89
+ shorturl
90
+ end
91
+ end
92
+ end # expand_each_in
93
+
94
+ end # Expander
95
+ end # LongURL
@@ -0,0 +1,121 @@
1
+ require "net/http"
2
+ require "cgi"
3
+ require "uri"
4
+ require "rubygems"
5
+ require "json"
6
+ require "longurl/constants"
7
+ require "longurl/exceptions"
8
+ require "longurl/url"
9
+
10
+ module LongURL
11
+
12
+ class Service
13
+
14
+ def initialize(params = {})
15
+ if params[:cache].nil?
16
+ @@cache = Hash.new
17
+ elsif params[:cache] == false
18
+ @@cache = nil
19
+ else
20
+ @@cache = params[:cache]
21
+ end
22
+ @@supported_services = cached_or_fetch_supported_services
23
+ end
24
+
25
+ def query_supported_service_only(url, options={ })
26
+ check url
27
+ raise LongURL::UnsupportedService unless service_supported?(url)
28
+ (@@cache && cached_query(url, options)) || query(url, options)
29
+ end
30
+
31
+ def cached_query(url, options={ })
32
+ @@cache[url] ||= query(url, options)
33
+ end
34
+
35
+ def query(url, options={ })
36
+ escaped_url = check_and_escape(url)
37
+ api_url = "#{EndPoint.path}?format=json&url=#{escaped_url}"
38
+ if options[:first_only]
39
+ api_url += "&all-redirects=1"
40
+ end
41
+ Net::HTTP.start(EndPoint.host, EndPoint.port) do |http|
42
+ handle_response http.get(api_url)
43
+ end
44
+ rescue Timeout::Error, Errno::ENETUNREACH, Errno::ETIMEDOUT, SocketError
45
+ raise LongURL::NetworkError
46
+ end
47
+
48
+ # Check among supported services by longurl.org if given <tt>url</tt> is supported.
49
+ # Returns true if supported, false otherwise.
50
+ def service_supported?(url)
51
+ @@supported_services.include? URI.parse(url).host.downcase
52
+ end
53
+
54
+ protected
55
+
56
+ # Returns a list of supported services.
57
+ # Use cache to get the list or fetch it if cache is empty.
58
+ def cached_or_fetch_supported_services
59
+ if @@cache
60
+ @@cache['supported_services'] ||= fetch_supported_services
61
+ else
62
+ fetch_supported_services
63
+ end
64
+ end
65
+
66
+ # Check given <tt>url</tt> using LongURL::URL.check
67
+ def check(url)
68
+ LongURL::URL.check url
69
+ end
70
+
71
+ # Check given url and escape it for http query argument passing.
72
+ def check_and_escape(url)
73
+ check url
74
+ CGI.escape url
75
+ end
76
+
77
+ # Fetch supported services from longurl.org api.
78
+ # Returns supported services in an Array.
79
+ # Raises LongURL::NetworkError in case of a network error (timeout, ...)
80
+ def fetch_supported_services
81
+ Net::HTTP.start(ServiceEndPoint.host, ServiceEndPoint.port) do |http|
82
+ response = http.get("#{ServiceEndPoint.path}?format=json")
83
+ parsed = JSON.parse(response.body)
84
+ parsed.values.collect { |x| x["domain"] }.flatten
85
+ end
86
+ rescue Timeout::Error, Errno::ENETUNREACH, Errno::ETIMEDOUT, SocketError
87
+ raise LongURL::NetworkError
88
+ rescue
89
+ raise LongURL::UnknownError
90
+ end
91
+
92
+ def handle_response(response)
93
+ parsed = JSON.parse(response.body)
94
+ parsed = parsed.first if parsed.is_a?(Array)
95
+ if parsed['all-redirects']
96
+ parsed['all-redirects'].first
97
+ elsif parsed['long-url']
98
+ parsed['long-url']
99
+ elsif parsed['message'] # Error
100
+ raise exception_regarding_message(parsed['message'])
101
+ else
102
+ raise LongURL::UnknownError
103
+ end
104
+ end
105
+
106
+ def exception_class_regarding_message(message)
107
+ case message
108
+ when 'Unsupported service.'
109
+ LongURL::UnsupportedService
110
+ when 'Connection timeout'
111
+ LongURL::NetworkError
112
+ when 'Could not expand URL. Please check that you have submitted a valid URL.'
113
+ LongURL::InvalidURL
114
+ else
115
+ LongURL::UnknownError
116
+ end
117
+ end
118
+
119
+ end
120
+
121
+ end
@@ -0,0 +1,18 @@
1
+ require 'uri'
2
+ require "longurl/exceptions"
3
+
4
+ module LongURL
5
+ module URL
6
+ # Check given <tt>url</tt>
7
+ # Raises LongURL::InvalidURL if <tt>url</tt> is invalid.
8
+ # Returns a parsed http uri object on success.
9
+ def self.check(url)
10
+ raise LongURL::InvalidURL if url.nil? or url.empty?
11
+ result = URI.parse(url)
12
+ raise LongURL::InvalidURL unless result.is_a?(URI::HTTP)
13
+ result
14
+ rescue URI::InvalidURIError
15
+ raise LongURL::InvalidURL
16
+ end
17
+ end
18
+ end
data/longurl.gemspec ADDED
@@ -0,0 +1,75 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{longurl}
5
+ s.version = "0.1.6"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Fabien Jakimowicz"]
9
+ s.date = %q{2009-07-20}
10
+ s.default_executable = %q{longurl}
11
+ s.description = %q{LongURL expands short urls (tinyurl, is.gd, ...) to original ones, using on LongURL.org, internal resolution or direct resolution}
12
+ s.email = %q{fabien@jakimowicz.com}
13
+ s.executables = ["longurl"]
14
+ s.extra_rdoc_files = [
15
+ "LICENSE",
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "LICENSE",
21
+ "Manifest",
22
+ "README.rdoc",
23
+ "Rakefile",
24
+ "TODO",
25
+ "VERSION.yml",
26
+ "bin/longurl",
27
+ "lib/longurl.rb",
28
+ "lib/longurl/constants.rb",
29
+ "lib/longurl/direct.rb",
30
+ "lib/longurl/exceptions.rb",
31
+ "lib/longurl/expand.rb",
32
+ "lib/longurl/expander.rb",
33
+ "lib/longurl/service.rb",
34
+ "lib/longurl/url.rb",
35
+ "longurl.gemspec",
36
+ "test/cache_mock.rb",
37
+ "test/constants.rb",
38
+ "test/expander_test.rb",
39
+ "test/service/no_cache_service_test.rb",
40
+ "test/service/service_cache_test.rb",
41
+ "test/service/service_test.rb",
42
+ "test/service/supported_services_test.rb",
43
+ "test/url_test.rb"
44
+ ]
45
+ s.has_rdoc = true
46
+ s.homepage = %q{http://longurl.rubyforge.org}
47
+ s.rdoc_options = ["--charset=UTF-8"]
48
+ s.require_paths = ["lib"]
49
+ s.rubyforge_project = %q{longurl}
50
+ s.rubygems_version = %q{1.3.1}
51
+ s.summary = %q{LongURL expands shorten urls (tinyurl, is.gd, ...)}
52
+ s.test_files = [
53
+ "test/cache_mock.rb",
54
+ "test/constants.rb",
55
+ "test/expander_test.rb",
56
+ "test/service/no_cache_service_test.rb",
57
+ "test/service/service_cache_test.rb",
58
+ "test/service/service_test.rb",
59
+ "test/service/supported_services_test.rb",
60
+ "test/url_test.rb"
61
+ ]
62
+
63
+ if s.respond_to? :specification_version then
64
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
65
+ s.specification_version = 2
66
+
67
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
68
+ s.add_runtime_dependency(%q<json>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<json>, [">= 0"])
71
+ end
72
+ else
73
+ s.add_dependency(%q<json>, [">= 0"])
74
+ end
75
+ end
@@ -0,0 +1,20 @@
1
+ class CacheMock
2
+
3
+ attr_accessor :keys_stored, :keys_asked, :storage
4
+
5
+ def initialize(params = {})
6
+ @storage = {}
7
+ @keys_stored = []
8
+ @keys_asked = []
9
+ end
10
+
11
+ def [](key)
12
+ @keys_asked << key
13
+ @storage[key]
14
+ end
15
+
16
+ def []=(key, value)
17
+ @keys_stored << key
18
+ @storage[key] = value
19
+ end
20
+ end
data/test/constants.rb ADDED
@@ -0,0 +1,15 @@
1
+ ShortToLong = {
2
+ :tiny_url => {
3
+ "http://tinyurl.com/cnuw9a" => "http://fabien.jakimowicz.com",
4
+ "http://tinyurl.com/blnhsg" => "http://www.google.com/search?q=number+of+horns+on+a+unicorn&ie=UTF-8"
5
+ },
6
+ :is_gd => {
7
+ "http://is.gd/iUKg" => "http://fabien.jakimowicz.com",
8
+ "http://is.gd/iYCo" => "http://www.google.com/search?q=number+of+horns+on+a+unicorn&ie=UTF-8"
9
+ },
10
+ :friendfeed => {
11
+ "http://ff.im/-31OFh" => "http://en.wikipedia.org/wiki/Product_requirements_document",
12
+ "http://ff.im/-31MWm" => "http://www.infrasystems.com/how-to-write-an-mrd.html"
13
+ }
14
+ }
15
+ MultipleRedirectURL = "http://bit.ly/9NiEKB"
@@ -0,0 +1,64 @@
1
+ $test_lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
2
+ $:.unshift($test_lib_dir)
3
+
4
+ require "test/unit"
5
+ require "longurl/expander"
6
+
7
+ class TextExpander < Test::Unit::TestCase
8
+ def setup
9
+ @expander = LongURL::Expander.new
10
+ end
11
+
12
+ def test_expand_each_in_should_expand_friendfeed_urls
13
+ assert_equal "Product requirements document - Wikipedia, http://en.wikipedia.org/wiki/Product_requirements_document the free encyclopedia",
14
+ @expander.expand_each_in("Product requirements document - Wikipedia, http://ff.im/-31OFh the free encyclopedia")
15
+ end
16
+
17
+ def test_expand_each_in_should_not_change_strings_with_no_urls
18
+ assert_equal "i'm not to be changed !!!", @expander.expand_each_in("i'm not to be changed !!!")
19
+ end
20
+
21
+ def test_expand_each_in_should_be_able_to_expand_multiple_urls
22
+ assert_equal "Those websites are great: http://www.flickr.com/photos/jakimowicz & http://www.google.com/profiles/fabien.jakimowicz",
23
+ @expander.expand_each_in("Those websites are great: http://tinyurl.com/r9cm9p & http://is.gd/Bnxy")
24
+ end
25
+
26
+ def test_expand_each_in_should_not_replace_unexpandable_urls_if_direct_resolution_is_false
27
+ assert_equal "Those websites are great: http://www.flickr.com/photos/jakimowicz & http://www.google.com/profiles/fabien.jakimowicz",
28
+ @expander.expand_each_in( "Those websites are great: http://www.flickr.com/photos/jakimowicz & http://is.gd/Bnxy",
29
+ :direct_resolution => false )
30
+ end
31
+
32
+ def test_expand_should_raise_UnsupportedService_if_service_is_not_supported_and_direct_resolution_disabled
33
+ assert_raise(LongURL::UnsupportedService) do
34
+ @expander.expand("http://www.google.com", :direct_resolution => false)
35
+ end
36
+ end
37
+
38
+ def test_expand_should_use_direct_resolution_by_default
39
+ assert_nothing_raised do
40
+ assert_equal "http://fabien.jakimowicz.com", @expander.expand("http://fabien.jakimowicz.com")
41
+ end
42
+ end
43
+
44
+ def test_expand_should_expand_supported_services
45
+ assert_equal "http://www.flickr.com/photos/jakimowicz", @expander.expand('http://tinyurl.com/r9cm9p')
46
+ end
47
+
48
+ def test_expand_should_handle_direct_resolution_if_asked
49
+ assert_equal "http://fabien.jakimowicz.com", @expander.expand("http://fabien.jakimowicz.com", :direct_resolution => true)
50
+ end
51
+
52
+ def test_expand_with_should_continue_to_resolve_urls_even_if_direct_resolution_is_true
53
+ assert_equal "http://www.flickr.com/photos/jakimowicz", @expander.expand('http://tinyurl.com/r9cm9p', :direct_resolution => true)
54
+ end
55
+
56
+ def test_first_only_defaults_to_false
57
+ assert !@expander.first_only
58
+ end
59
+
60
+ def test_expand_with_first_only_should_resolve_to_first_not_final
61
+ expander = LongURL::Expander.new :first_only => true
62
+ assert_equal "http://gmail.com", expander.expand(MultipleRedirectURL)
63
+ end
64
+ end
@@ -0,0 +1,31 @@
1
+ $test_lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
2
+ $:.unshift($test_lib_dir)
3
+
4
+ require "test/unit"
5
+ require "constants"
6
+ require "longurl/exceptions"
7
+ require "longurl/service"
8
+
9
+ class TestNoCacheService < Test::Unit::TestCase
10
+ # OPTIMIZE : all these tests are a plain copy from service_test.rb, we can make something better.
11
+
12
+ def setup
13
+ @service = LongURL::Service.new(:cache => false)
14
+ end
15
+
16
+ def test_query_should_raise_invalid_url_if_url_is_nil
17
+ assert_raise(LongURL::InvalidURL) { @service.query(nil) }
18
+ end
19
+
20
+ def test_query_should_raise_invalid_url_if_url_is_empty
21
+ assert_raise(LongURL::InvalidURL) { @service.query('') }
22
+ end
23
+
24
+ def test_query_should_returns_given_url_if_not_shorten_url
25
+ assert_equal "http://www.google.com", @service.query("http://www.google.com")
26
+ end
27
+
28
+ def test_query_should_returns_expanded_url_for_supported_services
29
+ ShortToLong.each_value {|service| service.each {|short, long| assert_equal long, @service.query(short)}}
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ $test_lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
2
+ $:.unshift($test_lib_dir)
3
+
4
+ require "test/unit"
5
+ require "cache_mock"
6
+ require "constants"
7
+ require "longurl/exceptions"
8
+ require "longurl/service"
9
+
10
+ class TestServiceCache < Test::Unit::TestCase
11
+
12
+ def setup
13
+ @cache = CacheMock.new
14
+ @service = LongURL::Service.new(:cache => @cache)
15
+ end
16
+
17
+ def test_query_should_use_cache_before_external_fetch
18
+ url = ShortToLong[:is_gd].keys.first
19
+ @service.query_supported_service_only(url)
20
+ assert_equal ['supported_services', url], @cache.keys_asked
21
+ @service.query_supported_service_only(url)
22
+ assert_equal ['supported_services', url, url], @cache.keys_asked
23
+ end
24
+
25
+ def test_query_should_cache_results_from_supported_services
26
+ ShortToLong.each_value do |service|
27
+ service.each do |short, long|
28
+ @service.query_supported_service_only(short)
29
+ assert @cache.keys_stored.include?(short)
30
+ assert_equal long, @cache[short]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ $test_lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
2
+ $:.unshift($test_lib_dir)
3
+
4
+ require "test/unit"
5
+ require "longurl/exceptions"
6
+ require "longurl/service"
7
+
8
+ class TestService < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @service = LongURL::Service.new
12
+ end
13
+
14
+ def test_query_should_raise_invalid_url_if_url_is_nil
15
+ assert_raise(LongURL::InvalidURL) { @service.query(nil) }
16
+ end
17
+
18
+ def test_query_should_raise_invalid_url_if_url_is_empty
19
+ assert_raise(LongURL::InvalidURL) { @service.query('') }
20
+ end
21
+
22
+ def test_query_should_returns_given_url_if_not_shorten_url
23
+ assert_equal "http://www.google.com", @service.query("http://www.google.com")
24
+ end
25
+
26
+ def test_query_should_returns_expanded_url_for_supported_services
27
+ ShortToLong.each_value {|service| service.each {|short, long| assert_equal long, @service.query(short)}}
28
+ end
29
+ end
@@ -0,0 +1,41 @@
1
+ $test_lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
2
+ $:.unshift($test_lib_dir)
3
+
4
+ require "test/unit"
5
+ require "cache_mock"
6
+ require "constants"
7
+ require "longurl/exceptions"
8
+ require "longurl/service"
9
+
10
+ class TestSupportedServices < Test::Unit::TestCase
11
+
12
+ def setup
13
+ @cache = CacheMock.new
14
+ end
15
+
16
+ def test_service_should_check_if_available_services_are_in_cache
17
+ assert_equal [], @cache.keys_asked
18
+ @service = LongURL::Service.new(:cache => @cache)
19
+ assert_equal ['supported_services'], @cache.keys_asked
20
+ end
21
+
22
+ def test_service_should_store_available_services_in_cache
23
+ assert_equal [], @cache.keys_stored
24
+ @service = LongURL::Service.new(:cache => @cache)
25
+ assert_equal ['supported_services'], @cache.keys_stored
26
+ end
27
+
28
+ def test_supported_services_stored_in_cache_should_be_a_flat_array_of_strings
29
+ @service = LongURL::Service.new(:cache => @cache)
30
+ assert_kind_of Array, @cache['supported_services']
31
+ assert @cache['supported_services'].all? {|object| object.is_a?(String)}
32
+ end
33
+
34
+ def test_service_should_use_supported_services_stored_in_cache_if_available
35
+ @cache['supported_services'] = ['bleh.com', 'bli.com']
36
+ @service = LongURL::Service.new(:cache => @cache)
37
+ assert_equal ['supported_services'], @cache.keys_asked
38
+ assert_equal ['supported_services'], @cache.keys_stored
39
+ assert_raise(LongURL::UnsupportedService) { @service.query_supported_service_only(ShortToLong[:is_gd].keys.first) }
40
+ end
41
+ end
data/test/url_test.rb ADDED
@@ -0,0 +1,44 @@
1
+ $test_lib_dir = File.join(File.dirname(__FILE__), "..", "lib")
2
+ $:.unshift($test_lib_dir)
3
+
4
+ require "test/unit"
5
+ require "uri"
6
+ require "longurl/exceptions"
7
+ require "longurl/url"
8
+
9
+ class TestURL < Test::Unit::TestCase
10
+
11
+ BadURLs = ["http:",
12
+ "http://",
13
+ "http://hoth.entp..."]
14
+
15
+ NotHTTPURLs = ["bleh",
16
+ "bleh://",
17
+ "bleh://asfd.com",
18
+ "ftp://asdf.com",
19
+ "google.com",
20
+ "asdf@toto.com",
21
+ "httpd://asdf.com"]
22
+
23
+ GoodURLs = ["http://www.google.com", "https://rubyonrails.org"]
24
+
25
+ def test_check_should_raise_invalid_url_if_url_is_nil
26
+ assert_raise(LongURL::InvalidURL) { LongURL::URL.check nil }
27
+ end
28
+
29
+ def test_check_should_raise_invalid_url_if_url_is_empty
30
+ assert_raise(LongURL::InvalidURL) { LongURL::URL.check "" }
31
+ end
32
+
33
+ def test_check_should_raise_invalid_url_if_url_is_invalid
34
+ BadURLs.each {|bad_url| assert_raise(LongURL::InvalidURL) { LongURL::URL.check bad_url } }
35
+ end
36
+
37
+ def test_check_should_raise_invalid_url_if_url_is_not_http
38
+ NotHTTPURLs.each {|bad_url| assert_raise(LongURL::InvalidURL) { LongURL::URL.check bad_url } }
39
+ end
40
+
41
+ def test_check_should_returns_parsed_url_on_success
42
+ GoodURLs.each {|good_url| assert_equal URI.parse(good_url), LongURL::URL.check(good_url)}
43
+ end
44
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ivey-longurl
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.7
5
+ platform: ruby
6
+ authors:
7
+ - Fabien Jakimowicz
8
+ - Michael D. Ivey
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2010-05-12 00:00:00 -04:00
14
+ default_executable: longurl
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: json
18
+ type: :runtime
19
+ version_requirement:
20
+ version_requirements: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ version:
26
+ description: LongURL expands short urls (tinyurl, is.gd, ...) to original ones, using on LongURL.org, internal resolution or direct resolution
27
+ email: ivey@gweezlebur.com
28
+ executables:
29
+ - longurl
30
+ extensions: []
31
+
32
+ extra_rdoc_files:
33
+ - LICENSE
34
+ - README.rdoc
35
+ - TODO
36
+ files:
37
+ - .gitignore
38
+ - LICENSE
39
+ - Manifest
40
+ - README.rdoc
41
+ - Rakefile
42
+ - TODO
43
+ - VERSION.yml
44
+ - bin/longurl
45
+ - lib/longurl.rb
46
+ - lib/longurl/constants.rb
47
+ - lib/longurl/direct.rb
48
+ - lib/longurl/exceptions.rb
49
+ - lib/longurl/expand.rb
50
+ - lib/longurl/expander.rb
51
+ - lib/longurl/service.rb
52
+ - lib/longurl/url.rb
53
+ - longurl.gemspec
54
+ - test/cache_mock.rb
55
+ - test/constants.rb
56
+ - test/expander_test.rb
57
+ - test/service/no_cache_service_test.rb
58
+ - test/service/service_cache_test.rb
59
+ - test/service/service_test.rb
60
+ - test/service/supported_services_test.rb
61
+ - test/url_test.rb
62
+ has_rdoc: true
63
+ homepage: http://github.com/ivey/longurl
64
+ licenses: []
65
+
66
+ post_install_message:
67
+ rdoc_options:
68
+ - --charset=UTF-8
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: "0"
82
+ version:
83
+ requirements: []
84
+
85
+ rubyforge_project: longurl
86
+ rubygems_version: 1.3.5
87
+ signing_key:
88
+ specification_version: 3
89
+ summary: LongURL expands shorten urls (tinyurl, is.gd, ...)
90
+ test_files:
91
+ - test/cache_mock.rb
92
+ - test/constants.rb
93
+ - test/expander_test.rb
94
+ - test/service/no_cache_service_test.rb
95
+ - test/service/service_cache_test.rb
96
+ - test/service/service_test.rb
97
+ - test/service/supported_services_test.rb
98
+ - test/url_test.rb