blogmarks 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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: