mailit 2009.08

Sign up to get free protection for your applications and to get access to all the features.
@@ -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