newman 0.1.1 → 0.2.0

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.
@@ -1,30 +1,99 @@
1
+ # `Newman::Controller` provides a context for application callbacks to run in,
2
+ # and provides most of the core functionality for preparing a response email.
3
+ #
4
+ # For a full example of `Newman::Controller` in action, be sure to check out
5
+ # [Jester](https://github.com/mendicant-university/jester).
6
+ #
7
+ # `Newman::Controller` is part of Newman's **external interface**.
8
+
1
9
  module Newman
2
10
  class Controller
11
+
12
+ #---
13
+
14
+ # A `Newman::Controller` object is initialized with parameters that match
15
+ # what is provided by the low level `Newman::Server` object. Generally
16
+ # speaking, you won't instantiate controller objects yourself, but instead
17
+ # will rely on `Newman::Application` to instantiate them for you.
18
+
3
19
  def initialize(params)
4
20
  self.settings = params.fetch(:settings)
5
21
  self.request = params.fetch(:request)
6
22
  self.response = params.fetch(:response)
23
+ self.logger = params.fetch(:logger)
7
24
  end
8
25
 
9
- attr_accessor :params
26
+ # ---
27
+
28
+ # All of the fields on `Newman::Controller` are public, so that they can
29
+ # freely be manipulated by callbacks. We may lock this down a bit more
30
+ # in a future version of Newman once we figure out what data actually needs
31
+ # to be exposed in callbacks, but for now you can feel free to depend on
32
+ # any of these fields.
33
+
34
+ attr_accessor :settings, :request, :response, :logger, :params
35
+
36
+
37
+ # ---
38
+
39
+ # `Newman::Controller#respond` is used to modify the response email object,
40
+ # and is used in the manner shown below:
41
+ #
42
+ # respond :subject => "Hello There",
43
+ # :body => "It's nice to meet you, pal!"
44
+ #
45
+ # Because this method simply provides syntactic sugar on top of the
46
+ # `Mail::Message` object's interface, you should be sure to take a look at
47
+ # the documentation for the [mail gem](http://github.com/mikel/mail) to
48
+ # discover what options are available.
10
49
 
11
50
  def respond(params)
12
51
  params.each { |k,v| response.send("#{k}=", v) }
13
52
  end
14
53
 
54
+ # ---
55
+
56
+ # `Newman::Controller#template` is used to invoke a template file within the
57
+ # context of the current controller object using Tilt. A name for the template is
58
+ # provided and then looked up in the directory referenced by
59
+ # `settings.service.templates_dir`. While an example of using templates is
60
+ # included in Newman's source, this feature hasn't really been tested
61
+ # adequately. Please report any problems with this method in our
62
+ # [issue tracker](https://github.com/mendicant-university/newman/issues).
63
+
15
64
  def template(name)
16
65
  Tilt.new(Dir.glob("#{settings.service.templates_dir}/#{name}.*").first)
17
66
  .render(self)
18
67
  end
19
68
 
20
- def sender
21
- request.from.first.to_s
22
- end
69
+ # ---
23
70
 
24
- def domain
25
- settings.service.domain
71
+ # `Newman::Controller#skip_response` is used for disabling the delivery of
72
+ # the response email. Use this for situations where no response is required,
73
+ # such as when a spam email or a bounce has been detected, or if you are
74
+ # building an application which simply passively monitors incoming email
75
+ # rather than replying to it.
76
+
77
+ def skip_response
78
+ response.perform_deliveries = false
26
79
  end
27
80
 
81
+ # ---
82
+
83
+ # `Newman::Controller#forward_message` works in a similar fashion to
84
+ # `Newman::Controller#response`, but copies the request FROM, SUBJECT
85
+ # and BODY fields and sets the REPLY TO field to be equal to
86
+ # `settings.service.default_sender`. This feature is convenient for
87
+ # implementing mailing-list style functionality, such as in the
88
+ # following example:
89
+ #
90
+ # if list.subscriber?(sender)
91
+ # forward_message :bcc => list.subscribers.join(", ")
92
+ # else
93
+ # respond :subject => "You are not subscribed",
94
+ # :body => template("non-subscriber-error")
95
+ # end
96
+
28
97
  def forward_message(params={})
29
98
  response.from = request.from
30
99
  response.reply_to = settings.service.default_sender
@@ -42,8 +111,24 @@ module Newman
42
111
  end
43
112
  end
44
113
 
45
- private
114
+ # ---
115
+
116
+ # `Newman::Controller#sender` is used as a convenient shortcut for
117
+ # retrieving the sender's email address from the request object.
118
+
119
+ def sender
120
+ request.from.first.to_s
121
+ end
122
+
123
+ # ---
124
+
125
+ # `Newman::Controller#domain` is used as a convenient shortcut for
126
+ # referencing `settings.service.domain`.
127
+
128
+ def domain
129
+ settings.service.domain
130
+ end
131
+
46
132
 
47
- attr_accessor :settings, :request, :response
48
133
  end
49
134
  end
@@ -0,0 +1,54 @@
1
+ # `Newman::EmailLogger` provides rudimentary logging support for email objects,
2
+ # and primarily exists to support the `Newman::RequestLogger` and
3
+ # `Newman::ResponseLogger` objects.
4
+ #
5
+ # If you are only interested in making use of this logging functionality and not
6
+ # extending it or changing it in some way, you do not need to be familiar with
7
+ # the code in this file. Just be sure to note that if you add
8
+ # `service.debug_mode = true` to your configuration file, or set the Ruby
9
+ # `$DEBUG` global variable, you will get much more verbose output from
10
+ # Newman's logging system.
11
+ #
12
+ # `Newman::EmailLogger` is part of Newman's **internal interface**.
13
+
14
+ module Newman
15
+ module EmailLogger
16
+
17
+ # ---
18
+
19
+ # `Newman::EmailLogger#log_email` takes a logger object, a prefix, and a `Mail` object and
20
+ # then outputs relevant debugging details.
21
+ #
22
+ # This method always at least provides a summary of the provided `email`
23
+ # at the `INFO` level When in debugging mode, the full contents of
24
+ # the `email` will also gets logged.
25
+ #
26
+ # The main purpose of this method is to be used by `Newman::RequestLogger`
27
+ # and `Newman::ResponseLogger`, but may also optionally be used as a helper
28
+ # for those who are rolling their own logging functionality.
29
+
30
+ def log_email(logger, prefix, email)
31
+ logger.debug(prefix) { "\n#{email}" }
32
+ logger.info(prefix) { email_summary(email) }
33
+ end
34
+
35
+ # ---
36
+
37
+ # **NOTE: Methods below this point in the file are implementation details,
38
+ # and should not be depended upon**
39
+
40
+ private
41
+
42
+ # ---
43
+
44
+ # `Newman::EmailLogger#email_summary` returns a hash with a summary of the provided `email` object.
45
+
46
+ def email_summary(email)
47
+ { :from => email.from,
48
+ :to => email.to,
49
+ :bcc => email.bcc,
50
+ :subject => email.subject,
51
+ :reply_to => email.reply_to }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,123 @@
1
+ # `Newman::Filters` provides the standard filtering mechanisms for
2
+ # Newman applications.
3
+ #
4
+ # Unless you are building a server-side extension for
5
+ # Newman, you probably only need to be familiar with how these filter methods
6
+ # are used and can treat their implementation details as a black box.
7
+ #
8
+ # `Newman::Filters` is part of Newman's **external interface**.
9
+
10
+ module Newman
11
+ module Filters
12
+
13
+ # ---
14
+
15
+ # `Newman::Filters#to` takes a `filter_type`, a `pattern`,
16
+ # and an `action` and then registers a callback which gets run for
17
+ # each new request the application handles. If the filter matches the
18
+ # incoming message, the `action` block gets run in the context of
19
+ # a `Newman::Controller` object. Otherwise, the `action` block
20
+ # does not get run at all.
21
+ #
22
+ # Currently, the only supported `filter_type` is `:tag`, which leverages the
23
+ # `+` extension syntax for email addresses to filter out emails with certain
24
+ # tags in their TO field. For example, we could build a filter that responds
25
+ # to messages sent to `USERNAME+ping@HOST` using the following filter
26
+ # setup:
27
+ #
28
+ # to(:tag, "ping") do
29
+ # respond(:subject => "pong")
30
+ # end
31
+ #
32
+ # Because this method runs the `pattern` through
33
+ # `Newman::Application#compile_regex`, it can also be used in
34
+ # combination with `Newman::Application#match` to do more
35
+ # complex matching. For example, the code below could be used to support
36
+ # complex TO field mappings, such as `USERNAME+somelist.subscribe@HOST`:
37
+ #
38
+ # match :list_id, "[^.]+"
39
+ #
40
+ # to(:tag, "{list_id}.subscribe") do
41
+ # list = load_list(params[:list_id])
42
+ #
43
+ # if list.subscriber?(sender)
44
+ # # send failure email, already subscribed
45
+ # else
46
+ # # add user to list and send success email
47
+ # end
48
+ # end
49
+ #
50
+ # Note that currently everything before the `+` in the email address is
51
+ # ignored, and that the domain is hardcoded to match the
52
+ # `Controller#domain`, which currently directly references the
53
+ # `service.domain` setting. It'd be nice to make this a bit more
54
+ # flexible and also support other filter types such as a match against the
55
+ # whole email address at some point in the future.
56
+
57
+ def to(filter_type, pattern, &action)
58
+ raise NotImplementedError unless filter_type == :tag
59
+
60
+ regex = compile_regex(pattern)
61
+
62
+ callback action, ->(controller) {
63
+ controller.request.to.each do |e|
64
+ md = e.match(/\+#{regex}@#{Regexp.escape(controller.domain)}/)
65
+ return md if md
66
+ end
67
+
68
+ false
69
+ }
70
+ end
71
+
72
+ # ---
73
+
74
+ # `Newman::Filters#subject` takes a `filter_type`,
75
+ # a `pattern`, and an `action` and
76
+ # then registers a callback which gets run for each new request the
77
+ # application handles. If the filter matches the incoming message, the
78
+ # `action` block gets run in the context of a `Newman::Controller` object.
79
+ # Otherwise, the `action` block does not get run at all.
80
+ #
81
+ # Currently, the only supported `filter_type` is `:match`, which matches the
82
+ # pattern against the full SUBJECT field. This can be used for simple
83
+ # subject based filtering, such as the code shown below:
84
+ #
85
+ # subject(:match, "what stories do you know?") do
86
+ # respond :subject => "All of Jester's stories",
87
+ # :body => story_library.map { |e| e.title }.join("\n")
88
+ # end
89
+ #
90
+ # Because this method runs the `pattern` through
91
+ # `Newman::Application#compile_regex`, it can also be used in
92
+ # combination with `Newman::Application#match` to do more
93
+ # complex matching, such as in the following example:
94
+ #
95
+ # match :genre, '\S+'
96
+ # match :title, '.*'
97
+ #
98
+ # subject(:match, "a {genre} story '{title}'") do
99
+ # story_library.add_story(:genre => params[:genre],
100
+ # :title => params[:title],
101
+ # :body => request.body.to_s)
102
+ #
103
+ # respond :subject => "Jester saved '#{params[:title]}'"
104
+ # end
105
+ #
106
+ # It'd be nice to support more kinds of matching strategies at some point in
107
+ # the future, so be sure to let us know if you have ideas.
108
+
109
+ def subject(filter_type, pattern, &action)
110
+ raise NotImplementedError unless filter_type == :match
111
+
112
+ regex = compile_regex(pattern)
113
+
114
+ callback action, ->(controller) {
115
+ subject = controller.request.subject
116
+
117
+ return false unless subject
118
+
119
+ subject.match(/#{regex}/) || false
120
+ }
121
+ end
122
+ end
123
+ end
@@ -1,41 +1,107 @@
1
+ # `Newman::Mailer` allows you to easily receive mail via IMAP and send mail via
2
+ # SMTP, and is the default mailing strategy used by `Newman::Server.simple`.
3
+ # This class mostly exists to serve as an adapter that bridges the gap between
4
+ # the mail gem and Newman's configuration system.
5
+ #
6
+ # `Newman::Mailer`'s interface is minimal by design so that other objects
7
+ # can easily stand in for it as long as they respond to the same set of
8
+ # messages. Be sure to see `Newman::TestMailer` for an example of how to build a
9
+ # custom object that can be used in place of a `Newman::Mailer` object.
10
+ #
11
+ #
12
+ # `Newman::Mailer` is part of Newman's **internal interface**.
13
+
1
14
  module Newman
2
- Mailer = Object.new
15
+ class Mailer
16
+
17
+ # ---
18
+
19
+ # To initialize a `Newman::Mailer` object, a settings object must be
20
+ # provided, i.e:
21
+ #
22
+ # settings = Newman::Settings.from_file('config/environment.rb')
23
+ # mailer = Newman::Mailer.new(settings)
24
+ #
25
+ # This is done automatically for you by `Newman::Server.simple`, but must be
26
+ # done manually if you are creating a `Newman::Server` instance from
27
+ # scratch.
28
+ #
29
+ # Currently, not all of the settings supported by the mail gem are mapped by
30
+ # Newman. This is by design, to limit the amount of configuration options
31
+ # need to think about. However, if this is causing you a problem,
32
+ # please [file an issue](https://github.com/mendicant-university/newman/issues).
3
33
 
4
- class << Mailer
5
- def configure(settings)
34
+ def initialize(settings)
6
35
  imap = settings.imap
7
36
  smtp = settings.smtp
8
37
 
9
- Mail.defaults do
10
- retriever_method :imap,
11
- :address => imap.address,
12
- :user_name => imap.user,
13
- :password => imap.password,
14
- :enable_ssl => imap.ssl_enabled || false,
15
- :port => imap.port
16
-
17
- delivery_method :smtp,
18
- :address => smtp.address,
19
- :user_name => smtp.user,
20
- :password => smtp.password,
21
- :authentication => :plain,
22
- :enable_starttls_auto => smtp.starttls_enabled || false,
23
- :port => smtp.port
24
- end
25
-
26
- def messages
27
- Mail.all(:delete_after_find => true)
28
- end
29
-
30
- def new_message(*a, &b)
31
- Mail.new(*a, &b)
32
- end
33
-
34
- def deliver_message(*a, &b)
35
- new_message(*a, &b).deliver
36
- end
38
+ self.retriever_settings = {
39
+ :address => imap.address,
40
+ :user_name => imap.user,
41
+ :password => imap.password,
42
+ :enable_ssl => imap.ssl_enabled || false,
43
+ :port => imap.port
44
+ }
45
+
46
+ self.delivery_settings = {
47
+ :address => smtp.address,
48
+ :user_name => smtp.user,
49
+ :password => smtp.password,
50
+ :authentication => :plain,
51
+ :enable_starttls_auto => smtp.starttls_enabled || false,
52
+ :port => smtp.port
53
+ }
54
+ end
55
+
56
+ # ---
57
+
58
+ # `Newman::Mailer#messages` is used to retrieve all messages currently in the inbox
59
+ # and then delete them from the server. This method returns an array of
60
+ # `Mail::Message` objects if any messages were found, and returns
61
+ # an empty array otherwise.
62
+
63
+ def messages
64
+ Mail::IMAP.new(retriever_settings).all(:delete_after_find => true)
65
+ end
66
+
67
+ # ---
68
+
69
+ # `Newman::Mailer#new_message` is used to construct a new `Mail::Message` object,
70
+ # with the delivery settings that were set up at initialization time.
71
+ # This method passes all its arguments on to `Mail.new`, so be sure
72
+ # to refer to the [mail gem's documentation](http://github.com/mikel/mail)
73
+ # for details.
74
+ #
75
+ def new_message(*a, &b)
76
+ msg = Mail.new(*a, &b)
77
+ msg.delivery_method(:smtp, delivery_settings)
78
+
79
+ msg
37
80
  end
38
81
 
39
- attr_accessor :settings
82
+ # ---
83
+
84
+ # `Newman::Mailer#deliver_message` is used to construct and immediately deliver a
85
+ # message using the delivery settings that were set up at initialization
86
+ # time.
87
+
88
+ def deliver_message(*a, &b)
89
+ new_message(*a, &b).deliver
90
+ end
91
+
92
+ # ---
93
+
94
+ # **NOTE: Methods below this point in the file are implementation
95
+ # details, and should not be depended upon**
96
+
97
+ private
98
+
99
+ # ---
100
+
101
+ # These accessors have been made private to reflect the fact that
102
+ # `Newman::Mailer` objects are meant to be treated as immutable constructs
103
+ # once they are created.
104
+
105
+ attr_accessor :retriever_settings, :delivery_settings
40
106
  end
41
107
  end