shared_book 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +3 -0
- data/Manifest.txt +9 -0
- data/README.rdoc +100 -0
- data/Rakefile +19 -0
- data/lib/shared_book.rb +280 -0
- data/spec/lib/shared_book_spec.rb +398 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- data/tasks/rspec.rake +21 -0
- metadata +112 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
= shared_book
|
2
|
+
|
3
|
+
* http://github.com/xunker/shared_book
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A Ruby Gem to connect to the SharedBook.com publishing API.
|
8
|
+
|
9
|
+
This version provides 1:1 method call structure to the SharedBook rest-like API.
|
10
|
+
|
11
|
+
== SYNOPSIS:
|
12
|
+
|
13
|
+
s = SharedBook.new(
|
14
|
+
:product_api_key => "xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-x",
|
15
|
+
:product_secret_word => "xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-x",
|
16
|
+
:auth_token => "xxxxx_xxx",
|
17
|
+
:get_session_token => true
|
18
|
+
)
|
19
|
+
|
20
|
+
s.bmscreate_init('test book',
|
21
|
+
[{:chapterTitle => 'chapter 1', :chapterText => 'text'}, {:chapterTitle => 'chapter 2', :chapterText => 'text'}]
|
22
|
+
)
|
23
|
+
|
24
|
+
s.bmscreate_publish
|
25
|
+
|
26
|
+
s.bms_addComment(:comment_title => 'comment', :comment_text => 'text', :owner_name => 'George')
|
27
|
+
|
28
|
+
s.bms_addPhoto_by_url(:file_url => 'http://www.example.com/images/logo.png', :owner_name => 'Geroge')
|
29
|
+
s.bms_addPhoto_by_handle(:file_name => "../images/angry_squirrel.jpg", :file_mime => "image/jpeg", :owner_name => 'Gerogee')
|
30
|
+
s.bms_setFrontCoverPhoto(:file_name => "../images/angry_squirrel.jpg", :file_mime => "image/jpeg", :owner_name => 'Gerogee')
|
31
|
+
s.bms_setBackCoverPhoto(:file_name => "../images/angry_squirrel.jpg", :file_mime => "image/jpeg", :owner_name => 'Gerogee')
|
32
|
+
|
33
|
+
s.bms_publish
|
34
|
+
s.bookcreate_init
|
35
|
+
s.bookcreate_setDedication(:dedication_text => 'Dedicated!')
|
36
|
+
s.bookcreate_publish
|
37
|
+
s.book_preview
|
38
|
+
|
39
|
+
== USAGE:
|
40
|
+
|
41
|
+
Before calling SharedBook.new you will need to send you web client to the URL provided by SharedBook.auth_login_url. This URL will return the user to your SharedBook.com return URL and it will have a unique auth token appended to the query string. You must retrieve that token and pass it in to SharedBook.new as :auth_token.
|
42
|
+
|
43
|
+
For testing you can call SharedBook.new with an :auth_token value of 'auto' which will automatically get this token for you but the preview URL given my book_preview will be inaccessible. :auth_token => 'auto' should never, ever, ever be used for production purposes.
|
44
|
+
|
45
|
+
During a real usage scenario, the order of operations should follow roughtly:
|
46
|
+
|
47
|
+
s = SharedBook.new
|
48
|
+
s.bmscreate_init
|
49
|
+
s.bmscreate_publish
|
50
|
+
s.bms_addComment
|
51
|
+
s.bms_addPhoto_by_url
|
52
|
+
s.bms_addPhoto_by_handle
|
53
|
+
s.bms_setFrontCoverPhoto
|
54
|
+
s.bms_setBackCoverPhoto
|
55
|
+
s.bms_publish
|
56
|
+
s.bookcreate_init
|
57
|
+
s.bookcreate_setDedication
|
58
|
+
s.bookcreate_publish
|
59
|
+
s.book_preview
|
60
|
+
|
61
|
+
== REQUIREMENTS:
|
62
|
+
|
63
|
+
This library is useless unless you have an account at SharedBook.com.
|
64
|
+
|
65
|
+
You will need to know your the API key and secret word for your product
|
66
|
+
and pass them in to the constructor.
|
67
|
+
|
68
|
+
== INSTALL:
|
69
|
+
|
70
|
+
gem install shared_book
|
71
|
+
|
72
|
+
== TODO:
|
73
|
+
|
74
|
+
* Add the ability to post a picture using data from a string, not just a URL or filename.
|
75
|
+
* Add a more ruby-like method for creating a new SharedBook object.
|
76
|
+
|
77
|
+
== LICENSE:
|
78
|
+
|
79
|
+
(The MIT License)
|
80
|
+
|
81
|
+
Copyright (c) 2010 Matthew Nielsen
|
82
|
+
|
83
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
84
|
+
a copy of this software and associated documentation files (the
|
85
|
+
'Software'), to deal in the Software without restriction, including
|
86
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
87
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
88
|
+
permit persons to whom the Software is furnished to do so, subject to
|
89
|
+
the following conditions:
|
90
|
+
|
91
|
+
The above copyright notice and this permission notice shall be
|
92
|
+
included in all copies or substantial portions of the Software.
|
93
|
+
|
94
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
95
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
96
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
97
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
98
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
99
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
100
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'hoe', '>= 2.1.0'
|
3
|
+
gem 'multipart-post', '>= 1.0.1'
|
4
|
+
require 'hoe'
|
5
|
+
require 'fileutils'
|
6
|
+
require './lib/shared_book'
|
7
|
+
|
8
|
+
VERSION = "0.1.0"
|
9
|
+
|
10
|
+
Hoe.plugin :newgem
|
11
|
+
|
12
|
+
$hoe = Hoe.spec 'shared_book' do
|
13
|
+
self.developer 'Matthew Nielsen', 'xunker@pyxidis.org'
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'newgem/tasks'
|
17
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
18
|
+
|
19
|
+
task :default => :spec
|
data/lib/shared_book.rb
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
# order of calls
|
2
|
+
#
|
3
|
+
# s = SharedBook.new { ... }
|
4
|
+
# s.get_session_token .. or .. pass :get_session_token => true to .new
|
5
|
+
# s.bmscreate_init('test book', {:chapterTitle => 'chapter 1', :chapterText => 'text'})
|
6
|
+
# ... or ...
|
7
|
+
# s.bmscreate_init('test book', [{:chapterTitle => 'chapter 1', :chapterText => 'text'}, {:chapterTitle => 'chapter 2', :chapterText => 'text'}])
|
8
|
+
# s.bmscreate_publish
|
9
|
+
# s.bms_addComment { ... }
|
10
|
+
# s.bms_addPhoto_by_url { ... }
|
11
|
+
# s.bms_addPhoto_by_handle { ... }
|
12
|
+
# s.bms_setFrontCoverPhoto { ... }
|
13
|
+
# s.bms_setBackCoverPhoto { ... }
|
14
|
+
# s.bms_publish
|
15
|
+
# s.bookcreate_init
|
16
|
+
# s.bookcreate_setDedication { ... }
|
17
|
+
# s.bookcreate_publish
|
18
|
+
# s.book_preview
|
19
|
+
|
20
|
+
class SharedBookError < StandardError; end
|
21
|
+
class MissingCredentialError < SharedBookError; end
|
22
|
+
class MissingAuthTokenError < MissingCredentialError; def to_s; "Please supply auth_token given from '/auth/login'"; end; end
|
23
|
+
class MissingSessionTokenError < MissingCredentialError; end
|
24
|
+
class MissingProductApiKeyError < MissingCredentialError; def to_s; "Please supply product_api_key"; end; end
|
25
|
+
class MissingProductSecretWordError < MissingCredentialError; def to_s; "Please supply product_secret_word"; end; end
|
26
|
+
class ResponseError < SharedBookError; end
|
27
|
+
|
28
|
+
class SharedBook
|
29
|
+
require 'rubygems'
|
30
|
+
require 'kconv'
|
31
|
+
require 'net/http'
|
32
|
+
require 'net/http/post/multipart'
|
33
|
+
require 'digest/md5'
|
34
|
+
require 'uri'
|
35
|
+
|
36
|
+
URL = "http://api.sharedbook.com/v0.6"
|
37
|
+
DEFAULT_THEME = 'vanilla'
|
38
|
+
|
39
|
+
attr_reader :product_api_key, :session_token, :auth_token, :product_secret_word, :bms_id, :book_id, :comment_ids, :photo_ids, :front_cover_photo_id, :back_cover_photo_id, :book_url
|
40
|
+
attr_accessor :book_title, :articles
|
41
|
+
|
42
|
+
def initialize(opts = {})
|
43
|
+
if opts[:product_api_key]
|
44
|
+
@product_api_key = opts[:product_api_key]
|
45
|
+
else
|
46
|
+
raise MissingProductApiKeyError
|
47
|
+
end
|
48
|
+
|
49
|
+
if opts[:product_secret_word]
|
50
|
+
@product_secret_word = opts[:product_secret_word]
|
51
|
+
else
|
52
|
+
raise MissingProductSecretWordError
|
53
|
+
end
|
54
|
+
|
55
|
+
if opts[:auth_token]
|
56
|
+
@auth_token = if opts[:auth_token] == 'auto'
|
57
|
+
get_new_auth_token
|
58
|
+
else
|
59
|
+
opts[:auth_token]
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise MissingAuthTokenError
|
63
|
+
end
|
64
|
+
|
65
|
+
if opts[:session_token]
|
66
|
+
@session_token = opts[:session_token]
|
67
|
+
else
|
68
|
+
@session_token = auth_getSessionToken if opts[:get_session_token]
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.auth_login_url
|
74
|
+
"#{URL}/auth/login"
|
75
|
+
end
|
76
|
+
|
77
|
+
def auth_getSessionToken
|
78
|
+
return session_token if session_token # you can't call the service twice with the same auth_token
|
79
|
+
response = get_url("#{URL}/auth/getSessionToken", :apiKey => @product_api_key, :authToken => @auth_token)
|
80
|
+
parse_response(response, /\<sessionToken\>(.*)\<\/sessionToken\>/)
|
81
|
+
end
|
82
|
+
|
83
|
+
def session_token
|
84
|
+
@session_token
|
85
|
+
end
|
86
|
+
|
87
|
+
def bmscreate_init(book_title=@book_title, articles=@articles, theme=DEFAULT_THEME)
|
88
|
+
article_hash = if articles.class == Array
|
89
|
+
hh = {}
|
90
|
+
articles.each_with_index do |article, i|
|
91
|
+
hh["chapterTitle#{i+1}".to_sym] = article[:chapterTitle]
|
92
|
+
hh["chapterText#{i+1}".to_sym] = article[:chapterText]
|
93
|
+
end
|
94
|
+
hh
|
95
|
+
else
|
96
|
+
articles # expected to be hash with :chapterTitle and :chapterText
|
97
|
+
end
|
98
|
+
response = post_url("#{URL}/bmscreate/init", {:bookTitle => book_title}.merge(article_hash))
|
99
|
+
@bms_id = parse_response(response, /\<bms id=\"(\d+)\"/)
|
100
|
+
end
|
101
|
+
|
102
|
+
def bmscreate_publish(bms_id = @bms_id)
|
103
|
+
response = post_url("#{URL}/bmscreate/publish", {:bmsId => bms_id})
|
104
|
+
parse_response(response, /status=\"ok\"/) { true }
|
105
|
+
end
|
106
|
+
|
107
|
+
def bms_addComment(opts = {})
|
108
|
+
required = {:bmsId => opts[:bms_id] || @bms_id, :commentTitle => opts[:comment_title], :commentText => opts[:comment_text],
|
109
|
+
:chapterNumber => opts[:chapter_number].to_s, :ownerName => opts[:owner_name]}
|
110
|
+
optional = {:time => opts[:time], :commentId => opts[:comment_id]}.reject{|k,v| v.nil?}
|
111
|
+
|
112
|
+
response = post_url("#{URL}/bms/addComment", required.merge(optional))
|
113
|
+
|
114
|
+
parse_response(response, /comment id=\"(.*)\"/) do |match|
|
115
|
+
@comment_ids ||= []
|
116
|
+
@comment_ids << match[1]
|
117
|
+
@comment_ids.last
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def bms_addPhoto_by_url(opts = {})
|
122
|
+
required = {:bmsId => opts[:bms_id] || @bms_id, :url => opts[:file_url], :ownerName => opts[:owner_name]}
|
123
|
+
optional = {:time => opts[:time], :caption => opts[:caption], :photoId => opts[:photo_id], :photoOrdinal => opts[:photo_ordinal]}.reject{|k,v| v.nil?}
|
124
|
+
|
125
|
+
response = post_url("#{URL}/bms/addPhoto", required.merge(optional))
|
126
|
+
parse_response(response, /\<photo id=\"(.*)\"/) do |match|
|
127
|
+
@photo_ids ||= []
|
128
|
+
@photo_ids << match[1]
|
129
|
+
@photo_ids.last
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def bms_addPhoto_by_handle(opts = {})
|
134
|
+
parse_response(post_photo_data("#{URL}/bms/addPhoto", opts), /\<photo id=\"(.*)\"/) do |match|
|
135
|
+
@photo_ids ||= []
|
136
|
+
@photo_ids << match[1]
|
137
|
+
@photo_ids.last
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def bms_setFrontCoverPhoto(opts = {})
|
142
|
+
@front_cover_photo_id = set_cover_photo(:front, opts)
|
143
|
+
end
|
144
|
+
|
145
|
+
def bms_setBackCoverPhoto(opts = {})
|
146
|
+
@back_cover_photo_id = set_cover_photo(:back, opts)
|
147
|
+
end
|
148
|
+
|
149
|
+
def bms_publish(bms_id = @bms_id)
|
150
|
+
response = post_url("#{URL}/bms/publish", {:bmsId => bms_id})
|
151
|
+
parse_response(response, /status=\"ok\"/) { true }
|
152
|
+
end
|
153
|
+
|
154
|
+
def bookcreate_init(bms_id = @bms_id)
|
155
|
+
response = post_url("#{URL}/bookcreate/init", {:bmsId => bms_id})
|
156
|
+
parse_response(response, /status=\"ok\"/) { true }
|
157
|
+
end
|
158
|
+
|
159
|
+
def bookcreate_setDedication(opts = {})
|
160
|
+
response = post_url("#{URL}/bookcreate/setDedication", {:bmsId => opts[:bms_id] || @bms_id, :dedicationText => opts[:dedication_text]})
|
161
|
+
parse_response(response, /status=\"ok\"/) { true }
|
162
|
+
end
|
163
|
+
|
164
|
+
def bookcreate_publish(bms_id = @bms_id)
|
165
|
+
response = post_url("#{URL}/bookcreate/publish", {:bmsId => bms_id})
|
166
|
+
@book_id = parse_response(response, /\<book id=\"(\d+)\" \/\>/)
|
167
|
+
end
|
168
|
+
|
169
|
+
def book_preview(bms_id = @bms_id, book_id = @book_id)
|
170
|
+
query = {
|
171
|
+
:bmsId => bms_id.to_s, :bookId => book_id.to_s, :redirect => 'false', :apiKey => @product_api_key,
|
172
|
+
:sessionToken => session_token, :authToken => @auth_token
|
173
|
+
}
|
174
|
+
query.merge!(generate_signature(query))
|
175
|
+
response = get_url("#{URL}/book/preview", query)
|
176
|
+
@book_url = parse_response(response, /\<url\>(.*)\<\/url\>/).gsub(/&\;/, "&")
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
|
181
|
+
def parse_response(response, pattern, &block)
|
182
|
+
if match = response.match(pattern)
|
183
|
+
if block_given?
|
184
|
+
yield match
|
185
|
+
else
|
186
|
+
match[1]
|
187
|
+
end
|
188
|
+
else
|
189
|
+
raise ResponseError, response
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def get_url(path, query = {})
|
194
|
+
url = URI.parse("#{path}?#{query_params(query)}")
|
195
|
+
|
196
|
+
req = Net::HTTP::Get.new([url.path, url.query].join('?'))
|
197
|
+
res = Net::HTTP.start(url.host, url.port) {|http|
|
198
|
+
http.request(req)
|
199
|
+
}
|
200
|
+
res.body
|
201
|
+
end
|
202
|
+
|
203
|
+
def post_url(path, query = {})
|
204
|
+
query.merge!({:apiKey => @product_api_key, :sessionToken => session_token, :authToken => @auth_token})
|
205
|
+
query.merge!(generate_signature(query)) # must be added last
|
206
|
+
|
207
|
+
res = Net::HTTP.post_form(URI.parse(path), query)
|
208
|
+
|
209
|
+
case res
|
210
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
211
|
+
res.body
|
212
|
+
else
|
213
|
+
raise ResponseError, res.error! #res.error!?
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
def post_url_file(path, query = {})
|
219
|
+
query.merge!({:apiKey => @product_api_key, :sessionToken => session_token, :authToken => @auth_token})
|
220
|
+
query.merge!(generate_signature(query)) # must be added last
|
221
|
+
|
222
|
+
url = URI.parse(path)
|
223
|
+
req = Net::HTTP::Post::Multipart.new url.path, query
|
224
|
+
res = Net::HTTP.start(url.host, url.port) do |http|
|
225
|
+
http.request(req)
|
226
|
+
end
|
227
|
+
|
228
|
+
case res
|
229
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
230
|
+
res.body
|
231
|
+
else
|
232
|
+
raise ResponseError, res.error! #res.error!?
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
def query_params(query={})
|
238
|
+
query.map{|k,v| [k, v].join('=')}.join('&')
|
239
|
+
end
|
240
|
+
|
241
|
+
def generate_signature(query)
|
242
|
+
str=""
|
243
|
+
query.keys.sort{|a,b| a.to_s <=> b.to_s}.each do |key|
|
244
|
+
str << key.to_s+query[key] unless query[key].class == File
|
245
|
+
end
|
246
|
+
|
247
|
+
{:signature => Digest::MD5.hexdigest(@product_secret_word.toutf8+str.toutf8)}
|
248
|
+
end
|
249
|
+
|
250
|
+
def get_new_auth_token
|
251
|
+
if ENV['RAILS_ENV']
|
252
|
+
puts "*** YOU SHOULD ONLY USE THIS IN DEVELOPMENT MODE ***" unless ENV['RAILS_ENV'] == 'development'
|
253
|
+
end
|
254
|
+
|
255
|
+
res = Net::HTTP.post_form(URI.parse(SharedBook.auth_login_url), {:apiKey => @product_api_key})
|
256
|
+
|
257
|
+
case res
|
258
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
259
|
+
res.body.match(/\?authToken=(.*)\"\>/)[1]
|
260
|
+
else
|
261
|
+
raise ResponseError, res.error! #res.error!?
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def set_cover_photo(cover, opts={})
|
266
|
+
raise "only Front or Back cover" unless %w[front back].include?(cover.to_s)
|
267
|
+
|
268
|
+
parse_response(post_photo_data("#{URL}/bms/set#{cover.to_s.capitalize}CoverPhoto", opts), /\<photo id=\"(.*)\"/)
|
269
|
+
end
|
270
|
+
|
271
|
+
def post_photo_data(url, opts={})
|
272
|
+
required = {:bmsId => opts[:bms_id] || @bms_id, :file_name => opts[:file_name], :file_mime => opts[:file_mime], :ownerName => opts[:owner_name]}
|
273
|
+
optional = {:time => opts[:time], :caption => opts[:caption], :photoId => opts[:photo_id], :photoOrdinal => opts[:photo_ordinal]}.reject{|k,v| v.nil?}
|
274
|
+
|
275
|
+
File.open(opts[:file_name]) do |file_upload|
|
276
|
+
post_url_file(url, required.merge(optional).merge({"photo" => UploadIO.new(file_upload, opts[:file_mime], opts[:file_name])}))
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
end
|
@@ -0,0 +1,398 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def gem_root
|
4
|
+
File.dirname(__FILE__) + '/../..'
|
5
|
+
end
|
6
|
+
|
7
|
+
def post_should_get_response_error(method, arg=nil)
|
8
|
+
@sharedbook.should_receive(:post_url).and_return(@bad_return)
|
9
|
+
method_should_get_response_error(method, arg)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_should_get_response_error(method, arg=nil)
|
13
|
+
@sharedbook.should_receive(:get_url).and_return(@bad_return)
|
14
|
+
method_should_get_response_error(method, arg)
|
15
|
+
end
|
16
|
+
|
17
|
+
def method_should_get_response_error(method, arg=nil)
|
18
|
+
lambda { arg ? @sharedbook.send(method, arg) : @sharedbook.send(method) }.should raise_error(ResponseError)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe SharedBook do
|
22
|
+
before(:each) do
|
23
|
+
@valid = {:product_api_key => 'x', :product_secret_word => 'x', :auth_token => 'x', :session_token => 'aabbcc'}
|
24
|
+
@sharedbook = SharedBook.new(@valid)
|
25
|
+
@bad_return = "<p>B0RKEN!</p>"
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.new' do
|
29
|
+
it 'should require product_api_key, product_secret_word and auth_token' do
|
30
|
+
lambda { SharedBook.new(:product_api_key => 'x', :product_secret_word => 'x') }.should raise_error(MissingAuthTokenError)
|
31
|
+
|
32
|
+
lambda { SharedBook.new(:product_api_key => 'x', :auth_token => 'x') }.should raise_error(MissingProductSecretWordError)
|
33
|
+
|
34
|
+
lambda { SharedBook.new(:auth_token => 'x', :product_secret_word => 'x') }.should raise_error(MissingProductApiKeyError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.auth_login_url' do
|
39
|
+
it 'should return the login url that the web user should be sent to' do
|
40
|
+
SharedBook.auth_login_url.should == "#{SharedBook::URL}/auth/login"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe '#auth_getSessionToken' do
|
45
|
+
before(:each) do
|
46
|
+
@sharedbook = SharedBook.new(@valid.merge(:session_token => nil))
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should get the session token for a given auth_token' do
|
50
|
+
# expect
|
51
|
+
@sharedbook.should_receive(:get_url).with("#{SharedBook::URL}/auth/getSessionToken", {:apiKey=>"x", :authToken=>"x"}).and_return(
|
52
|
+
"<auth.getSessionToken status=\"ok\">\n\t<sessionToken>a1b2c3</sessionToken>\n</auth.getSessionToken>"
|
53
|
+
)
|
54
|
+
|
55
|
+
@sharedbook.auth_getSessionToken.should == "a1b2c3"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return the already-known session ID if we already know it" do
|
59
|
+
# given
|
60
|
+
@sharedbook.stub(:session_token).and_return('xyz')
|
61
|
+
|
62
|
+
# expect
|
63
|
+
@sharedbook.should_not_receive(:get_url)
|
64
|
+
|
65
|
+
# when
|
66
|
+
@sharedbook.auth_getSessionToken.should == 'xyz'
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should raise ResponseError if the response cannot be parsed" do
|
70
|
+
# expect
|
71
|
+
get_should_get_response_error(:auth_getSessionToken)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#session_token' do
|
76
|
+
it "should return the current session token" do
|
77
|
+
# given
|
78
|
+
sharedbook = SharedBook.new(@valid.merge(:session_token => nil))
|
79
|
+
# expect
|
80
|
+
sharedbook.session_token.should be_nil
|
81
|
+
|
82
|
+
# given
|
83
|
+
sharedbook = SharedBook.new(@valid.merge(:session_token => 'abc'))
|
84
|
+
# expect
|
85
|
+
sharedbook.session_token.should == 'abc'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#bmscreate_init' do
|
90
|
+
before(:all) do
|
91
|
+
@return = "<bmscreate.init status=\"ok\">\n\t<bms id=\"11235\" />\n</bmscreate.init>"
|
92
|
+
end
|
93
|
+
context 'with one article' do
|
94
|
+
it 'should post to the init url and return a bms_id' do
|
95
|
+
# expect
|
96
|
+
@sharedbook.should_receive(:post_url).with(
|
97
|
+
"#{SharedBook::URL}/bmscreate/init",
|
98
|
+
{:bookTitle=>"book title", :chapterTitle=>"chapter 1", :chapterText=>"text"}
|
99
|
+
).and_return(@return)
|
100
|
+
|
101
|
+
# given
|
102
|
+
@sharedbook.bmscreate_init("book title", {:chapterTitle => 'chapter 1', :chapterText => 'text'}).should == '11235'
|
103
|
+
@sharedbook.bms_id.should == "11235"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
context 'with more than one article' do
|
107
|
+
it 'should post to the init url and return a bms_id' do
|
108
|
+
# expect
|
109
|
+
@sharedbook.should_receive(:post_url).with(
|
110
|
+
"#{SharedBook::URL}/bmscreate/init",
|
111
|
+
{:chapterText1=>"text", :bookTitle=>"book title", :chapterTitle2=>"chapter 2", :chapterText2=>"text", :chapterTitle1=>"chapter 1"}
|
112
|
+
).and_return(@return)
|
113
|
+
|
114
|
+
# given
|
115
|
+
@sharedbook.bmscreate_init("book title", [
|
116
|
+
{:chapterTitle => 'chapter 1', :chapterText => 'text'}, {:chapterTitle => 'chapter 2', :chapterText => 'text'}
|
117
|
+
]).should == '11235'
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
122
|
+
# expect
|
123
|
+
@sharedbook.should_receive(:post_url).and_return(@bad_return)
|
124
|
+
|
125
|
+
# given
|
126
|
+
lambda {
|
127
|
+
@sharedbook.bmscreate_init("book title", {:chapterTitle => 'chapter 1', :chapterText => 'text'}).should == '11235'
|
128
|
+
}.should raise_error(ResponseError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#bmscreate_publish' do
|
133
|
+
it 'should post the the publish url' do
|
134
|
+
# expect
|
135
|
+
@sharedbook.should_receive(:post_url).with(
|
136
|
+
"#{SharedBook::URL}/bmscreate/publish",
|
137
|
+
{:bmsId=>123}
|
138
|
+
).and_return("<publish status=\"ok\" />")
|
139
|
+
|
140
|
+
# given
|
141
|
+
@sharedbook.bmscreate_publish(123).should be_true
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
145
|
+
# expect
|
146
|
+
post_should_get_response_error(:bmscreate_publish, 123)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe '#bms_addComment' do
|
151
|
+
it 'should post the add comment url and return a comment id' do
|
152
|
+
# expect
|
153
|
+
@sharedbook.should_receive(:post_url).with(
|
154
|
+
"#{SharedBook::URL}/bms/addComment",
|
155
|
+
{:ownerName=>"Joe", :commentTitle=>"title", :bmsId=>123, :commentText=>"text", :chapterNumber=>"1"}
|
156
|
+
).and_return(
|
157
|
+
"<bms.addComment status=\"ok\">\n\t<comment id=\"x1y1z1\" />\n</bms.addComment>"
|
158
|
+
)
|
159
|
+
|
160
|
+
# given
|
161
|
+
@sharedbook.bms_addComment({
|
162
|
+
:bms_id => 123, :comment_title => 'title', :comment_text => 'text', :chapter_number => 1, :owner_name => 'Joe'
|
163
|
+
}).should == 'x1y1z1'
|
164
|
+
@sharedbook.comment_ids.should == ["x1y1z1"]
|
165
|
+
end
|
166
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
167
|
+
# expect
|
168
|
+
@sharedbook.should_receive(:post_url).and_return(@bad_return)
|
169
|
+
|
170
|
+
# given
|
171
|
+
lambda { @sharedbook.bms_addComment({
|
172
|
+
:bms_id => 123, :comment_title => 'title', :comment_text => 'text', :chapter_number => 1, :owner_name => 'Joe'
|
173
|
+
}) }.should raise_error(ResponseError)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
describe '#bms_addPhoto_by_url' do
|
178
|
+
before(:each) do
|
179
|
+
@image = "http://examp.le/x.jpg"
|
180
|
+
@valid_post = {:bms_id => 123, :file_url => @image, :chapter_number => 1, :owner_name => 'Joe'}
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should post to the add photo url with the url of the photo to add and return a photo id' do
|
184
|
+
# expect
|
185
|
+
@sharedbook.should_receive(:post_url).with(
|
186
|
+
"#{SharedBook::URL}/bms/addPhoto",
|
187
|
+
{:ownerName=>"Joe", :bmsId=>123, :url => @image}
|
188
|
+
).and_return(
|
189
|
+
"<bms.addPhoto status=\"ok\">\n\t<photo id=\"q1w2e3\" />\n</bms.addPhoto>"
|
190
|
+
)
|
191
|
+
|
192
|
+
# given
|
193
|
+
@sharedbook.bms_addPhoto_by_url(@valid_post).should == 'q1w2e3'
|
194
|
+
@sharedbook.photo_ids.should == ["q1w2e3"]
|
195
|
+
end
|
196
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
197
|
+
# expect
|
198
|
+
@sharedbook.should_receive(:post_url).and_return(@bad_return)
|
199
|
+
|
200
|
+
# given
|
201
|
+
lambda { @sharedbook.bms_addPhoto_by_url(@valid_post) }.should raise_error(ResponseError)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe '#bms_addPhoto_by_handle' do
|
206
|
+
before(:each) do
|
207
|
+
@image = gem_root+"/spec/images/angry_squirrel.jpg"
|
208
|
+
@valid_post = {:bms_id => 123, :file_name => @image, :file_mime => "image/jpeg", :chapter_number => 1, :owner_name => 'Joe'}
|
209
|
+
UploadIO.stub!(:new)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should post to the add photo url with the data of the photo to add and return a photo id' do
|
213
|
+
# expect
|
214
|
+
@sharedbook.should_receive(:post_url_file).with(
|
215
|
+
"#{SharedBook::URL}/bms/addPhoto",
|
216
|
+
{"photo"=>nil, :ownerName=>"Joe", :file_name=>@image, :file_mime=>"image/jpeg", :bmsId=>123}
|
217
|
+
).and_return(
|
218
|
+
"<bms.addPhoto status=\"ok\">\n\t<photo id=\"q1w2e3\" />\n</bms.addPhoto>"
|
219
|
+
)
|
220
|
+
|
221
|
+
# given
|
222
|
+
@sharedbook.bms_addPhoto_by_handle(@valid_post).should == 'q1w2e3'
|
223
|
+
@sharedbook.photo_ids.should == ["q1w2e3"]
|
224
|
+
end
|
225
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
226
|
+
# expect
|
227
|
+
@sharedbook.should_receive(:post_url_file).and_return(@bad_return)
|
228
|
+
|
229
|
+
# given
|
230
|
+
lambda { @sharedbook.bms_addPhoto_by_handle(@valid_post) }.should raise_error(ResponseError)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe '#bms_setFrontCoverPhoto' do
|
235
|
+
before(:each) do
|
236
|
+
@image = gem_root+"/spec/images/angry_squirrel.jpg"
|
237
|
+
@id = "q1w2e3"
|
238
|
+
@valid_post = {:bms_id => 123, :file_name => @image, :file_mime => "image/jpeg", :chapter_number => 1, :owner_name => 'Joe'}
|
239
|
+
UploadIO.stub!(:new)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'should post the image data to the url for setting the front cover and return an id' do
|
243
|
+
# expect
|
244
|
+
@sharedbook.should_receive(:post_url_file).with(
|
245
|
+
"#{SharedBook::URL}/bms/setFrontCoverPhoto",
|
246
|
+
{"photo"=>nil, :ownerName=>"Joe", :file_name=>@image, :file_mime=>"image/jpeg", :bmsId=>123}
|
247
|
+
).and_return(
|
248
|
+
"<bms.addPhoto status=\"ok\">\n\t<photo id=\"#{@id}\" />\n</bms.addPhoto>"
|
249
|
+
)
|
250
|
+
|
251
|
+
# given
|
252
|
+
@sharedbook.bms_setFrontCoverPhoto(@valid_post).should == @id
|
253
|
+
@sharedbook.front_cover_photo_id.should == @id
|
254
|
+
end
|
255
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
256
|
+
# expect
|
257
|
+
@sharedbook.should_receive(:post_url_file).and_return(@bad_return)
|
258
|
+
|
259
|
+
# given
|
260
|
+
lambda { @sharedbook.bms_setFrontCoverPhoto(@valid_post) }.should raise_error(ResponseError)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe '#bms_setBackCoverPhoto' do
|
265
|
+
before(:each) do
|
266
|
+
@image = gem_root+"/spec/images/angry_squirrel.jpg"
|
267
|
+
@id = "q1w2e3"
|
268
|
+
@valid_post = {:bms_id => 123, :file_name => @image, :file_mime => "image/jpeg", :chapter_number => 1, :owner_name => 'Joe'}
|
269
|
+
UploadIO.stub!(:new)
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should post the image data to the url for setting the front cover and return an id' do
|
273
|
+
# expect
|
274
|
+
@sharedbook.should_receive(:post_url_file).with(
|
275
|
+
"#{SharedBook::URL}/bms/setBackCoverPhoto",
|
276
|
+
{"photo"=>nil, :ownerName=>"Joe", :file_name=>@image, :file_mime=>"image/jpeg", :bmsId=>123}
|
277
|
+
).and_return(
|
278
|
+
"<bms.addPhoto status=\"ok\">\n\t<photo id=\"#{@id}\" />\n</bms.addPhoto>"
|
279
|
+
)
|
280
|
+
|
281
|
+
# given
|
282
|
+
@sharedbook.bms_setBackCoverPhoto(@valid_post).should == @id
|
283
|
+
@sharedbook.back_cover_photo_id.should == @id
|
284
|
+
end
|
285
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
286
|
+
# expect
|
287
|
+
@sharedbook.should_receive(:post_url_file).and_return(@bad_return)
|
288
|
+
|
289
|
+
# given
|
290
|
+
lambda { @sharedbook.bms_setBackCoverPhoto(@valid_post) }.should raise_error(ResponseError)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
describe '#bms_publish' do
|
295
|
+
it 'should post the the publish url' do
|
296
|
+
# expect
|
297
|
+
@sharedbook.should_receive(:post_url).with(
|
298
|
+
"#{SharedBook::URL}/bms/publish",
|
299
|
+
{:bmsId=>123}
|
300
|
+
).and_return("<bms.publish status=\"ok\" />")
|
301
|
+
|
302
|
+
# given
|
303
|
+
@sharedbook.bms_publish(123).should be_true
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
307
|
+
# expect
|
308
|
+
post_should_get_response_error(:bms_publish, 123)
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
describe '#bookcreate_init' do
|
313
|
+
it 'should post the the publish url' do
|
314
|
+
# expect
|
315
|
+
@sharedbook.should_receive(:post_url).with(
|
316
|
+
"#{SharedBook::URL}/bookcreate/init",
|
317
|
+
{:bmsId=>123}
|
318
|
+
).and_return("<bookcreate.init status=\"ok\" />")
|
319
|
+
|
320
|
+
# given
|
321
|
+
@sharedbook.bookcreate_init(123).should be_true
|
322
|
+
end
|
323
|
+
|
324
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
325
|
+
# expect
|
326
|
+
post_should_get_response_error(:bookcreate_init, 123)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
describe '#bookcreate_setDedication' do
|
331
|
+
it 'should post the the publish url' do
|
332
|
+
# expect
|
333
|
+
@sharedbook.should_receive(:post_url).with(
|
334
|
+
"#{SharedBook::URL}/bookcreate/setDedication",
|
335
|
+
{:dedicationText=>"Dedicated!", :bmsId=>123}
|
336
|
+
).and_return("<bookcreate.setDedication status=\"ok\" />")
|
337
|
+
|
338
|
+
# given
|
339
|
+
@sharedbook.bookcreate_setDedication({:bms_id => 123, :dedication_text => "Dedicated!"}).should be_true
|
340
|
+
end
|
341
|
+
|
342
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
343
|
+
# expect
|
344
|
+
post_should_get_response_error(:bookcreate_setDedication, 123)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe '#bookcreate_publish' do
|
349
|
+
it 'should post to the bookcreate publish url and return a book_id' do
|
350
|
+
# expect
|
351
|
+
@sharedbook.should_receive(:post_url).with(
|
352
|
+
"#{SharedBook::URL}/bookcreate/publish",
|
353
|
+
{:bmsId=>123}
|
354
|
+
).and_return(
|
355
|
+
"<bookcreate.publish status=\"ok\" />\n\t<book id=\"54321\" />\n</bookcreate.init>"
|
356
|
+
)
|
357
|
+
|
358
|
+
# given
|
359
|
+
@sharedbook.bookcreate_publish(123).should == "54321"
|
360
|
+
@sharedbook.book_id.should == "54321"
|
361
|
+
end
|
362
|
+
|
363
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
364
|
+
# expect
|
365
|
+
post_should_get_response_error(:bookcreate_publish, 123)
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
describe '#book_preview' do
|
370
|
+
before(:all) do
|
371
|
+
@preview_url = 'http://www.examp.le/12345'
|
372
|
+
end
|
373
|
+
it 'should post to the book preview url and return a preview url' do
|
374
|
+
# expect
|
375
|
+
@sharedbook.should_receive(:get_url).with(
|
376
|
+
"#{SharedBook::URL}/book/preview",
|
377
|
+
{:sessionToken=>"aabbcc", :apiKey=>"x", :authToken=>"x", :bookId=>"321", :redirect=>"false", :bmsId=>"123", :signature=>"5949fa652c1dba35111d978541ecb44e"}
|
378
|
+
).and_return(
|
379
|
+
"<book.preview status=\"ok\">\n\t<url>#{@preview_url}</url></book.preview>"
|
380
|
+
)
|
381
|
+
|
382
|
+
# given
|
383
|
+
@sharedbook.book_preview(123,321).should == @preview_url
|
384
|
+
@sharedbook.book_url.should == @preview_url
|
385
|
+
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should raise ResponseError if the return cannot be parsed' do
|
389
|
+
# expect
|
390
|
+
@sharedbook.should_receive(:get_url).and_return(@bad_return)
|
391
|
+
|
392
|
+
# given
|
393
|
+
lambda { @sharedbook.book_preview(123,321) }.should raise_error(ResponseError)
|
394
|
+
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
end
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/spec/spec_helper.rb
ADDED
data/tasks/rspec.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
5
|
+
require 'spec'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
rescue LoadError
|
10
|
+
puts <<-EOS
|
11
|
+
To use rspec for testing you must install rspec gem:
|
12
|
+
gem install rspec
|
13
|
+
EOS
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run the specs under spec/models"
|
18
|
+
Spec::Rake::SpecTask.new do |t|
|
19
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
20
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shared_book
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthew Nielsen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-11-08 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rubyforge
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 0
|
33
|
+
- 4
|
34
|
+
version: 2.0.4
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: hoe
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 21
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 6
|
49
|
+
- 1
|
50
|
+
version: 2.6.1
|
51
|
+
type: :development
|
52
|
+
version_requirements: *id002
|
53
|
+
description: |-
|
54
|
+
A Ruby Gem to connect to the SharedBook.com publishing API.
|
55
|
+
|
56
|
+
This version provides 1:1 method call structure to the SharedBook rest-like API.
|
57
|
+
email:
|
58
|
+
- xunker@pyxidis.org
|
59
|
+
executables: []
|
60
|
+
|
61
|
+
extensions: []
|
62
|
+
|
63
|
+
extra_rdoc_files:
|
64
|
+
- History.txt
|
65
|
+
- Manifest.txt
|
66
|
+
files:
|
67
|
+
- History.txt
|
68
|
+
- Manifest.txt
|
69
|
+
- README.rdoc
|
70
|
+
- Rakefile
|
71
|
+
- lib/shared_book.rb
|
72
|
+
- spec/lib/shared_book_spec.rb
|
73
|
+
- spec/spec.opts
|
74
|
+
- spec/spec_helper.rb
|
75
|
+
- tasks/rspec.rake
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/xunker/shared_book
|
78
|
+
licenses: []
|
79
|
+
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- --main
|
83
|
+
- README.rdoc
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
hash: 3
|
92
|
+
segments:
|
93
|
+
- 0
|
94
|
+
version: "0"
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
hash: 3
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
version: "0"
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: shared_book
|
107
|
+
rubygems_version: 1.3.7
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: A Ruby Gem to connect to the SharedBook.com publishing API
|
111
|
+
test_files: []
|
112
|
+
|