dewey 0.1.2

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/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
+