bitly4r 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ = 0.1.0
2
+
3
+ * Initial gem release of 0.1 coebase.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Dan Foley / CantRemember.com
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,67 @@
1
+ = Bitly4R
2
+
3
+ A Ruby API for the http://bit.ly URL-shortening service
4
+
5
+ == SimpleClient
6
+
7
+ This example uses a simpler construction and invocation technique.
8
+ For when you just need the URLs, and not any additional data.
9
+
10
+ require 'rubygems'
11
+ require 'bitly4r'
12
+
13
+ login, api_key = 'bitlyapidemo', 'R_0da49e0a9118ff35f52f629d2d71bf07'
14
+ long_url = 'http://rubyforge.org/'
15
+
16
+ client = Bitly4R.Keyed(login, api_key)
17
+
18
+ # shorten
19
+ short_url = client.shorten(long_url)
20
+
21
+ # re-expand
22
+ raise 'what the?' unless (long_url == client.expand(short_url))
23
+
24
+ == Client
25
+
26
+ This example uses a full-fledged construction and invocation technique.
27
+ The URLs are returned by the <tt>to_s</tt> methods of the Response objects, and additional data is made available.
28
+
29
+ require 'rubygems'
30
+ require 'bitly4r'
31
+
32
+ login, api_key = 'bitlyapidemo', 'R_0da49e0a9118ff35f52f629d2d71bf07'
33
+ long_url = 'http://rubyforge.org/'
34
+
35
+ client = Bitly4R::Client.new(:login => login, :api_key => api_key)
36
+
37
+ # shorten
38
+ short_url = client.shorten(long_url).to_s
39
+
40
+ # re-expand
41
+ raise 'aww, cmon!' unless (long_url == client.expand(short_url).to_s)
42
+
43
+ == Support
44
+
45
+ This gem supports the following API commands:
46
+
47
+ * shorten
48
+ * expand
49
+ * info
50
+ * stats
51
+ * errors
52
+
53
+ For more information, see the API documentation:
54
+
55
+ * http://code.google.com/p/bitly-api/wiki/ApiDocumentation
56
+
57
+ == Contributing
58
+
59
+ === Issue Tracking and Feature Requests
60
+
61
+ * http://bitly4r.rubyforge.org
62
+
63
+ == Community
64
+
65
+ === Wiki
66
+
67
+ * http://wiki.cantremember.com/Bitly4R
@@ -0,0 +1,162 @@
1
+ require 'rubygems'
2
+ require 'rake/clean'
3
+ require 'fileutils'
4
+
5
+ task :default => :test
6
+
7
+ # SPECS ===============================================================
8
+
9
+ desc 'Run specs with story style output'
10
+ task :spec do
11
+ sh 'specrb --specdox -Ilib:test test/*_test.rb'
12
+ end
13
+
14
+ desc 'Run specs with unit test style output'
15
+ task :test => FileList['test/*_test.rb'] do |t|
16
+ suite = t.prerequisites.map{|f| "-r#{f.chomp('.rb')}"}.join(' ')
17
+ sh "ruby -Ilib:test #{suite} -e ''", :verbose => false
18
+ end
19
+
20
+ # PACKAGING ============================================================
21
+
22
+ # Load the gemspec using the same limitations as github
23
+ def spec
24
+ @spec ||=
25
+ begin
26
+ require 'rubygems/specification'
27
+ data = File.read('sinatra.gemspec')
28
+ spec = nil
29
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
30
+ spec
31
+ end
32
+ end
33
+
34
+ def package(ext='')
35
+ "dist/sinatra-#{spec.version}" + ext
36
+ end
37
+
38
+ desc 'Build packages'
39
+ task :package => %w[.gem .tar.gz].map {|e| package(e)}
40
+
41
+ desc 'Build and install as local gem'
42
+ task :install => package('.gem') do
43
+ sh "gem install #{package('.gem')}"
44
+ end
45
+
46
+ directory 'dist/'
47
+
48
+ file package('.gem') => %w[dist/ sinatra.gemspec] + spec.files do |f|
49
+ sh "gem build sinatra.gemspec"
50
+ mv File.basename(f.name), f.name
51
+ end
52
+
53
+ file package('.tar.gz') => %w[dist/] + spec.files do |f|
54
+ sh "git archive --format=tar HEAD | gzip > #{f.name}"
55
+ end
56
+
57
+ # Rubyforge Release / Publish Tasks ==================================
58
+
59
+ desc 'Publish website to rubyforge'
60
+ task 'publish:doc' => 'doc/api/index.html' do
61
+ sh 'scp -rp doc/* rubyforge.org:/var/www/gforge-projects/sinatra/'
62
+ end
63
+
64
+ task 'publish:gem' => [package('.gem'), package('.tar.gz')] do |t|
65
+ sh <<-end
66
+ rubyforge add_release sinatra sinatra #{spec.version} #{package('.gem')} &&
67
+ rubyforge add_file sinatra sinatra #{spec.version} #{package('.tar.gz')}
68
+ end
69
+ end
70
+
71
+ # Website ============================================================
72
+ # Building docs requires HAML and the hanna gem:
73
+ # gem install mislav-hanna --source=http://gems.github.com
74
+
75
+ task 'doc' => ['doc:api','doc:site']
76
+
77
+ desc 'Generate Hanna RDoc under doc/api'
78
+ task 'doc:api' => ['doc/api/index.html']
79
+
80
+ file 'doc/api/index.html' => FileList['lib/**/*.rb','README.rdoc'] do |f|
81
+ rb_files = f.prerequisites
82
+ sh((<<-end).gsub(/\s+/, ' '))
83
+ hanna --charset utf8 \
84
+ --fmt html \
85
+ --inline-source \
86
+ --line-numbers \
87
+ --main README.rdoc \
88
+ --op doc/api \
89
+ --title 'Sinatra API Documentation' \
90
+ #{rb_files.join(' ')}
91
+ end
92
+ end
93
+ CLEAN.include 'doc/api'
94
+
95
+ def rdoc_to_html(file_name)
96
+ require 'rdoc/markup/to_html'
97
+ rdoc = RDoc::Markup::ToHtml.new
98
+ rdoc.convert(File.read(file_name))
99
+ end
100
+
101
+ def haml(locals={})
102
+ require 'haml'
103
+ template = File.read('doc/template.haml')
104
+ haml = Haml::Engine.new(template, :format => :html4, :attr_wrapper => '"')
105
+ haml.render(Object.new, locals)
106
+ end
107
+
108
+ desc 'Build website HTML and stuff'
109
+ task 'doc:site' => ['doc/index.html', 'doc/book.html']
110
+
111
+ file 'doc/index.html' => %w[README.rdoc doc/template.haml] do |file|
112
+ File.open(file.name, 'w') do |file|
113
+ file << haml(:title => 'Sinatra', :content => rdoc_to_html('README.rdoc'))
114
+ end
115
+ end
116
+ CLEAN.include 'doc/index.html'
117
+
118
+ file 'doc/book.html' => ['book/output/sinatra-book.html'] do |file|
119
+ File.open(file.name, 'w') do |file|
120
+ book_content = File.read('book/output/sinatra-book.html')
121
+ file << haml(:title => 'Sinatra Book', :content => book_content)
122
+ end
123
+ end
124
+ CLEAN.include 'doc/book.html'
125
+
126
+ file 'book/output/sinatra-book.html' => FileList['book/**'] do |f|
127
+ unless File.directory?('book')
128
+ sh 'git clone git://github.com/cschneid/sinatra-book.git book'
129
+ end
130
+ sh((<<-SH).strip.gsub(/\s+/, ' '))
131
+ cd book &&
132
+ git fetch origin &&
133
+ git rebase origin/master &&
134
+ thor book:build
135
+ SH
136
+ end
137
+ CLEAN.include 'book/output/sinatra-book.html'
138
+
139
+ desc 'Build the Sinatra book'
140
+ task 'doc:book' => ['book/output/sinatra-book.html']
141
+
142
+ # Gemspec Helpers ====================================================
143
+
144
+ file 'sinatra.gemspec' => FileList['{lib,test,images}/**','Rakefile'] do |f|
145
+ # read spec file and split out manifest section
146
+ spec = File.read(f.name)
147
+ parts = spec.split(" # = MANIFEST =\n")
148
+ fail 'bad spec' if parts.length != 3
149
+ # determine file list from git ls-files
150
+ files = `git ls-files`.
151
+ split("\n").
152
+ sort.
153
+ reject{ |file| file =~ /^\./ }.
154
+ reject { |file| file =~ /^doc/ }.
155
+ map{ |file| " #{file}" }.
156
+ join("\n")
157
+ # piece file back together and write...
158
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
159
+ spec = parts.join(" # = MANIFEST =\n")
160
+ File.open(f.name, 'w') { |io| io.write(spec) }
161
+ puts "updated #{f.name}"
162
+ end
@@ -0,0 +1,20 @@
1
+ #Requires all the individual Bitly4R components, in the proper sequence.
2
+ #That makes the use of this gem as easy as:
3
+ #
4
+ # require 'bitly4r'
5
+ #
6
+ #See:
7
+ #* Bitly4R::Client
8
+ #--
9
+
10
+ # external
11
+ %w{ net/http cgi }.each {|lib| require lib }
12
+
13
+ # internal, and in the proper sequence
14
+ %w{
15
+ bitly4r/objects
16
+ bitly4r/client
17
+ bitly4r/definitions
18
+ }.each do |file|
19
+ require File.expand_path(File.join(File.dirname(__FILE__), file))
20
+ end
@@ -0,0 +1,211 @@
1
+ #Client objects for Bitly4R.
2
+ #
3
+ #See:
4
+ #* Bitly4R::Client
5
+ #* Bitly4R::SimpleClient
6
+
7
+ module Bitly4R
8
+ #= Client
9
+ #
10
+ #A client object for accessing the bit.ly API.
11
+ #
12
+ #* Supports both API key and HTTP Auth credentials (although HTTP Auth / password-based doesn't seem to work)
13
+ #* Works with version 2.0.1 of the API
14
+ #* Uses XML for marshalling, for cheap & easy text parsing.
15
+ #
16
+ #See the API documentation:
17
+ #* http://code.google.com/p/bitly-api/wiki/ApiDocumentation
18
+ #* http://bit.ly
19
+ class Client
20
+ #:nodoc:
21
+ BASE_PARAMS = Params.new
22
+ BASE_PARAMS[:version] = '2.0.1'
23
+ BASE_PARAMS[:format] = 'xml'
24
+
25
+ #The login credential provided at construction.
26
+ attr_reader :login
27
+ #The password credential provided at construction.
28
+ attr_reader :password
29
+ #The API key credential provided at construction.
30
+ attr_reader :api_key
31
+
32
+ #Constructs a new client.
33
+ #
34
+ #Any tuples provided in the optional Hash are injected into instance variables.
35
+ #
36
+ #You must provide a login, and either an API key (<tt>api_key</tt>) or a password.
37
+ def initialize(ops={})
38
+ # for the readers
39
+ # not necessary, but polite
40
+ ops.each do |k, v|
41
+ instance_variable_set "@#{k}".to_sym, v
42
+ end
43
+
44
+ raise Error.new("you must provide a login") unless self.login
45
+ raise Error.new("you must provide either an API key or a password") unless (self.api_key or self.password)
46
+
47
+ # now, a client-izec set of parameters
48
+ @client_params = BASE_PARAMS.clone.merge(ops)
49
+ end
50
+
51
+
52
+ #Invokes the API's <b>shorten</b> method.
53
+ #That's pretty much what makes bit.ly a valuable service.
54
+ #
55
+ #A Response is returned.
56
+ #Response.to_s will return the <tt>shortUrl</tt> / <tt>short_url</tt> value.
57
+ #
58
+ #You can take the shortened URL and re- expand it.
59
+ def shorten(long_url)
60
+ return nil unless long_url
61
+ assert_codes(execute(:shorten, :short_url) do |params|
62
+ params[:longUrl] = long_url
63
+ end)
64
+ end
65
+
66
+ #Invokes the API's <b>expand</b> method.
67
+ #It reverses a shorten; the original full URL is re-hydrated.
68
+ #
69
+ #For <i>param</i>, you can provide a previously-shortened URL from the bit.ly service.
70
+ #If so, you do not need to provide <i>param_type</i> (though <tt>:short_url</tt> would be the proper symbol).
71
+ #
72
+ #Alternately, you can provide a hash code returned by the bit.ly service.
73
+ #In this case, provide <tt>:hash</tt> as the <i>param_type</i>.
74
+ #
75
+ #A Response is returned.
76
+ #Response.to_s will return the <tt>longUrl</tt> / <tt>long_url</tt> value.
77
+ def expand(param, param_type=:short_url)
78
+ return nil unless param && param_type
79
+ assert_codes(execute(:expand, :long_url) do |params|
80
+ params[Utility::camelize(param_type).to_sym] = param
81
+ end)
82
+ end
83
+
84
+ #Invokes the API's <b>info</b> method.
85
+ #Information about the shortened URL is returned by the service.
86
+ #
87
+ #For <i>param</i>, you can provide a previously-shortened URL from the bit.ly service.
88
+ #If so, you do not need to provide <i>param_type</i> (though <tt>:short_url</tt> would be the proper symbol).
89
+ #
90
+ #Alternately, you can provide a hash code returned by the bit.ly service.
91
+ #In this case, provide <tt>:hash</tt> as the <i>param_type</i>.
92
+ #
93
+ #You can optionally provide an arbitrary Hash of HTTP parameters.
94
+ #The one that bit.ly cares about is called <tt>:key</tt>.
95
+ #It should be an array of camel-cased or underscored element names which you would like to receive.
96
+ #In theory, this should limit the response content, but I haven't seen that work yet.
97
+ #The arbitrary Hash capability is left intact for future re-purposing.
98
+ #
99
+ #A Response is returned.
100
+ #Response.to_s will return the <tt>longUrl</tt> / <tt>long_url</tt> value.
101
+ #
102
+ #There is plenty of other data in the response besides the original full URL.
103
+ #Feel free to access the Response.body and use Response.method_missing to pull out specific element values.
104
+ def info(param, param_type=:short_url, ops={})
105
+ return nil unless param_type && param
106
+ assert_codes(execute(:info, :long_url) do |params|
107
+ params[Utility::camelize(param_type).to_sym] = param
108
+
109
+ # optional keys
110
+ if (keys = ops[:keys])
111
+ keys = [keys] unless Array === keys
112
+ params[:keys] = (keys.inject([]) do |a, key|
113
+ a << Utility::camelize(key)
114
+ a
115
+ end).join(',')
116
+ end
117
+ end)
118
+ end
119
+
120
+ #Invokes the API's <b>stats</b> method.
121
+ #Statistics about the shortened URL are returned by the service.
122
+ #
123
+ #For <i>param</i>, you can provide a previously-shortened URL from the bit.ly service.
124
+ #If so, you do not need to provide <i>param_type</i> (though <tt>:short_url</tt> would be the proper symbol).
125
+ #
126
+ #Alternately, you can provide a hash code returned by the bit.ly service.
127
+ #In this case, provide <tt>:hash</tt> as the <i>param_type</i>.
128
+ #
129
+ #A Response is returned.
130
+ #Response.to_s will return the <tt>longUrl</tt> / <tt>long_url</tt> value.
131
+ #
132
+ #There is plenty of other data in the response besides the original full URL.
133
+ #Feel free to access the Response.body and use Response.method_missing to pull out specific element values.
134
+ def stats(param, param_type=:short_url)
135
+ return nil unless param_type && param
136
+ assert_codes(execute(:info, :long_url) do |params|
137
+ params[Utility::camelize(param_type).to_sym] = param
138
+ end)
139
+ end
140
+
141
+ def errors
142
+ execute(:errors)
143
+ end
144
+
145
+
146
+
147
+ # - - - - -
148
+ protected
149
+
150
+ def execute(command, to_s_sym=nil) #:nodoc:
151
+ #http://api.bit.ly/shorten?version=2.0.1&longUrl=http://cnn.com&login=bitlyapidemo&apiKey=R_0da49e0a9118ff35f52f629d2d71bf07
152
+ uri = URI.parse('http://api.bit.ly')
153
+
154
+ # the client-izec set
155
+ params = @client_params.clone
156
+ params[:login] = self.login
157
+ params[:apiKey] = self.api_key if self.api_key
158
+
159
+ # altered in whatever way the caller desires
160
+ yield params if block_given?
161
+
162
+ response = Net::HTTP.start(uri.host, uri.port) do |http|
163
+ path = "/#{command}?#{params}"
164
+ request = Net::HTTP::Get.new(path)
165
+ if self.password
166
+ # HTTP auth expected
167
+ request.basic_auth self.login, self.password
168
+ end
169
+
170
+ http.request request
171
+ end
172
+
173
+ raise Error.new('did not receive HTTP 200 OK') unless Net::HTTPOK === response
174
+
175
+ # a parsing response
176
+ Response.new(response, to_s_sym)
177
+ end
178
+
179
+ def assert_error_code(response) #:nodoc:
180
+ raise Error.new("errorCode #{response.errorCode} : #{response.errorMesage}") unless '0' == response.errorCode
181
+ response
182
+ end
183
+
184
+ def assert_status_code(response) #:nodoc:
185
+ raise Error.new("status #{response.statusCode}") unless 'OK' == response.statusCode
186
+ response
187
+ end
188
+
189
+ def assert_codes(response) #:nodoc:
190
+ assert_error_code(assert_status_code(response))
191
+ end
192
+ end
193
+
194
+
195
+
196
+ #Constructs a new 'simple' client.
197
+ #
198
+ #Just like a standard Client, except that several methods are overridden to provide the 'likely' value, vs. a Response.
199
+ class SimpleClient < Client
200
+ #Same as Client.shorten, except that the shortened URL is returned (vs. a Response)
201
+ def shorten(*args)
202
+ (super *args).to_s
203
+ end
204
+
205
+ #Same as Client.expand, except that the long URL is returned (vs. a Response)
206
+ def expand(*args)
207
+ # just the default value, not the Response
208
+ (super *args).to_s
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,34 @@
1
+ #Simple constructor methods, so that you don't have to deal directly with Client if you don't want to.
2
+ #
3
+ #See also:
4
+ #* Bitly4R.Keyed
5
+ #* Bitly4R.Authed
6
+ #
7
+ #--
8
+ #Many thanks to Hpricot for introducing me to this convention.
9
+ #++
10
+
11
+ #Constructs a SimpleClient with the usual Hash of arguments.
12
+
13
+ def Bitly4R(ops={})
14
+ # general options
15
+ Bitly4R::SimpleClient.new(ops)
16
+ end
17
+
18
+ #Constructs a SimpleClient with login and API key credentials.
19
+ #
20
+ #No password is involved.
21
+ #Simple HTTP GETs will be the order of the day.
22
+ def Bitly4R.Keyed(login, api_key)
23
+ # using an API key
24
+ Bitly4R::SimpleClient.new(:login => login, :api_key => api_key)
25
+ end
26
+
27
+ #Constructs a SimpleClient with login and password credentials.
28
+ #
29
+ #No API key is involved.
30
+ #HTTP GETs with HTTP Basic Auth will be the order of the day.
31
+ def Bitly4R.Authed(login, password)
32
+ # using an API key
33
+ Bitly4R::SimpleClient.new(:login => login, :password => password)
34
+ end
@@ -0,0 +1,123 @@
1
+ #Supporting objects for Bitly4R.
2
+ #
3
+ #See:
4
+ #* Bitly4R::Error
5
+ #* Bitly4R::Params
6
+ #* Bitly4R::Response
7
+
8
+ module Bitly4R
9
+
10
+ #= Error
11
+ #
12
+ #A Module-specific Exception class
13
+ #
14
+ #--
15
+ # i would have called it Bitly4R::Exception
16
+ # except that i don't know how to access Kernel::Exception within the initialize logic
17
+ #++
18
+ class Error < Exception
19
+ #The propagated cause of this Exception, if appropriate
20
+ attr_accessor :cause
21
+
22
+ #Provide a message and an optional 'causing' Exception.
23
+ #
24
+ #If no message is passed -- eg. only an Exception -- then this Exception inherits its message.
25
+ def initialize(message, cause=nil)
26
+ if (Exception === message)
27
+ super message.to_s
28
+ @cause = message
29
+ else
30
+ super message
31
+ @cause = cause
32
+ end
33
+ end
34
+ end
35
+
36
+ #= Params
37
+ #
38
+ #Extends the Hash class to provide simply composition of a URL-encoded string.
39
+ #
40
+ #Could have extended Hash, but chose instead to 'leave no trace'.
41
+ class Params < Hash
42
+ # An encoded composite of the parameters, ready for use in a URL
43
+ def to_s
44
+ (self.to_a.collect do |k, v|
45
+ "#{CGI.escape(k.to_s)}=#{CGI.escape(v.to_s)}"
46
+ end).join('&')
47
+ end
48
+ end
49
+
50
+
51
+
52
+ #= Response
53
+ #
54
+ #A response from the bit.ly API.
55
+ #
56
+ #The to_s method should always return the 'likely' value, assuming that there is a likely one.
57
+ #For example:
58
+ #* <tt>shorten => shortUrl</tt>
59
+ #* <tt>expand => longUrl</tt>
60
+ #
61
+ #All other response values can be retrieved via method_missing.
62
+ #
63
+ #<b>NOTE:</b> This is <i>not</i> a sophisticated XML parser. It's just Regexp's, with handling for CDATA blocks.
64
+ class Response
65
+ #The body of the bit.ly API response, as XML
66
+ attr_reader :body
67
+
68
+ #Constructs a bit.ly API response wrapper.
69
+ #
70
+ #<i>response</i> can be:
71
+ #* a String, which becomes the body
72
+ #* a Net::HTTPResponse, in which case its body is extracted
73
+ #
74
+ #<i>to_s_sym</i> is optional, and it references the element which will become the to_s value of this Response.
75
+ #It can be either camel-case or underscored.
76
+ #See method_missing.
77
+ def initialize(response, to_s_sym=nil)
78
+ response = response.body if Net::HTTPResponse === response
79
+ @body = response
80
+ @to_s_sym = to_s_sym
81
+ end
82
+
83
+ #Provides access the other text elements of the response via camel-case or underscored names.
84
+ #For example, <tt>longUrl</tt> and <tt>long_url</tt> are equivalent.
85
+ #If no such element exists, you'll get nil.
86
+ def method_missing(sym, *args)
87
+ sym = Utility::camelize(sym.to_s)
88
+ match = (self.body || '').match(%r{<#{sym}>(.*)</#{sym}>})
89
+
90
+ unless match
91
+ nil
92
+ else
93
+ match[1].gsub(/^<!\[CDATA\[(.*)\]\]>$/, '\1')
94
+ end
95
+ end
96
+
97
+ #Provides the 'likely' value from the response.
98
+ def to_s
99
+ @to_s_sym ? self.__send__(@to_s_sym) : super
100
+ end
101
+ alias :to_str :to_s
102
+
103
+ #Provides the 'likely' value from the response, as a symbol.
104
+ def to_sym
105
+ @to_s_sym ? self.to_s.to_sym : super
106
+ end
107
+ end
108
+
109
+ class Utility #:nodoc: all
110
+ class << self
111
+ def camelize(string)
112
+ ((string || '').to_s.split(/_/).inject([]) do |a, s|
113
+ s = s[0].chr.upcase + s[1..s.size] unless a.empty?
114
+ a << s
115
+ a
116
+ end).join('')
117
+ end
118
+ def decamelize(string)
119
+ (string.to_s || '').gsub(/([^_])([A-Z])/, '\1_\2').downcase
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,130 @@
1
+ #--
2
+
3
+ require 'test_helper'
4
+
5
+
6
+
7
+ class ClientTest < Test::Unit::TestCase #:nodoc: all
8
+ def test_construction
9
+ any = 'any'
10
+
11
+ assert_raises Bitly4R::Error do
12
+ # no login
13
+ Bitly4R::Client.new
14
+ end
15
+
16
+ assert_raises Bitly4R::Error do
17
+ # needs API key or password
18
+ Bitly4R::Client.new(:login => any)
19
+ end
20
+
21
+ Bitly4R::Client.new(:login => any, :api_key => any)
22
+ Bitly4R::Client.new(:login => any, :password => any)
23
+ end
24
+
25
+ def test_shorten
26
+ # shorten
27
+ client = new_client
28
+ response = client.shorten LONG_URL
29
+
30
+ assert_is_response_ok response
31
+
32
+ # we get back what we provided
33
+ assert_equal LONG_URL, response.node_key
34
+
35
+ # no assumption as to the value, just how they inter-relate
36
+ hash = response.user_hash
37
+ assert hash && (! hash.empty?)
38
+ short = response.short_url
39
+ assert short =~ Regexp.compile(hash + '$')
40
+ end
41
+
42
+ def test_expand
43
+ # shorten ...
44
+ client = new_client
45
+
46
+ response = client.shorten(LONG_URL)
47
+ assert_is_response_ok response
48
+
49
+ hash = response.user_hash
50
+ short = response.to_s
51
+
52
+ # ... and re-expand
53
+ # again, we don't have to know anything
54
+ response = client.expand(short)
55
+ assert_is_response_ok response
56
+
57
+ assert_equal LONG_URL, response.to_s
58
+
59
+ # note sure what purpose it serves
60
+ # but it will contain a hash-wrapped element
61
+ assert ! response.__send__(hash.to_sym).empty?
62
+
63
+ # ... and re-expand
64
+ # again, we don't have to know anything
65
+ response = client.expand(hash, :hash)
66
+ assert_is_response_ok response
67
+
68
+ assert_equal LONG_URL, response.to_s
69
+
70
+ # again...
71
+ assert ! response.__send__(hash.to_sym).empty?
72
+ end
73
+
74
+ def test_info
75
+ # shorten ...
76
+ client = new_client
77
+
78
+ response = client.shorten(LONG_URL)
79
+ assert_is_response_ok response
80
+
81
+ hash = response.user_hash
82
+ short = response.to_s
83
+
84
+ # short url, with no key limit
85
+ response = client.info(short)
86
+ assert_is_response_ok response
87
+
88
+ assert_equal LONG_URL, response.long_url
89
+
90
+ # hash, key limit
91
+ response = client.info(hash, :hash, :keys => [:long_url, :html_title])
92
+ assert_is_response_ok response
93
+
94
+ # well, we're getting non-included keys back
95
+ # then again, the demo doesn't constrain the keys either
96
+ # http://code.google.com/p/bitly-api/wiki/ApiDocumentation
97
+ ###assert response.thumbnail.empty?
98
+ ###assert ! response.html_title.empty?
99
+ assert_equal LONG_URL, response.to_s
100
+ end
101
+
102
+ def test_stats
103
+ # shorten ...
104
+ client = new_client
105
+
106
+ response = client.shorten(LONG_URL)
107
+ assert_is_response_ok response
108
+
109
+ hash = response.user_hash
110
+ short = response.to_s
111
+
112
+ { :hash => hash, :short_url => short }.each do |param_type, param|
113
+ response = client.info(param, param_type)
114
+ assert_is_response_ok response
115
+
116
+ # we could choose anything
117
+ assert_equal LONG_URL, response.to_s
118
+ end
119
+ end
120
+
121
+ def test_errors
122
+ # errors ...
123
+ client = new_client
124
+
125
+ response = client.errors
126
+ assert ! response.results.empty?
127
+ assert ! response.error_code.empty?
128
+ assert ! response.status_code.empty?
129
+ end
130
+ end
@@ -0,0 +1,50 @@
1
+ #--
2
+
3
+ require 'test_helper'
4
+
5
+
6
+
7
+ class DefinitionsTest < Test::Unit::TestCase #:nodoc: all
8
+ def test_keyed
9
+ # shorten ...
10
+ client = Bitly4R(:login => LOGIN, :api_key => API_KEY)
11
+ short = client.shorten(LONG_URL)
12
+ assert short && (! short.empty?)
13
+
14
+ # ... and expand
15
+ assert_equal LONG_URL, Bitly4R.Keyed(LOGIN, API_KEY).expand(short)
16
+ end
17
+
18
+ def test_authed
19
+ unless PASSWORD
20
+ puts %Q{
21
+ NOTE:
22
+ the text login (#{LOGIN}) did not publish a password
23
+ cannot be tested without that private information
24
+ }.strip
25
+ return
26
+ end
27
+
28
+ # http://code.google.com/p/bitly-api/wiki/ApiDocumentation
29
+ # sure, the documentation claims there's HTTP Auth support
30
+ # but i don't see it yet
31
+ short = nil
32
+ assert_raises Bitly4R::Error do
33
+ # shorten ...
34
+ client = Bitly4R(:login => LOGIN, :password => API_KEY)
35
+ short = client.shorten(LONG_URL)
36
+ assert short && (! short.empty?)
37
+ end
38
+
39
+ # alright, let's use the API key
40
+ client = Bitly4R(:login => LOGIN, :api_key => API_KEY)
41
+ short = client.shorten(LONG_URL)
42
+ assert short && (! short.empty?)
43
+
44
+ # same deal. *sigh*
45
+ assert_raises Bitly4R::Error do
46
+ # ... and expand
47
+ assert_equal LONG_URL, Bitly4R.Authed(LOGIN, PASSWORD).expand(short)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,101 @@
1
+ #--
2
+
3
+ require 'test_helper'
4
+
5
+
6
+
7
+ class ObjectsTest < Test::Unit::TestCase #:nodoc: all
8
+ def test_decamelize
9
+ u = Bitly4R::Utility
10
+
11
+ assert_equal '', u.decamelize(nil)
12
+ assert_equal '', u.decamelize('')
13
+ assert_equal 'a', u.decamelize('a')
14
+ assert_equal 'a', u.decamelize('A')
15
+ assert_equal 'aa', u.decamelize('Aa')
16
+ assert_equal 'a_a', u.decamelize('aA')
17
+ assert_equal 'a_a', u.decamelize('AA')
18
+ assert_equal 'a_a', u.decamelize('a_a')
19
+ assert_equal 'a_a', u.decamelize(:A_A)
20
+ end
21
+
22
+ def test_camelize
23
+ u = Bitly4R::Utility
24
+
25
+ assert_equal '', u.camelize(nil)
26
+ assert_equal '', u.camelize('')
27
+ assert_equal '', u.camelize('_')
28
+ assert_equal 'a', u.camelize('a_')
29
+ assert_equal 'A', u.camelize('_a')
30
+ assert_equal 'aA', u.camelize('aA')
31
+ assert_equal 'aA', u.camelize('a_a')
32
+ assert_equal 'AA', u.camelize(:A_A)
33
+ assert_equal 'aA1.', u.camelize('a_A_1_.')
34
+ end
35
+
36
+ def test_error
37
+ # string only
38
+ e = Bitly4R::Error.new('message')
39
+ assert_equal 'message', e.message
40
+ assert_nil e.cause
41
+
42
+ e = Bitly4R::Error.new(Exception.new('exception'))
43
+ assert_equal 'exception', e.message
44
+ assert_not_nil e.cause
45
+
46
+ # exception
47
+ ee = nil
48
+ begin
49
+ 1/0
50
+ rescue ZeroDivisionError => raised
51
+ ee = raised
52
+ e = Bitly4R::Error.new('rescued', raised)
53
+ end
54
+ assert_equal 'rescued', e.message
55
+ assert_not_nil e.cause
56
+ assert_equal ee, e.cause
57
+ end
58
+
59
+ def test_params
60
+ params = Bitly4R::Params.new
61
+ assert_equal '', params.to_s
62
+
63
+ params[1] = :one
64
+ assert_equal '1=one', params.to_s
65
+
66
+ params[:b] = Exception.new('an exception')
67
+ assert_equal ['1=one', 'b=an+exception'], params.to_s.split('&').sort
68
+
69
+ params['key is'] = '&escaped'
70
+ assert_equal ['1=one', 'b=an+exception', 'key+is=%26escaped'], params.to_s.split('&').sort
71
+ end
72
+
73
+ def test_response
74
+ # nil response *and* to_s symbol
75
+ response = Bitly4R::Response.new(nil)
76
+ assert_nil response.body
77
+ assert_not_nil response.to_s
78
+ assert_nil response.to_sym
79
+
80
+ # crap
81
+ response = Bitly4R::Response.new('string', :bogus)
82
+ assert_equal 'string', response.body
83
+ assert_nil response.to_s
84
+
85
+ # simple
86
+ response = Bitly4R::Response.new('<a>value</a>', :b)
87
+ assert_equal 'value', response.a
88
+ assert_nil response.to_s
89
+
90
+ # camelization
91
+ response = Bitly4R::Response.new('<uD>down</uD><dU>up</dU>', :d_u)
92
+ assert_equal 'down', response.uD
93
+ assert_equal 'down', response.u_d
94
+ assert_equal 'up', response.dU
95
+ assert_equal 'up', response.to_s
96
+
97
+ # CDATA, plus to_sym
98
+ response = Bitly4R::Response.new('<a><b>b</b><cData><![CDATA[data]]></cData><c>c</c></a>', :c_data)
99
+ assert_equal :data, response.to_sym
100
+ end
101
+ end
@@ -0,0 +1,41 @@
1
+ #--
2
+
3
+ #
4
+ # Thank you masked man. Or, rather, Hpricot.
5
+ #
6
+ $LOAD_PATH.unshift File.dirname(File.dirname(__FILE__)) + "/../lib"
7
+
8
+ %w{ rubygems bitly4r test/unit }.each {|lib| require lib }
9
+
10
+ # if we're lucky...
11
+ begin
12
+ require 'ruby-debug'
13
+ rescue Object => e
14
+ end
15
+
16
+
17
+
18
+ class Test::Unit::TestCase #:nodoc: all
19
+
20
+ # trailing slash makes a difference! they normalize!
21
+ LONG_URL = 'http://rubyforge.org/'
22
+
23
+ # credentials from
24
+ # http://code.google.com/p/bitly-api/wiki/ApiDocumentation
25
+ # no password provided
26
+ LOGIN = 'bitlyapidemo'
27
+ API_KEY = 'R_0da49e0a9118ff35f52f629d2d71bf07'
28
+ PASSWORD = nil
29
+
30
+
31
+
32
+ def new_client
33
+ Bitly4R::Client.new(:login => LOGIN, :api_key => API_KEY)
34
+ end
35
+
36
+ def assert_is_response_ok(response)
37
+ assert_equal '0', response.error_code
38
+ assert_equal '', response.error_message
39
+ assert_equal 'OK', response.status_code
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bitly4r
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Dan Foley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-02-02 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "Bitly4R : A Ruby API for the http://bit.ly URL-shortening service"
17
+ email: admin@cantremember.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ - CHANGELOG
25
+ - LICENSE
26
+ files:
27
+ - CHANGELOG
28
+ - LICENSE
29
+ - README.rdoc
30
+ - Rakefile
31
+ - lib/bitly4r/client.rb
32
+ - lib/bitly4r/definitions.rb
33
+ - lib/bitly4r/objects.rb
34
+ - lib/bitly4r.rb
35
+ has_rdoc: true
36
+ homepage: http://wiki.cantremember.com/Bitly4R
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --line-numbers
40
+ - --inline-source
41
+ - --title
42
+ - Bitly4R
43
+ - --main
44
+ - README.rdoc
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "1.8"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project: bitly4r
62
+ rubygems_version: 1.3.1
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: bitly4r 0.1.0
66
+ test_files:
67
+ - test/client_test.rb
68
+ - test/definitions_test.rb
69
+ - test/objects_test.rb
70
+ - test/test_helper.rb