dewey 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ ## 0.1.2 (June 28, 2010)
2
+
3
+ Features:
4
+
5
+ - Removed dependency on xmlsimple. Processing was so minimal it was pointless.
6
+
7
+ ---
8
+ ## 0.1.1 (June 28, 2010)
9
+
10
+ Bugfixes:
11
+
12
+ - Mandate ~ 1.3 version of rspec
13
+ - Clear up YAML load issues
data/README.md ADDED
@@ -0,0 +1,32 @@
1
+ Dewey allows you to simply upload, download and delete files from your Google
2
+ Docs account. Let Google do all of the hard work of converting your documents!
3
+
4
+ ## Usage
5
+
6
+ First, create a new Dewey::Document instance using your google docs account and
7
+ password.
8
+
9
+ dewey = Dewey::Document.new(:account => 'example.account', :password => 'example')
10
+
11
+ Next, choose a local document to upload or convert.
12
+
13
+ document = File.new('my_document.doc')
14
+ spreadsheet = File.new('my_spreadsheet.xls')
15
+
16
+ Finally, handle the upload, download, and delete manually or use the convenient
17
+ `convert` method.
18
+
19
+ dewey.convert(document, :html)
20
+ dewey.convert(spreadsheet, :csv)
21
+
22
+ ## Testing
23
+
24
+ Until testing is converted to an offline solution you will have to provide some
25
+ credentials for testing. All that is required is a valid Google Docs account, no
26
+ fussing about with getting an application API.
27
+
28
+ By default the spec will look for a file called dewey.yml inside of the spec
29
+ folder. The file should contain two lines:
30
+
31
+ email: <your gmail account>
32
+ password: <your gmail password>
data/TODO.md ADDED
@@ -0,0 +1,4 @@
1
+ ## Dewey TODO List
2
+ - Enable proper feed / service synchronization
3
+ - Correct spreadsheet support
4
+ - Add presentation support
data/lib/dewey.rb ADDED
@@ -0,0 +1,232 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+ require 'open-uri'
4
+ require 'rexml/document'
5
+ require 'tempfile'
6
+ require 'dewey/https'
7
+ require 'dewey/mime'
8
+ require 'dewey/utils'
9
+
10
+ module Dewey
11
+ GOOGLE_BASE_URL = "https://www.google.com"
12
+ GOOGLE_DOCS_URL = "https://docs.google.com"
13
+ GOOGLE_SPRD_URL = "https://spreadsheets.google.com"
14
+ GOOGLE_LOGIN_URL = GOOGLE_BASE_URL + "/accounts/ClientLogin"
15
+ GOOGLE_FEED_URL = GOOGLE_DOCS_URL + "/feeds/default/private/full"
16
+
17
+ GOOGLE_DOCUMENT_URL = GOOGLE_DOCS_URL + "/feeds/download/documents/Export"
18
+ GOOGLE_SPREADSHEET_URL = GOOGLE_SPRD_URL + "/feeds/download/spreadsheets/Export"
19
+
20
+ GOOGLE_SERVICES = { :document => 'writely', :spreadsheet => 'wise' }
21
+
22
+ DOCUMENT_EXPORT_FORMATS = %w(txt odt pdf html rtf doc png zip)
23
+ PRESENTATION_EXPORT_FORMATS = %w(swf pdf png ppt)
24
+ SPREADSHEET_EXPORT_FORMATS = %W(xls csv pdf ods tsv html)
25
+
26
+ class DeweyException < Exception
27
+ end
28
+
29
+ # Doc
30
+ # This base class handles authentication and requests
31
+ #
32
+ class Document
33
+ attr_accessor :account, :password, :service, :token, :cached
34
+
35
+ # Determine wether or not a format is available for download.
36
+ #
37
+ # * format - The file format to check, i.e. 'txt', 'doc', 'pdf'
38
+ # * service - The service that would be used. Must be document, presentation,
39
+ # or spreadsheet.
40
+ def self.valid_upload_format?(format, service = :document)
41
+ case service
42
+ when :document then Dewey::DOCUMENT_MIMETYPES.has_key?(format.to_sym)
43
+ when :presentation then Dewey::PRESENTATION_MIMETYPES.has_key?(format.to_sym)
44
+ when :spreadsheet then Dewey::SPREADSHEET_MIMETYPES.has_key?(format.to_sym)
45
+ else
46
+ raise DeweyException, "Unknown service: #{service}"
47
+ end
48
+ end
49
+
50
+ # Determine whether or not a format is available for export.
51
+ #
52
+ # * format - The file format to check, i.e. 'txt', 'doc', 'pdf'
53
+ # * service - The service that would be used. Must be document, presentation,
54
+ # or spreadsheet.
55
+ def self.valid_export_format?(format, service = :document)
56
+ case service
57
+ when :document then DOCUMENT_EXPORT_FORMATS.include?(format)
58
+ when :presentation then PRESENTATION_EXPORT_FORMATS.include?(format)
59
+ when :spreadsheet then SPREADSHEET_EXPORT_FORMATS.include?(format)
60
+ else
61
+ raise DeweyException, "Unknown service: #{service}"
62
+ end
63
+ end
64
+
65
+ # Create a new Doc object
66
+ # Options specified in +opts+ consist of:
67
+ #
68
+ # * :account - The Google Doc's account that will be used for authentication.
69
+ # This will most typically be a gmail account, i.e. +example@gmail.com+
70
+ # * :password - The password for the Google Doc's account.
71
+ # * :service - The default service to connect to, either :document or :spreadsheet
72
+ def initialize(options = {})
73
+ @account = options[:account]
74
+ @password = options[:password]
75
+ @service = options[:service] || :document
76
+ @cached = {}
77
+ end
78
+
79
+ # Returns true if this instance has been authorized
80
+ def authorized?
81
+ !! @token
82
+ end
83
+
84
+ # Gets an authorization token for this instance. Raises an error if no
85
+ # credentials have been provided, +false+ if authorization fails, and +true+ if
86
+ # authorization is successful.
87
+ def authorize!
88
+ if @account.nil? || @password.nil?
89
+ raise DeweyException, "Account or password missing."
90
+ end
91
+
92
+ url = URI.parse(GOOGLE_LOGIN_URL)
93
+ params = { 'accountType' => 'HOSTED_OR_GOOGLE', 'Email' => @account,
94
+ 'Passwd' => @password, 'service'=> GOOGLE_SERVICES[@service] }
95
+
96
+ response = Net::HTTPS.post_form(url, params)
97
+
98
+ case response
99
+ when Net::HTTPSuccess
100
+ @token = response.body.split(/=/).last
101
+ true
102
+ when Net::HTTPForbidden
103
+ false
104
+ else
105
+ raise DeweyException, "Unexpected response: #{response}"
106
+ end
107
+ end
108
+
109
+ def upload(file, title = nil)
110
+ extension = File.extname(file.path).sub('.', '')
111
+ basename = File.basename(file.path, ".#{extension}")
112
+ mimetype = Dewey::Mime.mime_type(file)
113
+ service = Dewey::Mime.guess_service(mimetype)
114
+
115
+ title ||= basename
116
+
117
+ raise DeweyException, "Invalid file type: #{extension}" unless Dewey::Document.valid_upload_format?(extension, service)
118
+
119
+ headers = base_headers
120
+ headers['Content-Length'] = File.size?(file).to_s
121
+ headers['Content-Type'] = mimetype
122
+ headers['Slug'] = Dewey::Utils.escape(title)
123
+
124
+ # Rewind the file in the case of multiple uploads, or conversions
125
+ file.rewind
126
+
127
+ response = post_request(GOOGLE_FEED_URL, file.read.to_s, headers)
128
+
129
+ case response
130
+ when Net::HTTPCreated
131
+ rid = @cached[basename] = extract_rid(response.body)
132
+ else
133
+ nil
134
+ end
135
+ end
136
+
137
+ # Download, or export more accurately, a file to a specified format
138
+ # * id - A resource id or exact file name matching a document in the account
139
+ # * format - The output format, see *_EXPORT_FORMATS for possiblibilites
140
+ def download(id, format = nil)
141
+ spreadsheet = !! id.match(/spreadsheet/)
142
+
143
+ url = (spreadsheet ? GOOGLE_SPREADSHEET_URL : GOOGLE_DOCUMENT_URL)
144
+ url << (spreadsheet ? "?key=#{id}" : "?docID=#{id}")
145
+ url << "&exportFormat=#{format.to_s}" unless format.nil?
146
+
147
+ headers = base_headers
148
+
149
+ file = Tempfile.new([id, format].join('.'))
150
+ file.binmode
151
+
152
+ open(url, headers) { |data| file.write data.read }
153
+
154
+ file
155
+ end
156
+
157
+ # Deletes a document referenced either by resource id or by name.
158
+ def delete(id)
159
+ headers = base_headers
160
+ headers['If-Match'] = '*' # We don't care if others have modified
161
+
162
+ url = GOOGLE_FEED_URL + "/#{Dewey::Utils.escape(id)}?delete=true"
163
+ response = delete_request(url, headers)
164
+
165
+ case response
166
+ when Net::HTTPOK
167
+ true
168
+ else
169
+ false
170
+ end
171
+ end
172
+
173
+ # Convenience method for +upload+, +download+, +delete+. Returns a Tempfile
174
+ # with in the provided type.
175
+ # * file - The file that will be converted
176
+ # * options - Takes :title and :format. See +upload+ for title, and +download+
177
+ # for format.
178
+ def convert(file, options = {})
179
+ rid = upload(file, options[:title])
180
+ con = download(rid, options[:format])
181
+
182
+ delete(rid)
183
+
184
+ con
185
+ end
186
+
187
+ private
188
+
189
+ def post_request(url, data, headers) #:nodoc:
190
+ http_request(:post, url, headers, data)
191
+ end
192
+
193
+ def delete_request(url, headers) #:nodoc:
194
+ http_request(:delete, url, headers)
195
+ end
196
+
197
+ def http_request(method, url, headers, data = nil) #:nodoc:
198
+ url = URI.parse(url) if url.kind_of? String
199
+
200
+ connection = Net::HTTP.new(url.host, url.port)
201
+ connection.use_ssl = (url.scheme == 'https')
202
+
203
+ case method
204
+ when :post
205
+ connection.post(url.path, data, headers)
206
+ when :delete
207
+ connection.delete(url.path, headers)
208
+ else
209
+ raise DeweyException, "Invalid request type. Valid options are :post and :delete"
210
+ end
211
+ end
212
+
213
+ def base_headers #:nodoc:
214
+ base = {}
215
+ base['GData-Version'] = '3.0'
216
+ base['Content-Type'] = 'application/x-www-form-urlencoded'
217
+ base['Authorization'] = "GoogleLogin auth=#{@token}" if authorized?
218
+
219
+ base
220
+ end
221
+
222
+ def extract_rid(source) #:nodoc:
223
+ xml = REXML::Document.new(source)
224
+
225
+ begin
226
+ "#{$1}:#{$2}" if xml.elements['//id'].text =~ /.+(document|spreadsheet|presentation)%3A([0-9a-zA-Z]+)/
227
+ rescue
228
+ raise DeweyException, "id could not be extracted from: #{body}"
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,11 @@
1
+ require 'net/http'
2
+
3
+ # Shortcut method for using +http.use_ssl+.
4
+ module Net
5
+ class HTTPS < Net::HTTP
6
+ def initialize(address, port = 443)
7
+ super(address, port)
8
+ self.use_ssl = true
9
+ end
10
+ end
11
+ end
data/lib/dewey/mime.rb ADDED
@@ -0,0 +1,84 @@
1
+ # (c) Copyright 2010 Parker Selbert <parker@sorentwo.com>
2
+ #
3
+ # Dewey is freely distributable under the terms of an MIT-style license.
4
+ # See LICENSE or http://www.opensource.org/licenses/mit-license.php
5
+
6
+ module Dewey
7
+
8
+ DOCUMENT_MIMETYPES = {
9
+ :doc => 'application/msword',
10
+ :docx => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
11
+ :htm => 'text/html',
12
+ :html => 'text/html',
13
+ :odt => 'application/vnd.oasis.opendocument.text',
14
+ :pdf => 'application/pdf',
15
+ :rtf => 'application/rtf',
16
+ :sxw => 'application/vnd.sun.xml.writer',
17
+ :txt => 'text/plain'
18
+ }
19
+
20
+ PRESENTATION_MIMETYPES = {
21
+ :pps => 'application/vnd.ms-powerpoint',
22
+ :ppt => 'application/vnd.ms-powerpoint'
23
+ }
24
+
25
+ SPREADSHEET_MIMETYPES = {
26
+ :csv => 'text/csv',
27
+ :ods => 'application/x-vnd.oasis.opendocument.spreadsheet',
28
+ :tab => 'text/tab-separated-values',
29
+ :tsv => 'text/tab-separated-values',
30
+ :xls => 'application/vnd.ms-excel',
31
+ :xlsx => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
32
+ }
33
+
34
+ class Mime
35
+ # Attempts to provide a mime_type that Google Docs finds acceptable. Certain
36
+ # types, when gathered from +file+ require extra coercion and will be handled
37
+ # automatically.
38
+ def self.mime_type(file)
39
+ type = (file.path.match(/\.(\w+)/)[1]).downcase
40
+ case type
41
+ when /csv/ then SPREADSHEET_MIMETYPES[:csv]
42
+ when /doc$/ then DOCUMENT_MIMETYPES[:doc]
43
+ when /docx/ then DOCUMENT_MIMETYPES[:docx]
44
+ when /htm/ then DOCUMENT_MIMETYPES[:html]
45
+ when /ods/ then SPREADSHEET_MIMETYPES[:ods]
46
+ when /odt/ then DOCUMENT_MIMETYPES[:odt]
47
+ when /pdf/ then DOCUMENT_MIMETYPES[:pdf]
48
+ when /pps/ then PRESENTATION_MIMETYPES[:pps]
49
+ when /ppt/ then PRESENTATION_MIMETYPES[:ppt]
50
+ when /rtf/ then DOCUMENT_MIMETYPES[:rtf]
51
+ when /sxw/ then DOCUMENT_MIMETYPES[:sxw]
52
+ when /tab/ then SPREADSHEET_MIMETYPES[:tab]
53
+ when /tsv/ then SPREADSHEET_MIMETYPES[:tsv]
54
+ when /txt/ then DOCUMENT_MIMETYPES[:txt]
55
+ when /xls$/ then SPREADSHEET_MIMETYPES[:xls]
56
+ when /xlsx/ then SPREADSHEET_MIMETYPES[:xlsx]
57
+ else
58
+ Mime.coerce((`file --mime-type #{file.path}`).split(':').last.strip) rescue "application/#{type}"
59
+ end
60
+ end
61
+
62
+ # Attempt to take mime types that are known to be guessed incorrectly and
63
+ # cast them to one that Google Docs will accept.
64
+ def self.coerce(mime_type)
65
+ case mime_type
66
+ when /vnd.ms-office/ then 'application/msword'
67
+ when /x-docx/ then 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
68
+ else mime_type
69
+ end
70
+ end
71
+
72
+ def self.guess_service(mime_type)
73
+ services = { :document => DOCUMENT_MIMETYPES,
74
+ :presentation => PRESENTATION_MIMETYPES,
75
+ :spreadsheet => SPREADSHEET_MIMETYPES }
76
+
77
+ services.each_key do |service|
78
+ return service if services[service].has_value?(mime_type)
79
+ end
80
+
81
+ nil
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,19 @@
1
+ module Dewey
2
+ module Utils #:nodoc:
3
+ # Performs URI escaping. (Stolen from Camping via Rake).
4
+ def escape(s)
5
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
6
+ '%'+$1.unpack('H2' * bytesize($1)).join('%').upcase
7
+ }.tr(' ', '+')
8
+ end
9
+
10
+ module_function :escape
11
+
12
+ # Return the bytesize of String; uses String#length under Ruby 1.8 and
13
+ def bytesize(string)
14
+ string.bytesize
15
+ end
16
+
17
+ module_function :bytesize
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dewey
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 2
9
+ version: 0.1.2
10
+ platform: ruby
11
+ authors:
12
+ - Parker Selbert
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-06-29 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :development
32
+ version_requirements: *id001
33
+ description: Dewey is a no-frills tool for utilizing Google Docs ability to convert documents between formats
34
+ email:
35
+ - parker@sorentwo.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - lib/dewey/https.rb
44
+ - lib/dewey/mime.rb
45
+ - lib/dewey/utils.rb
46
+ - lib/dewey.rb
47
+ - README.md
48
+ - CHANGELOG.md
49
+ - TODO.md
50
+ has_rdoc: true
51
+ homepage: http://github.com/sorentwo/dewey
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options: []
56
+
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ none: false
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 1
74
+ - 3
75
+ - 6
76
+ version: 1.3.6
77
+ requirements: []
78
+
79
+ rubyforge_project: dewey
80
+ rubygems_version: 1.3.7
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Google Docs fueled document conversion
84
+ test_files: []
85
+