kennethkalmer-daemon-kit 0.1.7.3 → 0.1.7.4

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 (42) hide show
  1. data/History.txt +8 -0
  2. data/Manifest.txt +30 -0
  3. data/app_generators/daemon_kit/templates/bin/daemon.erb +1 -1
  4. data/daemon_generators/amqp/templates/config/amqp.yml +5 -5
  5. data/daemon_generators/deploy_capistrano/templates/config/deploy.rb +3 -1
  6. data/lib/daemon_kit/amqp.rb +2 -1
  7. data/lib/daemon_kit/application.rb +1 -0
  8. data/lib/daemon_kit/deployment/capistrano.rb +6 -9
  9. data/lib/daemon_kit/error_handlers/mail.rb +52 -15
  10. data/lib/daemon_kit/initializer.rb +14 -22
  11. data/lib/daemon_kit/tasks/log.rake +8 -0
  12. data/lib/daemon_kit.rb +23 -1
  13. data/spec/error_handlers_spec.rb +23 -0
  14. data/vendor/tmail-1.2.3/tmail/address.rb +426 -0
  15. data/vendor/tmail-1.2.3/tmail/attachments.rb +46 -0
  16. data/vendor/tmail-1.2.3/tmail/base64.rb +46 -0
  17. data/vendor/tmail-1.2.3/tmail/compat.rb +41 -0
  18. data/vendor/tmail-1.2.3/tmail/config.rb +67 -0
  19. data/vendor/tmail-1.2.3/tmail/core_extensions.rb +63 -0
  20. data/vendor/tmail-1.2.3/tmail/encode.rb +581 -0
  21. data/vendor/tmail-1.2.3/tmail/header.rb +960 -0
  22. data/vendor/tmail-1.2.3/tmail/index.rb +9 -0
  23. data/vendor/tmail-1.2.3/tmail/interface.rb +1130 -0
  24. data/vendor/tmail-1.2.3/tmail/loader.rb +3 -0
  25. data/vendor/tmail-1.2.3/tmail/mail.rb +578 -0
  26. data/vendor/tmail-1.2.3/tmail/mailbox.rb +495 -0
  27. data/vendor/tmail-1.2.3/tmail/main.rb +6 -0
  28. data/vendor/tmail-1.2.3/tmail/mbox.rb +3 -0
  29. data/vendor/tmail-1.2.3/tmail/net.rb +248 -0
  30. data/vendor/tmail-1.2.3/tmail/obsolete.rb +132 -0
  31. data/vendor/tmail-1.2.3/tmail/parser.rb +1476 -0
  32. data/vendor/tmail-1.2.3/tmail/port.rb +379 -0
  33. data/vendor/tmail-1.2.3/tmail/quoting.rb +118 -0
  34. data/vendor/tmail-1.2.3/tmail/require_arch.rb +58 -0
  35. data/vendor/tmail-1.2.3/tmail/scanner.rb +49 -0
  36. data/vendor/tmail-1.2.3/tmail/scanner_r.rb +261 -0
  37. data/vendor/tmail-1.2.3/tmail/stringio.rb +280 -0
  38. data/vendor/tmail-1.2.3/tmail/utils.rb +337 -0
  39. data/vendor/tmail-1.2.3/tmail/version.rb +39 -0
  40. data/vendor/tmail-1.2.3/tmail.rb +5 -0
  41. data/vendor/tmail.rb +13 -0
  42. metadata +33 -3
data/History.txt CHANGED
@@ -1,3 +1,11 @@
1
+ == 0.1.7.4 (Not released)
2
+
3
+ * Enhanced deploy.rb template to check for current dk gem verion,
4
+ unless vendored
5
+ * Fix bug in capistrano recipe for restarting daemons
6
+ * Added log:truncate rake task
7
+ * Error mails now handled by TMail
8
+
1
9
  == 0.1.7.3 2009-05-31
2
10
 
3
11
  * Removed dependency on daemons gem, now handled in house
data/Manifest.txt CHANGED
@@ -75,6 +75,7 @@ lib/daemon_kit/tasks.rb
75
75
  lib/daemon_kit/tasks/environment.rake
76
76
  lib/daemon_kit/tasks/framework.rake
77
77
  lib/daemon_kit/tasks/god.rake
78
+ lib/daemon_kit/tasks/log.rake
78
79
  lib/daemon_kit/tasks/monit.rake
79
80
  rubygems_generators/install_rspec/USAGE
80
81
  rubygems_generators/install_rspec/install_rspec_generator.rb
@@ -89,6 +90,7 @@ script/txt2html
89
90
  spec/argument_spec.rb
90
91
  spec/config_spec.rb
91
92
  spec/daemon_kit_spec.rb
93
+ spec/error_handlers_spec.rb
92
94
  spec/fixtures/env.yml
93
95
  spec/fixtures/noenv.yml
94
96
  spec/initializer_spec.rb
@@ -106,3 +108,31 @@ test/test_generator_helper.rb
106
108
  test/test_helper.rb
107
109
  test/test_jabber_generator.rb
108
110
  test/test_nanite_agent_generator.rb
111
+ vendor/tmail-1.2.3/tmail.rb
112
+ vendor/tmail-1.2.3/tmail/address.rb
113
+ vendor/tmail-1.2.3/tmail/attachments.rb
114
+ vendor/tmail-1.2.3/tmail/base64.rb
115
+ vendor/tmail-1.2.3/tmail/compat.rb
116
+ vendor/tmail-1.2.3/tmail/config.rb
117
+ vendor/tmail-1.2.3/tmail/core_extensions.rb
118
+ vendor/tmail-1.2.3/tmail/encode.rb
119
+ vendor/tmail-1.2.3/tmail/header.rb
120
+ vendor/tmail-1.2.3/tmail/index.rb
121
+ vendor/tmail-1.2.3/tmail/interface.rb
122
+ vendor/tmail-1.2.3/tmail/loader.rb
123
+ vendor/tmail-1.2.3/tmail/mail.rb
124
+ vendor/tmail-1.2.3/tmail/mailbox.rb
125
+ vendor/tmail-1.2.3/tmail/main.rb
126
+ vendor/tmail-1.2.3/tmail/mbox.rb
127
+ vendor/tmail-1.2.3/tmail/net.rb
128
+ vendor/tmail-1.2.3/tmail/obsolete.rb
129
+ vendor/tmail-1.2.3/tmail/parser.rb
130
+ vendor/tmail-1.2.3/tmail/port.rb
131
+ vendor/tmail-1.2.3/tmail/quoting.rb
132
+ vendor/tmail-1.2.3/tmail/require_arch.rb
133
+ vendor/tmail-1.2.3/tmail/scanner.rb
134
+ vendor/tmail-1.2.3/tmail/scanner_r.rb
135
+ vendor/tmail-1.2.3/tmail/stringio.rb
136
+ vendor/tmail-1.2.3/tmail/utils.rb
137
+ vendor/tmail-1.2.3/tmail/version.rb
138
+ vendor/tmail.rb
@@ -4,4 +4,4 @@
4
4
 
5
5
  require File.dirname(__FILE__) + '/../config/environment'
6
6
 
7
- DaemonKit::Application.run( DAEMON_ROOT + '/libexec/<%= daemon_name %>-daemon.rb' )
7
+ DaemonKit::Application.exec( DAEMON_ROOT + '/libexec/<%= daemon_name %>-daemon.rb' )
@@ -4,8 +4,8 @@
4
4
  # omitted will let the gem use it's own defaults.
5
5
 
6
6
  # The configuration specifies the following keys:
7
- # * username - Username for the broker
8
- # * password - Password for the broker
7
+ # * user - Username for the broker
8
+ # * pass - Password for the broker
9
9
  # * host - Hostname where the broker is running
10
10
  # * vhost - Vhost to connect to
11
11
  # * port - Port where the broker is running
@@ -13,8 +13,8 @@
13
13
  # * timeout - Timeout
14
14
 
15
15
  defaults: &defaults
16
- username: guest
17
- password: guest
16
+ user: guest
17
+ pass: guest
18
18
  host: localhost
19
19
  vhost: /
20
20
 
@@ -25,4 +25,4 @@ test:
25
25
  <<: *defaults
26
26
 
27
27
  production:
28
- <<: *defaults
28
+ <<: *defaults
@@ -34,7 +34,9 @@ set :config_files, %w{}
34
34
  set :shared_children, %w{log tmp}
35
35
 
36
36
  # Record our dependencies
37
- depend :remote, :gem, "daemon-kit", ">=0.0.0"
37
+ unless File.directory?( "#{DaemonKit.root}/vendor/daemon_kit" )
38
+ depend :remote, :gem, "daemon-kit", ">=#{DaemonKit::VERSION}"
39
+ end
38
40
 
39
41
  # Hook into capistrano's events
40
42
  before "deploy:update_code", "deploy:check"
@@ -30,7 +30,8 @@ module DaemonKit
30
30
  DaemonKit.trap('INT') { ::AMQP.stop { ::EM.stop } }
31
31
  DaemonKit.trap('TERM') { ::AMQP.stop { ::EM.stop } }
32
32
 
33
- # Start our event loop
33
+ # Start our event loop and AMQP client
34
+ DaemonKit.logger.debug("AMQP.start(#{@config.inspect})")
34
35
  ::AMQP.start(@config, &block)
35
36
  end
36
37
  end
@@ -50,6 +50,7 @@ module DaemonKit
50
50
  @pid_file = PidFile.new( DaemonKit.configuration.pid_file )
51
51
 
52
52
  unless @pid_file.running?
53
+ @pid_file.cleanup
53
54
  puts "Nothing to stop"
54
55
  exit
55
56
  end
@@ -305,18 +305,15 @@ namespace :deploy do
305
305
  end
306
306
 
307
307
  desc <<-DESC
308
- Restarts your application. This works by calling the bin/:application \
309
- script under the current path with 'restart'
308
+ Restarts your application. This works by calling 'stop' task, \
309
+ followed by the 'start' task.
310
310
 
311
- By default, this will be invoked via sudo as the `app' user. If \
312
- you wish to run it as a different user, set the :runner variable to \
313
- that user. If you are in an environment where you can't use sudo, set \
314
- the :use_sudo variable to false:
315
-
316
- set :use_sudo, false
311
+ See the descriptions for the 'start' and 'stop' tasks for any \
312
+ additional info.
317
313
  DESC
318
314
  task :restart, :except => { :no_release => true } do
319
- try_runner "/usr/bin/env DAEMON_ENV=#{fetch(:daemon_env)} #{current_path}/bin/#{application} restart"
315
+ stop
316
+ start
320
317
  end
321
318
 
322
319
  namespace :rollback do
@@ -1,33 +1,66 @@
1
+ require DaemonKit.framework_root + '/vendor/tmail'
1
2
  require 'net/smtp'
2
3
 
3
4
  module DaemonKit
4
5
  module ErrorHandlers
5
- # Send an email notification of the exception via SMTP
6
+ # Send an email notification of the exception via SMTP.
6
7
  class Mail < Base
7
-
8
+
8
9
  # SMTP hostname
9
10
  @host = 'localhost'
10
- attr_accessor :host
11
-
11
+
12
+ # SMTP port
13
+ @port = 25
14
+
12
15
  # Recipients of the notification
13
16
  @recipients = []
14
- attr_accessor :recipients
15
17
 
16
18
  # Subject prefix
17
19
  @prefix = '[DAEMON-KIT]'
18
- attr_accessor :prefix
19
20
 
20
21
  # Sender address
21
22
  @sender = 'daemon-kit'
22
- attr_accessor :sender
23
+
24
+ # SMTP username
25
+ @username = nil
26
+
27
+ # SMTP password
28
+ @password = nil
29
+
30
+ # Authentication mechanism (:plain, :login, or :cram_md5)
31
+ @authentication = nil
32
+
33
+ # Use TLS?
34
+ @tls = false
35
+
36
+ # Domain used when talking to SMTP server
37
+ @domain = 'localhost.localdomain'
38
+
39
+ class << self
40
+ attr_accessor :host, :port, :recipients, :prefix, :sender, :username,
41
+ :password, :authentication, :tls, :domain
42
+ end
43
+
44
+ [ :host, :port, :recipients, :prefix, :sender, :username, :password,
45
+ :authentication, :tls, :domain ].each do |cm|
46
+ class_eval(<<-EOM, __FILE__, __LINE__)
47
+ def #{cm}=( val )
48
+ self.class.#{cm} = val
49
+ end
50
+ EOM
51
+ end
23
52
 
24
53
  def handle_exception( exception )
25
- email = <<EOF
26
- To: #{self.recipients.map { |r| '<' + r + '>' }.join(', ')}
27
- From: <#{self.sender}>
28
- Subject: #{self.prefix} #{exception.message}
29
- Date: #{Time.now}
30
54
 
55
+ mail = TMail::Mail.new
56
+ mail.to = self.class.recipients
57
+ mail.from = self.class.sender
58
+ mail.subject = "#{self.class.prefix} #{exception.message}"
59
+ mail.set_content_type 'text', 'plain'
60
+ mail.mime_version = '1.0'
61
+ mail.date = Time.now
62
+
63
+ mail.body = <<EOF
31
64
  DaemonKit caught an exception inside #{DaemonKit.configuration.daemon_name}.
32
65
 
33
66
  Message: #{exception.message}
@@ -37,10 +70,14 @@ Backtrace:
37
70
  Environment: #{ENV.inspect}
38
71
  EOF
39
72
  begin
40
- Net::SMTP.start( self.host ) do |smtp|
41
- smtp.send_message( email, self.sender, self.recipients )
73
+ smtp = Net::SMTP.new( self.class.host, self.class.port )
74
+ smtp.enable_starttls_auto if self.class.tls && smtp.respond_to?(:enable_starttls_auto)
75
+ smtp.start( self.class.domain, self.class.username, self.class.password,
76
+ self.class.authentication ) do |smtp|
77
+ smtp.sendmail( mail.to_s, mail.from, mail.to )
42
78
  end
43
- rescue
79
+ rescue => e
80
+ DaemonKit.logger.error "Failed to send exception mail: #{e.message}" if DaemonKit.logger
44
81
  end
45
82
  end
46
83
  end
@@ -18,14 +18,6 @@ module DaemonKit
18
18
 
19
19
  class << self
20
20
 
21
- def logger
22
- @logger
23
- end
24
-
25
- def logger=( logger )
26
- @logger = logger
27
- end
28
-
29
21
  def configuration
30
22
  @configuration
31
23
  end
@@ -46,17 +38,6 @@ module DaemonKit
46
38
  self.configuration.trap( *args, &block )
47
39
  end
48
40
 
49
- def framework_root
50
- @framework_root ||= File.join( File.dirname(__FILE__), '..', '..' ).to_absolute_path
51
- end
52
-
53
- def root
54
- DAEMON_ROOT
55
- end
56
-
57
- def env
58
- DAEMON_ENV
59
- end
60
41
  end
61
42
 
62
43
 
@@ -81,7 +62,7 @@ module DaemonKit
81
62
  end
82
63
 
83
64
  def self.shutdown
84
- DaemonKit.logger.warn "Shutting down"
65
+ DaemonKit.logger.warn "Shutting down #{DaemonKit.configuration.daemon_name}"
85
66
  exit
86
67
  end
87
68
 
@@ -107,6 +88,8 @@ module DaemonKit
107
88
  load_postdaemonize_configs
108
89
 
109
90
  set_process_name
91
+
92
+ DaemonKit.logger.info( "DaemonKit (#{DaemonKit::VERSION}) booted, now running #{DaemonKit.configuration.daemon_name}" )
110
93
  end
111
94
 
112
95
  def set_load_path
@@ -158,6 +141,8 @@ module DaemonKit
158
141
 
159
142
  DaemonKit.logger = logger
160
143
 
144
+ DaemonKit.logger.info "DaemonKit (#{DaemonKit::VERSION}) booting in #{DAEMON_ENV} mode"
145
+
161
146
  configuration.trap("USR1") {
162
147
  DaemonKit.logger.level = DaemonKit.logger.debug? ? Logger::INFO : Logger::DEBUG
163
148
  DaemonKit.logger.info "Log level changed to #{DaemonKit.logger.debug? ? 'DEBUG' : 'INFO' }"
@@ -166,8 +151,6 @@ module DaemonKit
166
151
  DaemonKit.logger.level = Logger::DEBUG
167
152
  DaemonKit.logger.info "Log level changed to DEBUG"
168
153
  }
169
-
170
- DaemonKit.logger.info "DaemonKit up and running in #{DAEMON_ENV} mode"
171
154
  end
172
155
 
173
156
  def initialize_signal_traps
@@ -281,6 +264,8 @@ module DaemonKit
281
264
  end
282
265
 
283
266
  def parse_arguments!
267
+ return unless own_args?
268
+
284
269
  configs = Arguments.configuration( ARGV ).first
285
270
  @unused_arguments = {}
286
271
 
@@ -352,6 +337,13 @@ module DaemonKit
352
337
  STDERR.puts msg
353
338
  end
354
339
  end
340
+
341
+ # If we are executed with any of these commands, don't allow
342
+ # arguments to be parsed cause they will interfere with the
343
+ # script encapsulating DaemonKit, like capistrano
344
+ def own_args?
345
+ ![ 'cap' ].include?( File.basename( $0 ) )
346
+ end
355
347
  end
356
348
 
357
349
 
@@ -0,0 +1,8 @@
1
+ namespace :log do
2
+ desc "Truncate all log files found in DAEMON_ROOT/log/"
3
+ task :truncate => 'environment' do
4
+ Dir[ "#{DaemonKit.root}/log/*.log" ].each do |l|
5
+ File.open( l, 'w+' ) { |f| f.write('') }
6
+ end
7
+ end
8
+ end
data/lib/daemon_kit.rb CHANGED
@@ -7,7 +7,7 @@ $:.unshift( File.dirname(__FILE__).to_absolute_path ) unless
7
7
  $:.include?( File.dirname(__FILE__).to_absolute_path )
8
8
 
9
9
  module DaemonKit
10
- VERSION = '0.1.7.3'
10
+ VERSION = '0.1.7.4'
11
11
 
12
12
  autoload :Initializer, 'daemon_kit/initializer'
13
13
  autoload :Application, 'daemon_kit/application'
@@ -20,4 +20,26 @@ module DaemonKit
20
20
  autoload :Jabber, 'daemon_kit/jabber'
21
21
  autoload :AMQP, 'daemon_kit/amqp'
22
22
  autoload :Nanite, 'daemon_kit/nanite'
23
+
24
+ class << self
25
+ def logger
26
+ @logger
27
+ end
28
+
29
+ def logger=( logger )
30
+ @logger = logger
31
+ end
32
+
33
+ def root
34
+ DAEMON_ROOT
35
+ end
36
+
37
+ def env
38
+ DAEMON_ENV
39
+ end
40
+
41
+ def framework_root
42
+ @framework_root ||= File.join( File.dirname(__FILE__), '..' ).to_absolute_path
43
+ end
44
+ end
23
45
  end
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe DaemonKit::Safety do
4
+ end
5
+
6
+ describe DaemonKit::ErrorHandlers::Mail do
7
+ it "should send an email report" do
8
+ conf = Object.new
9
+ conf.stubs(:daemon_name).returns('test')
10
+ DaemonKit.stubs(:configuration).returns(conf)
11
+
12
+ fake_smtp = Object.new
13
+ fake_smtp.expects(:start).with('localhost.localdomain', nil, nil, nil)
14
+ Net::SMTP.expects(:new).with('localhost', 25).returns(fake_smtp)
15
+
16
+ begin
17
+ raise RuntimeError, "specs don't fail :)"
18
+ rescue => e
19
+ handler = DaemonKit::ErrorHandlers::Mail.instance
20
+ handler.handle_exception( e )
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,426 @@
1
+ =begin rdoc
2
+
3
+ = Address handling class
4
+
5
+ =end
6
+ #--
7
+ # Copyright (c) 1998-2003 Minero Aoki <aamine@loveruby.net>
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+ #
28
+ # Note: Originally licensed under LGPL v2+. Using MIT license for Rails
29
+ # with permission of Minero Aoki.
30
+ #++
31
+
32
+ require 'tmail/encode'
33
+ require 'tmail/parser'
34
+
35
+
36
+ module TMail
37
+
38
+ # = Class Address
39
+ #
40
+ # Provides a complete handling library for email addresses. Can parse a string of an
41
+ # address directly or take in preformatted addresses themselves. Allows you to add
42
+ # and remove phrases from the front of the address and provides a compare function for
43
+ # email addresses.
44
+ #
45
+ # == Parsing and Handling a Valid Address:
46
+ #
47
+ # Just pass the email address in as a string to Address.parse:
48
+ #
49
+ # email = TMail::Address.parse('Mikel Lindsaar <mikel@lindsaar.net>)
50
+ # #=> #<TMail::Address mikel@lindsaar.net>
51
+ # email.address
52
+ # #=> "mikel@lindsaar.net"
53
+ # email.local
54
+ # #=> "mikel"
55
+ # email.domain
56
+ # #=> "lindsaar.net"
57
+ # email.name # Aliased as phrase as well
58
+ # #=> "Mikel Lindsaar"
59
+ #
60
+ # == Detecting an Invalid Address
61
+ #
62
+ # If you want to check the syntactical validity of an email address, just pass it to
63
+ # Address.parse and catch any SyntaxError:
64
+ #
65
+ # begin
66
+ # TMail::Mail.parse("mikel 2@@@@@ me .com")
67
+ # rescue TMail::SyntaxError
68
+ # puts("Invalid Email Address Detected")
69
+ # else
70
+ # puts("Address is valid")
71
+ # end
72
+ # #=> "Invalid Email Address Detected"
73
+ class Address
74
+
75
+ include TextUtils #:nodoc:
76
+
77
+ # Sometimes you need to parse an address, TMail can do it for you and provide you with
78
+ # a fairly robust method of detecting a valid address.
79
+ #
80
+ # Takes in a string, returns a TMail::Address object.
81
+ #
82
+ # Raises a TMail::SyntaxError on invalid email format
83
+ def Address.parse( str )
84
+ Parser.parse :ADDRESS, special_quote_address(str)
85
+ end
86
+
87
+ def Address.special_quote_address(str) #:nodoc:
88
+ # Takes a string which is an address and adds quotation marks to special
89
+ # edge case methods that the RACC parser can not handle.
90
+ #
91
+ # Right now just handles two edge cases:
92
+ #
93
+ # Full stop as the last character of the display name:
94
+ # Mikel L. <mikel@me.com>
95
+ # Returns:
96
+ # "Mikel L." <mikel@me.com>
97
+ #
98
+ # Unquoted @ symbol in the display name:
99
+ # mikel@me.com <mikel@me.com>
100
+ # Returns:
101
+ # "mikel@me.com" <mikel@me.com>
102
+ #
103
+ # Any other address not matching these patterns just gets returned as is.
104
+ case
105
+ # This handles the missing "" in an older version of Apple Mail.app
106
+ # around the display name when the display name contains a '@'
107
+ # like 'mikel@me.com <mikel@me.com>'
108
+ # Just quotes it to: '"mikel@me.com" <mikel@me.com>'
109
+ when str =~ /\A([^"].+@.+[^"])\s(<.*?>)\Z/
110
+ return "\"#{$1}\" #{$2}"
111
+ # This handles cases where 'Mikel A. <mikel@me.com>' which is a trailing
112
+ # full stop before the address section. Just quotes it to
113
+ # '"Mikel A. <mikel@me.com>"
114
+ when str =~ /\A(.*?\.)\s(<.*?>)\Z/
115
+ return "\"#{$1}\" #{$2}"
116
+ else
117
+ str
118
+ end
119
+ end
120
+
121
+ def address_group? #:nodoc:
122
+ false
123
+ end
124
+
125
+ # Address.new(local, domain)
126
+ #
127
+ # Accepts:
128
+ #
129
+ # * local - Left of the at symbol
130
+ #
131
+ # * domain - Array of the domain split at the periods.
132
+ #
133
+ # For example:
134
+ #
135
+ # Address.new("mikel", ["lindsaar", "net"])
136
+ # #=> "#<TMail::Address mikel@lindsaar.net>"
137
+ def initialize( local, domain )
138
+ if domain
139
+ domain.each do |s|
140
+ raise SyntaxError, 'empty word in domain' if s.empty?
141
+ end
142
+ end
143
+
144
+ # This is to catch an unquoted "@" symbol in the local part of the
145
+ # address. Handles addresses like <"@"@me.com> and makes sure they
146
+ # stay like <"@"@me.com> (previously were becoming <@@me.com>)
147
+ if local && (local.join == '@' || local.join =~ /\A[^"].*?@.*?[^"]\Z/)
148
+ @local = "\"#{local.join}\""
149
+ else
150
+ @local = local
151
+ end
152
+
153
+ @domain = domain
154
+ @name = nil
155
+ @routes = []
156
+ end
157
+
158
+ # Provides the name or 'phrase' of the email address.
159
+ #
160
+ # For Example:
161
+ #
162
+ # email = TMail::Address.parse("Mikel Lindsaar <mikel@lindsaar.net>")
163
+ # email.name
164
+ # #=> "Mikel Lindsaar"
165
+ def name
166
+ @name
167
+ end
168
+
169
+ # Setter method for the name or phrase of the email
170
+ #
171
+ # For Example:
172
+ #
173
+ # email = TMail::Address.parse("mikel@lindsaar.net")
174
+ # email.name
175
+ # #=> nil
176
+ # email.name = "Mikel Lindsaar"
177
+ # email.to_s
178
+ # #=> "Mikel Lindsaar <mikel@me.com>"
179
+ def name=( str )
180
+ @name = str
181
+ @name = nil if str and str.empty?
182
+ end
183
+
184
+ #:stopdoc:
185
+ alias phrase name
186
+ alias phrase= name=
187
+ #:startdoc:
188
+
189
+ # This is still here from RFC 822, and is now obsolete per RFC2822 Section 4.
190
+ #
191
+ # "When interpreting addresses, the route portion SHOULD be ignored."
192
+ #
193
+ # It is still here, so you can access it.
194
+ #
195
+ # Routes return the route portion at the front of the email address, if any.
196
+ #
197
+ # For Example:
198
+ # email = TMail::Address.parse( "<@sa,@another:Mikel@me.com>")
199
+ # => #<TMail::Address Mikel@me.com>
200
+ # email.to_s
201
+ # => "<@sa,@another:Mikel@me.com>"
202
+ # email.routes
203
+ # => ["sa", "another"]
204
+ def routes
205
+ @routes
206
+ end
207
+
208
+ def inspect #:nodoc:
209
+ "#<#{self.class} #{address()}>"
210
+ end
211
+
212
+ # Returns the local part of the email address
213
+ #
214
+ # For Example:
215
+ #
216
+ # email = TMail::Address.parse("mikel@lindsaar.net")
217
+ # email.local
218
+ # #=> "mikel"
219
+ def local
220
+ return nil unless @local
221
+ return '""' if @local.size == 1 and @local[0].empty?
222
+ # Check to see if it is an array before trying to map it
223
+ if @local.respond_to?(:map)
224
+ @local.map {|i| quote_atom(i) }.join('.')
225
+ else
226
+ quote_atom(@local)
227
+ end
228
+ end
229
+
230
+ # Returns the domain part of the email address
231
+ #
232
+ # For Example:
233
+ #
234
+ # email = TMail::Address.parse("mikel@lindsaar.net")
235
+ # email.local
236
+ # #=> "lindsaar.net"
237
+ def domain
238
+ return nil unless @domain
239
+ join_domain(@domain)
240
+ end
241
+
242
+ # Returns the full specific address itself
243
+ #
244
+ # For Example:
245
+ #
246
+ # email = TMail::Address.parse("mikel@lindsaar.net")
247
+ # email.address
248
+ # #=> "mikel@lindsaar.net"
249
+ def spec
250
+ s = self.local
251
+ d = self.domain
252
+ if s and d
253
+ s + '@' + d
254
+ else
255
+ s
256
+ end
257
+ end
258
+
259
+ alias address spec
260
+
261
+ # Provides == function to the email. Only checks the actual address
262
+ # and ignores the name/phrase component
263
+ #
264
+ # For Example
265
+ #
266
+ # addr1 = TMail::Address.parse("My Address <mikel@lindsaar.net>")
267
+ # #=> "#<TMail::Address mikel@lindsaar.net>"
268
+ # addr2 = TMail::Address.parse("Another <mikel@lindsaar.net>")
269
+ # #=> "#<TMail::Address mikel@lindsaar.net>"
270
+ # addr1 == addr2
271
+ # #=> true
272
+ def ==( other )
273
+ other.respond_to? :spec and self.spec == other.spec
274
+ end
275
+
276
+ alias eql? ==
277
+
278
+ # Provides a unique hash value for this record against the local and domain
279
+ # parts, ignores the name/phrase value
280
+ #
281
+ # email = TMail::Address.parse("mikel@lindsaar.net")
282
+ # email.hash
283
+ # #=> 18767598
284
+ def hash
285
+ @local.hash ^ @domain.hash
286
+ end
287
+
288
+ # Duplicates a TMail::Address object returning the duplicate
289
+ #
290
+ # addr1 = TMail::Address.parse("mikel@lindsaar.net")
291
+ # addr2 = addr1.dup
292
+ # addr1.id == addr2.id
293
+ # #=> false
294
+ def dup
295
+ obj = self.class.new(@local.dup, @domain.dup)
296
+ obj.name = @name.dup if @name
297
+ obj.routes.replace @routes
298
+ obj
299
+ end
300
+
301
+ include StrategyInterface #:nodoc:
302
+
303
+ def accept( strategy, dummy1 = nil, dummy2 = nil ) #:nodoc:
304
+ unless @local
305
+ strategy.meta '<>' # empty return-path
306
+ return
307
+ end
308
+
309
+ spec_p = (not @name and @routes.empty?)
310
+ if @name
311
+ strategy.phrase @name
312
+ strategy.space
313
+ end
314
+ tmp = spec_p ? '' : '<'
315
+ unless @routes.empty?
316
+ tmp << @routes.map {|i| '@' + i }.join(',') << ':'
317
+ end
318
+ tmp << self.spec
319
+ tmp << '>' unless spec_p
320
+ strategy.meta tmp
321
+ strategy.lwsp ''
322
+ end
323
+
324
+ end
325
+
326
+
327
+ class AddressGroup
328
+
329
+ include Enumerable
330
+
331
+ def address_group?
332
+ true
333
+ end
334
+
335
+ def initialize( name, addrs )
336
+ @name = name
337
+ @addresses = addrs
338
+ end
339
+
340
+ attr_reader :name
341
+
342
+ def ==( other )
343
+ other.respond_to? :to_a and @addresses == other.to_a
344
+ end
345
+
346
+ alias eql? ==
347
+
348
+ def hash
349
+ map {|i| i.hash }.hash
350
+ end
351
+
352
+ def []( idx )
353
+ @addresses[idx]
354
+ end
355
+
356
+ def size
357
+ @addresses.size
358
+ end
359
+
360
+ def empty?
361
+ @addresses.empty?
362
+ end
363
+
364
+ def each( &block )
365
+ @addresses.each(&block)
366
+ end
367
+
368
+ def to_a
369
+ @addresses.dup
370
+ end
371
+
372
+ alias to_ary to_a
373
+
374
+ def include?( a )
375
+ @addresses.include? a
376
+ end
377
+
378
+ def flatten
379
+ set = []
380
+ @addresses.each do |a|
381
+ if a.respond_to? :flatten
382
+ set.concat a.flatten
383
+ else
384
+ set.push a
385
+ end
386
+ end
387
+ set
388
+ end
389
+
390
+ def each_address( &block )
391
+ flatten.each(&block)
392
+ end
393
+
394
+ def add( a )
395
+ @addresses.push a
396
+ end
397
+
398
+ alias push add
399
+
400
+ def delete( a )
401
+ @addresses.delete a
402
+ end
403
+
404
+ include StrategyInterface
405
+
406
+ def accept( strategy, dummy1 = nil, dummy2 = nil )
407
+ strategy.phrase @name
408
+ strategy.meta ':'
409
+ strategy.space
410
+ first = true
411
+ each do |mbox|
412
+ if first
413
+ first = false
414
+ else
415
+ strategy.meta ','
416
+ end
417
+ strategy.space
418
+ mbox.accept strategy
419
+ end
420
+ strategy.meta ';'
421
+ strategy.lwsp ''
422
+ end
423
+
424
+ end
425
+
426
+ end # module TMail