postful 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.
Files changed (64) hide show
  1. data/doc/classes/Postful/AuthorizationException.html +111 -0
  2. data/doc/classes/Postful/Base.html +255 -0
  3. data/doc/classes/Postful/Base.src/M000001.html +21 -0
  4. data/doc/classes/Postful/Base.src/M000002.html +23 -0
  5. data/doc/classes/Postful/Base.src/M000003.html +22 -0
  6. data/doc/classes/Postful/Base.src/M000004.html +22 -0
  7. data/doc/classes/Postful/Base.src/M000005.html +20 -0
  8. data/doc/classes/Postful/Base.src/M000006.html +18 -0
  9. data/doc/classes/Postful/InternalServerError.html +111 -0
  10. data/doc/classes/Postful/Letter.html +347 -0
  11. data/doc/classes/Postful/Letter.src/M000015.html +18 -0
  12. data/doc/classes/Postful/Letter.src/M000016.html +18 -0
  13. data/doc/classes/Postful/Letter.src/M000017.html +18 -0
  14. data/doc/classes/Postful/Letter.src/M000018.html +18 -0
  15. data/doc/classes/Postful/Letter.src/M000019.html +18 -0
  16. data/doc/classes/Postful/Letter.src/M000020.html +18 -0
  17. data/doc/classes/Postful/Letter.src/M000021.html +18 -0
  18. data/doc/classes/Postful/Letter.src/M000022.html +18 -0
  19. data/doc/classes/Postful/Letter.src/M000023.html +18 -0
  20. data/doc/classes/Postful/Letter.src/M000024.html +18 -0
  21. data/doc/classes/Postful/Letter.src/M000025.html +18 -0
  22. data/doc/classes/Postful/Letter.src/M000026.html +18 -0
  23. data/doc/classes/Postful/NotFoundException.html +111 -0
  24. data/doc/classes/Postful/Order.html +276 -0
  25. data/doc/classes/Postful/Order.src/M000007.html +19 -0
  26. data/doc/classes/Postful/Order.src/M000008.html +24 -0
  27. data/doc/classes/Postful/Order.src/M000009.html +26 -0
  28. data/doc/classes/Postful/Order.src/M000010.html +18 -0
  29. data/doc/classes/Postful/Service.html +191 -0
  30. data/doc/classes/Postful/Service.src/M000011.html +19 -0
  31. data/doc/classes/Postful/Service.src/M000012.html +24 -0
  32. data/doc/classes/Postful/Service.src/M000013.html +19 -0
  33. data/doc/classes/Postful/Service.src/M000014.html +24 -0
  34. data/doc/classes/Postful/UnknownResponseException.html +111 -0
  35. data/doc/classes/Postful/ValidationException.html +111 -0
  36. data/doc/created.rid +1 -0
  37. data/doc/files/MIT-LICENSE.html +129 -0
  38. data/doc/files/README.html +164 -0
  39. data/doc/files/lib/postful/base_rb.html +108 -0
  40. data/doc/files/lib/postful/instance_exec_module_rb.html +101 -0
  41. data/doc/files/lib/postful/letter_rb.html +109 -0
  42. data/doc/files/lib/postful/order_rb.html +110 -0
  43. data/doc/files/lib/postful/service_rb.html +109 -0
  44. data/doc/files/lib/postful/util_rb.html +101 -0
  45. data/doc/files/lib/postful_rb.html +110 -0
  46. data/doc/fr_class_index.html +35 -0
  47. data/doc/fr_file_index.html +35 -0
  48. data/doc/fr_method_index.html +52 -0
  49. data/doc/index.html +24 -0
  50. data/doc/rdoc-style.css +208 -0
  51. data/lib/postful.rb +3 -0
  52. data/lib/postful/base.rb +163 -0
  53. data/lib/postful/instance_exec_module.rb +25 -0
  54. data/lib/postful/letter.rb +134 -0
  55. data/lib/postful/order.rb +130 -0
  56. data/lib/postful/service.rb +40 -0
  57. data/lib/postful/util.rb +69 -0
  58. data/test/suite.rb +5 -0
  59. data/test/test_base.rb +131 -0
  60. data/test/test_letter.rb +115 -0
  61. data/test/test_order.rb +49 -0
  62. data/test/test_postcard.rb +12 -0
  63. data/test/test_service.rb +52 -0
  64. metadata +128 -0
@@ -0,0 +1,3 @@
1
+ require 'postful/base'
2
+ require 'postful/letter'
3
+ require 'postful/service'
@@ -0,0 +1,163 @@
1
+ require 'builder'
2
+ require File.join(File.dirname(__FILE__), 'order')
3
+ require File.join(File.dirname(__FILE__), 'util')
4
+
5
+ module Postful #:nodoc:
6
+ class AuthorizationException < StandardError; end
7
+ class ValidationException < StandardError; end
8
+ class UnknownResponseException < StandardError; end
9
+ class NotFoundException < StandardError; end
10
+ class InternalServerError < StandardError; end
11
+
12
+ #--
13
+ # TODO: address validations and SimpleValidation
14
+ #++
15
+ class Base
16
+ attr_accessor :tag1
17
+ attr_accessor :tag2
18
+ attr_accessor :tag3
19
+ attr_accessor :return_address
20
+
21
+ include Util
22
+
23
+ def initialize(email, password)
24
+ @email = email
25
+ @password = password
26
+ @documents = []
27
+ @addressees = []
28
+ end
29
+
30
+ # Add a domestic recipient to a mailing
31
+ def add_address(attributes = {})
32
+ returning DomesticAddress.new do |address|
33
+ [:name, :company, :address, :address2, :city, :state, :postal_code].each do |field|
34
+ address.send("#{field}=", attributes[field] || attributes[field.to_s])
35
+ end
36
+ @addressees << address
37
+ end
38
+ end
39
+
40
+ # Add an international recipient to a mailing
41
+ def add_international_address(lines, country)
42
+ returning InternationalAddress.new do |address|
43
+ address.lines = lines
44
+ address.country = country
45
+ @addressees << address
46
+ end
47
+ end
48
+
49
+ def mail!
50
+ if !valid?
51
+ raise errors.join("\n")
52
+ else
53
+ mail_without_validation!
54
+ end
55
+ end
56
+
57
+ def valid?
58
+ errors.clear
59
+ validate
60
+ errors.empty?
61
+ end
62
+
63
+ def errors
64
+ @errors ||= []
65
+ end
66
+
67
+ protected
68
+
69
+ def build_request #:nodoc:
70
+ request = ''
71
+ xml = Builder::XmlMarkup.new(:target => request)
72
+ xml.mail do
73
+ xml.documents do
74
+ @documents.each do |document|
75
+ document.build_request(xml)
76
+ end
77
+ end
78
+ xml.addressees do
79
+ @addressees.each do |addressee|
80
+ addressee.build_request(xml)
81
+ end
82
+ end
83
+ xml.tag1 tag1 if tag1
84
+ xml.tag2 tag2 if tag2
85
+ xml.tag3 tag3 if tag3
86
+ if return_address
87
+ xml.return_address do
88
+ return_address.each_with_index do |line, i|
89
+ xml.tag! "line#{i + 1}", line
90
+ end
91
+ end
92
+ end
93
+ end
94
+ request
95
+ end
96
+
97
+ def validate #:nodoc:
98
+ errors << 'Requires at least one addressee' if @addressees.empty?
99
+ end
100
+
101
+ def mail_without_validation! #:nodoc:
102
+ upload_documents
103
+ xml = build_request
104
+ send_request(xml)
105
+ end
106
+
107
+ def send_request(xml) #:nodoc:
108
+ response = post_request_on_path('/service/mail', xml, @email, @password)
109
+ case response.code
110
+ when "201" then
111
+ Postful::Order.build_from_response(response.body, @email, @password)
112
+ when "422" then
113
+ raise Postful::ValidationException, build_error_message_from_response(response.body)
114
+ else
115
+ process_standard_responses(response)
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ class DomesticAddress #:nodoc:
122
+ attr_accessor :name
123
+ attr_accessor :company
124
+ attr_accessor :address
125
+ attr_accessor :address2
126
+ attr_accessor :city
127
+ attr_accessor :state
128
+ attr_accessor :postal_code
129
+
130
+ def build_request(builder)
131
+ builder.addressee do
132
+ builder.name name
133
+ builder.company company if company
134
+ builder.address address
135
+ builder.address2 address2 if address2
136
+ builder.city city
137
+ builder.state state
138
+ builder.postal_code postal_code
139
+ end
140
+ end
141
+ end
142
+
143
+ class InternationalAddress #:nodoc:
144
+ attr_accessor :country
145
+ attr_accessor :lines
146
+
147
+ def build_request(builder)
148
+ builder.addressee do
149
+ lines.each_with_index do |line, i|
150
+ builder.tag! "line#{i + 1}", line
151
+ end
152
+ builder.country country
153
+ end
154
+ end
155
+ end
156
+
157
+ def returning(value)
158
+ yield(value)
159
+ value
160
+ end
161
+
162
+ end
163
+ end
@@ -0,0 +1,25 @@
1
+ module InstanceExecModule #:nodoc:
2
+ unless defined? instance_exec # 1.9
3
+ module InstanceExecMethods #:nodoc:
4
+ end
5
+ include InstanceExecMethods
6
+
7
+ # From Mauricio's http://eigenclass.org/hiki/bounded+space+instance_exec
8
+ def instance_exec(*args, &block)
9
+ begin
10
+ old_critical, Thread.critical = Thread.critical, true
11
+ n = 0
12
+ n += 1 while respond_to?(method_name = "__instance_exec#{n}")
13
+ InstanceExecMethods.module_eval { define_method(method_name, &block) }
14
+ ensure
15
+ Thread.critical = old_critical
16
+ end
17
+
18
+ begin
19
+ send(method_name, *args)
20
+ ensure
21
+ InstanceExecMethods.module_eval { remove_method(method_name) } rescue nil
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,134 @@
1
+ require 'net/https'
2
+ require 'builder'
3
+ require File.join(File.dirname(__FILE__), 'base')
4
+ require File.join(File.dirname(__FILE__), 'util')
5
+
6
+ module Postful
7
+ # Builder for mailing letters with the Postful Web Service.
8
+
9
+ class Letter < Base
10
+ # Add an HTML document to the request
11
+ def add_html_document(text)
12
+ add_document('html', text)
13
+ end
14
+
15
+ # Add an HTML document to the request
16
+ def add_html_document_from_file(filename)
17
+ add_document_from_file('html', filename)
18
+ end
19
+
20
+ # Add a Microsoft Word document to the request
21
+ def add_word_document(text)
22
+ add_document('doc', text)
23
+ end
24
+
25
+ # Add a Microsoft Word document to the request
26
+ def add_word_document_from_file(filename)
27
+ add_document_from_file('doc', filename)
28
+ end
29
+
30
+ # Add a Rich Text Format document to the request
31
+ def add_rtf_document(text)
32
+ add_document('rtf', text)
33
+ end
34
+
35
+ # Add a Rich Text Format document to the request
36
+ def add_rtf_document_from_file(filename)
37
+ add_document_from_file('rtf', filename)
38
+ end
39
+
40
+ # Add an Open Office document to the request
41
+ def add_open_office_document(text)
42
+ add_document('odt', text)
43
+ end
44
+
45
+ # Add an Open Office document to the request
46
+ def add_open_office_document_from_file(filename)
47
+ add_document_from_file('odt', filename)
48
+ end
49
+
50
+ # Add a text document to the request
51
+ def add_text_document(text)
52
+ add_document('txt', text)
53
+ end
54
+
55
+ # Add a text document to the request
56
+ def add_text_document_from_file(filename)
57
+ add_document_from_file('txt', filename)
58
+ end
59
+
60
+ # Add a PDF document to the request
61
+ def add_pdf_document(text)
62
+ add_document('pdf', text)
63
+ end
64
+
65
+ # Add a PDF document to the request
66
+ def add_pdf_document_from_file(filename)
67
+ add_document_from_file('pdf', filename)
68
+ end
69
+
70
+ protected
71
+
72
+ def validate #:nodoc:
73
+ super
74
+ errors << 'Requires at least one document' if @documents.empty?
75
+ end
76
+
77
+ private
78
+
79
+ def upload_documents
80
+ @documents.each do |document|
81
+ document.upload
82
+ end
83
+ end
84
+
85
+ def add_document(format, content)
86
+ returning Document.new(@email, @password) do |document|
87
+ document.format = format
88
+ document.content = content
89
+ @documents << document
90
+ end
91
+ end
92
+
93
+ def add_document_from_file(format, filename)
94
+ content = open(filename, "rb") do |io|
95
+ io.read
96
+ end
97
+ add_document(format, content)
98
+ end
99
+
100
+ class Document #:nodoc:
101
+ include Util
102
+
103
+ attr_accessor :format
104
+ attr_accessor :content
105
+ attr_accessor :attachment_name
106
+
107
+ def initialize(email, password)
108
+ @email = email
109
+ @password = password
110
+ end
111
+
112
+ def build_request(builder)
113
+ builder.document do
114
+ builder.type format
115
+ builder.attachment attachment_name
116
+ end
117
+ end
118
+
119
+ def upload
120
+ headers = {
121
+ 'Content-Type' => 'application/octet-stream',
122
+ 'Content-Length' => content.size.to_s
123
+ }
124
+ response = post_request_on_path('/service/upload', content, @email, @password, headers)
125
+ if response.is_a?(Net::HTTPOK) && response.body =~ /<id>(.*)<\/id>/
126
+ self.attachment_name = $1
127
+ else
128
+ # raise stuff
129
+ end
130
+ end
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,130 @@
1
+ require 'rexml/document'
2
+ require 'open-uri'
3
+ require 'date'
4
+ require File.join(File.dirname(__FILE__), 'instance_exec_module')
5
+ require File.join(File.dirname(__FILE__), 'util')
6
+
7
+ module Postful
8
+ # Represent a Postful order.
9
+ class Order
10
+ attr :id
11
+ attr :status
12
+ attr :source
13
+ attr :pages
14
+ attr :preview_url
15
+ attr :price
16
+ attr :tag1
17
+ attr :tag2
18
+ attr :tag3
19
+ attr :history
20
+ attr :addressees
21
+ attr :warnings
22
+ attr :return_address
23
+ attr :errors
24
+
25
+ include Util
26
+
27
+ def preview
28
+ url = URI.parse(preview_url)
29
+ url.read
30
+ end
31
+
32
+ def refresh
33
+ response = get_request_on_path("/service/mail/#{id}", @email, @password)
34
+ case response.code
35
+ when "200" then
36
+ build_from_response(response.body)
37
+ else
38
+ process_standard_responses(response)
39
+ end
40
+ end
41
+
42
+ def cancel
43
+ response = delete_request_on_path("/service/mail/#{id}", @email, @password)
44
+ case response.code
45
+ when "200" then
46
+ build_from_response(response.body)
47
+ when "422" then
48
+ raise Postful::ValidationException, build_error_message_from_response(response.body)
49
+ else
50
+ process_standard_responses(response)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def self.build_all_from_response(response, email, password)
57
+
58
+ document = REXML::Document.new response
59
+ root = document.root
60
+ results = []
61
+ root.elements.each("mail") do |mail|
62
+ results << new do
63
+ @email = email
64
+ @password = password
65
+ build_from_root(mail)
66
+ end
67
+ end
68
+ results
69
+ end
70
+
71
+ def build_from_root(root)
72
+ @id = root.elements['id'].text.to_i
73
+ @status = root.elements['status'].text
74
+ @source = root.elements['source'].text
75
+ @pages = root.elements['pages'].text.to_i
76
+ @preview_url = root.elements['preview'].text
77
+ @price = root.elements['price'].text
78
+ @tag1 = root.elements['tag1'].text
79
+ @tag2 = root.elements['tag2'].text
80
+ @tag3 = root.elements['tag3'].text
81
+ @history = []
82
+ root.elements.each("history/status") do |element|
83
+ status = []
84
+ status << Time.parse(element.elements['at'].text)
85
+ status << element.elements['message'].text
86
+ @history << status
87
+ end
88
+ @warnings = []
89
+ root.elements.each("warnings/warning") do |element|
90
+ @warnings << element.elements['message'].text
91
+ end
92
+ @return_address = []
93
+ root.elements["return-address"].elements.each do |element|
94
+ @return_address << element.text
95
+ end
96
+ @addressees = []
97
+ root.elements["addressees"].elements.each do |addressee|
98
+ address = []
99
+ addressee.elements.each do |line|
100
+ address << line.text
101
+ end
102
+ @addressees << address
103
+ end
104
+ @errors = []
105
+ root.elements.each("fatal-errors/fatal-error") do |element|
106
+ @errors << element.elements['message'].text
107
+ end
108
+ self
109
+ end
110
+
111
+ def build_from_response(response)
112
+ document = REXML::Document.new response
113
+ build_from_root(document.root)
114
+ end
115
+
116
+ def initialize(&initializer)
117
+ instance_exec &initializer
118
+ end
119
+
120
+ def self.build_from_response(response, email, password)
121
+ o = new do
122
+ @email = email
123
+ @password = password
124
+ build_from_response(response)
125
+ end
126
+ end
127
+
128
+ include InstanceExecModule
129
+ end
130
+ end