simple_invoice-emailer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ simple_invoice-emailer (0.0.0)
5
+ mail
6
+ text-table
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ diff-lcs (1.2.4)
12
+ mail (2.5.4)
13
+ mime-types (~> 1.16)
14
+ treetop (~> 1.4.8)
15
+ mime-types (1.25)
16
+ polyglot (0.3.3)
17
+ rspec (2.14.1)
18
+ rspec-core (~> 2.14.0)
19
+ rspec-expectations (~> 2.14.0)
20
+ rspec-mocks (~> 2.14.0)
21
+ rspec-core (2.14.5)
22
+ rspec-expectations (2.14.3)
23
+ diff-lcs (>= 1.1.3, < 2.0)
24
+ rspec-mocks (2.14.3)
25
+ text-table (1.2.3)
26
+ treetop (1.4.15)
27
+ polyglot
28
+ polyglot (>= 0.3.1)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ rspec
35
+ simple_invoice-emailer!
@@ -0,0 +1,108 @@
1
+ require 'text-table'
2
+
3
+ module SimpleInvoice
4
+ module Emailer
5
+ class EmailInvoiceTextTable
6
+
7
+ def initialize
8
+ @table = Text::Table.new
9
+ @table.rows = []
10
+ @invoice_rows = []
11
+ end
12
+
13
+ def line_item values
14
+ @invoice_rows << values.values_at(*[:description, :price, :quantity, :total]).collect(&:to_s)
15
+ end
16
+
17
+ def total total
18
+ @total = total
19
+ end
20
+
21
+ # @return [String]
22
+ def text_table
23
+ @table.head = left ['Description', 'Price', 'Quantity', 'Total']
24
+ set_column_alignments [:left, :right, :right, :right]
25
+ @invoice_rows.each do |row|
26
+ add_row row
27
+ end
28
+ @table.foot = [{:value => 'Total', :colspan => 3}, right(@total)]
29
+ @table
30
+ end
31
+
32
+ def to_s
33
+ text_table.to_s
34
+ end
35
+
36
+ private
37
+
38
+ def set_column_alignments alignments
39
+ @alignments = alignments
40
+ end
41
+
42
+ # @param row [Array<String>]
43
+ def add_row row
44
+ rows = rows_multi_line row
45
+ rows.each do |row|
46
+ @table.rows << row_with_alignments(row)
47
+ end
48
+ end
49
+
50
+ # @param row [Array<String>]
51
+ def rows_multi_line row
52
+ if (n = number_of_lines row) > 1
53
+ split_into_lines = row.collect do |value|
54
+ value.split("\n")
55
+ end
56
+ (0...n).collect do |index|
57
+ split_into_lines.collect do |lines|
58
+ lines[index]
59
+ end
60
+ end
61
+ else
62
+ [row]
63
+ end
64
+ end
65
+
66
+ # @param row [Array<String>]
67
+ def number_of_lines row
68
+ row.collect do |value|
69
+ value.count "\n"
70
+ end.max + 1
71
+ end
72
+
73
+ # @param row [Array<String>]
74
+ # @return [Array<Hash>]
75
+ def row_with_alignments row
76
+ row.zip(@alignments).collect do |value, alignment|
77
+ align value, alignment
78
+ end
79
+ end
80
+
81
+ # @param value [String]
82
+ # @return [Hash]
83
+ def left value
84
+ align value, :left
85
+ end
86
+
87
+ # @param value [String]
88
+ # @return [Hash]
89
+ def right value
90
+ align value, :right
91
+ end
92
+
93
+ # @param value [String, Array<String>]
94
+ # @param alignment [Symbol]
95
+ # @return [Hash, Array<Hash>]
96
+ def align value, alignment
97
+ if value.is_a? Array
98
+ value.collect do |val|
99
+ align val, alignment
100
+ end
101
+ else
102
+ {:value => value, :align => alignment}
103
+ end
104
+ end
105
+
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,46 @@
1
+ module SimpleInvoice
2
+ module Emailer
3
+ class MailConfig
4
+
5
+ class << self
6
+
7
+ extend Forwardable
8
+ def_delegator :instance, :[]
9
+
10
+ def instance
11
+ @instance ||= new
12
+ end
13
+
14
+ # @param email [String]
15
+ def email_from email
16
+ instance[:email_from] = email
17
+ end
18
+
19
+ # @param subject [#call]
20
+ def email_subject subject
21
+ instance[:email_subject] = subject
22
+ end
23
+
24
+ # @param name [String]
25
+ def organisation_name name
26
+ instance[:organisation_name] = name
27
+ end
28
+
29
+ end
30
+
31
+ def initialize
32
+ @config_hash = {}
33
+ end
34
+
35
+ def [](key)
36
+ @config_hash[key]
37
+ end
38
+
39
+ def []=(key, value)
40
+ @config_hash[key] = value
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+
@@ -0,0 +1,45 @@
1
+ module SimpleInvoice
2
+ module Emailer
3
+ class Mailer
4
+
5
+ # @param mail_config [SimpleInvoice::Emailer::MailConfig]
6
+ def initialize mail_config=MailConfig
7
+ @mail_config = mail_config
8
+ end
9
+
10
+ # @param invoice [SimpleInvoice::Invoice]
11
+ def deliver! invoice
12
+ mail_instance(invoice).deliver
13
+ end
14
+
15
+ private
16
+
17
+ def mail_instance invoice
18
+ _email_from = get_config(:email_from)
19
+ _email_subject = email_subject(invoice)
20
+ _email_body = email_body(invoice)
21
+ Mail.new do
22
+ to invoice.contact.email
23
+ from _email_from
24
+ subject _email_subject
25
+ body _email_body
26
+ end
27
+ end
28
+
29
+ def email_body invoice
30
+ TextBody.new(invoice, @mail_config).email_body
31
+ end
32
+
33
+ def email_subject invoice
34
+ get_config(:email_subject)[invoice]
35
+ end
36
+
37
+ def get_config key
38
+ @mail_config[key].tap do |value|
39
+ raise "Missing config key #{key}" if value.nil?
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,67 @@
1
+ module SimpleInvoice
2
+ module Emailer
3
+ class TextBody
4
+
5
+ # @param invoice [SimpleInvoice::Invoice]
6
+ def initialize invoice, mail_config=MailConfig
7
+ @invoice = invoice
8
+ @mail_config = mail_config
9
+ end
10
+
11
+ # @return [String]
12
+ def email_body
13
+ salutation + "\n" +
14
+ "\n" +
15
+ preamble + "\n" +
16
+ "\n" +
17
+ invoice_data +
18
+ "\n" +
19
+ "Invoice Items:\n" +
20
+ line_items_table +
21
+ "\n" +
22
+ postscript + "\n"
23
+ end
24
+
25
+ def salutation
26
+ "Dear #{@invoice.contact.name}"
27
+ end
28
+
29
+ def preamble
30
+ "This email is an invoice for services by #{@mail_config[:organisation_name]}."
31
+ end
32
+
33
+ def postscript
34
+ ""
35
+ end
36
+
37
+ def invoice_data
38
+ list = TextList.new
39
+ list.add 'Invoice Number', @invoice.invoice_number
40
+ list.add 'Amount Due', dollars(@invoice.line_items.total)
41
+ list.add 'Due Date', @invoice.due_date
42
+ list.to_s
43
+ end
44
+
45
+ def line_items_table
46
+ table = EmailInvoiceTextTable.new
47
+ @invoice.line_items.to_a.each do |line_item|
48
+ table.line_item :description => line_item.description,
49
+ :price => dollars(line_item.price),
50
+ :quantity => line_item.quantity,
51
+ :total => dollars(line_item.total)
52
+ end
53
+ table.total dollars(@invoice.line_items.total)
54
+ table.to_s
55
+ end
56
+
57
+ # @param total_cents [Fixnum]
58
+ # @return [String]
59
+ def dollars total_cents
60
+ dollar = total_cents / 100
61
+ cents = total_cents % 100
62
+ "$%d.%02d" % [dollar, cents]
63
+ end
64
+
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ require 'text-table'
2
+
3
+ module SimpleInvoice
4
+ module Emailer
5
+ class TextList
6
+
7
+ def initialize
8
+ @table = Text::Table.new :horizontal_padding => 1,
9
+ :vertical_boundary => ' ',
10
+ :horizontal_boundary => ' ',
11
+ :boundary_intersection => ' '
12
+ @table.rows = []
13
+ end
14
+
15
+ def add label, value
16
+ @table.rows << [{:value => "#{label}:", :align => :right}, value.to_s]
17
+ end
18
+
19
+ def to_s
20
+ reduce_gap(unindent(remove_empty_lines(@table.to_s))) + "\n"
21
+ end
22
+
23
+ private
24
+
25
+ def remove_empty_lines str
26
+ str.gsub(/\n\s+\n/, "\n").gsub(/^\s+\n/, "")
27
+ end
28
+
29
+ # also removes trailing spaces from each line
30
+ def unindent str
31
+ lines = str.split("\n")
32
+ spaces = minimum_leading_spaces lines
33
+ lines.collect do |line|
34
+ line.slice(spaces, line.length).rstrip
35
+ end.join("\n")
36
+ end
37
+
38
+ def minimum_leading_spaces lines
39
+ lines.collect do |line|
40
+ /^\s+/.match(line).to_s.length
41
+ end.min
42
+ end
43
+
44
+ def reduce_gap str
45
+ str.gsub(': ', ': ')
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,18 @@
1
+ require 'mail'
2
+
3
+ module SimpleInvoice
4
+ module Emailer
5
+
6
+ autoload :TextBody, "simple_invoice/emailer/text_body"
7
+ autoload :EmailInvoiceTextTable, "simple_invoice/emailer/email_invoice_text_table"
8
+ autoload :TextList, "simple_invoice/emailer/text_list"
9
+ autoload :Mailer, "simple_invoice/emailer/mailer"
10
+ autoload :MailConfig, "simple_invoice/emailer/mail_config"
11
+
12
+ # @param invoice [SimpleInvoice::Invoice]
13
+ def self.deliver! invoice
14
+ Mailer.new.deliver! invoice
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "simple_invoice-emailer"
6
+ s.version = "0.0.1"
7
+ s.authors = ["Joel Plane"]
8
+ s.email = ["joel.plane@gmail.com"]
9
+ s.homepage = "https://github.com/joelplane/simple_invoice-emailer"
10
+ s.summary = %q{emailer for SimpleInvoice}
11
+ s.description = %q{sends SimpleInvoices via email}
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+
18
+ s.add_dependency "text-table"
19
+ s.add_dependency "mail"
20
+ s.add_development_dependency "rspec"
21
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ module SimpleInvoice::Emailer
4
+ describe EmailInvoiceTextTable do
5
+
6
+ example do
7
+ expected_output =
8
+ "+-------------+--------+----------+---------+\n" \
9
+ "| Description | Price | Quantity | Total |\n" \
10
+ "+-------------+--------+----------+---------+\n" \
11
+ "| Gardening | $50.00 | 1 | $50.00 |\n" \
12
+ "| Lawn Mowing | $60.00 | 1 | $60.00 |\n" \
13
+ "+-------------+--------+----------+---------+\n" \
14
+ "| Total | $110.00 |\n" \
15
+ "+-------------+--------+----------+---------+\n"
16
+ table = EmailInvoiceTextTable.new
17
+ table.line_item :description => 'Gardening', :price => "$50.00", :quantity => 1, :total => "$50.00"
18
+ table.line_item :description => 'Lawn Mowing', :price => "$60.00", :quantity => 1, :total => "$60.00"
19
+ table.total "$110.00"
20
+ table.to_s.should == expected_output
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,50 @@
1
+ module SimpleInvoice::Emailer
2
+ describe Mailer do
3
+
4
+ let :mail_config do
5
+ email_subject = lambda do |invoice|
6
+ "Invoice ##{invoice.invoice_number}"
7
+ end
8
+ {
9
+ :email_subject => email_subject,
10
+ :email_from => 'accounts@example.com'
11
+ }
12
+ end
13
+
14
+ let :contact do
15
+ double 'contact', :name => "John Smith", :email => 'john.smith@customer.example.com'
16
+ end
17
+
18
+ let :line_items do
19
+ double 'line_items', :total => 110_00,
20
+ :to_a => [
21
+ double('line_item', :description => 'Gardening', :price => 50_00, :quantity => 1, :total => 50_00),
22
+ double('line_item', :description => 'Lawn Mowing', :price => 60_00, :quantity => 1, :total => 50_00)
23
+ ]
24
+ end
25
+
26
+ let :invoice do
27
+ double 'invoice', :invoice_number => 123456,
28
+ :issue_date => "2013-01-02",
29
+ :due_date => "2013-01-09",
30
+ :contact => contact,
31
+ :line_items => line_items
32
+ end
33
+
34
+
35
+ subject do
36
+ Mailer.new(mail_config).tap do |mailer|
37
+ mailer.stub(:email_body => 'body')
38
+ end
39
+ end
40
+
41
+ example do
42
+ mail = subject.send(:mail_instance, invoice)
43
+ mail.should be_a Mail::Message
44
+ mail.subject.should == 'Invoice #123456'
45
+ mail.body.should == 'body'
46
+ mail.from.should == ['accounts@example.com']
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,8 @@
1
+ require 'simple_invoice/emailer'
2
+
3
+ RSpec.configure do |config|
4
+ config.treat_symbols_as_metadata_keys_with_true_values = true
5
+ config.run_all_when_everything_filtered = true
6
+ config.filter_run :focus
7
+ config.order = 'random'
8
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ module SimpleInvoice::Emailer
4
+ describe TextBody do
5
+
6
+ let(:contact) do
7
+ double 'contact', :name => "John Smith"
8
+ end
9
+ let(:line_items) do
10
+ double 'line_items', :total => 110_00,
11
+ :to_a => [
12
+ double('line_item', :description => 'Gardening', :price => 50_00, :quantity => 1, :total => 50_00),
13
+ double('line_item', :description => 'Lawn Mowing', :price => 60_00, :quantity => 1, :total => 50_00)
14
+ ]
15
+ end
16
+ let(:invoice) do
17
+ double 'invoice', :invoice_number => 123456,
18
+ :issue_date => "2013-01-02",
19
+ :due_date => "2013-01-09",
20
+ :contact => contact,
21
+ :line_items => line_items
22
+ end
23
+ let(:mail_config) do
24
+ {:organisation_name => "Business"}
25
+ end
26
+
27
+ example do
28
+ expected_output =
29
+ "Dear John Smith\n" \
30
+ "\n" \
31
+ "This email is an invoice for services by Business.\n" \
32
+ "\n" \
33
+ "Invoice Number: 123456\n" \
34
+ " Amount Due: $110.00\n" \
35
+ " Due Date: 2013-01-09\n" \
36
+ "\n" \
37
+ "Invoice Items:\n" \
38
+ "+-------------+--------+----------+---------+\n" \
39
+ "| Description | Price | Quantity | Total |\n" \
40
+ "+-------------+--------+----------+---------+\n" \
41
+ "| Gardening | $50.00 | 1 | $50.00 |\n" \
42
+ "| Lawn Mowing | $60.00 | 1 | $50.00 |\n" \
43
+ "+-------------+--------+----------+---------+\n" \
44
+ "| Total | $110.00 |\n" \
45
+ "+-------------+--------+----------+---------+"
46
+ SimpleInvoice::Emailer::TextBody.new(invoice, mail_config).email_body.strip.should == expected_output
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ module SimpleInvoice::Emailer
4
+ describe TextList do
5
+
6
+ example do
7
+ expected_output =
8
+ "Invoice Number: 123456\n" \
9
+ " Amount Due: $1,100.00\n" \
10
+ " Due Date: 2013-01-01\n"
11
+ list = TextList.new
12
+ list.add 'Invoice Number', 123456
13
+ list.add 'Amount Due', '$1,100.00'
14
+ list.add 'Due Date', '2013-01-01'
15
+ list.to_s.should == expected_output
16
+ end
17
+
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simple_invoice-emailer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Joel Plane
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: text-table
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mail
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ description: sends SimpleInvoices via email
63
+ email:
64
+ - joel.plane@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - Gemfile
70
+ - Gemfile.lock
71
+ - lib/simple_invoice/emailer.rb
72
+ - lib/simple_invoice/emailer/email_invoice_text_table.rb
73
+ - lib/simple_invoice/emailer/mail_config.rb
74
+ - lib/simple_invoice/emailer/mailer.rb
75
+ - lib/simple_invoice/emailer/text_body.rb
76
+ - lib/simple_invoice/emailer/text_list.rb
77
+ - simple_invoice-emailer.gemspec
78
+ - spec/email_invoice_text_table_spec.rb
79
+ - spec/mailer_spec.rb
80
+ - spec/spec_helper.rb
81
+ - spec/text_body_spec.rb
82
+ - spec/text_list_spec.rb
83
+ homepage: https://github.com/joelplane/simple_invoice-emailer
84
+ licenses: []
85
+ post_install_message:
86
+ rdoc_options: []
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ! '>='
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubyforge_project:
103
+ rubygems_version: 1.8.24
104
+ signing_key:
105
+ specification_version: 3
106
+ summary: emailer for SimpleInvoice
107
+ test_files: []