www-delicious 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|