bitly4r 0.1.0

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,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