jbgutierrez-delicious_api 1.0.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/README.textile ADDED
@@ -0,0 +1,136 @@
1
+ h1. Delicious_api
2
+
3
+ h2. Description
4
+
5
+ delicious_api is a pure Ruby client for the "Delicious API":http://delicious.com/help/api. It provides an easy
6
+ way to read/write bookmarks, tags and bundles to Delicious accounts.
7
+
8
+ h2. Features
9
+
10
+ * an object oriented design: @Bookmark@, @Tag@ and @Bundle@ classes
11
+ * 100% covered by tests (check it for yourself!)
12
+ * 100% documented
13
+ * 100% Ruby 1.9 compatibility
14
+
15
+ h2. Installation
16
+
17
+ The following gems are required:
18
+
19
+ <pre>
20
+ sudo gem install activesupport hpricot
21
+ </pre>
22
+
23
+ and then you can (and should) get it as a gem:
24
+
25
+ <pre>
26
+ sudo gem install jbgutierrez-delicious_api --source http://gems.github.com
27
+ </pre>
28
+
29
+ h2. Basic Usage
30
+
31
+ h3. Set up a DeliciousApi::Wrapper.
32
+
33
+ <pre>
34
+ DeliciousApi::Base.wrapper = DeliciousApi::Wrapper.new 'account', 'secret'
35
+ </pre>
36
+
37
+ Optionally, name your client (a.k.a. 'user agent') to something identifiable, such as your app's name.
38
+
39
+ <pre>
40
+ DeliciousApi::Base.wrapper = DeliciousApi::Wrapper.new 'account', 'secret', :user_agent => 'your-app-name'
41
+ </pre>
42
+
43
+ In order not to get throttled, it will be a time gap of 1 second between each inner http request. Change this behaviour at your own risk:
44
+
45
+ <pre>
46
+ DeliciousApi::Base.wrapper = DeliciousApi::Wrapper.new 'account', 'secret', :waiting_time_gap => 0
47
+ </pre>
48
+
49
+ h3. Easy examples
50
+
51
+ h4. Bookmarks retrieval
52
+
53
+ <pre>
54
+ bookmark = DeliciousApi::Bookmark.find 'http://www.yahoo.com/'
55
+ bookmarks = DeliciousApi::Bookmark.find_by_date Date.now, :tags => %w[yahoo web search]
56
+ bookmarks = DeliciousApi::Bookmark.find_recent :tag => 'yahoo', :limit => 10
57
+ bookmarks = DeliciousApi::Bookmark.find_all :tag => 'yahoo', :limit => 10
58
+ bookmarks = DeliciousApi::Bookmark.find_all :start_time => 2.days.ago, :end_time => Time.now
59
+ </pre>
60
+
61
+ h4. Creating a new bookmark
62
+
63
+ <pre>
64
+ bookmark = DeliciousApi::Bookmark.new 'http://www.yahoo.com/'
65
+ bookmark.save
66
+ </pre>
67
+
68
+ h4. Deleting a bookmark
69
+
70
+ <pre>
71
+ bookmark = DeliciousApi::Bookmark.find :url => 'http://www.yahoo.com/'
72
+ bookmark.destroy
73
+ </pre>
74
+
75
+ h4. Updating a bookmark
76
+
77
+ <pre>
78
+ bookmark = DeliciousApi::Bookmark.find :url => 'http://www.yahoo.com/'
79
+
80
+ bookmark.description = 'Yahoo!'
81
+ bookmark.extended = 'My favorite site ever'
82
+ bookmark.tags = %w[array of tags]
83
+
84
+ bookmark.save # returns false
85
+ bookmark.save! # returns true
86
+ </pre>
87
+
88
+ h4. Copy Bookmarks between diferent accounts
89
+
90
+ <pre>
91
+ #!/usr/bin/env ruby -wKU
92
+ require 'rubygems'
93
+ require 'delicious_api'
94
+ DeliciousApi::Base.wrapper = DeliciousApi::Wrapper.new 'source_account', 'secret'
95
+ bookmarks = DeliciousApi::Bookmark.all
96
+ DeliciousApi::Base.wrapper = DeliciousApi::Wrapper.new 'target_account', 'secret'
97
+ bookmarks.each { |b| b.save }
98
+ </pre>
99
+
100
+ h2. Todo
101
+
102
+ The following specs are pending:
103
+
104
+ # should check to see when a user last posted an item. (PENDING: Not Yet Implemented)
105
+ # should list dates on which bookmarks were posted (PENDING: Not Yet Implemented)
106
+ # should fetch a change detection manifest of all items (PENDING: Not Yet Implemented)
107
+ # should set User-Agent to something identifiable (PENDING: Not Yet Implemented)
108
+
109
+ h2. Author
110
+
111
+ "Javier Blanco Gutierrez":http://github.com/jbgutierrez
112
+
113
+ h2. License
114
+
115
+ See the terms of usage for the "Delicious API":http://delicious.com/help/api
116
+
117
+ Copyright (c) 2009 by Javier Blanco Gutierrez under the MIT License
118
+
119
+ Permission is hereby granted, free of charge, to any person obtaining
120
+ a copy of this software and associated documentation files (the
121
+ 'Software'), to deal in the Software without restriction, including
122
+ without limitation the rights to use, copy, modify, merge, publish,
123
+ distribute, sublicense, and/or sell copies of the Software, and to
124
+ permit persons to whom the Software is furnished to do so, subject to
125
+ the following conditions:
126
+
127
+ The above copyright notice and this permission notice shall be
128
+ included in all copies or substantial portions of the Software.
129
+
130
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
131
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
132
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
133
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
134
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
135
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
136
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{delicious_api}
5
+ s.version = "1.0.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Javier Blanco Gutierrez"]
9
+ s.date = %q{2009-05-05}
10
+ s.default_executable = %q{delicious_api}
11
+ s.description = %q{delicious_api is a pure Ruby client for the "Delicious API" (http://delicious.com/help/api). It provides an easy
12
+ way to read/write bookmarks, tags and bundles to Delicious accounts.}
13
+ s.email = %q{jbgutierrez@gmail.com}
14
+ s.files = ["README.textile", "delicious_api.gemspec", "lib/delicious_api.rb", "lib/delicious_api/base.rb", "lib/delicious_api/bookmark.rb", "lib/delicious_api/bundle.rb", "lib/delicious_api/extensions.rb", "lib/delicious_api/extensions/hash.rb", "lib/delicious_api/tag.rb", "lib/delicious_api/wrapper.rb", "spec/custom_macros.rb", "spec/custom_matchers.rb", "spec/delicious_api_spec.rb", "spec/delicious_api_spec/base_spec.rb", "spec/delicious_api_spec/bookmark_spec.rb", "spec/delicious_api_spec/bundle_spec.rb", "spec/delicious_api_spec/tag_spec.rb", "spec/delicious_api_spec/wrapper_spec.rb", "spec/spec_helper.rb"]
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://github.com/jbgutierrez/delicious_api}
17
+ s.require_paths = ["lib"]
18
+ s.rubygems_version = %q{1.3.1}
19
+ s.summary = %q{delicious_api is a pure Ruby client for the "Delicious API" (http://delicious.com/help/api). It provides an easy
20
+ way to read/write bookmarks, tags and bundles to Delicious accounts.}
21
+ s.test_files = ["spec/delicious_api_spec.rb", "spec/delicious_api_spec/base_spec.rb", "spec/delicious_api_spec/bookmark_spec.rb", "spec/delicious_api_spec/bundle_spec.rb", "spec/delicious_api_spec/tag_spec.rb", "spec/delicious_api_spec/wrapper_spec.rb"]
22
+
23
+ if s.respond_to? :specification_version then
24
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
25
+ s.specification_version = 2
26
+
27
+ s.add_dependency('activesupport', '>= 2.3.2')
28
+ s.add_dependency('hpricot', '>= 0.8.1')
29
+ s.add_dependency('rspec', '>= 1.2.6')
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+
2
+ module DeliciousApi
3
+
4
+ # Generic Delicious Api exception class.
5
+ class DeliciousApiError < StandardError; end
6
+
7
+ # :stopdoc:
8
+ NAME = 'DeliciousApi'
9
+ VERSION = '1.0.0'
10
+ LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
11
+ PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
12
+ # :startdoc:
13
+
14
+ # Returns the version string for the library.
15
+ #
16
+ def self.version
17
+ VERSION
18
+ end
19
+
20
+ # Returns the library path for the module. If any arguments are given,
21
+ # they will be joined to the end of the libray path using
22
+ # <tt>File.join</tt>.
23
+ #
24
+ def self.libpath( *args )
25
+ args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
26
+ end
27
+
28
+ # Returns the lpath for the module. If any arguments are given,
29
+ # they will be joined to the end of the path using
30
+ # <tt>File.join</tt>.
31
+ #
32
+ def self.path( *args )
33
+ args.empty? ? PATH : ::File.join(PATH, args.flatten)
34
+ end
35
+
36
+ # Utility method used to require all files ending in .rb that lie in the
37
+ # directory below this file that has the same name as the filename passed
38
+ # in. Optionally, a specific _directory_ name can be passed in such that
39
+ # the _filename_ does not have to be equivalent to the directory.
40
+ #
41
+ def self.require_all_libs_relative_to( fname, dir = nil )
42
+ dir ||= ::File.basename(fname, '.*')
43
+ search_me = ::File.expand_path(
44
+ ::File.join(::File.dirname(fname), dir, '**', '*.rb'))
45
+
46
+ Dir.glob(search_me).sort.each {|rb| require rb}
47
+ end
48
+
49
+ end # module DeliciousApi
50
+
51
+ require 'rubygems'
52
+ DeliciousApi.require_all_libs_relative_to(__FILE__)
53
+
54
+ # EOF
@@ -0,0 +1,67 @@
1
+ module DeliciousApi
2
+
3
+ # Raised when the 'wrapper' has not been specified
4
+ class WrapperNotInitialized < DeliciousApiError; end
5
+
6
+ # Raised when you've trying to use the 'wrapper' with incorrect parameters.
7
+ class MissingAttributeError < DeliciousApiError; end
8
+
9
+ # Raised when something goes wrong at the 'wrapper'
10
+ class OperationFailed < DeliciousApiError; end
11
+
12
+ class Base
13
+
14
+ class << self
15
+
16
+ @@wrapper = nil
17
+
18
+ def wrapper=(wrapper) #:nodoc:
19
+ @@wrapper = wrapper
20
+ end
21
+
22
+ def wrapper #:nodoc:
23
+ raise WrapperNotInitialized, "Must initialize the 'wrapper' attribute first" if @@wrapper.nil?
24
+ @@wrapper
25
+ end
26
+
27
+ end
28
+
29
+ # Accepts an instance of a wrapper class, which will be used as a proxy of
30
+ # the methods provided by the subclases.
31
+ # This attribute can be both set and retrieved both at class and instance level by calling +wrapper+.
32
+ attr_accessor :wrapper
33
+
34
+ def wrapper #:nodoc:
35
+ @wrapper || Base.wrapper
36
+ end
37
+
38
+ protected
39
+
40
+ # Assign the values of the Hash +params+ to the attributes of +self+ with the same key name
41
+ def assign(params) #:nodoc:
42
+ params.each_pair do |key, value|
43
+ self.send("#{key}=", value) rescue self.instance_eval("@#{key}=value") rescue next
44
+ end
45
+ end
46
+
47
+ def self.before_assign(attribute, filter) #:nodoc:
48
+ alias_method "old_#{attribute}=", "#{attribute}="
49
+
50
+ module_eval <<-STR
51
+ def #{attribute}=(#{attribute})
52
+ self.old_#{attribute} = #{filter} #{attribute}
53
+ end
54
+ STR
55
+ end
56
+
57
+ def validate_presence_of(*attributes) #:nodoc:
58
+ missing_attributes = []
59
+ attributes.each do |attribute|
60
+ value = self.send(attribute)
61
+ missing_attributes << attribute if value.nil? || value.instance_of?(String) && value.empty?
62
+ end
63
+ raise(MissingAttributeError, "Missing required attribute(s): #{missing_attributes.join(", ")}") unless missing_attributes.empty?
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,162 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+ require 'time'
3
+
4
+ module DeliciousApi
5
+ class Bookmark < Base
6
+
7
+ # The url of the bookmark
8
+ attr_accessor :href
9
+
10
+ # The description of the bookmark
11
+ attr_accessor :description
12
+
13
+ # Notes for the bookmark
14
+ attr_accessor :extended
15
+
16
+ # URL MD5
17
+ attr_reader :hash
18
+
19
+ # Change detection signature
20
+ attr_reader :meta
21
+
22
+ # TODO: Comment!!
23
+ attr_reader :others
24
+
25
+ # <tt>Array</tt> of tags for the bookmark
26
+ attr_accessor :tags
27
+
28
+ # A <tt>Time</tt> object determining the datestamp for the bookmark
29
+ attr_accessor :time
30
+
31
+ # A <tt>boolean</tt> determining whether the bookmark should be marked privated or shared
32
+ attr_accessor :shared
33
+
34
+ before_assign :tags, :filter_tags
35
+ before_assign :time, :filter_time
36
+
37
+ ##
38
+ # Bookmark initialize method
39
+ # ==== Parameters
40
+ # * <tt>href</tt> - The url of the bookmark
41
+ # * <tt>params</tt> - An optional <tt>Hash</tt> containing any combination of the instance attributes
42
+ # ==== Result
43
+ # An new instance of the current class
44
+ def initialize(href, params = {})
45
+ params.symbolize_keys!.assert_valid_keys(:href, :description, :extended, :hash, :meta, :others, :tags, :tag, :time, :shared)
46
+ params.reverse_merge!(:href => href, :tags => params[:tag], :shared => true)
47
+ assign params
48
+ end
49
+
50
+ # Saves the bookmark in del.icio.us
51
+ # ==== Parameters
52
+ # * <tt>replace</tt> - A <tt>boolean</tt> determining whether the bookmark should be replaced when it has already been posted
53
+ def save(replace=false)
54
+ validate_presence_of :href, :description
55
+ options = pack_save_options.merge(:replace => replace ? 'yes' : 'no' )
56
+ wrapper.add_bookmark(@href, @description, options)
57
+ end
58
+
59
+ # Saves the bookmark in Delicious. If it has already been posted, forces replacement
60
+ # of the existing bookmark contents. (Same behaviour as save(true))
61
+ def save!
62
+ save(true) || raise(OperationFailed)
63
+ end
64
+
65
+ # Deletes the bookmark from Delicious
66
+ def destroy
67
+ validate_presence_of :href
68
+ wrapper.delete_bookmark @href || raise(OperationFailed)
69
+ end
70
+
71
+ # Retrieves an <tt>Array</tt> of suggested tag names from Delicious
72
+ def suggested_tags
73
+ validate_presence_of :href
74
+ wrapper.get_suggested_tags_for_url @href
75
+ end
76
+
77
+ # Returns one bookmark
78
+ def self.find(url)
79
+ Base.wrapper.get_bookmark_by_url(url)
80
+ end
81
+
82
+ ##
83
+ # Returns one or more bookmarks on a single day matching the arguments. If no date is given, most recent date will be used.
84
+ # ==== Parameters
85
+ # * <tt>options</tt> - A <tt>Hash</tt> containing any of the following:
86
+ # - <tt>tags</tt>
87
+ # - <tt>hashes</tt>
88
+ # ==== Result
89
+ # An <tt>Array</tt> of <tt>Bookmarks</tt> matching the criteria
90
+ def self.find_by_date(date, options = {})
91
+ options.assert_valid_keys(:tags, :hashes)
92
+ date = date.iso8601 unless date.nil?
93
+ Base.wrapper.get_bookmarks_by_date date, pack_find_options(options)
94
+ end
95
+
96
+ ##
97
+ # Returns a list of the most recent bookmarks, filtered by argument. Maximum 100.
98
+ # ==== Parameters
99
+ # * <tt>options</tt> - A <tt>Hash</tt> containing any of the following:
100
+ # - <tt>limit</tt>
101
+ # - <tt>tag</tt>
102
+ # ==== Result
103
+ # An <tt>Array</tt> of <tt>Bookmarks</tt> matching the criteria
104
+ def self.find_recent(options = {})
105
+ options.assert_valid_keys(:tag, :limit)
106
+ options.reverse_merge!(:limit => 10)
107
+ Base.wrapper.get_recent_bookmarks options
108
+ end
109
+
110
+ ##
111
+ # Returns a list with all the bookmarks, filtered by argument.
112
+ # ==== Parameters
113
+ # * <tt>options</tt> - A <tt>Hash</tt> containing any of the following:
114
+ # - <tt>tag</tt>
115
+ # - <tt>limit</tt>
116
+ # - <tt>start_time</tt>
117
+ # - <tt>end_time</tt>
118
+ # ==== Result
119
+ # An <tt>Array</tt> of <tt>Bookmarks</tt> matching the criteria
120
+ def self.find_all(options = {})
121
+ options.assert_valid_keys(:tag, :limit, :start_time, :end_time)
122
+ wrapper.get_all_bookmarks pack_all_options(options)
123
+ end
124
+
125
+ protected
126
+
127
+ def filter_tags(tags) #:nodoc:
128
+ tags.instance_of?(String) ? tags.split : tags
129
+ end
130
+
131
+ def filter_time(time) #:nodoc:
132
+ time.instance_of?(String) ? Time.xmlschema(time) : time
133
+ end
134
+
135
+ def pack_save_options #:nodoc:
136
+ opt = {}
137
+ opt[:tags] = @tags.nil? ? '' : @tags.join(' ')
138
+ opt[:extended] = @extended unless @extended.nil?
139
+ opt[:dt] = @time.iso8601 unless @time.nil?
140
+ opt[:shared] = @shared ? 'yes' : 'no' unless @shared.nil?
141
+ opt
142
+ end
143
+
144
+ def self.pack_find_options(options) #:nodoc:
145
+ opt = {}
146
+ opt[:tag] = options[:tags].join(' ') unless options[:tags].nil?
147
+ opt[:hashes] = options[:hashes].join(' ') unless options[:hashes].nil?
148
+ opt[:meta] = 'yes'
149
+ opt
150
+ end
151
+
152
+ def self.pack_all_options(options) #:nodoc:
153
+ opt = {}
154
+ opt[:tag] = options[:tag] unless options[:tag].nil?
155
+ opt[:limit] = options[:limit] unless options[:limit].nil?
156
+ opt[:fromdt] = options[:start_time].iso8601 unless options[:start_time].nil?
157
+ opt[:todt] = options[:end_time].iso8601 unless options[:end_time].nil?
158
+ opt
159
+ end
160
+
161
+ end
162
+ end