blogmarks 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,7 @@
1
+ = BlogMarks Changelog
2
+
3
+ == Version 0.1.0
4
+
5
+ Initial version release.
6
+
7
+ * This first version let you search public/private marks
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Jonathan Tron
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.
data/README ADDED
@@ -0,0 +1,44 @@
1
+ = Project: Builder
2
+
3
+ == Goal
4
+
5
+ Provide a simple way to interact with BlogMarks.net online bookmarking system.
6
+
7
+ == Classes
8
+
9
+ BlogMarks::Client:: Client to interact with BlogMarks.net API
10
+ BlogMarks::Tag:: Define a Tag
11
+ BlogMarks::Mark:: Define a Mark
12
+ BlogMarks::Feed:: Define a Feed
13
+
14
+ == Usage
15
+
16
+ # Accessing last blogmarks :
17
+ client = BlogMarks::Client.new('username', 'mypassword')
18
+ client.find_marks # Without other options, the api will return the last 30 marks (subject to change)
19
+ # then get the default feed and iterate over fetched mark
20
+ client.feed.marks.each do |mark|
21
+ puts "Mark Link : " + mark.link_related
22
+ puts "Mark Summary : " + mark.summary
23
+ end
24
+
25
+ or
26
+
27
+ # Accessing YOUR last blogmarks :
28
+ client = BlogMarks::Client.new('username', 'mypassword')
29
+ client.find_my_marks # Without other options, the api will return your last 30 marks (subject to change)
30
+
31
+ or
32
+
33
+ # Accessing only the 10 latest blogmarks
34
+ client = BlogMarks::Client.new('username', 'mypassword')
35
+ client.find_marks( :last => 10 )
36
+
37
+ TODO : Show some example to post/update/edit blogmarks and tags
38
+
39
+ == Contact
40
+
41
+ Author:: Jonathan Tron
42
+ Email:: jonathan@tron.name
43
+ Home Page:: http://projects.tron.name/ruby/blogmarks/
44
+ License:: MIT Licence (http://www.opensource.org/licenses/mit-license.html)
data/lib/blogmarks.rb ADDED
@@ -0,0 +1,29 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require File.dirname(__FILE__) + '/blogmarks/class_attribute_accessors'
26
+ require File.dirname(__FILE__) + '/blogmarks/feed'
27
+ require File.dirname(__FILE__) + '/blogmarks/tag'
28
+ require File.dirname(__FILE__) + '/blogmarks/mark'
29
+ require File.dirname(__FILE__) + '/blogmarks/client'
@@ -0,0 +1,67 @@
1
+ #--
2
+ # Copyright (c) 2005 David Heinemeier Hansson
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ # Extends the class object with class and instance accessors for class attributes,
25
+ # just like the native attr* accessors for instance attributes.
26
+ class Class # :nodoc:
27
+ def cattr_reader(*syms)
28
+ syms.flatten.each do |sym|
29
+ class_eval(<<-EOS, __FILE__, __LINE__)
30
+ unless defined? @@#{sym}
31
+ @@#{sym} = nil
32
+ end
33
+
34
+ def self.#{sym}
35
+ @@#{sym}
36
+ end
37
+
38
+ def #{sym}
39
+ @@#{sym}
40
+ end
41
+ EOS
42
+ end
43
+ end
44
+
45
+ def cattr_writer(*syms)
46
+ syms.flatten.each do |sym|
47
+ class_eval(<<-EOS, __FILE__, __LINE__)
48
+ unless defined? @@#{sym}
49
+ @@#{sym} = nil
50
+ end
51
+
52
+ def self.#{sym}=(obj)
53
+ @@#{sym} = obj
54
+ end
55
+
56
+ def #{sym}=(obj)
57
+ @@#{sym} = obj
58
+ end
59
+ EOS
60
+ end
61
+ end
62
+
63
+ def cattr_accessor(*syms)
64
+ cattr_reader(*syms)
65
+ cattr_writer(*syms)
66
+ end
67
+ end
@@ -0,0 +1,301 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ # Standard ruby libs
26
+ require 'uri'
27
+ require 'net/http'
28
+ require 'base64'
29
+ require 'digest/md5'
30
+ require 'digest/sha1'
31
+ require 'rexml/document'
32
+
33
+ # Gem dependencies
34
+ require 'rubygems'
35
+ require 'builder'
36
+
37
+ # Inner dependencies
38
+ require File.dirname(__FILE__) + '/feed'
39
+
40
+ module BlogMarks
41
+
42
+ class Client
43
+ # API URL to interact with BlogMarks.net
44
+ cattr_accessor :api_url
45
+ @@api_url = 'http://api.blogmarks.net'
46
+ # User agent to report to blogmarks server
47
+ cattr_reader :user_agent
48
+ @@user_agent = 'Ruby BlogmarksClient Library'
49
+ # Text used to compute the nonce for authentication
50
+ cattr_accessor :private_text
51
+ @@private_text = 'Ipsum Dolor Sit Amet'
52
+
53
+ # Store the fetched Feed
54
+ attr_reader :feed
55
+ # User login
56
+ attr_accessor :user
57
+ # User password
58
+ attr_accessor :password
59
+
60
+ # Blogmarks client constructor
61
+ def initialize( user, password)
62
+ @user = user
63
+ @password = password
64
+ @feed = nil
65
+ end
66
+
67
+ # Add a Mark
68
+ def add_mark( mark )
69
+
70
+ end
71
+
72
+ # Update a Mark
73
+ def update_mark( mark )
74
+
75
+ end
76
+
77
+ # Delete a Mark
78
+ def delete_mark( mark )
79
+
80
+ end
81
+
82
+ # Find some marks in public marks
83
+ #
84
+ # This function accept an options hash, where options can be :
85
+ #
86
+ # * <tt>:last</tt> <i>integer from -1 to ...</i>
87
+ # Limit number of results (default: 30)
88
+ # * <tt>:offset</tt> <i>positive integer</i>
89
+ # First result offset, for paging... (default: 0)
90
+ # * <tt>:search</tt> <i>string</i>
91
+ # Searches for string in blogmarks (title, desc) + tags
92
+ # * <tt>:tags</tt> <i>string</i>
93
+ # Searches for requested public tags
94
+ # * <tt>:private_tags</tt> <i>string</i>
95
+ # Recherche les blogmarks contenant les tags privés indiqués (*).
96
+ # * <tt>:related</tt> <i>regexp</i>
97
+ # ???
98
+ # * <tt>:author</tt> <i>string <user></i>
99
+ # Searches only in <user>'s marks
100
+ # * <tt>:private</tt> <i>true | false</i>
101
+ # If true, also search in private marks (default: false)
102
+ # * <tt>:month</tt> <i>integer 01 to 12</i>
103
+ # if used with year, search only in marks posted durint that month
104
+ # * <tt>:year</tt> <i>integer YYYY</i>
105
+ # Restrict search to marks posted during that year
106
+ # * <tt>:level</tt> <i>positive integer <level></i>
107
+ # Restrict search to marks posted by at least <level> users
108
+ # * <tt>:order_by</tt> <i>issued|modified|created|popularity</i>
109
+ # Results sorting mode (default: issued)
110
+ # * <tt>:order_type</tt> <i>asc|desc</i>
111
+ # Results dorting direction (default: desc)
112
+ def find_marks( options={} )
113
+ url = build_url( "#{api_url}/marks", options )
114
+
115
+ res = send_request(url)
116
+
117
+ parse_xml_response(res)
118
+ end
119
+
120
+ # Find some marks in my marks
121
+ #
122
+ # This function is an alias to find_mark which set the :author option to current user in options hash.
123
+ # For accepted options see #find_marks
124
+ def find_my_marks( options={} )
125
+ options[:author] = @user
126
+ find_marks(options)
127
+ end
128
+
129
+ protected
130
+
131
+ def send_request( url, verb='GET', content=nil) #:nodoc:
132
+ nonce = get_nonce
133
+ creation_timestamp = Time.new.strftime("%Y-%m-%d\T%H:%M:%S\Z")
134
+ password_hash = Digest::MD5.hexdigest(password)
135
+ password_digest = Base64.encode64(Digest::SHA1.hexdigest("#{nonce}#{creation_timestamp}#{password_hash}"))
136
+
137
+ uri = URI.parse(url)
138
+ req = nil
139
+
140
+ case verb
141
+ when 'POST'
142
+ req = Net::HTTP::Post.new(uri.request_uri)
143
+ when 'PUT'
144
+ req = Net::HTTP::Put.new(uri.request_uri)
145
+ when 'DELETE'
146
+ req = Net::HTTP::Delete.new(uri.request_uri)
147
+ else
148
+ req = Net::HTTP::Get.new(uri.request_uri)
149
+ end
150
+
151
+ req['X-WSSE'] = "UsernameToken Username=\"#{user}\", PasswordDigest=\"#{password_digest}\", Nonce=\"#{nonce}\", Created=\"#{creation_timestamp}\""
152
+ req['User-Agent'] = user_agent
153
+
154
+ res = Net::HTTP.new( uri.host, uri.port ).start do | http |
155
+ http.request(req)
156
+ end
157
+
158
+ case verb
159
+ when 'POST'
160
+ return true if res.kind_of? Net::HTTPCreated
161
+ when 'PUT'
162
+ return true if res.kind_of? Net::HTTPResetContent
163
+ when 'DELETE'
164
+ return true if res.kind_of? Net::HTTPOK
165
+ when 'GET'
166
+ return res.body if res.kind_of? Net::HTTPOK
167
+ end
168
+
169
+ # If reached we have an error
170
+ handle_error(res)
171
+ end
172
+
173
+ # Build an xml entry element to use during Mark post from params
174
+ #--
175
+ # TODO : Remove/Modify this method as we now pass a Mark Object to POST/DELETE/UPDATE method
176
+ #++
177
+ def params_to_xml( params={} ) #:nodoc:
178
+
179
+ return 'No Link' if !params.has_key? :url
180
+ return 'No Title' if !params.has_key? :title
181
+
182
+ buffer = ""
183
+ xm = Builder::XmlMarkup.new( :target => buffer )
184
+
185
+ xm.instruct!
186
+
187
+ xm.entry {
188
+
189
+ xm.title(params[:title])
190
+
191
+ xm.link( "rel" => "related", "href" => "#{params[:url]}")
192
+
193
+ xm.summary( "#{params[:description]}" ) if params.has_key? :description
194
+
195
+ xm.published( "0000-00-00" ) if params.has_key?(:private) && params[:private]
196
+
197
+ xm.published( Time.new.strftime("%Y-%m-%d\T%H:%M:%S\Z") ) if params.has_key?(:private) && !params[:private]
198
+
199
+ params[:tags].each do |tag|
200
+ xm.category( "term" => "#{api_url}/tags/", "sheme" => "#{tag}") if !tag.empty?
201
+ end if params.has_key? :tags
202
+
203
+ params[:private_tags].each do |tag|
204
+ xm.category( "term" => "#{api_url}/tags/user/#{user}/", "sheme" => "#{tag}") if !tag.empty?
205
+ end if params.has_key? :private_tags
206
+
207
+ xm.link( "rel" => "via", "href" => "#{params[:via]}" ) if params.has_key? :via
208
+ }
209
+
210
+ buffer
211
+ end
212
+
213
+ # Parse raw xml data and if successful build a Feed from it
214
+ def parse_xml_response( xml ) #:nodoc:
215
+ xml_doc = create_xml_document( xml )
216
+
217
+ # If we successfuly build an REXML document, let's begin the work :)
218
+ if !xml_doc.nil?
219
+ @feed = BlogMarks::Feed.build_from_xml_element(xml_doc.root)
220
+ end
221
+ end
222
+
223
+ # Get a unique nonce to use for authentication
224
+ # As specified in API Spec,
225
+ # nonce is a string containing "timestamp SHA1(timestamp:private_text)"
226
+ def get_nonce
227
+ timestamp = Time.new.utc.strftime("%Y-%m-%d\T%H:%M:%S\Z")
228
+ temp = Digest::SHA1.hexdigest("#{timestamp}:#{private_text}")
229
+ return "#{timestamp} #{temp}"
230
+ end
231
+
232
+ # Build an URI with querystring based on params as in BlogMarks API Spec
233
+ # * Multiple values for a param will be delimited by a space
234
+ # * Multi-words values will be double-quoted
235
+ def build_url( ressource, params={} ) #:nodoc:
236
+ querystring = ""
237
+
238
+ params.each do |index, param|
239
+ querystring << "&" unless querystring.empty?
240
+
241
+ # Handle params composed of multiple value
242
+ if param.kind_of? Array
243
+ # Let's our query play nicely with tags composed of multiple word
244
+ param.collect! do |el|
245
+ if el =~ /\s+/
246
+ "\"#{el}\""
247
+ else
248
+ el
249
+ end
250
+ end
251
+ param = param.join(' ')
252
+ end
253
+
254
+ querystring << "#{index}=#{param}"
255
+ end
256
+
257
+
258
+ ressource << "?" << querystring if !querystring.empty?
259
+
260
+ return URI.escape(ressource)
261
+ end
262
+
263
+ # Handle error when occurs
264
+ # Raise a simple message explaining error
265
+ #--
266
+ # TODO : add a simple subclass to base Exception with neat message access
267
+ #++
268
+ def handle_error( response_error )
269
+ message = ''
270
+ message = "HTTP authentication failed" if response_error.kind_of? Net::HTTPUnauthorized
271
+ message = "Requested URI does not reference a ressource or search returned no results." if response_error.kind_of? Net::HTTPNotFound
272
+ message = "Server-side error occured" if response_error.kind_of? Net::HTTPInternalServerError
273
+
274
+ if response_error.kind_of? Net::HTTPInternalServerError
275
+ xml_doc = create_xml_document( response_error )
276
+ unless xml_doc.nil?
277
+ raise(message + "\n" + xml_doc.root.text)
278
+ end
279
+ end
280
+ end
281
+
282
+ # Parse a string containing raw xml data and return its REXML::Document representation or nil if failed
283
+ def create_xml_document( xml )
284
+ xml_doc = nil
285
+ begin
286
+ begin
287
+ # Let's try to parse the xml
288
+ xml_doc = REXML::Document.new( xml, :ignore_whitespace_nodes => :all )
289
+ rescue Object
290
+ # If error occurs try to repair it with the htree
291
+ xml_doc = REXML::HTree.parse( xml ).to_rexml
292
+ end
293
+ rescue Object
294
+ xml_doc = nil
295
+ end
296
+
297
+ return xml_doc
298
+ end
299
+ end
300
+
301
+ end
@@ -0,0 +1,98 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require 'uri'
26
+ require 'rexml/document'
27
+ require File.dirname(__FILE__) + '/mark'
28
+
29
+ module BlogMarks
30
+
31
+ class Feed
32
+ # Feed title
33
+ attr_reader :title
34
+ # Blogmarks.net corresponding feed page
35
+ attr_reader :link_alternate
36
+ # Previous Feed URI
37
+ attr_reader :link_prev
38
+ # Next Feed URI
39
+ attr_reader :link_next
40
+ # Feed update date
41
+ attr_reader :updated
42
+
43
+ # Feed Marks
44
+ attr_reader :marks
45
+
46
+ # Create a feed from params
47
+ #
48
+ # params hash can contains any of the object vars
49
+ def initialize( params={} )
50
+ @title = params[:title] || nil
51
+ @alternate = params[:link_alternate] || nil
52
+ @link_prev = params[:link_prev] || nil
53
+ @link_next = params[:link_next] || nil
54
+ @updated = params[:updated] || nil
55
+ @marks = params[:marks] || []
56
+ end
57
+
58
+
59
+ class << self # Class methods
60
+
61
+ # Parse an REXML::Element corresponding to the feed
62
+ def build_from_xml_element( feed )
63
+ # We need a valide REXML::Element parameters :D
64
+ return nil unless feed.kind_of?(REXML::Element)
65
+
66
+ # Let's build the params to build a BlogMarks::Feed
67
+ params = {}
68
+ # Iterate over child nodes
69
+ feed.each_element do |element|
70
+ case element.name
71
+ when 'head'
72
+ element.each_element do |headElement|
73
+ case headElement.name
74
+ when 'title'
75
+ params[:title] = headElement.text
76
+ when 'updated'
77
+ params[:updated] = Time.parse(headElement.text)
78
+ when 'link'
79
+ params[:link_alternate] = URI.parse( headElement.attributes['href'] ) if headElement.attributes['rel'] == 'alternate'
80
+ params[:link_prev] = URI.parse( headElement.attributes['href'] ) if headElement.attributes['rel'] == 'prev'
81
+ params[:link_next] = URI.parse( headElement.attributes['href'] ) if headElement.attributes['rel'] == 'next'
82
+ end
83
+ end
84
+
85
+ when 'entry'
86
+ params[:marks] = [] unless params.has_key?(:marks)
87
+ params[:marks] << BlogMarks::Mark.build_from_xml_element(element)
88
+ end
89
+ end
90
+
91
+ # Create and return a new Feed constructs using params hash
92
+ return Feed.new(params)
93
+ end
94
+
95
+ end
96
+ end
97
+
98
+ end
@@ -0,0 +1,136 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require 'uri'
26
+ require 'rexml/document'
27
+
28
+ module BlogMarks
29
+
30
+ class Mark
31
+ # Mark Id
32
+ attr_reader :id
33
+ # Mark Title
34
+ attr_accessor :title
35
+ # Mark summary
36
+ attr_accessor :summary
37
+ # Mark's url
38
+ attr_accessor :link_related
39
+ # Mark's html representation
40
+ attr_reader :link_alternate
41
+ # Mark's site screenshot
42
+ attr_reader :link_image
43
+ # Mark's last update date
44
+ attr_reader :updated
45
+ # Mark's published date
46
+ attr_accessor :published
47
+ # Mark's author
48
+ attr_accessor :author
49
+ # Mark's edit
50
+ attr_reader :edit
51
+ # Mark's creation date
52
+ attr_accessor :bm_created
53
+ # Mark's public tags
54
+ attr_accessor :public_tags
55
+ # Mark's private tags
56
+ attr_accessor :private_tags
57
+
58
+ # Create a mark from params
59
+ #
60
+ # params hash can contains any of the object vars, but :title and :link_related is mandatory
61
+ def initialize( params={} )
62
+ @id = params[:id] || nil
63
+ @title = params[:title] || raise( ArgumentError, ":title can't be empty")
64
+ @link_related = params[:link_related] || raise( ArgumentError, ":link_related can't be empty")
65
+ @link_alternate = params[:link_alternate] || nil
66
+ @link_image = params[:link_image] || nil
67
+ @summary = params[:summary] || nil
68
+ @updated = params[:updated] || nil
69
+ @published = params[:published] || nil
70
+ @author = params[:author] || nil
71
+ @edit = params[:edit] || nil
72
+ @bm_created = params[:bm_created] || nil
73
+ @public_tags = params[:public_tags] || []
74
+ @private_tags = params[:private_tags] || []
75
+ end
76
+
77
+
78
+ # Return an xml representation of the mark according to BlogMarks.net Specs
79
+ def to_xml
80
+
81
+ end
82
+
83
+ class << self # Class methods
84
+
85
+ # Parse an REXML::Element corresponding to the entry
86
+ def build_from_xml_element( entry )
87
+ # We need a valide REXML::Element parameters :D
88
+ return nil unless entry.kind_of?(REXML::Element)
89
+
90
+ # Let's build the params to build a BlogMarks::Mark
91
+ params = {}
92
+
93
+ # Iterate over child nodes
94
+ entry.each_element do |element|
95
+ case element.name
96
+ when 'id'
97
+ params[:id] = element.text
98
+ when 'title'
99
+ params[:title] = element.text
100
+ when 'updated'
101
+ params[:updated] = Time.parse(element.text)
102
+ when 'published'
103
+ params[:published] = Time.parse(element.text)
104
+ when 'author'
105
+ params[:author] = element.elements['name'].text
106
+ when 'edit'
107
+ params[:edit] = URI.parse(element.text)
108
+ when 'bm:created'
109
+ params[:bm_created] = Time.parse(element.text)
110
+ when 'summary'
111
+ params[:summary] = element.text
112
+ when 'link'
113
+ params[:link_related] = URI.parse( element.attributes['href'] ) if element.attributes['rel'] == 'related'
114
+ params[:link_alernate] = URI.parse( element.attributes['href'] ) if element.attributes['rel'] == 'alternate'
115
+ params[:link_image] = URI.parse( element.attributes['href'] ) if element.attributes['rel'] == 'image'
116
+ when 'category'
117
+ if element.attributes['term'] == 'http://api.blogmarks.net/tags'
118
+ params[:public_tags] = [] unless params.has_key?(:public_tags)
119
+ params[:public_tags] << element.attributes['label']
120
+ end
121
+
122
+ if element.attributes['term'] != 'http://api.blogmarks.net/tags'
123
+ params[:private_tags] = [] unless params.has_key?(:private_tags)
124
+ params[:private_tags] << element.attributes['label']
125
+ end
126
+ end
127
+ end
128
+
129
+ # Create and return a new Mark constructs using params hash
130
+ return Mark.new(params)
131
+ end
132
+
133
+ end
134
+ end
135
+
136
+ end
@@ -0,0 +1,106 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require 'uri'
26
+ require 'rexml/document'
27
+
28
+ module BlogMarks
29
+
30
+ class Tag
31
+ # Tag id
32
+ attr_reader :id
33
+ # Tag html representation uri
34
+ attr_reader :link_alternate
35
+ # Tag image uri
36
+ attr_reader :link_image
37
+ # Tag edit uri
38
+ attr_reader :edit
39
+ # Tag title
40
+ attr_accessor :title
41
+ # Tag summary
42
+ attr_accessor :summary
43
+ # Tag publish date
44
+ attr_accessor :published
45
+ # Tag creation date
46
+ attr_reader :bm_created
47
+ # Tag update date
48
+ attr_reader :updated
49
+
50
+ # Create a tag from params
51
+ #
52
+ # params hash can contains any of the object vars, but :title is mandatory
53
+ def initialize( params={} )
54
+ @id = params[:id] || nil
55
+ @title = params[:title] || raise( ArgumentError, ":title can't be empty")
56
+ @link_alternate = params[:link_alternate] || nil
57
+ @link_image = params[:link_image] || nil
58
+ @summary = params[:summary] || nil
59
+ @updated = params[:updated] || nil
60
+ @published = params[:published] || nil
61
+ @edit = params[:edit] || nil
62
+ @bm_created = params[:bm_created] || nil
63
+ end
64
+
65
+
66
+ class << self # Class methods
67
+
68
+ # Parse an REXML::Element corresponding to the tag
69
+ def build_from_xml_element( tag )
70
+ # We need a valide REXML::Element parameters :D
71
+ return nil unless tag.kind_of?(REXML::Element)
72
+
73
+ # Let's build the params to build a BlogMarks::Tag
74
+ params = {}
75
+
76
+ # Iterate over child nodes
77
+ tag.each_element do |element|
78
+ case element.name
79
+ when 'id'
80
+ params[:id] = element.text
81
+ when 'title'
82
+ params[:title] = element.text
83
+ when 'updated'
84
+ params[:updated] = Time.parse(element.text)
85
+ when 'published'
86
+ params[:published] = Time.parse(element.text)
87
+ when 'edit'
88
+ params[:edit] = URI.parse(element.text)
89
+ when 'bm:created'
90
+ params[:bm_created] = Time.parse(element.text)
91
+ when 'summary'
92
+ params[:summary] = element.text
93
+ when 'link'
94
+ params[:link_alternate] = URI.parse( element.attributes['href'] ) if element.attributes['rel'] == 'alternate'
95
+ params[:link_image] = URI.parse( element.attributes['href'] ) if element.attributes['rel'] == 'image'
96
+ end
97
+ end
98
+
99
+ # Create and return a new Tag constructs using params hash
100
+ return Tag.new(params)
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ end
@@ -0,0 +1,63 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require 'test/unit'
26
+ require 'blogmarks'
27
+
28
+ ## This test should use a mock object to simulate the server response
29
+ ## I'm not too familiar with this so it will come later :(
30
+ class TestClient < Test::Unit::TestCase
31
+
32
+ def setup
33
+ @client = BlogMarks::Client.new('', '')
34
+ end
35
+
36
+ def test_create
37
+ assert_not_nil(@client)
38
+ assert_instance_of(BlogMarks::Client, @client)
39
+ end
40
+
41
+ def test_find_marks
42
+ @client.find_marks
43
+ assert_not_nil(@client.feed)
44
+ assert(@client.feed.marks.size > 0)
45
+ end
46
+
47
+ def test_find_my_marks
48
+ @client.find_my_marks
49
+ assert_not_nil(@client.feed)
50
+ assert(@client.feed.marks.size > 0)
51
+ end
52
+
53
+ def test_find_marks_limited_to_5
54
+ @client.find_marks( :last => 5 )
55
+ assert_not_nil(@client.feed)
56
+ assert(@client.feed.marks.size <= 5)
57
+ end
58
+
59
+ ##
60
+ def test_find_marks_begining_at_5
61
+
62
+ end
63
+ end
@@ -0,0 +1,163 @@
1
+ #/bin/env ruby
2
+ #--
3
+ # Copyright (c) 2004 Jonathan Tron
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ #++
24
+
25
+ require 'test/unit'
26
+ require 'uri'
27
+ require 'rexml/document'
28
+ require 'blogmarks'
29
+
30
+ class TestMark < Test::Unit::TestCase
31
+
32
+ def setup
33
+ @mark = BlogMarks::Mark.new( :id => 'tag:blogmarks.net,2005:marks,432',
34
+ :url => URI.parse('http://jonathan.tron.name'),
35
+ :title => "Jonathan's DevLog",
36
+ :summary => "Test",
37
+ :link_related => URI.parse("http://jonathan.tron.name"),
38
+ :link_alternate => URI.parse("http://jonathan.tron.name"),
39
+ :link_image => URI.parse("http://jonathan.tron.name/images/test.png"),
40
+ :updated => Time.new,
41
+ :published => Time.new,
42
+ :author => 'Jonathan',
43
+ :edit => URI.parse("http://api.blogmarks.net/marks/432"),
44
+ :bm_created => Time.new,
45
+ :public_tags => %w(blog dev multi\ word),
46
+ :private_tags => %w(test unit) )
47
+ end
48
+
49
+ def test_create_error
50
+ assert_raise( ArgumentError ) {
51
+ BlogMarks::Mark.new()
52
+ }
53
+ end
54
+
55
+ def test_create
56
+ assert_not_nil @mark
57
+ assert_instance_of(BlogMarks::Mark, @mark)
58
+ end
59
+
60
+ def test_id
61
+ assert_equal('tag:blogmarks.net,2005:marks,432', @mark.id)
62
+ end
63
+
64
+ def test_title
65
+ assert_equal("Jonathan's DevLog", @mark.title)
66
+ end
67
+
68
+ def test_link_related
69
+ assert_instance_of(URI::HTTP, @mark.link_related)
70
+ assert_equal("http://jonathan.tron.name", @mark.link_related.to_s)
71
+ end
72
+
73
+ def test_link_alternate
74
+ assert_instance_of(URI::HTTP, @mark.link_alternate)
75
+ assert_equal("http://jonathan.tron.name", @mark.link_alternate.to_s)
76
+ end
77
+
78
+ def test_link_image
79
+ assert_instance_of(URI::HTTP, @mark.link_image)
80
+ assert_equal("http://jonathan.tron.name/images/test.png", @mark.link_image.to_s)
81
+ end
82
+
83
+ def test_updated
84
+ assert_instance_of(Time, @mark.updated)
85
+ end
86
+
87
+ def test_published
88
+ assert_instance_of(Time, @mark.published)
89
+ end
90
+
91
+ def test_author
92
+ assert_equal('Jonathan', @mark.author)
93
+ end
94
+
95
+ def test_edit
96
+ assert_instance_of(URI::HTTP, @mark.edit)
97
+ assert_equal('http://api.blogmarks.net/marks/432', @mark.edit.to_s )
98
+ end
99
+
100
+ def test_bm_created
101
+ assert_instance_of(Time, @mark.bm_created)
102
+ end
103
+
104
+ def test_public_tags
105
+ assert_instance_of(Array, @mark.public_tags)
106
+ assert_equal(3, @mark.public_tags.size)
107
+
108
+ assert_equal('blog', @mark.public_tags[0] )
109
+ assert_equal('dev', @mark.public_tags[1])
110
+ assert_equal("multi word", @mark.public_tags[2])
111
+ end
112
+
113
+ def test_private_tags
114
+ assert_instance_of(Array, @mark.private_tags)
115
+ assert_equal(2, @mark.private_tags.size)
116
+
117
+ assert_equal('test', @mark.private_tags[0] )
118
+ assert_equal('unit', @mark.private_tags[1])
119
+ end
120
+
121
+ def test_build_from_xml
122
+ xml = <<-ENTRY
123
+ <?xml version="1.0" encoding="UTF-8"?>
124
+ <feed version="draft-ietf-atompub-format-05:do not deploy" xmlns="http://purl.org/atom/ns#draft-ietf-atompub-format-05" xmlns:bm="http://api.blogmarks.net/ns#">
125
+ <head>
126
+ <title>Last public marks from user Jonathan</title>
127
+ <link rel="alternate" type="application/xhtml+xml" href="http://blogmarks.net/user/Jonathan"/>
128
+ <updated>2006-01-26T21:58:24Z</updated>
129
+ </head>
130
+ <entry>
131
+ <id>tag:blogmarks.net,2006:marks,323785</id>
132
+ <title type="TEXT">Don't Meet Your Heroes - The CSS &amp; Web Standards News Compilation Site - DMYH</title>
133
+ <link rel="related" href="http://www.dontmeetyourheroes.com/index.php" type="text/html"/>
134
+ <link rel="alternate" href="http://blogmarks.net/user/Jonathan/archives/2006/01/#mark323785" type="application/xhtml+xml" title="Don't Meet Your Heroes - The CSS &amp; Web Standards News Compilation Site - DMYH"/>
135
+ <link rel="via" href="http://blogmarks.net/user/qoodoll/archives/2006/01/#mark323762" type="text/html"/>
136
+ <link rel="image" href="http://www.blogmarks.net/screenshots/2006/01/26/1d8ec2781448c994f7e537f3b6d9a1a8.png" type="image/png"/>
137
+ <updated>2006-01-26T21:58:24Z</updated>
138
+ <published>2006-01-26T21:58:24Z</published>
139
+ <author><name>Jonathan</name></author>
140
+ <category term="http://api.blogmarks.net/tags" sheme="/web" label="web"/>
141
+ <category term="http://api.blogmarks.net/tags" sheme="/css" label="css"/>
142
+ <category term="http://api.blogmarks.net/tags" sheme="/site" label="site"/>
143
+ <category term="http://api.blogmarks.net/tags" sheme="/news" label="news"/>
144
+ <edit>http://api.blogmarks.net/atom/marks/323785</edit>
145
+ <bm:created>2006-01-26T21:58:24Z</bm:created>
146
+ </entry>
147
+ </feed>
148
+ ENTRY
149
+
150
+ xml_doc = REXML::Document.new( xml, :ignore_whitespace_nodes => :all )
151
+
152
+ mark = nil
153
+ xml_doc.root.elements.each( "/feed/entry" ) do |entry|
154
+ begin
155
+ mark = BlogMarks::Mark.build_from_xml_element(entry)
156
+ rescue
157
+ puts 'Error'
158
+ end
159
+ end
160
+
161
+ assert_instance_of(BlogMarks::Mark, mark)
162
+ end
163
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: blogmarks
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-01-31 00:00:00 +01:00
8
+ summary: Library to access BlogMarks.net bookmarks.
9
+ require_paths:
10
+ - lib
11
+ email: jonathan@tron.name
12
+ homepage: http://projets.tron.name/ruby/blogmarks
13
+ rubyforge_project:
14
+ description: BlogMarks provide a simple way to access BlogMarks.net bookmarks system.
15
+ autorequire: builder
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Jonathan Tron
30
+ files:
31
+ - lib/blogmarks.rb
32
+ - lib/blogmarks/class_attribute_accessors.rb
33
+ - lib/blogmarks/client.rb
34
+ - lib/blogmarks/feed.rb
35
+ - lib/blogmarks/mark.rb
36
+ - lib/blogmarks/tag.rb
37
+ - test/test_blogmarks_client.rb
38
+ - test/test_blogmarks_entry.rb
39
+ - README
40
+ - CHANGES
41
+ - MIT-LICENSE
42
+ test_files:
43
+ - test/test_blogmarks_client.rb
44
+ - test/test_blogmarks_entry.rb
45
+ rdoc_options:
46
+ - --title
47
+ - BlogMarks -- Access your Marks
48
+ - --main
49
+ - README
50
+ - --line-numbers
51
+ extra_rdoc_files:
52
+ - README
53
+ - CHANGES
54
+ - MIT-LICENSE
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ requirements: []
60
+
61
+ dependencies:
62
+ - !ruby/object:Gem::Dependency
63
+ name: builder
64
+ version_requirement:
65
+ version_requirements: !ruby/object:Gem::Version::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 1.2.4
70
+ version: