mailit 2009.08

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.
@@ -0,0 +1,131 @@
1
+ module Mailit
2
+ # The Mailer is an abstraction layer for different SMTP clients, it provides
3
+ # #send and #defer_send methods
4
+ #
5
+ # At the time of writing, Net::SMTP and EventMachine::Protocols::SmtpClient are
6
+ # supported, it should be trivial to add support for any other client.
7
+ #
8
+ # The difference between #send and #defer_send depends on the backend, but
9
+ # usually #send will block until the mail was sent while #defer_send does it
10
+ # in the background and allows you to continue execution immediately.
11
+ #
12
+ # @example Usage
13
+ #
14
+ # mail = Mailit::Mail.new
15
+ # mail.to = 'test@test.com'
16
+ # mail.from = 'sender@sender.com'
17
+ # mail.subject 'Here are some files for you!'
18
+ # mail.text = 'This is what you see with a plaintext mail reader'
19
+ # mail.attach('/home/manveru/.vimrc')
20
+ #
21
+ # # Send and wait until sending finished
22
+ # Mailit::Mailer.send(mail)
23
+ #
24
+ # # Send in background thread and continue doing other things
25
+ # Mailit::Mailer.defer_send(mail)
26
+ #
27
+ # The default Mailer backend is Net::SMTP, you can change the
28
+ # default by including another module into Mailit::mailer
29
+ #
30
+ # @example Using Mailt::Mailer::EventMachine by inclusion
31
+ #
32
+ # class Mailit::Mailer
33
+ # include Mailit::Mailer::EventMachine
34
+ # end
35
+ #
36
+ class Mailer
37
+ OPTIONS = {
38
+ :server => 'smtp.localhost',
39
+ :port => 25,
40
+ :domain => 'localhost',
41
+ :username => 'foo',
42
+ :password => 'foo',
43
+ :noop => false,
44
+ :auth_type => :login, # :plain, :login, :cram_md5
45
+ :starttls => false, # only useful for EventMachine::SmtpClient
46
+ }
47
+
48
+ def self.send(mail, options = {})
49
+ new.send(mail, options)
50
+ end
51
+
52
+ def self.defer_send(mail, options = {})
53
+ new.defer_send(mail, options)
54
+ end
55
+
56
+ attr_reader :options
57
+
58
+ def initialize(options = {})
59
+ @options = OPTIONS.merge(options)
60
+ end
61
+
62
+ def send(mail, override = {})
63
+ require 'net/smtp'
64
+
65
+ server, port, domain, username, password, auth_type, noop =
66
+ settings(override, :server, :port, :domain, :username, :password, :auth_type, :noop)
67
+
68
+ ::Net::SMTP.start(server, port, domain, username, password, auth_type) do |smtp|
69
+ return if noop
70
+ smtp.send_message(mail.to_s, mail.from, mail.to)
71
+ end
72
+ end
73
+
74
+ def defer_send(mail, override = {})
75
+ Thread.new{ send(mail, override) }
76
+ end
77
+
78
+ private
79
+
80
+ def settings(override, *keys)
81
+ options.merge(override).values_at(*keys)
82
+ end
83
+
84
+ # Allows you to comfortably use the EventMachine::Protocols::SmtpClient.
85
+ # In order to use it, you have to first include this module into Mailit::Mailer
86
+ module EventMachine
87
+ # This assumes that EventMachine was required and we are inside the
88
+ # EventMachine::run block.
89
+ #
90
+ # Since EM implements some parts of the mail building we'll have to
91
+ # deconstruct our mail a bit.
92
+ # On the upside, it seems like it supports STARTTLS (without certificate
93
+ # options though)
94
+ def self.included(base)
95
+ require 'em/protocols/smtpclient'
96
+ base.module_eval do
97
+ def send(mail, override = {})
98
+ server, port, domain, username, password, auth_type =
99
+ settings(override, :server, :port, :domain, :username, :password, :auth_type)
100
+
101
+ mail.construct # prepare headers and body
102
+
103
+ em_options = { :port => port, :host => server, :domain => domain,
104
+ :from => mail.from, :to => mail.to, :header => mail.header_string,
105
+ :body => mail.body_string }
106
+
107
+ if auth_type
108
+ em_options[:auth] = {
109
+ :type => auth_type, :username => username, :password => password }
110
+ end
111
+
112
+ email = EM::Protocols::SmtpClient.send(em_options)
113
+ email.callback &@callback if @callback
114
+ email.errback &@errback if @errback
115
+ end
116
+
117
+ def callback(proc=nil, &blk)
118
+ @callback = proc || blk
119
+ end
120
+
121
+ def errback(proc=nil, &blk)
122
+ @errback = proc || blk
123
+ end
124
+
125
+ alias defer_send send
126
+ end
127
+
128
+ end
129
+ end # module EventMachine
130
+ end # class Mailer
131
+ end # module Mailit
@@ -0,0 +1,49 @@
1
+ module Mailit
2
+ module Mime
3
+ module_function
4
+
5
+ def mime_for(filename)
6
+ detect_handler unless defined?(@mime_handler)
7
+ send(@mime_handler, filename)
8
+ end
9
+
10
+ def detect_handler
11
+ try_require('rubygems')
12
+
13
+ if try_require('mime/types')
14
+ @mime_handler = :from_mime_types
15
+ elsif try_require('rack') and try_require('rack/mime')
16
+ @mime_handler = :from_rack
17
+ else
18
+ require 'webrick/httputils'
19
+ @webrick_types = WEBrick::HTTPUtils::DefaultMimeTypes.dup
20
+ try_extend_webrick('/etc/mime.types')
21
+ @mime_handler = :from_webrick
22
+ end
23
+ end
24
+
25
+ def from_mime_types(filename)
26
+ MIME::Types.type_for(filename) || 'application/octet-stream'
27
+ end
28
+
29
+ def from_rack(filename)
30
+ Rack::Mime.mime_type(File.extname(filename))
31
+ end
32
+
33
+ def from_webrick(filename)
34
+ WEBrick::HTTPUtils.mime_type(filename, @webrick_types)
35
+ end
36
+
37
+ def try_extend_webrick(file)
38
+ hash = WEBrick::HTTPUtils.load_mime_types(file)
39
+ @webrick_types.merge!(hash)
40
+ rescue
41
+ end
42
+
43
+ def try_require(lib)
44
+ require lib
45
+ true
46
+ rescue LoadError
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ module Mailit
2
+ VERSION = "2009.08"
3
+ end
@@ -0,0 +1,3 @@
1
+ module Mailit
2
+ VERSION = '2009.08.25'
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{mailit}
5
+ s.version = "2009.08"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Kevin Berry"]
9
+ s.date = %q{2009-08-25}
10
+ s.description = %q{The Mailit library, by Kevin Berry}
11
+ s.email = %q{kevinberry@nrs.us}
12
+ s.files = ["AUTHORS", "CHANGELOG", "MANIFEST", "README.md", "Rakefile", "lib/mailit.rb", "lib/mailit/mail.rb", "lib/mailit/mailer.rb", "lib/mailit/mime.rb", "lib/mailit/version.rb", "lib/version.rb", "mailit.gemspec", "spec/helper.rb", "spec/mailit/mail.rb", "spec/mailit/mailer.rb", "tasks/authors.rake", "tasks/bacon.rake", "tasks/changelog.rake", "tasks/copyright.rake", "tasks/gem.rake", "tasks/gem_installer.rake", "tasks/install_dependencies.rake", "tasks/manifest.rake", "tasks/rcov.rake", "tasks/release.rake", "tasks/reversion.rake", "tasks/setup.rake", "tasks/yard.rake"]
13
+ s.homepage = %q{http://github.com/manveru/mailit}
14
+ s.require_paths = ["lib"]
15
+ s.rubygems_version = %q{1.3.4}
16
+ s.summary = %q{The Mailit library, by Kevin Berry}
17
+
18
+ if s.respond_to? :specification_version then
19
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
20
+ s.specification_version = 3
21
+
22
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
23
+ else
24
+ end
25
+ else
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ require File.expand_path("#{__FILE__}/../../lib/mailit")
2
+ require 'bacon'
3
+
4
+ Bacon.summary_on_exit
@@ -0,0 +1,108 @@
1
+ # Encoding: UTF-8
2
+ require 'spec/helper'
3
+
4
+ # The specs are translated from the Test::Unit tests of MailFactory.
5
+ #
6
+ # TODO:
7
+ # * test_attach_as
8
+ # * test_email
9
+
10
+ describe Mailit::Mail do
11
+ should 'set and get headers' do
12
+ mail = Mailit::Mail.new
13
+
14
+ mail.set_header('arbitrary', 'some value')
15
+ mail.get_header('arbitrary').should == ['some value']
16
+
17
+ mail.set_header('arbitrary-header', 'some _ value')
18
+ mail.get_header('arbitrary-header').should == ['some _ value']
19
+ end
20
+
21
+ should 'generate valid boundaries' do
22
+ 50.times do
23
+ boundary = Mailit::Mail.generate_boundary
24
+ boundary.should =~ /^----=_NextPart_[a-zA-Z0-9_.]{25}$/
25
+ end
26
+ end
27
+
28
+ should 'make mail with recipient' do
29
+ mail = Mailit::Mail.new
30
+
31
+ mail.to = 'test@test.com'
32
+ mail.to.should == 'test@test.com'
33
+
34
+ mail.to = 'test@test2.com'
35
+ mail.to.should == 'test@test2.com'
36
+
37
+ mail.headers.size.should == 1 # make sure the previous was deleted
38
+ end
39
+
40
+ should 'make mail with sender' do
41
+ mail = Mailit::Mail.new
42
+
43
+ mail.from = 'test@test.com'
44
+ mail.from.should == 'test@test.com'
45
+
46
+ mail.from = 'test@test2.com'
47
+ mail.from.should == 'test@test2.com'
48
+
49
+ mail.headers.size.should == 1 # make sure the previous was deleted
50
+ end
51
+
52
+ should 'set correct subject' do
53
+ mail = Mailit::Mail.new
54
+
55
+ mail.subject = 'Test Subject'
56
+ mail.subject.should == '=?utf-8?Q?Test_Subject?='
57
+
58
+ mail.subject = 'A Different Subject'
59
+ mail.subject.should == '=?utf-8?Q?A_Different_Subject?='
60
+
61
+ mail.headers.size.should == 1 # make sure the previous was deleted
62
+ end
63
+
64
+ should 'use quoted printable with instruction' do
65
+ mail = Mailit::Mail.new
66
+
67
+ mail.to = 'test@test.com'
68
+ mail.from = 'test@othertest.com'
69
+ mail.subject = "My email subject has a ? in it and also an = and a _ too... Also some non-quoted junk ()!@\#\{\$\%\}"
70
+ mail.text = "This is a test message with\na few\n\nlines."
71
+
72
+ mail.subject.should == "=?utf-8?Q?My_email_subject_has_a_=3F_in_it_and_also_an_=3D_and_a_=5F_too..._Also_some_non-quoted_junk_()!@\#\{\$\%\}?="
73
+ end
74
+
75
+ should 'use subject quoting for scandinavian string' do
76
+ mail = Mailit::Mail.new
77
+
78
+ mail.to = "test@test.com"
79
+ mail.from = "test@othertest.com"
80
+ # Three a with dots and three o with dots.
81
+ mail.subject = "\303\244\303\244\303\244\303\266\303\266\303\266"
82
+ mail.text = "This is a test message with\na few\n\nlines."
83
+
84
+ mail.subject.should == "=?utf-8?Q?=C3=A4=C3=A4=C3=A4=C3=B6=C3=B6=C3=B6?="
85
+ end
86
+
87
+ should 'use subject quoting for utf-8 string' do
88
+ mail = Mailit::Mail.new
89
+
90
+ mail.to = "test@test.com"
91
+ mail.from = "test@othertest.com"
92
+ mail.subject = "My email subject has a à which is utf8."
93
+ mail.text = "This is a test message with\na few\n\nlines."
94
+
95
+ mail.subject.should == "=?utf-8?Q?My_email_subject_has_a_=C3=83_which_is_utf8.?="
96
+ end
97
+
98
+ should 'encode html as quoted printable' do
99
+ mail = Mailit::Mail.new
100
+
101
+ mail.to = "test@test.com"
102
+ mail.from = "test@othertest.com"
103
+ mail.subject = "some html"
104
+ mail.html = "<a href=\"http://google.com\">click here</a>"
105
+
106
+ mail.to_s.should.include('<a href=3D"http://google.com">click here</a>')
107
+ end
108
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec/helper'
2
+
3
+ class MockSMTP
4
+ INSTANCES = []
5
+
6
+ def self.start(*args, &block)
7
+ INSTANCES << new(*args, &block)
8
+ end
9
+
10
+ attr_reader :start_args, :result, :send_message_args
11
+
12
+ def initialize(*args, &block)
13
+ @start_args = args
14
+ yield(self)
15
+ end
16
+
17
+ def send_message(*args)
18
+ @send_message_args = args
19
+ end
20
+ end
21
+
22
+ Mailit::Mail::OPTIONS[:message_id] = lambda{|mail| '1234' }
23
+
24
+ describe Mailit::Mailer do
25
+ it 'sends a mail' do
26
+ mail = Mailit::Mail.new(
27
+ :to => 'test@example.com',
28
+ :from => 'sender@example.com',
29
+ :subject => 'Here are some files for you!',
30
+ :text => 'Some text about that')
31
+
32
+ mailer = Mailit::Mailer.new
33
+
34
+ mailer.send(mail, :server => 'smtp.example.com', :port => 25,
35
+ :domain => 'example.com', :password => 'foo',
36
+ :mailer => MockSMTP)
37
+
38
+ mock = MockSMTP::INSTANCES.last
39
+ mock.start_args.should == [
40
+ 'smtp.example.com', 25,
41
+ 'example.com',
42
+ 'sender@example.com',
43
+ 'foo',
44
+ :cram_md5
45
+ ]
46
+ mock.send_message_args.should == [mail.to_s, mail.from, mail.to]
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ # Once git has a fix for the glibc in handling .mailmap and another fix for
2
+ # allowing empty mail address to be mapped in .mailmap we won't have to handle
3
+ # them manually.
4
+
5
+ desc 'Update AUTHORS'
6
+ task :authors do
7
+ authors = Hash.new(0)
8
+
9
+ `git shortlog -nse`.scan(/(\d+)\s(.+)\s<(.*)>$/) do |count, name, email|
10
+ # Examples of mappping, replace with your own or comment this out/delete it
11
+ case name
12
+ when /^(?:bougyman$|TJ Vanderpoel)/
13
+ name, email = "TJ Vanderpoel", "tj@rubyists.com"
14
+ when /^(?:manveru$|Michael Fellinger)/
15
+ name, email = "Michael Fellinger", "mf@rubyists.com"
16
+ when /^(?:deathsyn$|Kevin Berry)/
17
+ name, email = "Kevin Berry", "kb@rubyists.com"
18
+ when /^(?:(?:jayson|thedonvaughn|jvaughn)$|Jayson Vaughn)/
19
+ name, email = "Jayson Vaughn", "jv@rubyists.com"
20
+ end
21
+
22
+ authors[[name, email]] += count.to_i
23
+ end
24
+
25
+ File.open('AUTHORS', 'w+') do |io|
26
+ io.puts "Following persons have contributed to #{GEMSPEC.name}."
27
+ io.puts '(Sorted by number of submitted patches, then alphabetically)'
28
+ io.puts ''
29
+ authors.sort_by{|(n,e),c| [-c, n.downcase] }.each do |(name, email), count|
30
+ io.puts("%6d %s <%s>" % [count, name, email])
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,70 @@
1
+ desc 'Run all bacon specs with pretty output'
2
+ task :bacon => :install_dependencies do
3
+ require 'open3'
4
+ require 'scanf'
5
+ require 'matrix'
6
+
7
+ specs = PROJECT_SPECS
8
+
9
+ some_failed = false
10
+ specs_size = specs.size
11
+ if specs.size == 0
12
+ $stderr.puts "You have no specs! Put a spec in spec/ before running this task"
13
+ exit 1
14
+ end
15
+ len = specs.map{|s| s.size }.sort.last
16
+ total_tests = total_assertions = total_failures = total_errors = 0
17
+ totals = Vector[0, 0, 0, 0]
18
+
19
+ red, yellow, green = "\e[31m%s\e[0m", "\e[33m%s\e[0m", "\e[32m%s\e[0m"
20
+ left_format = "%4d/%d: %-#{len + 11}s"
21
+ spec_format = "%d specifications (%d requirements), %d failures, %d errors"
22
+
23
+ specs.each_with_index do |spec, idx|
24
+ print(left_format % [idx + 1, specs_size, spec])
25
+
26
+ Open3.popen3(RUBY, spec) do |sin, sout, serr|
27
+ out = sout.read.strip
28
+ err = serr.read.strip
29
+
30
+ # this is conventional, see spec/innate/state/fiber.rb for usage
31
+ if out =~ /^Bacon::Error: (needed .*)/
32
+ puts(yellow % ("%6s %s" % ['', $1]))
33
+ else
34
+ total = nil
35
+
36
+ out.each_line do |line|
37
+ scanned = line.scanf(spec_format)
38
+
39
+ next unless scanned.size == 4
40
+
41
+ total = Vector[*scanned]
42
+ break
43
+ end
44
+
45
+ if total
46
+ totals += total
47
+ tests, assertions, failures, errors = total_array = total.to_a
48
+
49
+ if tests > 0 && failures + errors == 0
50
+ puts((green % "%6d passed") % tests)
51
+ else
52
+ some_failed = true
53
+ puts(red % " failed")
54
+ puts out unless out.empty?
55
+ puts err unless err.empty?
56
+ end
57
+ else
58
+ some_failed = true
59
+ puts(red % " failed")
60
+ puts out unless out.empty?
61
+ puts err unless err.empty?
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ total_color = some_failed ? red : green
68
+ puts(total_color % (spec_format % totals.to_a))
69
+ exit 1 if some_failed
70
+ end