www-delicious 0.1.0 → 0.2.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.
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