simple_invoice-emailer 0.0.1

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/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: []