google-spreadsheet-ruby 0.1.8 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "forwardable"
5
+
6
+ require "google_spreadsheet/util"
7
+ require "google_spreadsheet/error"
8
+
9
+
10
+ module GoogleSpreadsheet
11
+
12
+ # Hash-like object returned by GoogleSpreadsheet::List#[].
13
+ class ListRow
14
+
15
+ include(Enumerable)
16
+ extend(Forwardable)
17
+
18
+ def_delegators(:to_hash,
19
+ :keys, :values, :each_key, :each_value, :each, :each_pair, :hash,
20
+ :assoc, :fetch, :flatten, :key, :invert, :size, :length, :rassoc,
21
+ :merge, :reject, :select, :sort, :to_a, :values_at)
22
+
23
+ def initialize(list, index) #:nodoc:
24
+ @list = list
25
+ @index = index
26
+ end
27
+
28
+ def [](key)
29
+ return @list.get(@index, key)
30
+ end
31
+
32
+ def []=(key, value)
33
+ @list.set(@index, key, value)
34
+ end
35
+
36
+ def has_key?(key)
37
+ return @list.keys.include?(key)
38
+ end
39
+
40
+ alias include? has_key?
41
+ alias key? has_key?
42
+ alias member? has_key?
43
+
44
+ def update(hash)
45
+ for k, v in hash
46
+ self[k] = v
47
+ end
48
+ end
49
+
50
+ alias merge! update
51
+
52
+ def replace(hash)
53
+ clear()
54
+ update(hash)
55
+ end
56
+
57
+ def clear()
58
+ for key in @list.keys
59
+ self[key] = ""
60
+ end
61
+ end
62
+
63
+ def to_hash()
64
+ result = {}
65
+ for key in @list.keys
66
+ result[key] = self[key]
67
+ end
68
+ return result
69
+ end
70
+
71
+ def ==(other)
72
+ return self.class == other.class && self.to_hash() == other.to_hash()
73
+ end
74
+
75
+ alias === ==
76
+ alias eql? ==
77
+
78
+ def inspect
79
+ return "\#<%p %p>" % [self.class, to_hash()]
80
+ end
81
+
82
+ end
83
+
84
+ end
@@ -0,0 +1,26 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "rubygems"
5
+ require "oauth"
6
+
7
+
8
+ module GoogleSpreadsheet
9
+
10
+ class OAuth1Fetcher #:nodoc:
11
+
12
+ def initialize(oauth1_token)
13
+ @oauth1_token = oauth1_token
14
+ end
15
+
16
+ def request_raw(method, url, data, extra_header, auth)
17
+ if method == :delete || method == :get
18
+ return @oauth1_token.__send__(method, url, extra_header)
19
+ else
20
+ return @oauth1_token.__send__(method, url, data, extra_header)
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,29 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "rubygems"
5
+ require "oauth2"
6
+
7
+
8
+ module GoogleSpreadsheet
9
+
10
+ class OAuth2Fetcher #:nodoc:
11
+
12
+ Response = Struct.new(:code, :body)
13
+
14
+ def initialize(oauth2_token)
15
+ @oauth2_token = oauth2_token
16
+ end
17
+
18
+ def request_raw(method, url, data, extra_header, auth)
19
+ if method == :delete || method == :get
20
+ raw_res = @oauth2_token.request(method, url, {:header => extra_header})
21
+ else
22
+ raw_res = @oauth2_token.request(method, url, {:header => extra_header, :body => data})
23
+ end
24
+ return Response.new(raw_res.status.to_s(), raw_res.body)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,31 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "google_spreadsheet/util"
5
+ require "google_spreadsheet/error"
6
+
7
+
8
+ module GoogleSpreadsheet
9
+
10
+ # DEPRECATED: Table and Record feeds are deprecated and they will not be available after
11
+ # March 2012.
12
+ #
13
+ # Use GoogleSpreadsheet::Table#records to get GoogleSpreadsheet::Record objects.
14
+ class Record < Hash
15
+ include(Util)
16
+
17
+ def initialize(session, entry) #:nodoc:
18
+ @session = session
19
+ entry.css("gs|field").each() do |field|
20
+ self[field["name"]] = field.inner_text
21
+ end
22
+ end
23
+
24
+ def inspect #:nodoc:
25
+ content = self.map(){ |k, v| "%p => %p" % [k, v] }.join(", ")
26
+ return "\#<%p:{%s}>" % [self.class, content]
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,297 @@
1
+ # Author: Hiroshi Ichikawa <http://gimite.net/>
2
+ # The license of this source is "New BSD Licence"
3
+
4
+ require "cgi"
5
+
6
+ require "rubygems"
7
+ require "nokogiri"
8
+
9
+ require "google_spreadsheet/util"
10
+ require "google_spreadsheet/client_login_fetcher"
11
+ require "google_spreadsheet/oauth1_fetcher"
12
+ require "google_spreadsheet/oauth2_fetcher"
13
+ require "google_spreadsheet/error"
14
+ require "google_spreadsheet/authentication_error"
15
+ require "google_spreadsheet/spreadsheet"
16
+ require "google_spreadsheet/worksheet"
17
+ require "google_spreadsheet/collection"
18
+
19
+
20
+ module GoogleSpreadsheet
21
+
22
+ # Use GoogleSpreadsheet.login or GoogleSpreadsheet.saved_session to get
23
+ # GoogleSpreadsheet::Session object.
24
+ class Session
25
+
26
+ include(Util)
27
+ extend(Util)
28
+
29
+ # The same as GoogleSpreadsheet.login.
30
+ def self.login(mail, password, proxy = nil)
31
+ session = Session.new(nil, ClientLoginFetcher.new({}, proxy))
32
+ session.login(mail, password)
33
+ return session
34
+ end
35
+
36
+ # The same as GoogleSpreadsheet.login_with_oauth.
37
+ def self.login_with_oauth(oauth_token)
38
+ case oauth_token
39
+ when OAuth::AccessToken
40
+ fetcher = OAuth1Fetcher.new(oauth_token)
41
+ when OAuth2::AccessToken
42
+ fetcher = OAuth2Fetcher.new(oauth_token)
43
+ else
44
+ raise(GoogleSpreadsheet::Error,
45
+ "oauth_token is neither OAuth::Token nor OAuth2::Token: %p" % oauth_token)
46
+ end
47
+ return Session.new(nil, fetcher)
48
+ end
49
+
50
+ # The same as GoogleSpreadsheet.restore_session.
51
+ def self.restore_session(auth_tokens, proxy = nil)
52
+ return Session.new(auth_tokens, nil, proxy)
53
+ end
54
+
55
+ # Creates a dummy GoogleSpreadsheet::Session object for testing.
56
+ def self.new_dummy()
57
+ return Session.new(nil, Object.new())
58
+ end
59
+
60
+ # DEPRECATED: Use GoogleSpreadsheet.restore_session instead.
61
+ def initialize(auth_tokens = nil, fetcher = nil, proxy = nil)
62
+ if fetcher
63
+ @fetcher = fetcher
64
+ else
65
+ @fetcher = ClientLoginFetcher.new(auth_tokens || {}, proxy)
66
+ end
67
+ end
68
+
69
+ # Authenticates with given +mail+ and +password+, and updates current session object
70
+ # if succeeds. Raises GoogleSpreadsheet::AuthenticationError if fails.
71
+ # Google Apps account is supported.
72
+ def login(mail, password)
73
+ if !@fetcher.is_a?(ClientLoginFetcher)
74
+ raise(GoogleSpreadsheet::Error,
75
+ "Cannot call login for session created by login_with_oauth.")
76
+ end
77
+ begin
78
+ @fetcher.auth_tokens = {
79
+ :wise => authenticate(mail, password, :wise),
80
+ :writely => authenticate(mail, password, :writely),
81
+ }
82
+ rescue GoogleSpreadsheet::Error => ex
83
+ return true if @on_auth_fail && @on_auth_fail.call()
84
+ raise(AuthenticationError, "Authentication failed for #{mail}: #{ex.message}")
85
+ end
86
+ end
87
+
88
+ # Authentication tokens.
89
+ def auth_tokens
90
+ if !@fetcher.is_a?(ClientLoginFetcher)
91
+ raise(GoogleSpreadsheet::Error,
92
+ "Cannot call auth_tokens for session created by " +
93
+ "login_with_oauth.")
94
+ end
95
+ return @fetcher.auth_tokens
96
+ end
97
+
98
+ # Authentication token.
99
+ def auth_token(auth = :wise)
100
+ return self.auth_tokens[auth]
101
+ end
102
+
103
+ # Proc or Method called when authentication has failed.
104
+ # When this function returns +true+, it tries again.
105
+ attr_accessor :on_auth_fail
106
+
107
+ # Returns list of spreadsheets for the user as array of GoogleSpreadsheet::Spreadsheet.
108
+ # You can specify query parameters described at
109
+ # http://code.google.com/apis/spreadsheets/docs/2.0/reference.html#Parameters
110
+ #
111
+ # e.g.
112
+ # session.spreadsheets
113
+ # session.spreadsheets("title" => "hoge")
114
+ def spreadsheets(params = {})
115
+ query = encode_query(params)
116
+ doc = request(
117
+ :get, "https://spreadsheets.google.com/feeds/spreadsheets/private/full?#{query}")
118
+ result = []
119
+ doc.css("feed > entry").each() do |entry|
120
+ title = entry.css("title").text
121
+ url = entry.css(
122
+ "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
123
+ result.push(Spreadsheet.new(self, url, title))
124
+ end
125
+ return result
126
+ end
127
+
128
+ # Returns GoogleSpreadsheet::Spreadsheet with given +key+.
129
+ #
130
+ # e.g.
131
+ # # http://spreadsheets.google.com/ccc?key=pz7XtlQC-PYx-jrVMJErTcg&hl=ja
132
+ # session.spreadsheet_by_key("pz7XtlQC-PYx-jrVMJErTcg")
133
+ def spreadsheet_by_key(key)
134
+ url = "https://spreadsheets.google.com/feeds/worksheets/#{key}/private/full"
135
+ return Spreadsheet.new(self, url)
136
+ end
137
+
138
+ # Returns GoogleSpreadsheet::Spreadsheet with given +url+. You must specify either of:
139
+ # - URL of the page you open to access the spreadsheet in your browser
140
+ # - URL of worksheet-based feed of the spreadseet
141
+ #
142
+ # e.g.
143
+ # session.spreadsheet_by_url(
144
+ # "https://docs.google.com/spreadsheet/ccc?key=pz7XtlQC-PYx-jrVMJErTcg")
145
+ # session.spreadsheet_by_url(
146
+ # "https://spreadsheets.google.com/feeds/" +
147
+ # "worksheets/pz7XtlQC-PYx-jrVMJErTcg/private/full")
148
+ def spreadsheet_by_url(url)
149
+ # Tries to parse it as URL of human-readable spreadsheet.
150
+ uri = URI.parse(url)
151
+ if ["spreadsheets.google.com", "docs.google.com"].include?(uri.host) &&
152
+ uri.path =~ /\/ccc$/
153
+ if (uri.query || "").split(/&/).find(){ |s| s=~ /^key=(.*)$/ }
154
+ return spreadsheet_by_key($1)
155
+ end
156
+ end
157
+ # Assumes the URL is worksheets feed URL.
158
+ return Spreadsheet.new(self, url)
159
+ end
160
+
161
+ # Returns GoogleSpreadsheet::Spreadsheet with given +title+.
162
+ # Returns nil if not found. If multiple spreadsheets with the +title+ are found, returns
163
+ # one of them.
164
+ def spreadsheet_by_title(title)
165
+ return spreadsheets({"title" => title})[0]
166
+ end
167
+
168
+ # Returns GoogleSpreadsheet::Worksheet with given +url+.
169
+ # You must specify URL of cell-based feed of the worksheet.
170
+ #
171
+ # e.g.
172
+ # session.worksheet_by_url(
173
+ # "http://spreadsheets.google.com/feeds/" +
174
+ # "cells/pz7XtlQC-PYxNmbBVgyiNWg/od6/private/full")
175
+ def worksheet_by_url(url)
176
+ return Worksheet.new(self, nil, url)
177
+ end
178
+
179
+ # Returns GoogleSpreadsheet::Collection with given +url+.
180
+ # You must specify either of:
181
+ # - URL of the page you get when you go to https://docs.google.com/ with your browser and
182
+ # open a collection
183
+ # - URL of collection (folder) feed
184
+ #
185
+ # e.g.
186
+ # session.collection_by_url(
187
+ # "https://docs.google.com/?pli=1&authuser=0#folders/" +
188
+ # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
189
+ # session.collection_by_url(
190
+ # "http://docs.google.com/feeds/default/private/full/folder%3A" +
191
+ # "0B9GfDpQ2pBVUODNmOGE0NjIzMWU3ZC00NmUyLTk5NzEtYaFkZjY1MjAyxjMc")
192
+ def collection_by_url(url)
193
+ uri = URI.parse(url)
194
+ if uri.host == "docs.google.com" && uri.fragment =~ /^folders\/(.+)$/
195
+ # Looks like a URL of human-readable collection page. Converts to collection feed URL.
196
+ url = "https://docs.google.com/feeds/default/private/full/folder%3A#{$1}"
197
+ end
198
+ return Collection.new(self, url)
199
+ end
200
+
201
+ # Creates new spreadsheet and returns the new GoogleSpreadsheet::Spreadsheet.
202
+ #
203
+ # e.g.
204
+ # session.create_spreadsheet("My new sheet")
205
+ def create_spreadsheet(
206
+ title = "Untitled",
207
+ feed_url = "https://docs.google.com/feeds/documents/private/full")
208
+
209
+ xml = <<-"EOS"
210
+ <atom:entry
211
+ xmlns:atom="http://www.w3.org/2005/Atom"
212
+ xmlns:docs="http://schemas.google.com/docs/2007">
213
+ <atom:category
214
+ scheme="http://schemas.google.com/g/2005#kind"
215
+ term="http://schemas.google.com/docs/2007#spreadsheet"
216
+ label="spreadsheet"/>
217
+ <atom:title>#{h(title)}</atom:title>
218
+ </atom:entry>
219
+ EOS
220
+
221
+ doc = request(:post, feed_url, :data => xml, :auth => :writely)
222
+ ss_url = doc.css(
223
+ "link[rel='http://schemas.google.com/spreadsheets/2006#worksheetsfeed']")[0]["href"]
224
+ return Spreadsheet.new(self, ss_url, title)
225
+
226
+ end
227
+
228
+ def request(method, url, params = {}) #:nodoc:
229
+
230
+ # Always uses HTTPS.
231
+ url = url.gsub(%r{^http://}, "https://")
232
+ data = params[:data]
233
+ auth = params[:auth] || :wise
234
+ if params[:header]
235
+ extra_header = params[:header]
236
+ elsif data
237
+ extra_header = {"Content-Type" => "application/atom+xml"}
238
+ else
239
+ extra_header = {}
240
+ end
241
+ response_type = params[:response_type] || :xml
242
+
243
+ while true
244
+ response = @fetcher.request_raw(method, url, data, extra_header, auth)
245
+ if response.code == "401" && @on_auth_fail && @on_auth_fail.call()
246
+ next
247
+ end
248
+ if !(response.code =~ /^2/)
249
+ raise(
250
+ response.code == "401" ? AuthenticationError : GoogleSpreadsheet::Error,
251
+ "Response code #{response.code} for #{method} #{url}: " +
252
+ CGI.unescapeHTML(response.body))
253
+ end
254
+ return convert_response(response, response_type)
255
+ end
256
+
257
+ end
258
+
259
+ def inspect
260
+ return "#<%p:0x%x>" % [self.class, self.object_id]
261
+ end
262
+
263
+ private
264
+
265
+ def convert_response(response, response_type)
266
+ case response_type
267
+ when :xml
268
+ return Nokogiri.XML(response.body)
269
+ when :raw
270
+ return response.body
271
+ else
272
+ raise(GoogleSpreadsheet::Error,
273
+ "Unknown params[:response_type]: %s" % response_type)
274
+ end
275
+ end
276
+
277
+ def authenticate(mail, password, auth)
278
+ params = {
279
+ "accountType" => "HOSTED_OR_GOOGLE",
280
+ "Email" => mail,
281
+ "Passwd" => password,
282
+ "service" => auth.to_s(),
283
+ "source" => "Gimite-RubyGoogleSpreadsheet-1.00",
284
+ }
285
+ header = {"Content-Type" => "application/x-www-form-urlencoded"}
286
+ response = request(:post,
287
+ "https://www.google.com/accounts/ClientLogin",
288
+ :data => encode_query(params),
289
+ :auth => :none,
290
+ :header => header,
291
+ :response_type => :raw)
292
+ return response.slice(/^Auth=(.*)$/, 1)
293
+ end
294
+
295
+ end
296
+
297
+ end