newman 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,23 @@
1
+ ### 0.2.0 (2012-02-08)
2
+
3
+ - Internals mostly rewritten, changes too numerous to outline meaningfully. We'll keep better track of these changes in the future.
4
+
5
+ - Basic logging support added.
6
+
7
+ - Lots of documentation added.
8
+
9
+ - Server no longer crashes upon application errors unless
10
+ service.raise_exceptions is set to true.
11
+
12
+ - Added Newman::Controller#skip_response which allows disabling delivery of a response
13
+ email upon demand.
14
+
15
+ - Make Newman::MailingList fail gracefully by checking subscriber status before
16
+ attempting subscribe / unsubscribe.
17
+
18
+ - Add a generic callback method to Newman::Application which allows for arbitrary
19
+ callbacks to be run based on filters against the Mail::Message object.
20
+
21
+ ### 0.1.1 (2012-02-03)
22
+
23
+ - First official release.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/README.md CHANGED
@@ -1,10 +1,129 @@
1
- ## Newman: Because the mail never stops!
1
+ ![Newman](http://i.imgur.com/92bZB.jpg)
2
2
 
3
- This is going to be an experiment in building a simple framework for developing
4
- email-based applications. It is based on ideas from an exercise in
5
- Mendicant University's January 2012 core skills course, and also serves as a
6
- demo application for an article in the Practicing Ruby Journal.
3
+ Newman is a microframework which aims to do for email-based
4
+ applications what Rack and Sinatra have done for web programming. **While our
5
+ goals may be ambitious, this project is
6
+ currently in a very early experimental stage, and is in no way safe for use in
7
+ production.**
7
8
 
8
- For a demonstration of how Newman is used, check out
9
- [Jester](http://github.com/mendicant-university/jester) as well as some of the
9
+ That said, Newman is already capable of doing a number of interesting things. In
10
+ particular:
11
+
12
+ * A simple polling server provides a basic interface for
13
+ reading email from a single inbox and then building up a response email.
14
+
15
+ * Filters are provided for handling emails based on their TO and SUBJECT fields.
16
+
17
+ * Filters can also be defined for handling messages based on arbitrary
18
+ conditions evaluated against a `Mail::Messsage` object.
19
+
20
+ * A rudimentary PStore backed storage mechanism is provided for persistence.
21
+
22
+ * Basic support for maintaining persistent lists of email addresses is
23
+ provided.
24
+
25
+ * Basic support for email templates are provided via Tilt.
26
+
27
+ * A barebones configuration system allows configuring email settings, service
28
+ settings, and application specific settings.
29
+
30
+ We are still working on figuring out what belongs in Newman and what doesn't,
31
+ and so all of these features are subject to change or disappear entirely. But if
32
+ you have a need for this sort of tool, it's worth noting that this software
33
+ isn't entirely vaporware, and that we could use your help!
34
+
35
+ ### Scary Warning:
36
+
37
+ DON'T HOOK UP NEWMAN TO ANY EMAIL INBOX THAT YOU CAN'T COMPLETELY WIPE OUT EVERY TIME IT RUNS. NEWMAN **WILL** DELETE YOUR EMAILS!!!
38
+
39
+ ### For a demonstration of how Newman is used:
40
+
41
+ Check out [Jester](http://github.com/mendicant-university/jester) as well as some of the
10
42
  simple examples in this repository.
43
+
44
+ ### For a walkthrough of Newman's codebase:
45
+
46
+ Check out [Newman's Rocco-based API documentation](http://mendicant-university.github.com/newman/lib/newman.html).
47
+
48
+ ### For general discussion, questions, and ideas about Newman:
49
+
50
+ Find seacreature or ericgj in the #newman channel on Freenode or send an email to newman@librelist.org
51
+
52
+ ### Contributing to Newman:
53
+
54
+ We do not yet have a clear roadmap or contributor guidelines, so be sure to talk
55
+ to us before working on bug fixes or patches. But assuming you do want to send
56
+ us some code, here is what you need to know:
57
+
58
+ * You get to keep the copyright to your code, but you must agree to license it
59
+ under the MIT license.
60
+
61
+ * Your code should come with tests. Integration tests are fine, but unit tests
62
+ would be nice where appropriate. Right now Newman is under tested and we don't
63
+ want to make that problem worse. You can of course submit a pull request for
64
+ feedback BEFORE writing tests.
65
+
66
+ * Your code should be fully documented, and properly formatted for use with
67
+ Rocco. Please try to emulate the style and conventions we've been using where
68
+ possible. Do the best you can with this, and we'll help tighten up wording and
69
+ clean up formatting as needed. You can of course submit a pull request for
70
+ feedback BEFORE writing documentation.
71
+
72
+ * Newman is taking a use-case oriented approach to design. Be prepared to
73
+ justify any proposed change with real or realistic scenarios, rather than
74
+ simply addressing theoretical concerns.
75
+
76
+ ### Versioning Policy:
77
+
78
+ We will try to follow the guidelines below when cutting new releases,
79
+ to the extent that it makes sense to do so.
80
+
81
+ 1) Clearly mark each object that Newman provides as being part of either
82
+ the 'external API' or the 'internal API'. The external API is for
83
+ application developers, the internal API is for Newman itself as well as
84
+ extension developers
85
+
86
+ 2) Before 1.0, allow backwards incompatible internal API changes during
87
+ every release, and allow backwards incompatible external API changes
88
+ during minor version bumps, but do not add or change external behavior
89
+ in tiny version bumps.
90
+
91
+ 3) After 1.0, do not allow external or internal API changes during tiny
92
+ version bumps (these will be bug fixes only). Allow changes to the
93
+ internal API during minor version bumps, but maintain backwards
94
+ compatibility with the 1.0 release (i.e. things introduced after 1.0 can
95
+ be changed / removed, but things which shipped with 1.0 should stay
96
+ supported, even internally). Allow external API changes or
97
+ backwards-incompatible changes to the internals only on major version
98
+ bumps (i.e. 2.0, 3.0, etc). Use semantic versioning and declare the
99
+ external API to be the 'public' API.
100
+
101
+ We plan to get to 1.0 quickly to reach a stabilizing point for application
102
+ developers and a slower moving target for extension developers. This means
103
+ that our 1.0 release will be more of a minimum-viable product and not
104
+ necessarily a full-stack framework.
105
+
106
+ ### Authorship:
107
+
108
+ Newman is being developed by [Gregory Brown](http://community.mendicantuniversity.org/people/sandal)
109
+ and [Eric Gjertsen](http://community.mendicantuniversity.org/people/ericgj), along with
110
+ help from several other folks.
111
+
112
+ It is based on an assignment from [Mendicant
113
+ University](http://mendicantuniversity.org)'s January 2011 core
114
+ skills course, and was also used as a sample application for the [Practicing Ruby](http://practicingruby.com)
115
+ journal. The original inspiration for this project came from some code
116
+ written by [Brent Vatne](http://community.mendicantuniversity.org/people/brentvatne),
117
+ as well as from the general ideas behind Rack and Sinatra.
118
+
119
+ [View the full list of contributors](https://github.com/mendicant-university/newman/contributors) to see who else has helped out.
120
+
121
+ ### License:
122
+
123
+ Copyright (c) 2012 Gregory Brown, Eric Gjertsen, et al.
124
+
125
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
126
+
127
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
128
+
129
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -1,9 +1,13 @@
1
+ # WARNING: DON'T HOOK UP NEWMAN TO ANY EMAIL INBOX THAT
2
+ # YOU CAN'T COMPLETELY WIPE OUT EVERY TIME IT RUNS.
3
+ # NEWMAN **WILL** DELETE YOUR EMAILS!!!
4
+
1
5
  imap.address = "mail.<<DOMAIN>>"
2
6
  imap.user = "<<USERNAME>>@<<DOMAIN>>"
3
7
  imap.password = "<<PASSWORD>>"
4
8
 
5
9
  smtp.address = "mail.<<DOMAIN>>"
6
- smtp.user = "<<USER>>@<<DOMAIN>>"
10
+ smtp.user = "<<USERNAME>>@<<DOMAIN>>"
7
11
  smtp.password = "<<PASSWORD>>"
8
12
 
9
13
  service.domain = "<<DOMAIN>>"
@@ -1,3 +1,7 @@
1
+ # WARNING: DON'T HOOK UP NEWMAN TO ANY EMAIL INBOX THAT
2
+ # YOU CAN'T COMPLETELY WIPE OUT EVERY TIME IT RUNS.
3
+ # NEWMAN **WILL** DELETE YOUR EMAILS!!!
4
+
1
5
  imap.address = "imap.gmail.com"
2
6
  imap.user = "<<USERNAME>>@gmail.com"
3
7
  imap.password = "<<PASSWORD>>"
@@ -2,13 +2,10 @@ require_relative "ping_pong"
2
2
 
3
3
 
4
4
  settings = Newman::Settings.from_file("config/environment.rb")
5
+ mailer = Newman::Mailer.new(settings)
5
6
 
6
- mailer = Newman::Mailer
7
- mailer.configure(settings)
8
-
9
- server = Newman::Server
10
- server.settings = settings
11
- server.mailer = mailer
7
+ server = Newman::Server.new(settings, mailer)
8
+ server.apps << Newman::Examples::PingPong
12
9
 
13
10
  mailer.deliver_message(:to => settings.application.ping_email,
14
11
  :from => settings.service.default_sender)
@@ -20,7 +17,7 @@ settings.application.live_test_delay.downto(1) do |i|
20
17
  end
21
18
  puts
22
19
 
23
- Newman::Server.tick(Newman::Examples::PingPong)
20
+ server.tick
24
21
 
25
22
  remaining_attempts = 3
26
23
  loop do
@@ -12,7 +12,7 @@ module Newman
12
12
  end
13
13
  end
14
14
 
15
- to(:tag, "subscribe") do
15
+ subject(:match, "subscribe") do
16
16
  if list.subscriber?(sender)
17
17
  respond :subject => "ERROR: Already subscribed",
18
18
  :body => template("subscribe-error")
@@ -24,7 +24,7 @@ module Newman
24
24
  end
25
25
  end
26
26
 
27
- to(:tag, "unsubscribe") do
27
+ subject(:match, "unsubscribe") do
28
28
  if list.subscriber?(sender)
29
29
  list.unsubscribe(sender)
30
30
 
@@ -1,3 +1,95 @@
1
+ # [Newman](https://github.com/mendicant-university/newman) is a
2
+ # microframework which aims to do for email-based applications what Rack and
3
+ # Sinatra have done for web programming. **While our goals may be ambitious,
4
+ # this project is currently in a very early experimental stage,
5
+ # and is in no way safe for use in production.**
6
+ #
7
+ # That said, we still welcome contributors to help us work on this project
8
+ # as we collectively figure out what exactly Newman should be. Even in its
9
+ # very early stages, Newman is already doing some useful things and provides
10
+ # a wide range of application development features via its external interface
11
+ # as well as extension points via its internal interface.
12
+ #
13
+ # The documentation you're currently reading is meant to help explain Newman's
14
+ # implementation to contributors and alpha testers. Before you dig deeper into
15
+ # the source, make sure to read
16
+ # [Newman's README](https://github.com/mendicant-university/newman)
17
+ # as well as the [Jester](https://github.com/mendicant-university/jester) demo
18
+ # application.
19
+ #
20
+ # Assuming you have done those things and are now familiar with the basic ideas
21
+ # behind Newman, the following outline may help you in your explorations of
22
+ # its source code.
23
+ #
24
+ # ### External interface (for application developers)
25
+ #
26
+ # * [Newman::Server](http://mendicant-university.github.com/newman/lib/newman/server.html)
27
+ # takes incoming mesages from a mailer object and passes them to applications as a
28
+ # request, and then delivers a response email that built up by its
29
+ # applications.
30
+ #
31
+ # * [Newman::Application](http://mendicant-university.github.com/newman/lib/newman/application.html)
32
+ # provides the main entry point for Newman application developers, and exists to tie together
33
+ # various Newman objects in a convenient way.
34
+ #
35
+ # * [Newman::Filters](http://mendicant-university.github.com/newman/lib/newman/filters.html)
36
+ # provides high level filters for matching incoming requests.
37
+ #
38
+ # * [Newman::Controller](http://mendicant-university.github.com/newman/lib/newman/controller.html)
39
+ # provides a context for application callbacks to run in, and provides most of
40
+ # the core functionality for preparing a response email.
41
+ #
42
+ # * [Newman::MailingList](http://mendicant-university.github.com/newman/lib/newman/mailing_list.html)
43
+ # implements a simple mechanism for storing persistent lists of email addresses keyed
44
+ # by a mailing list name.
45
+ #
46
+ # * [Newman::Store](http://mendicant-university.github.com/newman/lib/newman/store.html) provides
47
+ # a minimal persistence layer for storing non-relational data.
48
+ #
49
+ # * [Newman::Recorder](http://mendicant-university.github.com/newman/lib/newman/recorder.html)
50
+ # provides a mechanism for storing records with autoincrementing identifiers
51
+ # within a `Newman::Store` and supports some rudimentary CRUD functionality.
52
+ #
53
+ # * Implicitly, the settings files used by Newman as well as the structure of
54
+ # its low level data storage format are also considered part of external API,
55
+ # even though these are actually implementation details. This is simply
56
+ # because we want to make sure to clearly reflect backwards incompatible
57
+ # changes to these features via our versioning policy, as this sort of
58
+ # change could potentially cause update problems for application
59
+ # developers.
60
+ #
61
+ # ### Internal interface (for extension developers)
62
+ #
63
+ # * [Newman::EmailLogger](http://mendicant-university.github.com/newman/lib/newman/email_logger.html)
64
+ # provides rudimentary logging support for email objects, and primarily exists to
65
+ # support the `Newman::RequestLogger` and `Newman::ResponseLogger` objects.
66
+ #
67
+ # * [Newman::RequestLogger](http://mendicant-university.github.com/newman/lib/newman/request_logger.html)
68
+ # provides a mechanism for logging information about incoming emails.
69
+ #
70
+ # * [Newman::ResponseLogger](http://mendicant-university.github.com/newman/lib/newman/response_logger.html)
71
+ # provides a mechanism for logging information about outgoing emails.
72
+ #
73
+ # * [Newman::Mailer](http://mendicant-university.github.com/newman/lib/newman/mailer.html) provides a thin
74
+ # wrapper on top of the [mail gem](http://github.com/mikel/mail) which is
75
+ # designed to have a minimal API so that it can easily be swapped out with
76
+ # another mailer object.
77
+ #
78
+ # * [Newman::TestMailer](http://mendicant-university.github.com/newman/lib/newman/test_mailer.html)
79
+ # is a drop-in replacement for `Newman::Mailer` meant for use in automated testing.
80
+ #
81
+ # * [Newman::Settings](http://mendicant-university.github.com/newman/lib/newman/settings.html) provides
82
+ # the base functionality that is used by Newman's configuration files. Note
83
+ # that while this object is part of the internals, the settings actually used
84
+ # by Newman should be considered part of the external API.
85
+ #
86
+ # ### Getting help, or helping out:
87
+ #
88
+ # Please catch up with seacreature or ericgj in the #newman channel on Freenode,
89
+ # or send an email to newman@librelist.org. We'd love to hear any questions,
90
+ # ideas, or suggestions you'd like to share with us.
91
+
92
+ require "logger"
1
93
  require "pstore"
2
94
  require "ostruct"
3
95
  require "fileutils"
@@ -5,13 +97,17 @@ require "fileutils"
5
97
  require "mail"
6
98
  require "tilt"
7
99
 
100
+ require_relative "newman/email_logger"
101
+ require_relative "newman/request_logger"
102
+ require_relative "newman/response_logger"
8
103
  require_relative "newman/server"
9
- require_relative "newman/commands"
104
+ require_relative "newman/filters"
10
105
  require_relative "newman/application"
11
106
  require_relative "newman/controller"
12
107
  require_relative "newman/mailing_list"
13
108
  require_relative "newman/settings"
14
109
  require_relative "newman/store"
110
+ require_relative "newman/recorder"
15
111
  require_relative "newman/mailer"
16
112
  require_relative "newman/test_mailer"
17
113
  require_relative "newman/version"
@@ -1,6 +1,33 @@
1
+ # `Newman::Application` provides the main entry point for Newman application
2
+ # developers, and exists to tie together various Newman objects in a convenient
3
+ # way.
4
+ #
5
+ # For an fairly complete example of a `Newman::Application` object in use, be
6
+ # sure to check out [Jester](https://github.com/mendicant-university/jester).
7
+ #
8
+ # `Newman::Application` is part of Newman's **external interface**.
9
+
1
10
  module Newman
2
11
  class Application
3
- include Commands
12
+
13
+ include Filters
14
+ # ---
15
+
16
+ # A `Newman::Application` object is a blank slate upon creation, with fields
17
+ # set to hold `callbacks`, `matchers`, and `extensions`. A block may
18
+ # optionally be provided, which then gets executed within the context of the
19
+ # newly created `Newman::Application` instance. This is the common way of
20
+ # building applications, and is demonstrated by the example below:
21
+ #
22
+ # ping_pong = Newman::Application.new do
23
+ # subject("ping") do
24
+ # respond(:subject => "pong")
25
+ # end
26
+ # end
27
+ #
28
+ # Any method that can be called on a `Newman::Application` instance can be
29
+ # called within the provided block, including those methods mixed in by
30
+ # `Newman::Filters`.
4
31
 
5
32
  def initialize(&block)
6
33
  self.callbacks = []
@@ -9,6 +36,18 @@ module Newman
9
36
 
10
37
  instance_eval(&block) if block_given?
11
38
  end
39
+
40
+ # ---
41
+
42
+ # `Newman::Application#call` accepts a hash of parameters which gets used to
43
+ # create a new `Newman::Controller` object. The controller is then extended
44
+ # by all of the modules stored in the `extensions` field on the application
45
+ # object, and is finally passed along to
46
+ # `Newman::Application#trigger_callbacks`, which does the
47
+ # magical work of figuring out which callbacks to run, if any.
48
+ #
49
+ # This method is meant to be run by a `Newman::Server` object, and isn't
50
+ # especially useful on its own.
12
51
 
13
52
  def call(params)
14
53
  controller = Controller.new(params)
@@ -16,8 +55,206 @@ module Newman
16
55
  trigger_callbacks(controller)
17
56
  end
18
57
 
58
+ # ---
59
+
60
+ # `Newman::Application#default` is used to define a default callback
61
+ # which will run when no other callbacks match the incoming request.
62
+ # For example, you can define a callback such as the one below:
63
+ #
64
+ # default do
65
+ # respond(:subject => "REQUEST NOT UNDERSTOOD")
66
+ # end
67
+ #
68
+ # Unless you are building an application that will never fail to
69
+ # match at least one of its filters, you MUST set up a default callback
70
+ # if you want to avoid a possible application error. We know this is
71
+ # not exactly the most desireable behavior, and will try to fix this
72
+ # in a future version of Newman.
73
+
74
+ def default(&callback)
75
+ self.default_callback = callback
76
+ end
77
+
78
+ # ---
79
+
80
+ # `Newman:::Application#use` is used to register the extension
81
+ # modules that get mixed in to the controller
82
+ # objects created by `Newman::Application#call`. This allows
83
+ # an application object to provide extensions for use within its
84
+ # callbacks, as in the example shown below.
85
+ #
86
+ # module ListLoader
87
+ # def load_list(name)
88
+ # store = Newman::Store.new(settings.application.list_db)
89
+ # Newman::MailingList.new(name, store)
90
+ # end
91
+ # end
92
+ #
93
+ # list_app = Newman::Application.new do
94
+ # use ListLoader
95
+ #
96
+ # match :list_id, "[^.]+"
97
+ #
98
+ # to(:tag, "{list_id}.subscribe") do
99
+ # list = load_list(params[:list_id])
100
+ #
101
+ # if list.subscriber?(sender)
102
+ # # send a failure message
103
+ # else
104
+ # # susbcribe the user and send a welcome message
105
+ # end
106
+ # end
107
+ # end
108
+ #
109
+ # This method is mainly meant to be used with pre-packaged extensions or
110
+ # more complicated forms of callback helpers. This example was just shown
111
+ # for the sake of its simplicity, but for similar use cases it would
112
+ # actually be better to use `Newman::Application#helpers`
113
+
114
+ def use(extension)
115
+ extensions << extension
116
+ end
117
+
118
+ # ---
119
+
120
+ # `Newman::Application#helpers` is used to build simple controller
121
+ # extensions, and is mostly just syntactic sugar. For example,
122
+ # rather than using an explicit
123
+ # module, the example shown in the `Newman::Application#use` documentation
124
+ # can be rewritten as follows:
125
+ #
126
+ # list_app = Newman::Application.new do
127
+ # helpers do
128
+ # def load_list(name)
129
+ # store = Newman::Store.new(settings.application.list_db)
130
+ # Newman::MailingList.new(name, store)
131
+ # end
132
+ # end
133
+ #
134
+ # match :list_id, "[^.]+"
135
+ #
136
+ # to(:tag, "{list_id}.subscribe") do
137
+ # list = load_list(params[:list_id])
138
+ #
139
+ # if list.subscriber?(sender)
140
+ # # send a failure message
141
+ # else
142
+ # # susbcribe the user and send a welcome message
143
+ # end
144
+ # end
145
+ # end
146
+ #
147
+ # It's important to note that for any controller extensions that might be
148
+ # reusable, or for more complicated logic, `Newman::Application#use` is
149
+ # probably a better tool to use.
150
+
151
+ def helpers(&block)
152
+ use Module.new(&block)
153
+ end
154
+
155
+ # ---
156
+
157
+ # `Newman::Application#match` is used to define patterns which are used for
158
+ # extracting callback parameters. An example is shown below:
159
+ #
160
+ # jester = Newman::Application.new do
161
+ # match :genre, '\S+'
162
+ # match :title, '.*'
163
+ #
164
+ # subject(:match, "a {genre} story '{title}'") do
165
+ # story_library.add_story(:genre => params[:genre],
166
+ # :title => params[:title],
167
+ # :body => request.body.to_s)
168
+ #
169
+ # respond :subject => "Jester saved '#{params[:title]}'"
170
+ # end
171
+ # end
172
+ #
173
+ # Because Newman's built in filters are designed to escape regular
174
+ # expression syntax by default, `Newman::Application#match` provides the
175
+ # only high-level mechanism for dynamic filter matches. Low level matching
176
+ # is possible via `Newman::Application#callback`, but would
177
+ # be an exercise in tedium for most application developers.
178
+
179
+ def match(name, pattern)
180
+ matchers[name] = pattern
181
+ end
182
+
183
+ # ---
184
+
185
+ # `Newman::Application#callback` is a low level feature for defining custom
186
+ # callbacks. For ideas on how to roll your own filters with it, see the
187
+ # implementation of the `Newman::Filters` module.
188
+
189
+ def callback(action, filter)
190
+ callbacks << { :filter => filter,
191
+ :action => action }
192
+ end
193
+
194
+ # ---
195
+
196
+ # `Newman::Application#compile_regex` is used for converting pattern strings
197
+ # into a regular expression string suitable for use in filters. This method is a
198
+ # low-level feature, and is not meant for use by application developers. See
199
+ # the `Newman::Filters` module for how to use it to build your own callback
200
+ # filters.
201
+
202
+ def compile_regex(pattern)
203
+ Regexp.escape(pattern)
204
+ .gsub(/\\{(.*?)\\}/) { |m| "(?<#{$1}>#{matchers[$1]})" }
205
+ end
206
+
207
+ # ---
208
+
209
+ # **NOTE: Methods below this point in the file are implementation
210
+ # details, and should not be depended upon.**
211
+
19
212
  private
20
213
 
214
+ # ---
215
+
216
+ # `Newman::Application#trigger_callbacks` runs a two step process:
217
+ #
218
+ # 1) It grabs the `filter` Proc for each callback and executes it, passing in
219
+ # the provided `controller`. Any `filter` proc that returns a logically true
220
+ # value is selected to be run.
221
+ #
222
+ # 2) If the selection of `matched_callbacks` is empty, it executes the default
223
+ # callback in the context of a controller object. Otherwise, it runs each
224
+ # callback in sequence, in the context of a controller object.
225
+ #
226
+ # This method may have some weird side effects because it relies on
227
+ # awkward state mutations that could be either done in a better way or
228
+ # replaced with a mostly stateless approach. We will look at fixing
229
+ # this in a future Newman release.
230
+
231
+ def trigger_callbacks(controller)
232
+ matched_callbacks = callbacks.select do |e|
233
+ filter = e[:filter]
234
+ e[:match_data] = filter.call(controller)
235
+ end
236
+
237
+ if matched_callbacks.empty?
238
+ controller.instance_exec(&default_callback)
239
+ else
240
+ matched_callbacks.each do |e|
241
+ action = e[:action]
242
+ controller.params = e[:match_data] || {}
243
+ controller.instance_exec(&action)
244
+ end
245
+ end
246
+ end
247
+
248
+ # ---
249
+
250
+ # These accessors have been made private to reflect the fact that
251
+ # `Newman::Application` is meant to be customized via its high level methods
252
+ # such as `use`, `helpers`, `match`, and `callback`. Any extensions of
253
+ # `Newman::Application` should rely on those methods and not directly
254
+ # reference these fields at all. If there is some functionality missing
255
+ # that needs to be added to the public API for you to build your
256
+ # extension, just let us know.
257
+
21
258
  attr_accessor :callbacks, :default_callback, :matchers, :extensions
22
259
  end
23
260
  end