www-delicious 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +5 -0
- data/MIT-LICENSE +21 -0
- data/README +210 -0
- data/Rakefile +137 -0
- data/lib/www/delicious.rb +868 -0
- data/lib/www/delicious/bundle.rb +54 -0
- data/lib/www/delicious/errors.rb +45 -0
- data/lib/www/delicious/post.rb +113 -0
- data/lib/www/delicious/tag.rb +120 -0
- data/lib/www/delicious/version.rb +28 -0
- data/setup.rb +1585 -0
- data/test/helper.rb +108 -0
- data/test/unit/delicious_bundle_test.rb +90 -0
- data/test/unit/delicious_online_test.rb +143 -0
- data/test/unit/delicious_post_test.rb +102 -0
- data/test/unit/delicious_tag_test.rb +140 -0
- data/test/unit/delicious_test.rb +420 -0
- metadata +90 -0
data/CHANGELOG
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2008 Simone Carletti <weppos@weppos.net>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
data/README
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
= WWW::Delicious
|
2
|
+
|
3
|
+
WWW::Delicious is a Ruby client for http://del.icio.us XML API.
|
4
|
+
|
5
|
+
It provides both read and write functionalities. You can read user Posts, Tags
|
6
|
+
and Bundles but you can create new Posts, Tags and Bundles as well.
|
7
|
+
|
8
|
+
|
9
|
+
== Overview
|
10
|
+
|
11
|
+
WWW::Delicious maps all the original del.icio.us API calls and provides some
|
12
|
+
additional convenient methods to perform common tasks.
|
13
|
+
Please read the official documentation (http://del.icio.us/help/api/)
|
14
|
+
to learn more about del.icio.us API.
|
15
|
+
|
16
|
+
WWW::Delicious is 100% compatible with all del.icio.us API constraints,
|
17
|
+
including the requirement to set a valid user agent or wait at least
|
18
|
+
one second between queries.
|
19
|
+
Basically, the main benefit from using this library is that you don't need
|
20
|
+
to take care of all these low level details, if you don't want:
|
21
|
+
WWW::Delicious will try to give you the most with less efforts.
|
22
|
+
|
23
|
+
|
24
|
+
== Dependencies
|
25
|
+
|
26
|
+
* Ruby 1.8.6
|
27
|
+
|
28
|
+
|
29
|
+
== Source
|
30
|
+
|
31
|
+
WWW::Delicious source code is managed via GIT and hosted at GitHub: http://github.com/weppos/www-delicious/.
|
32
|
+
|
33
|
+
|
34
|
+
== Download and Installation
|
35
|
+
|
36
|
+
Installing WWW::Delicious as a GEM is probably the best and easiest way.
|
37
|
+
You must have RubyGems[http://rubyforge.org/projects/rubygems/] installed
|
38
|
+
for the following instruction to work:
|
39
|
+
|
40
|
+
$ sudo gem install www-delicious
|
41
|
+
|
42
|
+
To install the library manually grab the source code from the website,
|
43
|
+
navigate to the root library directory and enter:
|
44
|
+
|
45
|
+
$ sudo ruby setup.rb
|
46
|
+
|
47
|
+
If you need the latest development version you can download the source code
|
48
|
+
from the GIT repositories listed above.
|
49
|
+
Beware that the code might not as stable as the official release.
|
50
|
+
|
51
|
+
|
52
|
+
== Usage
|
53
|
+
|
54
|
+
In order to use this library you need a valid del.icio.us account.
|
55
|
+
Go to http://del.icio.us/ and register for a new account if you don't
|
56
|
+
already have one.
|
57
|
+
|
58
|
+
Then create a valid instance of WWW::Delicious with the account credentials.
|
59
|
+
|
60
|
+
require 'www/delicious'
|
61
|
+
|
62
|
+
# create a new instance with given username and password
|
63
|
+
d = WWW::Delicious.new('username', 'password')
|
64
|
+
|
65
|
+
Now you can use your delicious instance to call on of the API methods available.
|
66
|
+
|
67
|
+
|
68
|
+
=== Last account update
|
69
|
+
|
70
|
+
The following example show you how to get the last account update Time.
|
71
|
+
|
72
|
+
require 'www/delicious'
|
73
|
+
d = WWW::Delicious.new('username', 'password')
|
74
|
+
|
75
|
+
time = d.update # => Fri May 02 18:02:48 UTC 2008
|
76
|
+
|
77
|
+
|
78
|
+
=== Reading Posts
|
79
|
+
|
80
|
+
You can fetch your posts in 3 different ways:
|
81
|
+
|
82
|
+
require 'www/delicious'
|
83
|
+
d = WWW::Delicious.new('username', 'password')
|
84
|
+
|
85
|
+
# 1. get all posts
|
86
|
+
posts = d.posts_all
|
87
|
+
|
88
|
+
# 2. get recent posts
|
89
|
+
posts = d.posts_recent
|
90
|
+
|
91
|
+
# 3. get a single post (the latest one if no criteria is given)
|
92
|
+
posts = d.posts_get(:tag => 'ruby')
|
93
|
+
|
94
|
+
Each post call accepts some options to refine your search.
|
95
|
+
For example, you can always search for posts matching a specific tag.
|
96
|
+
|
97
|
+
posts = d.posts_all(:tag => 'ruby')
|
98
|
+
posts = d.posts_recent(:tag => 'ruby')
|
99
|
+
posts = d.posts_get(:tag => 'ruby')
|
100
|
+
|
101
|
+
|
102
|
+
=== Creating a new Post
|
103
|
+
|
104
|
+
require 'www/delicious'
|
105
|
+
d = WWW::Delicious.new('username', 'password')
|
106
|
+
|
107
|
+
# add a post from options
|
108
|
+
d.posts_add(:url => 'http://www.simonecarletti.com/', :title => 'Cool site!')
|
109
|
+
|
110
|
+
# add a post from WWW::Delicious::Post
|
111
|
+
d.posts_add(WWW::Delicious::Post.new(:url => 'http://www.simonecarletti.com/', :title => 'Cool site!'))
|
112
|
+
|
113
|
+
|
114
|
+
=== Deleting a Posts
|
115
|
+
|
116
|
+
require 'www/delicious'
|
117
|
+
d = WWW::Delicious.new('username', 'password')
|
118
|
+
|
119
|
+
# delete given post (the URL can be either a string or an URI)
|
120
|
+
d.posts_delete('http://www.foobar.com/')
|
121
|
+
|
122
|
+
Note. Actually you cannot delete a post from a WWW::Delicious::Post instance.
|
123
|
+
It means, the following example doesn't work as some ActiveRecord user might expect.
|
124
|
+
|
125
|
+
post = WWW::Delicious::Post.new(:url => 'http://www.foobar.com/')
|
126
|
+
post.delete
|
127
|
+
|
128
|
+
This feature is already in the TODO list. For now, use the following workaround
|
129
|
+
to delete a given Post.
|
130
|
+
|
131
|
+
# delete a post from an existing post = WWW::Delicious::Post
|
132
|
+
d.posts_delete(post.url)
|
133
|
+
|
134
|
+
|
135
|
+
=== Tags
|
136
|
+
|
137
|
+
Working with tags it's really easy. You can get all your tags or rename an existing tag.
|
138
|
+
|
139
|
+
require 'www/delicious'
|
140
|
+
d = WWW::Delicious.new('username', 'password')
|
141
|
+
|
142
|
+
# get all tags
|
143
|
+
tags = d.tags_get
|
144
|
+
|
145
|
+
# print all tag names
|
146
|
+
tags.each { |t| puts t.name }
|
147
|
+
|
148
|
+
# rename the tag gems to gem
|
149
|
+
d.tags_rename('gems', 'gem')
|
150
|
+
|
151
|
+
|
152
|
+
=== Bundles
|
153
|
+
|
154
|
+
WWW::Delicious enables you to get all bundles from given account.
|
155
|
+
|
156
|
+
require 'www/delicious'
|
157
|
+
d = WWW::Delicious.new('username', 'password')
|
158
|
+
|
159
|
+
# get all bundles
|
160
|
+
bundles = d.bundles_all
|
161
|
+
|
162
|
+
# print all bundle names
|
163
|
+
bundles.each { |b| puts b.name }
|
164
|
+
|
165
|
+
You can also create new bundles or delete existing ones.
|
166
|
+
|
167
|
+
require 'www/delicious'
|
168
|
+
d = WWW::Delicious.new('username', 'password')
|
169
|
+
|
170
|
+
# set a new bundle for tags ruby, rails and gem
|
171
|
+
d.bundles_set('MyBundle', %w(ruby rails gem))
|
172
|
+
|
173
|
+
# delete the old bundle
|
174
|
+
d.bundles_delete('OldBundle')
|
175
|
+
|
176
|
+
|
177
|
+
== Documentation
|
178
|
+
|
179
|
+
Visit the website[http://code.simonecarletti.com/] for the full documentation
|
180
|
+
and more examples.
|
181
|
+
|
182
|
+
|
183
|
+
== Website and Project Home
|
184
|
+
|
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/]
|
188
|
+
|
189
|
+
|
190
|
+
== FeedBack and Bug reports
|
191
|
+
|
192
|
+
Feel free to email {Simone Carletti}[mailto:weppos@weppos.net]
|
193
|
+
with any questions or feedback.
|
194
|
+
|
195
|
+
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)
|
205
|
+
|
206
|
+
|
207
|
+
== Changelog
|
208
|
+
|
209
|
+
See CHANGELOG file.
|
210
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake/gempackagetask'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
|
6
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + "/lib")
|
7
|
+
require 'www/delicious'
|
8
|
+
|
9
|
+
|
10
|
+
# Common package properties
|
11
|
+
PKG_NAME = ENV['PKG_NAME'] || WWW::Delicious::GEM
|
12
|
+
PKG_VERSION = ENV['PKG_VERSION'] || WWW::Delicious::VERSION
|
13
|
+
PKG_SUMMARY = "Ruby client for del.icio.us API."
|
14
|
+
PKG_FILES = FileList.new("{lib,test}/**/*.rb") do |fl|
|
15
|
+
fl.exclude 'TODO'
|
16
|
+
fl.include %w(README CHANGELOG MIT-LICENSE)
|
17
|
+
fl.include %w(Rakefile setup.rb)
|
18
|
+
end
|
19
|
+
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
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
#
|
36
|
+
# task::
|
37
|
+
# :rcov
|
38
|
+
# desc::
|
39
|
+
# Create code coverage report.
|
40
|
+
#
|
41
|
+
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
|
50
|
+
end
|
51
|
+
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"
|
137
|
+
end
|
@@ -0,0 +1,868 @@
|
|
1
|
+
#
|
2
|
+
# = WWW::Delicious
|
3
|
+
#
|
4
|
+
# Ruby client for del.icio.us API.
|
5
|
+
#
|
6
|
+
#
|
7
|
+
# Category:: WWW
|
8
|
+
# Package:: WWW::Delicious
|
9
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
10
|
+
#
|
11
|
+
#--
|
12
|
+
# SVN: $Id$
|
13
|
+
#++
|
14
|
+
|
15
|
+
|
16
|
+
require 'net/https'
|
17
|
+
require 'rexml/document'
|
18
|
+
require 'time'
|
19
|
+
require File.dirname(__FILE__) + '/delicious/bundle'
|
20
|
+
require File.dirname(__FILE__) + '/delicious/post'
|
21
|
+
require File.dirname(__FILE__) + '/delicious/tag'
|
22
|
+
require File.dirname(__FILE__) + '/delicious/errors'
|
23
|
+
require File.dirname(__FILE__) + '/delicious/version'
|
24
|
+
|
25
|
+
|
26
|
+
module WWW #:nodoc:
|
27
|
+
|
28
|
+
|
29
|
+
#
|
30
|
+
# = WWW::Delicious
|
31
|
+
#
|
32
|
+
# WWW::Delicious is a Ruby client for http://del.icio.us XML API.
|
33
|
+
#
|
34
|
+
# It provides both read and write functionalities.
|
35
|
+
# You can read user Posts, Tags and Bundles
|
36
|
+
# but you can create new Posts, Tags and Bundles as well.
|
37
|
+
#
|
38
|
+
#
|
39
|
+
# == Basic Usage
|
40
|
+
#
|
41
|
+
# The following is just a basic demonstration of the main features.
|
42
|
+
# See the README file for a deeper explanation about how to get the best
|
43
|
+
# from WWW::Delicious library.
|
44
|
+
#
|
45
|
+
# The examples in this page make the following assumptions
|
46
|
+
# * you have a valid del.icio.us account
|
47
|
+
# * +username+ is your account username
|
48
|
+
# * +password+ is your account password
|
49
|
+
#
|
50
|
+
# In order to make a query you first need to create
|
51
|
+
# a new WWW::Delicious instance as follows:
|
52
|
+
#
|
53
|
+
# require 'www/delicious'
|
54
|
+
#
|
55
|
+
# username = 'my delicious username'
|
56
|
+
# password = 'my delicious password'
|
57
|
+
#
|
58
|
+
# d = WWW::Delicious.new(username, password)
|
59
|
+
#
|
60
|
+
# The constructor accepts some additional options.
|
61
|
+
# For instance, if you want to customize the user agent:
|
62
|
+
#
|
63
|
+
# d = WWW::Delicious.new(username, password, :user_agent => 'FooAgent')
|
64
|
+
#
|
65
|
+
# Now you can use any of the API methods available.
|
66
|
+
#
|
67
|
+
# For example, you may want to know when your account was last updated
|
68
|
+
# to check whether someone else made some changes on behalf of you:
|
69
|
+
#
|
70
|
+
# datetime = d.update # => Wed Mar 12 08:41:20 UTC 2008
|
71
|
+
#
|
72
|
+
# Because the answer is a valid +Time+ instance, you can format it with +strftime+.
|
73
|
+
#
|
74
|
+
# datetime = d.update # => Wed Mar 12 08:41:20 UTC 2008
|
75
|
+
# datetime.strftime('%Y') # => 2008
|
76
|
+
#
|
77
|
+
#
|
78
|
+
#
|
79
|
+
# Category:: WWW
|
80
|
+
# Package:: WWW::Delicious
|
81
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
82
|
+
#
|
83
|
+
class Delicious
|
84
|
+
|
85
|
+
NAME = 'WWW::Delicious'
|
86
|
+
GEM = 'www-delicious'
|
87
|
+
AUTHOR = 'Simone Carletti <weppos@weppos.net>'
|
88
|
+
VERSION = defined?(Version) ? Version::STRING : nil
|
89
|
+
STATUS = 'alpha'
|
90
|
+
BUILD = ''.match(/(\d+)/).to_a.first
|
91
|
+
|
92
|
+
# del.icio.us account username
|
93
|
+
attr_reader :username
|
94
|
+
|
95
|
+
# del.icio.us account password
|
96
|
+
attr_reader :password
|
97
|
+
|
98
|
+
|
99
|
+
# API Base URL
|
100
|
+
API_BASE_URI = 'https://api.del.icio.us'
|
101
|
+
|
102
|
+
# API Path Update
|
103
|
+
API_PATH_UPDATE = '/v1/posts/update';
|
104
|
+
|
105
|
+
# API Path All Bundles
|
106
|
+
API_PATH_BUNDLES_ALL = '/v1/tags/bundles/all';
|
107
|
+
# API Path Set Bundle
|
108
|
+
API_PATH_BUNDLES_SET = '/v1/tags/bundles/set';
|
109
|
+
# API Path Delete Bundle
|
110
|
+
API_PATH_BUNDLES_DELETE = '/v1/tags/bundles/delete';
|
111
|
+
|
112
|
+
# API Path Get Tags
|
113
|
+
API_PATH_TAGS_GET = '/v1/tags/get';
|
114
|
+
# API Path Rename Tag
|
115
|
+
API_PATH_TAGS_RENAME = '/v1/tags/rename';
|
116
|
+
|
117
|
+
# API Path Get Posts
|
118
|
+
API_PATH_POSTS_GET = '/v1/posts/get';
|
119
|
+
# API Path Recent Posts
|
120
|
+
API_PATH_POSTS_RECENT = '/v1/posts/recent';
|
121
|
+
# API Path All Posts
|
122
|
+
API_PATH_POSTS_ALL = '/v1/posts/all';
|
123
|
+
# API Path Posts by Dates
|
124
|
+
API_PATH_POSTS_DATES = '/v1/posts/dates';
|
125
|
+
# API Path Add Post
|
126
|
+
API_PATH_POSTS_ADD = '/v1/posts/add';
|
127
|
+
# API Path Delete Post
|
128
|
+
API_PATH_POSTS_DELETE = '/v1/posts/delete';
|
129
|
+
|
130
|
+
# Time to wait before sending a new request, in seconds
|
131
|
+
SECONDS_BEFORE_NEW_REQUEST = 1
|
132
|
+
|
133
|
+
# Time converter converts a Time instance into the format
|
134
|
+
# requested by Delicious API
|
135
|
+
TIME_CONVERTER = lambda { |time| time.iso8601() }
|
136
|
+
|
137
|
+
|
138
|
+
public
|
139
|
+
#
|
140
|
+
# Constructs a new <tt>WWW::Delicious</tt> object
|
141
|
+
# with given +username+ and +password+.
|
142
|
+
#
|
143
|
+
# # create a new object with username 'user' and password 'psw
|
144
|
+
# obj = WWW::Delicious('user', 'psw')
|
145
|
+
# # => self
|
146
|
+
#
|
147
|
+
# If a block is given, the instance is passed to the block
|
148
|
+
# but this method always returns the instance itself.
|
149
|
+
#
|
150
|
+
# WWW::Delicious('user', 'psw') do |d|
|
151
|
+
# d.update() # => Fri May 02 18:02:48 UTC 2008
|
152
|
+
# end
|
153
|
+
# # => self
|
154
|
+
#
|
155
|
+
# === Options
|
156
|
+
# This class accepts an Hash with additional options.
|
157
|
+
# Here's the list of valid keys:
|
158
|
+
#
|
159
|
+
# <tt>:user_agent</tt>:: User agent to display in HTTP requests.
|
160
|
+
#
|
161
|
+
def initialize(username, password, options = {}, &block) # :yields: delicious
|
162
|
+
@username, @password = username.to_s, password.to_s
|
163
|
+
|
164
|
+
# set API base URI
|
165
|
+
@base_uri = URI.parse(API_BASE_URI)
|
166
|
+
|
167
|
+
init_user_agent(options)
|
168
|
+
init_http_client(options)
|
169
|
+
|
170
|
+
yield self if block_given?
|
171
|
+
self # ensure to always return self even if block is given
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
public
|
176
|
+
#
|
177
|
+
# Returns the reference to current <tt>@http_client</tt>.
|
178
|
+
# The http is always valid unless it has been previously set to +nil+.
|
179
|
+
#
|
180
|
+
# # nil client
|
181
|
+
# obj.http_client # => nil
|
182
|
+
#
|
183
|
+
# # valid client
|
184
|
+
# obj.http_client # => Net::HTTP
|
185
|
+
#
|
186
|
+
def http_client()
|
187
|
+
return @http_client
|
188
|
+
end
|
189
|
+
|
190
|
+
public
|
191
|
+
#
|
192
|
+
# Sets the internal <tt>@http_client</tt> to +client+.
|
193
|
+
#
|
194
|
+
# # nil client
|
195
|
+
# obj.http_client = nil
|
196
|
+
#
|
197
|
+
# # http client
|
198
|
+
# obj.http_client = Net::HTTP.new()
|
199
|
+
#
|
200
|
+
# # invalid client
|
201
|
+
# obj.http_client = 'foo' # => ArgumentError
|
202
|
+
#
|
203
|
+
def http_client=(client)
|
204
|
+
unless client.kind_of?(Net::HTTP) or client.nil?
|
205
|
+
raise ArgumentError, "`client` expected to be a kind of `Net::HTTP`, `#{client.class}` given"
|
206
|
+
end
|
207
|
+
@http_client = client
|
208
|
+
end
|
209
|
+
|
210
|
+
public
|
211
|
+
#
|
212
|
+
# Returns current user agent string.
|
213
|
+
#
|
214
|
+
def user_agent()
|
215
|
+
return @headers['User-Agent']
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
public
|
220
|
+
#
|
221
|
+
# Returns true if given account credentials are valid.
|
222
|
+
#
|
223
|
+
# d = WWW::Delicious.new('username', 'password')
|
224
|
+
# d.valid_account? # => true
|
225
|
+
#
|
226
|
+
# d = WWW::Delicious.new('username', 'invalid_password')
|
227
|
+
# d.valid_account? # => false
|
228
|
+
#
|
229
|
+
# This method is not "exception safe".
|
230
|
+
# It doesn't return false if an HTTP error or any kind of other error occurs,
|
231
|
+
# it raises back the exception to the caller instead.
|
232
|
+
#
|
233
|
+
# Raises:: WWW::Delicious::Error
|
234
|
+
# Raises:: WWW::Delicious::HTTPError
|
235
|
+
# Raises:: WWW::Delicious::ResponseError
|
236
|
+
#
|
237
|
+
def valid_account?
|
238
|
+
update()
|
239
|
+
return true
|
240
|
+
rescue HTTPError => e
|
241
|
+
return false if e.message =~ /invalid username or password/i
|
242
|
+
raise
|
243
|
+
end
|
244
|
+
|
245
|
+
public
|
246
|
+
#
|
247
|
+
# Checks to see when a user last posted an item
|
248
|
+
# and returns the last update +Time+ for the user.
|
249
|
+
#
|
250
|
+
# d.update() # => Fri May 02 18:02:48 UTC 2008
|
251
|
+
#
|
252
|
+
#
|
253
|
+
# Raises:: WWW::Delicious::Error
|
254
|
+
# Raises:: WWW::Delicious::HTTPError
|
255
|
+
# Raises:: WWW::Delicious::ResponseError
|
256
|
+
#
|
257
|
+
def update()
|
258
|
+
response = request(API_PATH_UPDATE)
|
259
|
+
return parse_update_response(response.body)
|
260
|
+
end
|
261
|
+
|
262
|
+
public
|
263
|
+
#
|
264
|
+
# Retrieves all of a user's bundles
|
265
|
+
# and returns an array of <tt>WWW::Delicious::Bundle</tt>.
|
266
|
+
#
|
267
|
+
# d.bundles_all() # => [#<WWW::Delicious::Bundle>, #<WWW::Delicious::Bundle>, ...]
|
268
|
+
# d.bundles_all() # => []
|
269
|
+
#
|
270
|
+
#
|
271
|
+
# Raises:: WWW::Delicious::Error
|
272
|
+
# Raises:: WWW::Delicious::HTTPError
|
273
|
+
# Raises:: WWW::Delicious::ResponseError
|
274
|
+
#
|
275
|
+
def bundles_all()
|
276
|
+
response = request(API_PATH_BUNDLES_ALL)
|
277
|
+
return parse_bundles_all_response(response.body)
|
278
|
+
end
|
279
|
+
|
280
|
+
public
|
281
|
+
#
|
282
|
+
# Assignes a set of tags to a single bundle,
|
283
|
+
# wipes away previous settings for bundle.
|
284
|
+
#
|
285
|
+
# # create from a bundle
|
286
|
+
# d.bundles_set(WWW::Delicious::Bundle.new('MyBundle'), %w(foo bar))
|
287
|
+
#
|
288
|
+
# # create from a string
|
289
|
+
# d.bundles_set('MyBundle', %w(foo bar))
|
290
|
+
#
|
291
|
+
#
|
292
|
+
# Raises:: WWW::Delicious::Error
|
293
|
+
# Raises:: WWW::Delicious::HTTPError
|
294
|
+
# Raises:: WWW::Delicious::ResponseError
|
295
|
+
#
|
296
|
+
def bundles_set(bundle_or_name, tags = [])
|
297
|
+
params = prepare_bundles_set_params(bundle_or_name, tags)
|
298
|
+
response = request(API_PATH_BUNDLES_SET, params)
|
299
|
+
return parse_and_eval_execution_response(response.body)
|
300
|
+
end
|
301
|
+
|
302
|
+
public
|
303
|
+
#
|
304
|
+
# Deletes a bundle.
|
305
|
+
#
|
306
|
+
# # delete from a bundle
|
307
|
+
# d.bundles_delete(WWW::Delicious::Bundle.new('MyBundle'))
|
308
|
+
#
|
309
|
+
# # delete from a string
|
310
|
+
# d.bundles_delete('MyBundle', %w(foo bar))
|
311
|
+
#
|
312
|
+
#
|
313
|
+
# Raises:: WWW::Delicious::Error
|
314
|
+
# Raises:: WWW::Delicious::HTTPError
|
315
|
+
# Raises:: WWW::Delicious::ResponseError
|
316
|
+
#
|
317
|
+
def bundles_delete(bundle_or_name)
|
318
|
+
params = prepare_bundles_delete_params(bundle_or_name)
|
319
|
+
response = request(API_PATH_BUNDLES_DELETE, params)
|
320
|
+
return parse_and_eval_execution_response(response.body)
|
321
|
+
end
|
322
|
+
|
323
|
+
public
|
324
|
+
#
|
325
|
+
# Retrieves the list of tags and number of times used by the user
|
326
|
+
# and returns an array of <tt>WWW::Delicious::Tag</tt>.
|
327
|
+
#
|
328
|
+
# Raises:: WWW::Delicious::Error
|
329
|
+
# Raises:: WWW::Delicious::HTTPError
|
330
|
+
# Raises:: WWW::Delicious::ResponseError
|
331
|
+
#
|
332
|
+
def tags_get()
|
333
|
+
response = request(API_PATH_TAGS_GET)
|
334
|
+
return parse_tags_get_response(response.body)
|
335
|
+
end
|
336
|
+
|
337
|
+
public
|
338
|
+
#
|
339
|
+
# Renames an existing tag with a new tag name.
|
340
|
+
#
|
341
|
+
# Raises:: WWW::Delicious::Error
|
342
|
+
# Raises:: WWW::Delicious::HTTPError
|
343
|
+
# Raises:: WWW::Delicious::ResponseError
|
344
|
+
#
|
345
|
+
def tags_rename(from_name_or_tag, to_name_or_tag)
|
346
|
+
params = prepare_tags_rename_params(from_name_or_tag, to_name_or_tag)
|
347
|
+
response = request(API_PATH_TAGS_RENAME, params)
|
348
|
+
return parse_and_eval_execution_response(response.body)
|
349
|
+
end
|
350
|
+
|
351
|
+
public
|
352
|
+
#
|
353
|
+
# Returns an array of <tt>WWW::Delicious::Post</tt> matching +options+.
|
354
|
+
# If no option is given, the last post is returned.
|
355
|
+
# If no date or url is given, most recent date will be used.
|
356
|
+
#
|
357
|
+
# === Options
|
358
|
+
# <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
|
359
|
+
# <tt>:dt</tt>:: a +Time+ with a date to filter by.
|
360
|
+
# <tt>:url</tt>:: a valid URI to filter by. It can be either an instance of +URI+ or a +String+.
|
361
|
+
#
|
362
|
+
# Raises:: WWW::Delicious::Error
|
363
|
+
# Raises:: WWW::Delicious::HTTPError
|
364
|
+
# Raises:: WWW::Delicious::ResponseError
|
365
|
+
#
|
366
|
+
def posts_get(options = {})
|
367
|
+
params = prepare_posts_params(options.clone, [:dt, :tag, :url])
|
368
|
+
response = request(API_PATH_POSTS_GET, params)
|
369
|
+
return parse_posts_response(response.body)
|
370
|
+
end
|
371
|
+
|
372
|
+
public
|
373
|
+
#
|
374
|
+
# Returns a list of the most recent posts, filtered by argument.
|
375
|
+
#
|
376
|
+
# === Options
|
377
|
+
# <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
|
378
|
+
# <tt>:count</tt>:: number of items to retrieve. (default: 15, maximum: 100).
|
379
|
+
#
|
380
|
+
def posts_recent(options = {})
|
381
|
+
params = prepare_posts_params(options.clone, [:count, :tag])
|
382
|
+
response = request(API_PATH_POSTS_RECENT, params)
|
383
|
+
return parse_posts_response(response.body)
|
384
|
+
end
|
385
|
+
|
386
|
+
public
|
387
|
+
#
|
388
|
+
# Returns a list of the most recent posts, filtered by argument.
|
389
|
+
#
|
390
|
+
# === Options
|
391
|
+
# <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
|
392
|
+
#
|
393
|
+
def posts_all(options = {})
|
394
|
+
params = prepare_posts_params(options.clone, [:tag])
|
395
|
+
response = request(API_PATH_POSTS_ALL, params)
|
396
|
+
return parse_posts_response(response.body)
|
397
|
+
end
|
398
|
+
|
399
|
+
public
|
400
|
+
#
|
401
|
+
# Returns a list of dates with the number of posts at each date.
|
402
|
+
#
|
403
|
+
# === Options
|
404
|
+
# <tt>:tag</tt>:: a tag to filter by. It can be either a <tt>WWW::Delicious::Tag</tt> or a +String+.
|
405
|
+
#
|
406
|
+
def posts_dates(options = {})
|
407
|
+
params = prepare_posts_params(options.clone, [:tag])
|
408
|
+
response = request(API_PATH_POSTS_DATES, params)
|
409
|
+
return parse_posts_dates_response(response.body)
|
410
|
+
end
|
411
|
+
|
412
|
+
public
|
413
|
+
#
|
414
|
+
# Add a post to del.icio.us.
|
415
|
+
#
|
416
|
+
def posts_add(post_or_values)
|
417
|
+
params = prepare_posts_add_params(post_or_values.clone)
|
418
|
+
response = request(API_PATH_POSTS_ADD, params)
|
419
|
+
return parse_and_eval_execution_response(response.body)
|
420
|
+
end
|
421
|
+
|
422
|
+
public
|
423
|
+
#
|
424
|
+
# Deletes a post from del.icio.us.
|
425
|
+
#
|
426
|
+
# === Params
|
427
|
+
# url::
|
428
|
+
# the url of the item.
|
429
|
+
# It can be either an +URI+ or a +String+.
|
430
|
+
#
|
431
|
+
def posts_delete(url)
|
432
|
+
params = prepare_posts_params({:url => url}, [:url])
|
433
|
+
response = request(API_PATH_POSTS_DELETE, params)
|
434
|
+
return parse_and_eval_execution_response(response.body)
|
435
|
+
end
|
436
|
+
|
437
|
+
|
438
|
+
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
|
+
|
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|
|
506
|
+
req = Net::HTTP::Get.new(uri.request_uri, @headers)
|
507
|
+
req.basic_auth(@username, @password)
|
508
|
+
http.request(req)
|
509
|
+
end
|
510
|
+
rescue => e # catch EOFError, SocketError and more
|
511
|
+
raise HTTPError, e.message
|
512
|
+
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}"
|
524
|
+
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)
|
546
|
+
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
|
+
|
557
|
+
if (value = options[:root_name]) && dom.root.name != value
|
558
|
+
raise ResponseError, "Invalid response, root node is not `#{value}`"
|
559
|
+
end
|
560
|
+
if (value = options[:root_text]) && dom.root.text != value
|
561
|
+
raise ResponseError, value
|
562
|
+
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
|
+
|
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 = []
|
610
|
+
|
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 = []
|
623
|
+
|
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
|
641
|
+
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?
|
659
|
+
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?
|
679
|
+
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)
|
695
|
+
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
|
+
end
|
724
|
+
|
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`'
|
747
|
+
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)
|
766
|
+
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())
|
785
|
+
end
|
786
|
+
|
787
|
+
yield(tag) if block_given?
|
788
|
+
raise "Invalid `tag` value supplied" unless tag.api_valid?
|
789
|
+
|
790
|
+
return tag
|
791
|
+
end
|
792
|
+
|
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
|
+
|
829
|
+
module XMLUtils #:nodoc:
|
830
|
+
|
831
|
+
public
|
832
|
+
#
|
833
|
+
# Returns the +xmlattr+ attribute value for given +node+.
|
834
|
+
#
|
835
|
+
# If block is given and attrivute value is not nil
|
836
|
+
# the content of the block is executed.
|
837
|
+
#
|
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.
|
847
|
+
#
|
848
|
+
def attribute_value(xmlattr, &block) #:nodoc:
|
849
|
+
value = if attr = self.attribute(xmlattr.to_s())
|
850
|
+
attr.value()
|
851
|
+
else
|
852
|
+
nil
|
853
|
+
end
|
854
|
+
value = yield value if !value.nil? and block_given?
|
855
|
+
return value
|
856
|
+
end
|
857
|
+
|
858
|
+
end
|
859
|
+
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
|
864
|
+
module REXML # :nodoc:
|
865
|
+
class Element < Parent # :nodoc:
|
866
|
+
include WWW::Delicious::XMLUtils
|
867
|
+
end
|
868
|
+
end
|