www-delicious 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. data/CHANGELOG.rdoc +41 -0
  2. data/{MIT-LICENSE → MIT-LICENSE.rdoc} +0 -0
  3. data/Manifest +49 -0
  4. data/{README → README.rdoc} +13 -14
  5. data/Rakefile +36 -118
  6. data/TODO +4 -0
  7. data/lib/www/delicious.rb +527 -446
  8. data/lib/www/delicious/bundle.rb +41 -22
  9. data/lib/www/delicious/element.rb +72 -0
  10. data/lib/www/delicious/errors.rb +2 -2
  11. data/lib/www/delicious/post.rb +71 -61
  12. data/lib/www/delicious/tag.rb +43 -62
  13. data/lib/www/delicious/version.rb +1 -1
  14. data/test/fixtures/net_response_invalid_account.yml +25 -0
  15. data/test/fixtures/net_response_success.yml +23 -0
  16. data/test/helper.rb +19 -79
  17. data/test/test_all.rb +17 -0
  18. data/test/test_offline.rb +17 -0
  19. data/test/test_online.rb +19 -0
  20. data/test/testcases/element/bundle.xml +1 -0
  21. data/test/testcases/element/invalid_root.xml +2 -0
  22. data/test/testcases/element/post.xml +2 -0
  23. data/test/testcases/element/post_unshared.xml +2 -0
  24. data/test/testcases/element/tag.xml +1 -0
  25. data/test/testcases/response/bundles_all.xml +5 -0
  26. data/test/testcases/response/bundles_all_empty.xml +2 -0
  27. data/test/testcases/response/bundles_delete.xml +2 -0
  28. data/test/testcases/response/bundles_set.xml +2 -0
  29. data/test/testcases/response/bundles_set_error.xml +2 -0
  30. data/test/testcases/response/posts_add.xml +2 -0
  31. data/test/testcases/response/posts_all.xml +12 -0
  32. data/test/testcases/response/posts_dates.xml +14 -0
  33. data/test/testcases/response/posts_dates_with_tag.xml +14 -0
  34. data/test/testcases/response/posts_delete.xml +2 -0
  35. data/test/testcases/response/posts_get.xml +7 -0
  36. data/test/testcases/response/posts_get_with_tag.xml +6 -0
  37. data/test/testcases/response/posts_recent.xml +19 -0
  38. data/test/testcases/response/posts_recent_with_tag.xml +19 -0
  39. data/test/testcases/response/tags_get.xml +5 -0
  40. data/test/testcases/response/tags_get_empty.xml +2 -0
  41. data/test/testcases/response/tags_rename.xml +2 -0
  42. data/test/testcases/response/update.delicious1.xml +2 -0
  43. data/test/testcases/response/update.xml +3 -0
  44. data/test/unit/bundle_test.rb +62 -0
  45. data/test/unit/delicious_test.rb +186 -238
  46. data/test/unit/online/online_test.rb +147 -0
  47. data/test/unit/post_test.rb +67 -0
  48. data/test/unit/tag_test.rb +68 -0
  49. data/www-delicious.gemspec +146 -0
  50. metadata +85 -31
  51. data/CHANGELOG +0 -5
  52. data/test/unit/delicious_bundle_test.rb +0 -90
  53. data/test/unit/delicious_online_test.rb +0 -143
  54. data/test/unit/delicious_post_test.rb +0 -102
  55. data/test/unit/delicious_tag_test.rb +0 -140
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,41 @@
1
+ = Changelog
2
+
3
+
4
+ == Release 0.2.0
5
+
6
+ * ADDED: :base_uri initialization option allows to create a new instance specifying a custom base_uri for all API calls. This is useful, for example, if you want to use ma.gno.lia Mirror'd APIs (http://wiki.ma.gnolia.com/Mirror%27d_API) instead the del.icio.us one (thanks to Jörg Battermann).
7
+
8
+ * ADDED: two new REXML::Element core extension elements to enhance interaction with node elements.
9
+
10
+ * FIXED: a wrong indentation in README file causes all list items to be rendered as source code.
11
+
12
+ * FIXED: Missing WWW::Delicious::Bundle#to_s method causes a class ID representation to be returned.
13
+
14
+ * FIXED: Missing unit tests for post_ calls (closes #18).
15
+
16
+ * FIXED: Added test for `shared` Post attribute and fixed an issue with duplicate `replace` method definition (closes #11).
17
+
18
+ * CHANGED: improved documentation and added more examples (closes #21).
19
+
20
+ * CHANGED: REXML::Element#attribute_value core extension has been renamed to REXML::Element#if_attribute_value.
21
+
22
+ * CHANGED: Renamed TESTCASE_PATH to TESTCASES_PATH.
23
+
24
+ * CHANGED: WWW::Delicious::Tag, WWW::Delicious::Bundle, WWW::Delicious::Post now extend WWW::Delicious::Element. Simplified classes.
25
+
26
+ * CHANGED: WWW::Delicious::Tag#to_s always returns a string even if name is nil.
27
+
28
+ * CHANGED: WWW::Delicious::Tag :count attribute is now stored and returned as Fixnum instead of String.
29
+
30
+ * CHANGED: Unit test reorganization (closes #22).
31
+
32
+ * CHANGED: Simplified and tidyfied test system with Mocha (closes #19).
33
+
34
+ * CHANGED: Various internal API methods have been renamed for coherence with their new scope.
35
+
36
+ * CHANGED: Integrated Echoe, cleaned Rakefile (closes #23).
37
+
38
+
39
+ == Release 0.1.0 (2008-05-11)
40
+
41
+ * Initial public release.
File without changes
data/Manifest ADDED
@@ -0,0 +1,49 @@
1
+ CHANGELOG.rdoc
2
+ lib/www/delicious/bundle.rb
3
+ lib/www/delicious/element.rb
4
+ lib/www/delicious/errors.rb
5
+ lib/www/delicious/post.rb
6
+ lib/www/delicious/tag.rb
7
+ lib/www/delicious/version.rb
8
+ lib/www/delicious.rb
9
+ MIT-LICENSE.rdoc
10
+ Rakefile
11
+ README.rdoc
12
+ setup.rb
13
+ test/fixtures/net_response_invalid_account.yml
14
+ test/fixtures/net_response_success.yml
15
+ test/helper.rb
16
+ test/test_all.rb
17
+ test/test_offline.rb
18
+ test/test_online.rb
19
+ test/testcases/element/bundle.xml
20
+ test/testcases/element/invalid_root.xml
21
+ test/testcases/element/post.xml
22
+ test/testcases/element/post_unshared.xml
23
+ test/testcases/element/tag.xml
24
+ test/testcases/response/bundles_all.xml
25
+ test/testcases/response/bundles_all_empty.xml
26
+ test/testcases/response/bundles_delete.xml
27
+ test/testcases/response/bundles_set.xml
28
+ test/testcases/response/bundles_set_error.xml
29
+ test/testcases/response/posts_add.xml
30
+ test/testcases/response/posts_all.xml
31
+ test/testcases/response/posts_dates.xml
32
+ test/testcases/response/posts_dates_with_tag.xml
33
+ test/testcases/response/posts_delete.xml
34
+ test/testcases/response/posts_get.xml
35
+ test/testcases/response/posts_get_with_tag.xml
36
+ test/testcases/response/posts_recent.xml
37
+ test/testcases/response/posts_recent_with_tag.xml
38
+ test/testcases/response/tags_get.xml
39
+ test/testcases/response/tags_get_empty.xml
40
+ test/testcases/response/tags_rename.xml
41
+ test/testcases/response/update.delicious1.xml
42
+ test/testcases/response/update.xml
43
+ test/unit/bundle_test.rb
44
+ test/unit/delicious_test.rb
45
+ test/unit/online/online_test.rb
46
+ test/unit/post_test.rb
47
+ test/unit/tag_test.rb
48
+ TODO
49
+ Manifest
@@ -23,7 +23,7 @@ WWW::Delicious will try to give you the most with less efforts.
23
23
 
24
24
  == Dependencies
25
25
 
26
- * Ruby 1.8.6
26
+ * Ruby 1.8.6
27
27
 
28
28
 
29
29
  == Source
@@ -176,15 +176,22 @@ You can also create new bundles or delete existing ones.
176
176
 
177
177
  == Documentation
178
178
 
179
- Visit the website[http://code.simonecarletti.com/] for the full documentation
179
+ Visit the website[http://code.simonecarletti.com/www-delicious] for the full documentation
180
180
  and more examples.
181
181
 
182
182
 
183
+ == Author
184
+
185
+ * {Simone Carletti}[http://www.simonecarletti.com/] <weppos@weppos.net>
186
+
187
+ If you like this software, please {recommend me}[http://www.workingwithrails.com/person/11967-simone-carletti] at Working with Rails.
188
+
189
+
183
190
  == Website and Project Home
184
191
 
185
- * {Project Homepage}[http://code.simonecarletti.com/]
186
- * {At GitHub}[http://github.com/weppos/www-delicious/]
187
- * {At RubyForge}[http://rubyforge.org/projects/www-delicious/]
192
+ * {Project Homepage}[http://code.simonecarletti.com/www-delicious]
193
+ * {At GitHub}[http://github.com/weppos/www-delicious/]
194
+ * {At RubyForge}[http://rubyforge.org/projects/www-delicious/]
188
195
 
189
196
 
190
197
  == FeedBack and Bug reports
@@ -193,15 +200,7 @@ Feel free to email {Simone Carletti}[mailto:weppos@weppos.net]
193
200
  with any questions or feedback.
194
201
 
195
202
  Please submit your bug reports to the Redmine installation for WWW::Delicious
196
- available at http://code.simonecarletti.com/.
197
-
198
-
199
- == TODO
200
-
201
- * allow Tags and Bundles to be passed as params for API calls
202
- * allow Tags, Bundles and Posts to be used for API call (no longer readonly)
203
- * improve the way new posts are created and updated
204
- * more (see issue tracker on the website)
203
+ available at http://code.simonecarletti.com/www-delicious.
205
204
 
206
205
 
207
206
  == Changelog
data/Rakefile CHANGED
@@ -1,137 +1,55 @@
1
1
  require 'rubygems'
2
- require 'rake/gempackagetask'
3
- require 'rake/testtask'
4
- require 'rake/rdoctask'
2
+ require 'echoe'
5
3
 
6
4
  $LOAD_PATH.unshift(File.dirname(__FILE__) + "/lib")
7
5
  require 'www/delicious'
8
6
 
9
-
7
+
10
8
  # Common package properties
11
- PKG_NAME = ENV['PKG_NAME'] || WWW::Delicious::GEM
9
+ PKG_NAME = ENV['PKG_NAME'] || WWW::Delicious::GEM
12
10
  PKG_VERSION = ENV['PKG_VERSION'] || WWW::Delicious::VERSION
13
11
  PKG_SUMMARY = "Ruby client for del.icio.us API."
14
- PKG_FILES = FileList.new("{lib,test}/**/*.rb") do |fl|
12
+ PKG_FILES = FileList.new("{lib,test}/**/*.rb") do |fl|
15
13
  fl.exclude 'TODO'
16
- fl.include %w(README CHANGELOG MIT-LICENSE)
14
+ fl.include %w(README.rdoc CHANGELOG.rdoc MIT-LICENSE.rdoc)
17
15
  fl.include %w(Rakefile setup.rb)
18
16
  end
19
17
  RUBYFORGE_PROJECT = 'www-delicious'
20
-
21
-
22
- #
23
- # task::
24
- # :test
25
- # desc::
26
- # Run all the tests.
27
- #
28
- desc "Run all the tests"
29
- Rake::TestTask.new(:test) do |t|
30
- t.test_files = FileList["test/unit/*.rb"]
31
- t.verbose = true
18
+
19
+ if ENV['SNAPSHOT'].to_i == 1
20
+ PKG_VERSION << "." << Time.now.utc.strftime("%Y%m%d%H%M%S")
21
+ end
22
+
23
+
24
+ Echoe.new(PKG_NAME, PKG_VERSION) do |p|
25
+ p.author = "Simone Carletti"
26
+ p.email = "weppos@weppos.net"
27
+ p.summary = PKG_SUMMARY
28
+ p.description = <<-EOF
29
+ WWW::Delicious is a del.icio.us API client implemented in Ruby. \
30
+ It provides access to all available del.icio.us API queries \
31
+ and returns the original XML response as a friendly Ruby object.
32
+ EOF
33
+ p.url = "http://code.simonecarletti.com/www-delicious"
34
+ p.project = RUBYFORGE_PROJECT
35
+
36
+ p.need_zip = true
37
+ p.rcov_options = ["-x Rakefile -x mocha -x rcov"]
38
+ p.rdoc_pattern = /^(lib|CHANGELOG.rdoc|README.rdoc)/
39
+
40
+ p.development_dependencies = ["rake >=0.8",
41
+ "echoe >=3",
42
+ "mocha >=0.9"]
32
43
  end
33
44
 
34
45
 
35
- #
36
- # task::
37
- # :rcov
38
- # desc::
39
- # Create code coverage report.
40
- #
41
46
  begin
42
- require 'rcov/rcovtask'
43
-
44
- desc "Create code coverage report"
45
- Rcov::RcovTask.new(:rcov) do |t|
46
- t.rcov_opts = ["-xRakefile"]
47
- t.test_files = FileList["test/unit/*.rb"]
48
- t.output_dir = "coverage"
49
- t.verbose = true
47
+ require 'code_statistics'
48
+ desc "Show library's code statistics"
49
+ task :stats do
50
+ CodeStatistics.new(["WWW::Delicious", "lib"],
51
+ ["Tests", "test"]).to_s
50
52
  end
51
53
  rescue LoadError
52
- puts "RCov is not available"
53
- end
54
-
55
-
56
- #
57
- # task::
58
- # :rdoc
59
- # desc::
60
- # Generate RDoc documentation.
61
- #
62
- desc "Generate RDoc documentation"
63
- Rake::RDocTask.new(:rdoc) do |rdoc|
64
- rdoc.rdoc_dir = 'doc'
65
- rdoc.title = "#{PKG_NAME} -- #{PKG_SUMMARY}"
66
- rdoc.main = "README"
67
- rdoc.options << "--inline-source" << "--line-numbers"
68
- rdoc.options << '--charset' << 'utf-8'
69
- rdoc.rdoc_files.include("README", "CHANGELOG", "MIT-LICENSE")
70
- rdoc.rdoc_files.include("lib/**/*.rb")
71
- end
72
-
73
-
74
- unless defined?(Gem)
75
- puts "Package Target requires RubyGEMs"
76
- else
77
-
78
- # Package requirements
79
- GEM_SPEC = Gem::Specification.new do |s|
80
-
81
- s.name = PKG_NAME
82
- s.version = PKG_VERSION
83
- s.summary = PKG_SUMMARY
84
- s.description = <<-EOF
85
- WWW::Delicious is a del.icio.us API client implemented in Ruby. \
86
- It provides access to all available del.icio.us API queries \
87
- and returns the original XML response as a friendly Ruby object.
88
- EOF
89
- s.platform = Gem::Platform::RUBY
90
- s.rubyforge_project = RUBYFORGE_PROJECT
91
-
92
- s.required_ruby_version = '>= 1.8.6'
93
- s.add_dependency('rake', '>= 0.7.3')
94
-
95
- s.files = PKG_FILES.to_a()
96
-
97
- s.has_rdoc = true
98
- s.rdoc_options << "--title" << "#{s.name} -- #{s.summary}"
99
- s.rdoc_options << "--inline-source" << "--line-numbers"
100
- s.rdoc_options << "--main" << "README"
101
- s.rdoc_options << '--charset' << 'utf-8'
102
- s.extra_rdoc_files = %w(README CHANGELOG MIT-LICENSE)
103
-
104
- s.test_files = FileList["test/unit/*.rb"]
105
-
106
- s.author = "Simone Carletti"
107
- s.email = "weppos@weppos.net"
108
- s.homepage = "http://code.simonecarletti.com/www-delicious"
109
-
110
- end
111
-
112
- #
113
- # task::
114
- # :gem
115
- # desc::
116
- # Generate the GEM package and all stuff.
117
- #
118
- Rake::GemPackageTask.new(GEM_SPEC) do |p|
119
- p.gem_spec = GEM_SPEC
120
- p.need_tar = true
121
- p.need_zip = true
122
- end
123
- end
124
-
125
-
126
- #
127
- # task::
128
- # :clean
129
- # desc::
130
- # Clean up generated directories and files.
131
- #
132
- desc "Clean up generated directories and files"
133
- task :clean do
134
- rm_rf "pkg"
135
- rm_rf "doc"
136
- rm_rf "coverage"
54
+ puts "CodeStatistics (Rails) is not available"
137
55
  end
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ == tags_delete(foo)
2
+
3
+ This method should fetch all posts with :tag => foo and update them deleting the foo tag.
4
+
data/lib/www/delicious.rb CHANGED
@@ -94,6 +94,9 @@ module WWW #:nodoc:
94
94
 
95
95
  # del.icio.us account password
96
96
  attr_reader :password
97
+
98
+ # base URI for del.icio.us API
99
+ attr_reader :base_uri
97
100
 
98
101
 
99
102
  # API Base URL
@@ -135,8 +138,7 @@ module WWW #:nodoc:
135
138
  TIME_CONVERTER = lambda { |time| time.iso8601() }
136
139
 
137
140
 
138
- public
139
- #
141
+ #
140
142
  # Constructs a new <tt>WWW::Delicious</tt> object
141
143
  # with given +username+ and +password+.
142
144
  #
@@ -151,19 +153,29 @@ module WWW #:nodoc:
151
153
  # d.update() # => Fri May 02 18:02:48 UTC 2008
152
154
  # end
153
155
  # # => self
156
+ #
157
+ # You can also specify some additional options, including a custom user agent
158
+ # or the base URI for del.icio.us API.
159
+ #
160
+ # WWW::Delicious('user', 'psw', :base_uri => 'https://ma.gnolia.com/api/mirrord') do |d|
161
+ # # the following call is mirrored by ma.gnolia
162
+ # d.update() # => Fri May 02 18:02:48 UTC 2008
163
+ # end
164
+ # # => self
154
165
  #
155
166
  # === Options
156
- # This class accepts an Hash with additional options.
167
+ # This class accepts a Hash with additional options.
157
168
  # Here's the list of valid keys:
158
169
  #
159
170
  # <tt>:user_agent</tt>:: User agent to display in HTTP requests.
171
+ # <tt>:base_uri</tt>:: The base URI to del.icio.us API.
160
172
  #
161
173
  def initialize(username, password, options = {}, &block) # :yields: delicious
162
174
  @username, @password = username.to_s, password.to_s
163
-
175
+
164
176
  # set API base URI
165
- @base_uri = URI.parse(API_BASE_URI)
166
-
177
+ @base_uri = URI.parse(options[:base_uri] || API_BASE_URI)
178
+
167
179
  init_user_agent(options)
168
180
  init_http_client(options)
169
181
 
@@ -171,9 +183,8 @@ module WWW #:nodoc:
171
183
  self # ensure to always return self even if block is given
172
184
  end
173
185
 
174
-
175
- public
176
- #
186
+
187
+ #
177
188
  # Returns the reference to current <tt>@http_client</tt>.
178
189
  # The http is always valid unless it has been previously set to +nil+.
179
190
  #
@@ -182,15 +193,14 @@ module WWW #:nodoc:
182
193
  #
183
194
  # # valid client
184
195
  # obj.http_client # => Net::HTTP
185
- #
196
+ #
186
197
  def http_client()
187
198
  return @http_client
188
199
  end
189
200
 
190
- public
191
- #
201
+ #
192
202
  # Sets the internal <tt>@http_client</tt> to +client+.
193
- #
203
+ #
194
204
  # # nil client
195
205
  # obj.http_client = nil
196
206
  #
@@ -206,18 +216,14 @@ module WWW #:nodoc:
206
216
  end
207
217
  @http_client = client
208
218
  end
209
-
210
- public
211
- #
219
+
212
220
  # Returns current user agent string.
213
- #
214
221
  def user_agent()
215
222
  return @headers['User-Agent']
216
223
  end
217
224
 
218
225
 
219
- public
220
- #
226
+ #
221
227
  # Returns true if given account credentials are valid.
222
228
  #
223
229
  # d = WWW::Delicious.new('username', 'password')
@@ -230,10 +236,11 @@ module WWW #:nodoc:
230
236
  # It doesn't return false if an HTTP error or any kind of other error occurs,
231
237
  # it raises back the exception to the caller instead.
232
238
  #
239
+ #
233
240
  # Raises:: WWW::Delicious::Error
234
241
  # Raises:: WWW::Delicious::HTTPError
235
242
  # Raises:: WWW::Delicious::ResponseError
236
- #
243
+ #
237
244
  def valid_account?
238
245
  update()
239
246
  return true
@@ -242,8 +249,7 @@ module WWW #:nodoc:
242
249
  raise
243
250
  end
244
251
 
245
- public
246
- #
252
+ #
247
253
  # Checks to see when a user last posted an item
248
254
  # and returns the last update +Time+ for the user.
249
255
  #
@@ -253,14 +259,13 @@ module WWW #:nodoc:
253
259
  # Raises:: WWW::Delicious::Error
254
260
  # Raises:: WWW::Delicious::HTTPError
255
261
  # Raises:: WWW::Delicious::ResponseError
256
- #
262
+ #
257
263
  def update()
258
264
  response = request(API_PATH_UPDATE)
259
265
  return parse_update_response(response.body)
260
266
  end
261
267
 
262
- public
263
- #
268
+ #
264
269
  # Retrieves all of a user's bundles
265
270
  # and returns an array of <tt>WWW::Delicious::Bundle</tt>.
266
271
  #
@@ -271,14 +276,13 @@ module WWW #:nodoc:
271
276
  # Raises:: WWW::Delicious::Error
272
277
  # Raises:: WWW::Delicious::HTTPError
273
278
  # Raises:: WWW::Delicious::ResponseError
274
- #
279
+ #
275
280
  def bundles_all()
276
281
  response = request(API_PATH_BUNDLES_ALL)
277
- return parse_bundles_all_response(response.body)
282
+ return parse_bundle_collection(response.body)
278
283
  end
279
284
 
280
- public
281
- #
285
+ #
282
286
  # Assignes a set of tags to a single bundle,
283
287
  # wipes away previous settings for bundle.
284
288
  #
@@ -292,16 +296,20 @@ module WWW #:nodoc:
292
296
  # Raises:: WWW::Delicious::Error
293
297
  # Raises:: WWW::Delicious::HTTPError
294
298
  # Raises:: WWW::Delicious::ResponseError
295
- #
299
+ #
296
300
  def bundles_set(bundle_or_name, tags = [])
297
301
  params = prepare_bundles_set_params(bundle_or_name, tags)
298
302
  response = request(API_PATH_BUNDLES_SET, params)
299
303
  return parse_and_eval_execution_response(response.body)
300
304
  end
301
305
 
302
- public
303
- #
304
- # Deletes a bundle.
306
+ #
307
+ # Deletes +bundle_or_name+ bundle from del.icio.us.
308
+ # +bundle_or_name+ can be either a WWW::Delicious::Bundle instance
309
+ # or a string with the name of the bundle.
310
+ #
311
+ # This method doesn't care whether the exists.
312
+ # If not, the execution will silently return without rising any error.
305
313
  #
306
314
  # # delete from a bundle
307
315
  # d.bundles_delete(WWW::Delicious::Bundle.new('MyBundle'))
@@ -313,47 +321,69 @@ module WWW #:nodoc:
313
321
  # Raises:: WWW::Delicious::Error
314
322
  # Raises:: WWW::Delicious::HTTPError
315
323
  # Raises:: WWW::Delicious::ResponseError
316
- #
324
+ #
317
325
  def bundles_delete(bundle_or_name)
318
326
  params = prepare_bundles_delete_params(bundle_or_name)
319
327
  response = request(API_PATH_BUNDLES_DELETE, params)
320
328
  return parse_and_eval_execution_response(response.body)
321
329
  end
322
330
 
323
- public
324
- #
331
+ #
325
332
  # Retrieves the list of tags and number of times used by the user
326
333
  # and returns an array of <tt>WWW::Delicious::Tag</tt>.
327
334
  #
335
+ # d.tags_get() # => [#<WWW::Delicious::Tag>, #<WWW::Delicious::Tag>, ...]
336
+ # d.tags_get() # => []
337
+ #
338
+ #
328
339
  # Raises:: WWW::Delicious::Error
329
340
  # Raises:: WWW::Delicious::HTTPError
330
341
  # Raises:: WWW::Delicious::ResponseError
331
- #
342
+ #
332
343
  def tags_get()
333
344
  response = request(API_PATH_TAGS_GET)
334
- return parse_tags_get_response(response.body)
345
+ return parse_tag_collection(response.body)
335
346
  end
336
347
 
337
- public
338
- #
348
+ #
339
349
  # Renames an existing tag with a new tag name.
340
350
  #
351
+ # # rename from a tag
352
+ # d.bundles_set(WWW::Delicious::Tag.new('old'), WWW::Delicious::Tag.new('new'))
353
+ #
354
+ # # rename from a string
355
+ # d.bundles_set('old', 'new')
356
+ #
357
+ #
341
358
  # Raises:: WWW::Delicious::Error
342
359
  # Raises:: WWW::Delicious::HTTPError
343
360
  # Raises:: WWW::Delicious::ResponseError
344
- #
361
+ #
345
362
  def tags_rename(from_name_or_tag, to_name_or_tag)
346
363
  params = prepare_tags_rename_params(from_name_or_tag, to_name_or_tag)
347
364
  response = request(API_PATH_TAGS_RENAME, params)
348
365
  return parse_and_eval_execution_response(response.body)
349
366
  end
350
367
 
351
- public
352
- #
368
+ #
353
369
  # Returns an array of <tt>WWW::Delicious::Post</tt> matching +options+.
354
370
  # If no option is given, the last post is returned.
355
371
  # If no date or url is given, most recent date will be used.
356
372
  #
373
+ # d.posts_get() # => [#<WWW::Delicious::Post>, #<WWW::Delicious::Post>, ...]
374
+ # d.posts_get() # => []
375
+ #
376
+ # # get all posts tagged with ruby
377
+ # d.posts_get(:tag => WWW::Delicious::Tag.new('ruby))
378
+ #
379
+ # # get all posts matching URL 'http://www.simonecarletti.com'
380
+ # d.posts_get(:url => URI.parse('http://www.simonecarletti.com'))
381
+ #
382
+ # # get all posts tagged with ruby and matching URL 'http://www.simonecarletti.com'
383
+ # d.posts_get(:tag => WWW::Delicious::Tag.new('ruby),
384
+ # :url => URI.parse('http://www.simonecarletti.com'))
385
+ #
386
+ #
357
387
  # === Options
358
388
  # <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
359
389
  # <tt>:dt</tt>:: a +Time+ with a date to filter by.
@@ -362,30 +392,42 @@ module WWW #:nodoc:
362
392
  # Raises:: WWW::Delicious::Error
363
393
  # Raises:: WWW::Delicious::HTTPError
364
394
  # Raises:: WWW::Delicious::ResponseError
365
- #
395
+ #
366
396
  def posts_get(options = {})
367
397
  params = prepare_posts_params(options.clone, [:dt, :tag, :url])
368
398
  response = request(API_PATH_POSTS_GET, params)
369
- return parse_posts_response(response.body)
399
+ return parse_post_collection(response.body)
370
400
  end
371
401
 
372
- public
373
- #
402
+ #
374
403
  # Returns a list of the most recent posts, filtered by argument.
375
404
  #
405
+ # # get the most recent posts
406
+ # d.posts_recent()
407
+ #
408
+ # # get the 10 most recent posts
409
+ # d.posts_recent(:count => 10)
410
+ #
411
+ #
376
412
  # === Options
377
413
  # <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
378
414
  # <tt>:count</tt>:: number of items to retrieve. (default: 15, maximum: 100).
379
- #
415
+ #
380
416
  def posts_recent(options = {})
381
417
  params = prepare_posts_params(options.clone, [:count, :tag])
382
418
  response = request(API_PATH_POSTS_RECENT, params)
383
- return parse_posts_response(response.body)
419
+ return parse_post_collection(response.body)
384
420
  end
385
-
386
- public
387
- #
388
- # Returns a list of the most recent posts, filtered by argument.
421
+
422
+ #
423
+ # Returns a list of all posts, filtered by argument.
424
+ #
425
+ # # get all (this is a very expensive query)
426
+ # d.posts_all
427
+ #
428
+ # # get all posts matching ruby
429
+ # d.posts_all(:tag => WWW::Delicious::Tag.new('ruby'))
430
+ #
389
431
  #
390
432
  # === Options
391
433
  # <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
@@ -393,13 +435,21 @@ module WWW #:nodoc:
393
435
  def posts_all(options = {})
394
436
  params = prepare_posts_params(options.clone, [:tag])
395
437
  response = request(API_PATH_POSTS_ALL, params)
396
- return parse_posts_response(response.body)
438
+ return parse_post_collection(response.body)
397
439
  end
398
440
 
399
- public
400
441
  #
401
442
  # Returns a list of dates with the number of posts at each date.
402
443
  #
444
+ # # get number of posts per date
445
+ # d.posts_dates
446
+ # # => { '2008-05-05' => 12, '2008-05-06' => 3, ... }
447
+ #
448
+ # # get number posts per date tagged as ruby
449
+ # d.posts_dates(:tag => WWW::Delicious::Tag.new('ruby'))
450
+ # # => { '2008-05-05' => 10, '2008-05-06' => 3, ... }
451
+ #
452
+ #
403
453
  # === Options
404
454
  # <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
405
455
  #
@@ -409,24 +459,38 @@ module WWW #:nodoc:
409
459
  return parse_posts_dates_response(response.body)
410
460
  end
411
461
 
412
- public
413
462
  #
414
463
  # Add a post to del.icio.us.
464
+ # +post_or_values+ can be either a +WWW::Delicious::Post+ instance
465
+ # or a Hash of params. This method accepts all params available
466
+ # to initialize a new +WWW::Delicious::Post+.
467
+ #
468
+ # # add a post from WWW::Delicious::Post
469
+ # d.posts_add(WWW::Delicious::Post.new(:url => 'http://www.foobar.com', :title => 'Hello world!'))
470
+ #
471
+ # # add a post from values
472
+ # d.posts_add(:url => 'http://www.foobar.com', :title => 'Hello world!')
473
+ #
415
474
  #
416
475
  def posts_add(post_or_values)
417
- params = prepare_posts_add_params(post_or_values.clone)
476
+ params = prepare_param_post(post_or_values).to_params
418
477
  response = request(API_PATH_POSTS_ADD, params)
419
478
  return parse_and_eval_execution_response(response.body)
420
479
  end
421
480
 
422
- public
423
481
  #
424
- # Deletes a post from del.icio.us.
482
+ # Deletes the post matching given +url+ from del.icio.us.
483
+ # +url+ can be either an URI instance or a string representation of a valid URL.
484
+ #
485
+ # This method doesn't care whether a post with given +url+ exists.
486
+ # If not, the execution will silently return without rising any error.
487
+ #
488
+ # # delete a post from URI
489
+ # d.post_delete(URI.parse('http://www.foobar.com/'))
490
+ #
491
+ # # delete a post from a string
492
+ # d.post_delete('http://www.foobar.com/')
425
493
  #
426
- # === Params
427
- # url::
428
- # the url of the item.
429
- # It can be either an +URI+ or a +String+.
430
494
  #
431
495
  def posts_delete(url)
432
496
  params = prepare_posts_params({:url => url}, [:url])
@@ -436,431 +500,448 @@ module WWW #:nodoc:
436
500
 
437
501
 
438
502
  protected
439
- #
440
- # Initializes HTTP client.
441
- #
442
- def init_http_client(options)
443
- http = Net::HTTP.new(@base_uri.host, 443)
444
- http.use_ssl = true if @base_uri.scheme == "https"
445
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE # FIXME: not 100% supported
446
- self.http_client = http
447
- end
448
-
449
- protected
450
- #
451
- # Initializes user agent value for HTTP requests.
452
- #
453
- def init_user_agent(options)
454
- user_agent = options[:user_agent] || default_user_agent()
455
- @headers ||= {}
456
- @headers['User-Agent'] = user_agent
457
- end
458
-
459
- protected
460
- #
461
- # Creates and returns the default user agent string.
462
- #
463
- # By default, the user agent is composed by the following schema:
464
- # <tt>NAME/VERSION (Ruby/RUBY_VERSION)</tt>
465
- #
466
- # * +NAME+ is the constant representing this library name
467
- # * +VERSION+ is the constant representing current library version
468
- # * +RUBY_VERSION+ is the version of Ruby interpreter the library is interpreted by
469
- #
470
- def default_user_agent()
471
- return "#{NAME}/#{VERSION} (Ruby/#{RUBY_VERSION})"
472
- end
473
-
474
-
475
- protected
476
- #
477
- # Composes an HTTP query string from an hash of +options+.
478
- #
479
- def http_build_query(params = {})
480
- return params.collect do |k,v|
481
- "#{URI.encode(k.to_s)}=#{URI.encode(v.to_s)}" unless v.nil?
482
- end.compact.join('&')
483
- end
484
503
 
485
- protected
486
- #
487
- # Sends an HTTP GET request to +path+ and appends given +params+.
488
- #
489
- # This method is 100% compliant with Delicious API reference.
490
- # It waits at least 1 second between each HTTP request and
491
- # provides an identifiable user agent by default,
492
- # or the custom user agent set by +user_agent+ option
493
- # when this istance has been created.
494
- #
495
- def request(path, params = {})
496
- raise Error, 'Invalid HTTP Client' unless http_client
497
- wait_before_new_request
498
-
499
- uri = @base_uri.merge(path)
500
- uri.query = http_build_query(params) unless params.empty?
501
-
502
- begin
503
- @last_request = Time.now # see #wait_before_new_request
504
- @last_request_uri = uri # useful for debug
505
- response = http_client.start do |http|
504
+ # Initializes the HTTP client.
505
+ # It automatically enable +use_ssl+ flag according to +@base_uri+ scheme.
506
+ def init_http_client(options)
507
+ http = Net::HTTP.new(@base_uri.host, 443)
508
+ http.use_ssl = true if @base_uri.scheme == "https"
509
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE # FIXME: not 100% supported
510
+ self.http_client = http
511
+ end
512
+
513
+ # Initializes user agent value for HTTP requests.
514
+ def init_user_agent(options)
515
+ user_agent = options[:user_agent] || default_user_agent()
516
+ @headers ||= {}
517
+ @headers['User-Agent'] = user_agent
518
+ end
519
+
520
+ #
521
+ # Creates and returns the default user agent string.
522
+ #
523
+ # By default, the user agent is composed by the following schema:
524
+ # <tt>NAME/VERSION (Ruby/RUBY_VERSION)</tt>
525
+ #
526
+ # * +NAME+ is the constant representing this library name
527
+ # * +VERSION+ is the constant representing current library version
528
+ # * +RUBY_VERSION+ is the version of Ruby interpreter the library is interpreted by
529
+ #
530
+ # default_user_agent
531
+ # # => WWW::Delicious/0.1.0 (Ruby/1.8.6)
532
+ #
533
+ def default_user_agent
534
+ return "#{NAME}/#{VERSION} (Ruby/#{RUBY_VERSION})"
535
+ end
536
+
537
+
538
+ #
539
+ # Composes an HTTP query string from an hash of +options+.
540
+ # The result is URI encoded.
541
+ #
542
+ # http_build_query(:foo => 'baa', :bar => 'boo')
543
+ # # => foo=baa&bar=boo
544
+ #
545
+ def http_build_query(params = {})
546
+ return params.collect do |k,v|
547
+ "#{URI.encode(k.to_s)}=#{URI.encode(v.to_s)}" unless v.nil?
548
+ end.compact.join('&')
549
+ end
550
+
551
+ #
552
+ # Sends an HTTP GET request to +path+ and appends given +params+.
553
+ #
554
+ # This method is 100% compliant with Delicious API reference.
555
+ # It waits at least 1 second between each HTTP request and
556
+ # provides an identifiable user agent by default,
557
+ # or the custom user agent set by +user_agent+ option
558
+ # when this istance has been created.
559
+ #
560
+ # request('/v1/api/path', :foo => 1, :bar => 2)
561
+ # # => sends a GET request to /v1/api/path?foo=1&bar=2
562
+ #
563
+ def request(path, params = {})
564
+ raise Error, 'Invalid HTTP Client' unless http_client
565
+ wait_before_new_request
566
+
567
+ uri = @base_uri.merge(path)
568
+ uri.query = http_build_query(params) unless params.empty?
569
+
570
+ begin
571
+ @last_request = Time.now # see #wait_before_new_request
572
+ @last_request_uri = uri # useful for debug
573
+ response = make_request(uri)
574
+ rescue => e # catch EOFError, SocketError and more
575
+ raise HTTPError, e.message
576
+ end
577
+
578
+ case response
579
+ when Net::HTTPSuccess
580
+ return response
581
+ when Net::HTTPUnauthorized # 401
582
+ raise HTTPError, 'Invalid username or password'
583
+ when Net::HTTPServiceUnavailable # 503
584
+ raise HTTPError, 'You have been throttled.' +
585
+ 'Please ensure you are waiting at least one second before each request.'
586
+ else
587
+ raise HTTPError, "HTTP #{response.code}: #{response.message}"
588
+ end
589
+ end
590
+
591
+ # Makes the real HTTP request to given +uri+ and returns the +response+.
592
+ # This method exists basically to simplify unit testing with mocha.
593
+ def make_request(uri)
594
+ http_client.start do |http|
506
595
  req = Net::HTTP::Get.new(uri.request_uri, @headers)
507
596
  req.basic_auth(@username, @password)
508
597
  http.request(req)
509
598
  end
510
- rescue => e # catch EOFError, SocketError and more
511
- raise HTTPError, e.message
512
599
  end
513
-
514
- case response
515
- when Net::HTTPSuccess
516
- return response
517
- when Net::HTTPUnauthorized # 401
518
- raise HTTPError, 'Invalid username or password'
519
- when Net::HTTPServiceUnavailable # 503
520
- raise HTTPError, 'You have been throttled.' +
521
- 'Please ensure you are waiting at least one second before each request.'
522
- else
523
- raise HTTPError, "HTTP #{response.code}: #{response.message}"
600
+
601
+ #
602
+ # Delicious API reference requests to wait AT LEAST ONE SECOND
603
+ # between queries or the client is likely to get automatically throttled.
604
+ #
605
+ # This method calculates the difference between current time
606
+ # and the last request time and wait for the necessary time to meet
607
+ # SECONDS_BEFORE_NEW_REQUEST requirement.
608
+ #
609
+ # The difference is not rounded. If you only have to wait for 0.034 seconds
610
+ # then your don't have to wait 0 or 1 seconds, but 0.034 seconds!
611
+ #
612
+ def wait_before_new_request
613
+ return unless @last_request # this is the first request
614
+ # puts "Last request at #{TIME_CONVERTER.call(@last_request)}" if debug?
615
+ diff = Time.now - @last_request
616
+ if diff < SECONDS_BEFORE_NEW_REQUEST
617
+ # puts "Sleeping for #{diff} before new request..." if debug?
618
+ sleep(SECONDS_BEFORE_NEW_REQUEST - diff)
619
+ end
524
620
  end
525
- end
526
-
527
- protected
528
- #
529
- # Delicious API reference requests to wait AT LEAST ONE SECOND
530
- # between queries or the client is likely to get automatically throttled.
531
- #
532
- # This method calculates the difference between current time
533
- # and the last request time and wait for the necessary time to meet
534
- # SECONDS_BEFORE_NEW_REQUEST requirement.
535
- #
536
- # The difference is not rounded. If you only have to wait for 0.034 seconds
537
- # then your don't have to wait 0 or 1 seconds, but 0.034 seconds!
538
- #
539
- def wait_before_new_request()
540
- return unless @last_request # this is the first request
541
- # puts "Last request at #{TIME_CONVERTER.call(@last_request)}" if debug?
542
- diff = Time.now - @last_request
543
- if diff < SECONDS_BEFORE_NEW_REQUEST
544
- # puts "Sleeping for #{diff} before new request..." if debug?
545
- sleep(SECONDS_BEFORE_NEW_REQUEST - diff)
621
+
622
+
623
+ #
624
+ # Parses the response <tt>body</tt> and runs a common set of validators.
625
+ # Returns <tt>body</tt> as parsed REXML::Document on success.
626
+ #
627
+ # Raises:: WWW::Delicious::ResponseError in case of invalid response.
628
+ #
629
+ def parse_and_validate_response(body, options = {})
630
+ dom = REXML::Document.new(body)
631
+
632
+ if (value = options[:root_name]) && dom.root.name != value
633
+ raise ResponseError, "Invalid response, root node is not `#{value}`"
634
+ end
635
+ if (value = options[:root_text]) && dom.root.text != value
636
+ raise ResponseError, value
637
+ end
638
+
639
+ return dom
546
640
  end
547
- end
548
-
549
-
550
- protected
551
- #
552
- # Parses the response +body+ and runs a common set of validators.
553
- #
554
- def parse_and_validate_response(body, options = {})
555
- dom = REXML::Document.new(body)
556
641
 
557
- if (value = options[:root_name]) && dom.root.name != value
558
- raise ResponseError, "Invalid response, root node is not `#{value}`"
642
+ #
643
+ # Parses and evaluates the response returned by an execution,
644
+ # usually an update/delete/insert operation.
645
+ #
646
+ # Raises:: WWW::Delicious::ResponseError in case of invalid response
647
+ # Raises:: WWW::Delicious::Error in case of execution error
648
+ #
649
+ def parse_and_eval_execution_response(body)
650
+ dom = parse_and_validate_response(body, :root_name => 'result')
651
+ response = dom.root.if_attribute_value(:code)
652
+ response = dom.root.text if response.nil?
653
+ raise Error, "Invalid response, #{response}" unless %w(done ok).include?(response)
654
+ true
559
655
  end
560
- if (value = options[:root_text]) && dom.root.text != value
561
- raise ResponseError, value
656
+
657
+ # Parses the response of an Update request
658
+ # and returns the update Timestamp.
659
+ def parse_update_response(body)
660
+ dom = parse_and_validate_response(body, :root_name => 'update')
661
+ dom.root.if_attribute_value(:time) { |v| Time.parse(v) }
562
662
  end
563
-
564
- return dom
565
- end
566
-
567
- protected
568
- #
569
- # Parses and evaluates the response returned by an execution,
570
- # usually an update/delete/insert operation.
571
- #
572
- def parse_and_eval_execution_response(body)
573
- dom = parse_and_validate_response(body, :root_name => 'result')
574
-
575
- rsp = dom.root.attribute_value(:code)
576
- rsp = dom.root.text if rsp.nil?
577
- raise Error, "Invalid response, #{rsp}" unless %w(done ok).include?(rsp)
578
- end
579
-
580
- protected
581
- #
582
- # Parses the response of an 'update' request.
583
- #
584
- def parse_update_response(body)
585
- dom = parse_and_validate_response(body, :root_name => 'update')
586
- return dom.root.attribute_value(:time) { |v| Time.parse(v) }
587
- end
588
-
589
- protected
590
- #
591
- # Parses the response of a 'bundles_all' request
592
- # and returns an array of <tt>WWW::Delicious::Bundle</tt>.
593
- #
594
- def parse_bundles_all_response(body)
595
- dom = parse_and_validate_response(body, :root_name => 'bundles')
596
- bundles = []
597
663
 
598
- dom.root.elements.each('bundle') { |xml| bundles << Bundle.from_rexml(xml) }
599
- return bundles
600
- end
601
-
602
- protected
603
- #
604
- # Parses the response of a 'tags_get' request
605
- # and returns an array of <tt>WWW::Delicious::Tag</tt>.
606
- #
607
- def parse_tags_get_response(body)
608
- dom = parse_and_validate_response(body, :root_name => 'tags')
609
- tags = []
664
+ # Parses a response containing a collection of Bundles
665
+ # and returns an array of <tt>WWW::Delicious::Bundle</tt>.
666
+ def parse_bundle_collection(body)
667
+ dom = parse_and_validate_response(body, :root_name => 'bundles')
668
+ dom.root.elements.collect('bundle') { |xml| Bundle.from_rexml(xml) }
669
+ end
610
670
 
611
- dom.root.elements.each('tag') { |xml| tags << Tag.new(xml) }
612
- return tags
613
- end
614
-
615
- protected
616
- #
617
- # Parses a response containing a list of Posts
618
- # and returns an array of <tt>WWW::Delicious::Post</tt>.
619
- #
620
- def parse_posts_response(body)
621
- dom = parse_and_validate_response(body, :root_name => 'posts')
622
- posts = []
671
+ # Parses a response containing a collection of Tags
672
+ # and returns an array of <tt>WWW::Delicious::Tag</tt>.
673
+ def parse_tag_collection(body)
674
+ dom = parse_and_validate_response(body, :root_name => 'tags')
675
+ dom.root.elements.collect('tag') { |xml| Tag.from_rexml(xml) }
676
+ end
623
677
 
624
- dom.root.elements.each('post') { |xml| posts << Post.new(xml) }
625
- return posts
626
- end
627
-
628
- protected
629
- #
630
- # Parses the response of a 'posts_dates' request
631
- # and returns an +Hash+ of date => count.
632
- #
633
- def parse_posts_dates_response(body)
634
- dom = parse_and_validate_response(body, :root_name => 'dates')
635
- results = {}
636
-
637
- dom.root.elements.each('date') do |xml|
638
- date = xml.attribute_value(:date)
639
- count = xml.attribute_value(:count).to_i()
640
- results[date] = count
678
+ # Parses a response containing a collection of Posts
679
+ # and returns an array of <tt>WWW::Delicious::Post</tt>.
680
+ def parse_post_collection(body)
681
+ dom = parse_and_validate_response(body, :root_name => 'posts')
682
+ dom.root.elements.collect('post') { |xml| Post.from_rexml(xml) }
641
683
  end
642
- return results
643
- end
644
-
645
-
646
- protected
647
- #
648
- # Prepares the params for a `bundles_set` request.
649
- #
650
- # === Returns
651
- # An +Hash+ with params to supply to the HTTP request.
652
- #
653
- # Raises::
654
- #
655
- def prepare_bundles_set_params(name_or_bundle, tags = [])
656
- bundle = prepare_param_bundle(name_or_bundle, tags) do |b|
657
- raise Error, "Bundle name is empty" if b.name.empty?
658
- raise Error, "Bundle must contain at least one tag" if b.tags.empty?
684
+
685
+ # Parses the response of a <tt>posts_dates</tt> request
686
+ # and returns a +Hash+ of date => count.
687
+ def parse_posts_dates_response(body)
688
+ dom = parse_and_validate_response(body, :root_name => 'dates')
689
+ return dom.root.get_elements('date').inject({}) do |collection, xml|
690
+ date = xml.if_attribute_value(:date)
691
+ count = xml.if_attribute_value(:count)
692
+ collection.merge({ date => count })
693
+ end
659
694
  end
660
-
661
- return {
662
- :bundle => bundle.name,
663
- :tags => bundle.tags.join(' '),
664
- }
665
- end
666
-
667
- protected
668
- #
669
- # Prepares the params for a `bundles_set` request.
670
- #
671
- # === Returns
672
- # An +Hash+ with params to supply to the HTTP request.
673
- #
674
- # Raises::
675
- #
676
- def prepare_bundles_delete_params(name_or_bundle)
677
- bundle = prepare_param_bundle(name_or_bundle) do |b|
678
- raise Error, "Bundle name is empty" if b.name.empty?
695
+
696
+
697
+ #
698
+ # Prepares the params for a `bundles_set` call
699
+ # and returns a Hash with the params ready for the HTTP request.
700
+ #
701
+ # Raises:: WWW::Delicious::Error
702
+ #
703
+ def prepare_bundles_set_params(name_or_bundle, tags = [])
704
+ bundle = prepare_param_bundle(name_or_bundle, tags) do |b|
705
+ raise Error, "Bundle name is empty" if b.name.empty?
706
+ raise Error, "Bundle must contain at least one tag" if b.tags.empty?
707
+ end
708
+ return { :bundle => bundle.name, :tags => bundle.tags.join(' ') }
679
709
  end
680
- return { :bundle => bundle.name }
681
- end
682
-
683
- protected
684
- #
685
- # Prepares the params for a `tags_rename` request.
686
- #
687
- # === Returns
688
- # An +Hash+ with params to supply to the HTTP request.
689
- #
690
- # Raises::
691
- #
692
- def prepare_tags_rename_params(from_name_or_tag, to_name_or_tag)
693
- from, to = [from_name_or_tag, to_name_or_tag].collect do |v|
694
- prepare_param_tag(v)
710
+
711
+ #
712
+ # Prepares the params for a `bundles_set` call
713
+ # and returns a Hash with the params ready for the HTTP request.
714
+ #
715
+ # Raises:: WWW::Delicious::Error
716
+ #
717
+ def prepare_bundles_delete_params(name_or_bundle)
718
+ bundle = prepare_param_bundle(name_or_bundle) do |b|
719
+ raise Error, "Bundle name is empty" if b.name.empty?
720
+ end
721
+ return { :bundle => bundle.name }
695
722
  end
696
- return { :old => from, :new => to }
697
- end
698
-
699
- protected
700
- #
701
- # Prepares the params for a `post_*` request.
702
- #
703
- # === Returns
704
- # An +Hash+ with params to supply to the HTTP request.
705
- #
706
- # Raises::
707
- #
708
- def prepare_posts_params(params, allowed_params = [])
709
- compare_params(params, allowed_params)
710
-
711
- # we don't need to check whether the following parameters
712
- # are valid for this request because compare_params
713
- # would raise if an invalid param is supplied
714
-
715
- params[:tag] = prepare_param_tag(params[:tag]) if params[:tag]
716
- params[:dt] = TIME_CONVERTER.call(params[:dt]) if params[:dt]
717
- params[:url] = URI.parse(params[:url]) if params[:url]
718
- params[:count] = if value = params[:count]
719
- raise Error, 'Expected `count` <= 100' if value.to_i() > 100 # requirement
720
- value.to_i()
721
- else
722
- 15 # default value
723
+
724
+ #
725
+ # Prepares the params for a `tags_rename` call
726
+ # and returns a Hash with the params ready for the HTTP request.
727
+ #
728
+ # Raises:: WWW::Delicious::Error
729
+ #
730
+ def prepare_tags_rename_params(from_name_or_tag, to_name_or_tag)
731
+ from, to = [from_name_or_tag, to_name_or_tag].collect do |v|
732
+ prepare_param_tag(v)
733
+ end
734
+ return { :old => from, :new => to }
723
735
  end
724
736
 
725
- return params
726
- end
727
-
728
- protected
729
- #
730
- # Prepares the params for a `post_add` request.
731
- #
732
- # === Returns
733
- # An +Hash+ with params to supply to the HTTP request.
734
- #
735
- # Raises::
736
- #
737
- def prepare_posts_add_params(post_or_values)
738
- post = case post_or_values
739
- when WWW::Delicious::Post
740
- post_or_values
741
- when Hash
742
- value = Post.new(post_or_values)
743
- raise ArgumentError, 'Both `url` and `title` are required' unless value.api_valid?
744
- value
745
- else
746
- raise ArgumentError, 'Expected `args` to be `WWW::Delicious::Post` or `Hash`'
737
+ #
738
+ # Prepares the params for a `post_*` call
739
+ # and returns a Hash with the params ready for the HTTP request.
740
+ #
741
+ # Raises:: WWW::Delicious::Error
742
+ #
743
+ def prepare_posts_params(params, allowed_params = [])
744
+ compare_params(params, allowed_params)
745
+
746
+ # we don't need to check whether the following parameters
747
+ # are valid for this request because compare_params
748
+ # would raise if an invalid param is supplied
749
+
750
+ params[:tag] = prepare_param_tag(params[:tag]) if params[:tag]
751
+ params[:dt] = TIME_CONVERTER.call(params[:dt]) if params[:dt]
752
+ params[:url] = URI.parse(params[:url]) if params[:url]
753
+ params[:count] = if value = params[:count]
754
+ raise Error, 'Expected `count` <= 100' if value.to_i() > 100 # requirement
755
+ value.to_i
756
+ else
757
+ 15 # default value
758
+ end
759
+
760
+ return params
747
761
  end
748
- return post.to_params()
749
- end
750
-
751
- protected
752
- #
753
- # Prepares the +bundle+ params.
754
- #
755
- # If +name_or_bundle+ is a string,
756
- # creates a new <tt>WWW::Delicious::Bundle</tt> with
757
- # +name_or_bundle+ as name and a collection of +tags+.
758
- # If +name_or_bundle+, +tags+ is ignored.
759
- #
760
- def prepare_param_bundle(name_or_bundle, tags = [], &block) # :yields: bundle
761
- bundle = case name_or_bundle
762
- when WWW::Delicious::Bundle
763
- name_or_bundle
764
- else
765
- Bundle.new(name_or_bundle.to_s(), tags)
762
+
763
+
764
+ #
765
+ # Prepares the +post+ param for an API request.
766
+ #
767
+ # Creates and returns a <tt>WWW::Delicious::Post</tt> instance from <tt>post_or_values</tt>.
768
+ # <tt>post_or_values</tt> can be either an Hash with post attributes
769
+ # or a <tt>WWW::Delicious::Post</tt> instance.
770
+ #
771
+ def prepare_param_post(post_or_values, &block)
772
+ post = case post_or_values
773
+ when WWW::Delicious::Post
774
+ post_or_values
775
+ when Hash
776
+ Post.new(post_or_values)
777
+ else
778
+ raise ArgumentError, 'Expected `args` to be `WWW::Delicious::Post` or `Hash`'
779
+ end
780
+
781
+ yield(post) if block_given?
782
+ # TODO: validate post with post.validate!
783
+ raise ArgumentError, 'Both `url` and `title` are required' unless post.api_valid?
784
+ post
766
785
  end
767
- yield(bundle) if block_given?
768
- return bundle
769
- end
770
-
771
- protected
772
- #
773
- # Prepares the +tag+ params.
774
- #
775
- # If +name_or_tag+ is a string,
776
- # it creates a new <tt>WWW::Delicious::Tag</tt> with
777
- # +name_or_tag+ as name.
778
- #
779
- def prepare_param_tag(name_or_tag, &block) # :yields: tag
780
- tag = case name_or_tag
781
- when WWW::Delicious::Tag
782
- name_or_tag
783
- else
784
- Tag.new(:name => name_or_tag.to_s())
786
+
787
+ #
788
+ # Prepares the +bundle+ param for an API request.
789
+ #
790
+ # Creates and returns a <tt>WWW::Delicious::Bundle</tt> instance from <tt>name_or_bundle</tt>.
791
+ # <tt>name_or_bundle</tt> can be either a string holding bundle name
792
+ # or a <tt>WWW::Delicious::Bundle</tt> instance.
793
+ #
794
+ def prepare_param_bundle(name_or_bundle, tags = [], &block) # :yields: bundle
795
+ bundle = case name_or_bundle
796
+ when WWW::Delicious::Bundle
797
+ name_or_bundle
798
+ else
799
+ Bundle.new(:name => name_or_bundle, :tags => tags)
800
+ end
801
+
802
+ yield(bundle) if block_given?
803
+ # TODO: validate bundle with bundle.validate!
804
+ bundle
785
805
  end
786
806
 
787
- yield(tag) if block_given?
788
- raise "Invalid `tag` value supplied" unless tag.api_valid?
789
-
790
- return tag
791
- end
807
+ #
808
+ # Prepares the +tag+ param for an API request.
809
+ #
810
+ # Creates and returns a <tt>WWW::Delicious::Tag</tt> instance from <tt>name_or_tag</tt>.
811
+ # <tt>name_or_tag</tt> can be either a string holding tag name
812
+ # or a <tt>WWW::Delicious::Tag</tt> instance.
813
+ #
814
+ def prepare_param_tag(name_or_tag, &block) # :yields: tag
815
+ tag = case name_or_tag
816
+ when WWW::Delicious::Tag
817
+ name_or_tag
818
+ else
819
+ Tag.new(:name => name_or_tag.to_s)
820
+ end
821
+
822
+ yield(tag) if block_given?
823
+ # TODO: validate tag with tag.validate!
824
+ raise "Invalid `tag` value supplied" unless tag.api_valid?
825
+ tag
826
+ end
827
+
828
+ #
829
+ # Checks whether user given +params+ are valid against a defined collection of +valid_params+.
830
+ #
831
+ # === Examples
832
+ #
833
+ # params = {:foo => 1, :bar => 2}
834
+ #
835
+ # compare_params(params, [:foo, :bar])
836
+ # # => valid
837
+ #
838
+ # compare_params(params, [:foo, :bar, :baz])
839
+ # # => raises
840
+ #
841
+ # compare_params(params, [:foo])
842
+ # # => raises
843
+ #
844
+ # Raises:: WWW::Delicious::Error
845
+ #
846
+ def compare_params(params, valid_params)
847
+ raise ArgumentError, "Expected `params` to be a kind of `Hash`" unless params.kind_of?(Hash)
848
+ raise ArgumentError, "Expected `valid_params` to be a kind of `Array`" unless valid_params.kind_of?(Array)
849
+
850
+ # compute options difference
851
+ difference = params.keys - valid_params
852
+ raise Error, "Invalid params: `#{difference.join('`, `')}`" unless difference.empty?
853
+ end
792
854
 
793
- protected
794
- #
795
- # Checks whether user given params are valid against valid params.
796
- #
797
- # === Params
798
- # params::
799
- # an +Hash+ with user given params to validate
800
- # valid_params::
801
- # an +Array+ of valid params keys to check against
802
- #
803
- # === Examples
804
- #
805
- # params = {:foo => 1, :bar => 2}
806
- #
807
- # compare_params(params, [:foo, :bar])
808
- # # => valid
809
- #
810
- # compare_params(params, [:foo, :bar, :baz])
811
- # # => raises
812
- #
813
- # compare_params(params, [:foo])
814
- # # => raises
815
- #
816
- # Raises:: WWW::Delicious::Error
817
- #
818
- def compare_params(params, valid_params)
819
- raise ArgumentError, "Expected `params` to be a kind of `Hash`" unless params.kind_of?(Hash)
820
- raise ArgumentError, "Expected `valid_params` to be a kind of `Array`" unless valid_params.kind_of?(Array)
821
-
822
- # compute options difference
823
- difference = params.keys - valid_params
824
- raise Error,
825
- "Invalid params: `#{difference.join('`, `')}`" unless difference.empty?
826
- end
827
-
828
855
 
829
856
  module XMLUtils #:nodoc:
830
-
831
- public
857
+
832
858
  #
833
- # Returns the +xmlattr+ attribute value for given +node+.
859
+ # Returns the +xmlattr+ attribute value for current <tt>REXML::Element</tt>.
834
860
  #
835
- # If block is given and attrivute value is not nil
861
+ # If block is given and attribute value is not nil,
836
862
  # the content of the block is executed.
837
863
  #
838
- # === Params
839
- # node::
840
- # The REXML::Element node context
841
- # xmlattr::
842
- # A String corresponding to the name of the XML attribute to search for
843
- #
844
- # === Return
845
- # The value of the +xmlattr+ if the attribute exists for given +node+,
846
- # +nil+ otherwise.
864
+ # === Examples
865
+ #
866
+ # dom = REXML::Document.new('<a name="1"><b>foo</b><b>bar</b></a>')
867
+ #
868
+ # dom.root.if_attribute_value(:name)
869
+ # # => "1"
870
+ #
871
+ # dom.root.if_attribute_value(:name) { |v| v.to_i }
872
+ # # => 1
873
+ #
874
+ # dom.root.if_attribute_value(:foo)
875
+ # # => nil
876
+ #
877
+ # dom.root.if_attribute_value(:name) { |v| v.to_i }
878
+ # # => nil
847
879
  #
848
- def attribute_value(xmlattr, &block) #:nodoc:
849
- value = if attr = self.attribute(xmlattr.to_s())
850
- attr.value()
880
+ def if_attribute_value(xmlattr, &block) #:nodoc:
881
+ value = if attr = self.attribute(xmlattr.to_s)
882
+ attr.value
851
883
  else
852
884
  nil
853
885
  end
854
886
  value = yield value if !value.nil? and block_given?
855
- return value
887
+ value
856
888
  end
857
-
858
- end
889
+
890
+ #
891
+ # Returns the value of +expression+ child of this element, if it exists.
892
+ # If blog is given, block is called on +expression+ element value
893
+ # and the result is returned.
894
+ #
895
+ def if_element_value(expression, &block)
896
+ if_element(expression) do |element|
897
+ value = element.text
898
+ value = yield value if block_given?
899
+ value
900
+ end
901
+ end
902
+
903
+ #
904
+ # Executes the content of +block+ on +expression+
905
+ # child of this element, if it exists.
906
+ # Returns the result or +nil+ if +xmlelement+ doesn't exist.
907
+ #
908
+ def if_element(expression, &block)
909
+ raise LocalJumpError, "no block given" unless block_given?
910
+ if element = self.elements[expression.to_s]
911
+ yield element
912
+ else
913
+ nil
914
+ end
915
+ end
916
+
917
+ end # XMLUtils
859
918
 
860
919
  end
861
920
  end
862
921
 
863
922
 
923
+ class Object
924
+
925
+ # An object is blank if it's false, empty, or a whitespace string.
926
+ # For example, "", " ", +nil+, [], and {} are blank.
927
+ #
928
+ # This simplifies
929
+ #
930
+ # if !address.nil? && !address.empty?
931
+ #
932
+ # to
933
+ #
934
+ # if !address.blank?
935
+ #
936
+ # Object#blank? comes from the GEM ActiveSupport 2.1.
937
+ #
938
+ def blank?
939
+ respond_to?(:empty?) ? empty? : !self
940
+ end unless Object.method_defined? :blanks?
941
+
942
+ end
943
+
944
+
864
945
  module REXML # :nodoc:
865
946
  class Element < Parent # :nodoc:
866
947
  include WWW::Delicious::XMLUtils