roda 2.24.0 → 2.25.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b17a05c593a91658be1194fb29c9564dcd6e3fe2
4
- data.tar.gz: 153d3095509dd43d37e9297c963aea4e39ef2cbe
3
+ metadata.gz: 8a70fe0e0ab208cb5ca42d42d60633e1c03ec77a
4
+ data.tar.gz: 5aedc804b0e884c1a05d87d325bf4c28e4798c6c
5
5
  SHA512:
6
- metadata.gz: d3871a3650c8490435fd94d13148a5ba90c636779cb1235c94c0bb61d76ef35efa8cf5c504e29271a3e34bcc818c179ed8a709e1576ccbeb53fb8c4825da2272
7
- data.tar.gz: 0ab43b3207200bfc783a00d714b058d04220a1ab1dd333670b8c2d590530cab87564e9323f4023a6c8e48f63a50fd3cf82f842429ae395bdf8f192905fb035bd
6
+ metadata.gz: c9ea6a8f1ea687572e874af5d6ff92a06617bc7881d1337a307b64f63c125912e836a577944f156da1c340a8b11eecf78cf780bcef42be122d29fe3887f8f457
7
+ data.tar.gz: 6ef1c484659735450a053f3f3d5dddf7bc4f65c78bfe52e54d1930529938f3a99c8810c2b0ea69846fc11cb757727f9dbe2df43ae90aae60af1c5d0bac75a893
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ = 2.25.0 (2017-04-18)
2
+
3
+ * Add error_mail plugin, similar to error_email but using mail instead of net/smtp directly (jeremyevans)
4
+
1
5
  = 2.24.0 (2017-03-15)
2
6
 
3
7
  * Have h plugin use cgi/escape if available for faster escaping (jeremyevans)
data/README.rdoc CHANGED
@@ -415,6 +415,50 @@ If +false+ or +nil+ is given directly as a matcher, it doesn't match anything.
415
415
 
416
416
  Everything else matches anything.
417
417
 
418
+ == Optional segments
419
+
420
+ There are multiple ways you can handle optional segments in Roda. For example,
421
+ let's say you want to accept both +/items/123+ and +/items/123/456+, with 123 being
422
+ the item's id, and 456 being some optional data.
423
+
424
+ The simplest way to handle this is by treating this as two separate routes with a
425
+ shared branch:
426
+
427
+ r.on "items", :id do |item_id|
428
+ # Shared code for branch here
429
+
430
+ # /items/123/456
431
+ r.is :opt_data do |optional_data|
432
+ end
433
+
434
+ # /items/123
435
+ r.is do
436
+ end
437
+ end
438
+
439
+ This works well for many cases, but there are also cases where you really want to
440
+ treat it as one route with an optional segment. One simple way to do that is to
441
+ use a parameter instead of an optional segment (e.g. +/items/123?opt=456+).
442
+
443
+ r.is "items", :id do |item_id|
444
+ optional_data = r['opt']
445
+ end
446
+
447
+ However, if you really do want to use a optional segment, there are a couple different
448
+ ways to use matchers to do so. One is using an array matcher where the last element
449
+ is true:
450
+
451
+ r.is "items", :id, [:opt_data, true] do |item_id, optional_data|
452
+ end
453
+
454
+ Note that this technically yields only one argument instead of two arguments if the
455
+ optional segment isn't provided.
456
+
457
+ An alternative way to implement this is via a regexp:
458
+
459
+ r.is "items", /(\d+)(?:\/(\d+))?/ do |item_id, optional_data|
460
+ end
461
+
418
462
  == Status codes
419
463
 
420
464
  When it comes time to finalize a response,
@@ -0,0 +1,14 @@
1
+ = New Features
2
+
3
+ * An error_mail plugin has been added for reporting exceptions raised
4
+ via email. This is similar to the existing error_email plugin, but
5
+ uses the mail library instead of net/smtp directly. If you are
6
+ already using the mail library and the error_email plugin in your
7
+ application, it's recommended to switch to the error_mail plugin.
8
+ Example:
9
+
10
+ plugin :error_mail, :to=>'to@example.com', :from=>'from@example.com'
11
+ plugin :error_handler do |e|
12
+ error_mail(e)
13
+ 'Internal Server Error'
14
+ end
@@ -14,6 +14,11 @@ class Roda
14
14
  # 'Internal Server Error'
15
15
  # end
16
16
  #
17
+ # It is similar to the error_mail plugin, except that it uses net/smtp
18
+ # directly instead of using the mail library. If you are not already using the
19
+ # mail library in your application, it makes sense to use error_email
20
+ # instead of error_mail.
21
+ #
17
22
  # Options:
18
23
  #
19
24
  # :from :: The From address to use in the email (required)
@@ -34,7 +39,7 @@ class Roda
34
39
  module ErrorEmail
35
40
  OPTS = {}.freeze
36
41
  DEFAULTS = {
37
- :headers=>{},
42
+ :headers=>OPTS,
38
43
  :host=>'localhost',
39
44
  # :nocov:
40
45
  :emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
@@ -0,0 +1,133 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'mail'
4
+
5
+ class Roda
6
+ module RodaPlugins
7
+ # The error_mail plugin adds an +error_mail+ instance method that
8
+ # send an email related to the exception. This is most useful if you are
9
+ # also using the error_handler plugin:
10
+ #
11
+ # plugin :error_mail, :to=>'to@example.com', :from=>'from@example.com'
12
+ # plugin :error_handler do |e|
13
+ # error_mail(e)
14
+ # 'Internal Server Error'
15
+ # end
16
+ #
17
+ # It is similar to the error_email plugin, except that it uses the mail
18
+ # library instead of net/smtp directly. If you are already using the
19
+ # mail library in your application, it makes sense to use error_mail
20
+ # instead of error_email.
21
+ #
22
+ # Options:
23
+ #
24
+ # :from :: The From address to use in the email (required)
25
+ # :headers :: A hash of additional headers to use in the email (default: empty hash)
26
+ # :prefix :: A prefix to use in the email's subject line (default: no prefix)
27
+ # :to :: The To address to use in the email (required)
28
+ #
29
+ # The subject of the error email shows the exception class and message.
30
+ # The body of the error email shows the backtrace of the error and the
31
+ # request environment, as well the request params and session variables (if any).
32
+ # You can also call error_mail with a plain string instead of an exception,
33
+ # in which case the string is used as the subject, and no backtrace is included.
34
+ #
35
+ # Note that emailing on every error as shown above is only appropriate
36
+ # for low traffic web applications. For high traffic web applications,
37
+ # use an error reporting service instead of this plugin.
38
+ module ErrorMail
39
+ OPTS = {}.freeze
40
+
41
+ # Set default opts for plugin. See ErrorEmail module RDoc for options.
42
+ def self.configure(app, opts=OPTS)
43
+ app.opts[:error_mail] = email_opts = (app.opts[:error_mail] || OPTS).merge(opts).freeze
44
+ unless email_opts[:to] && email_opts[:from]
45
+ raise RodaError, "must provide :to and :from options to error_mail plugin"
46
+ end
47
+ end
48
+
49
+ module InstanceMethods
50
+ # Send an email for the given error. +exception+ is usually an exception
51
+ # instance, but it can be a plain string which is used as the subject for
52
+ # the email.
53
+ def error_mail(exception)
54
+ _error_mail(exception).deliver!
55
+ end
56
+
57
+ # The content of the email to send, include the headers and the body.
58
+ # Takes the same argument as #error_mail.
59
+ def error_mail_content(exception)
60
+ _error_mail(exception).to_s
61
+ end
62
+
63
+ private
64
+
65
+ def _error_mail(e)
66
+ email_opts = self.class.opts[:error_mail]
67
+ subject = if e.respond_to?(:message)
68
+ "#{e.class}: #{e.message}"
69
+ else
70
+ e.to_s
71
+ end
72
+ subject = "#{email_opts[:prefix]}#{subject}"
73
+
74
+ format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
75
+
76
+ message = String.new
77
+ message << <<END
78
+ Path: #{request.path}
79
+
80
+ END
81
+ if e.respond_to?(:backtrace)
82
+ message << <<END
83
+ Backtrace:
84
+
85
+ #{e.backtrace.join("\n")}
86
+ END
87
+ end
88
+
89
+ message << <<END
90
+
91
+ ENV:
92
+
93
+ #{format[env]}
94
+ END
95
+
96
+ unless request.params.empty?
97
+ message << <<END
98
+
99
+ Params:
100
+
101
+ #{format[request.params]}
102
+ END
103
+ end
104
+
105
+ if env['rack.session']
106
+ message << <<END
107
+
108
+ Session:
109
+
110
+ #{format[session]}
111
+ END
112
+ end
113
+
114
+
115
+ Mail.new do
116
+ from email_opts[:from]
117
+ to email_opts[:to]
118
+ subject subject
119
+ body message
120
+
121
+ if headers = email_opts[:headers]
122
+ headers.each do |k,v|
123
+ header[k] = v
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ register_plugin(:error_mail, ErrorMail)
132
+ end
133
+ end
@@ -27,6 +27,14 @@ class Roda
27
27
  # end
28
28
  # end
29
29
  # end
30
+ #
31
+ # You can modify the flash for the current request (instead of
32
+ # the next request) by using the +flash.now+ method:
33
+ #
34
+ # r.get do
35
+ # flash.now['a'] = 'b'
36
+ # flash['a'] # = >'b'
37
+ # end
30
38
  module Flash
31
39
  # The internal session key used to store the flash.
32
40
  KEY = :_flash
data/lib/roda/version.rb CHANGED
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 2
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 24
7
+ RodaMinorVersion = 25
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -275,6 +275,39 @@ describe "matchers" do
275
275
  status('/123bard').must_equal 404
276
276
  end
277
277
 
278
+ it "should allow arrays to match optional segments with splats" do
279
+ app do |r|
280
+ r.on :foo, [:bar, true] do |*m|
281
+ m.inspect
282
+ end
283
+ end
284
+
285
+ body('/123').must_equal '["123"]'
286
+ body('/123/456').must_equal '["123", "456"]'
287
+ end
288
+
289
+ it "should allow arrays to match optional segments with separate arguments" do
290
+ app do |r|
291
+ r.on :foo, [:bar, true] do |f, b|
292
+ [f, b].inspect
293
+ end
294
+ end
295
+
296
+ body('/123').must_equal '["123", nil]'
297
+ body('/123/456').must_equal '["123", "456"]'
298
+ end
299
+
300
+ it "should allow regexp to match optional segments with separate arguments" do
301
+ app do |r|
302
+ r.on /(\d+)(?:\/(\d+))?/ do |f, b|
303
+ [f, b].inspect
304
+ end
305
+ end
306
+
307
+ body('/123').must_equal '["123", nil]'
308
+ body('/123/456').must_equal '["123", "456"]'
309
+ end
310
+
278
311
  it "should have array capture match string if match" do
279
312
  app do |r|
280
313
  r.on %w'p q' do |id|
@@ -0,0 +1,93 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ begin
4
+ require 'mail'
5
+ rescue LoadError
6
+ warn "mail not installed, skipping mail plugin test"
7
+ else
8
+ Mail.defaults do
9
+ delivery_method :test
10
+ end
11
+
12
+ describe "error_mail plugin" do
13
+ def app(opts={})
14
+ @emails = emails = [] unless defined?(@emails)
15
+ @app ||= super(:bare) do
16
+ plugin :error_mail, {:to=>'t', :from=>'f'}.merge(opts)
17
+
18
+ route do |r|
19
+ r.get('noerror'){error_mail("Problem"); 'g'}
20
+ raise ArgumentError, 'bad foo' rescue error_mail($!)
21
+ 'e'
22
+ end
23
+ end
24
+ end
25
+
26
+ before do
27
+ Mail::TestMailer.deliveries.clear
28
+ end
29
+
30
+ def email
31
+ Mail::TestMailer.deliveries.last
32
+ end
33
+
34
+ it "adds error_mail method for emailing exceptions" do
35
+ app
36
+ body('rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'}).must_equal 'e'
37
+ email.to.must_equal ['t']
38
+ email.from.must_equal ['f']
39
+ email.header.to_s.must_match(/^Subject: ArgumentError: bad foo/)
40
+ email.body.to_s.must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
41
+ end
42
+
43
+ it "have error_mail method support string arguments" do
44
+ app
45
+ body('/noerror', 'rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'}).must_equal 'g'
46
+ email.to.must_equal ['t']
47
+ email.from.must_equal ['f']
48
+ email.header.to_s.must_match(/^Subject: Problem/)
49
+ email.body.to_s.must_match(/^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
50
+ email.body.to_s.wont_include('Backtrace')
51
+ end
52
+
53
+ it "supports error_mail_content for the content of the email" do
54
+ app.route do |r|
55
+ raise ArgumentError, 'bad foo' rescue error_mail_content($!)
56
+ end
57
+ b = body('rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'})
58
+ b.must_match(/^Subject: ArgumentError: bad foo/)
59
+ b.must_match(/^Backtrace:.+^ENV:.+^"rack\.input" => .+^Params:\s+^"b" => "c"\s+^Session:\s+^"d" => "e"/m)
60
+ end
61
+
62
+ it "adds :prefix option to subject line" do
63
+ app(:prefix=>'TEST ')
64
+ body('rack.input'=>StringIO.new).must_equal 'e'
65
+ email.header.to_s.must_match(/^Subject: TEST ArgumentError/)
66
+ end
67
+
68
+ it "uses :headers option for additional headers" do
69
+ app(:headers=>{'Foo'=>'Bar', 'Baz'=>'Quux'})
70
+ body('rack.input'=>StringIO.new).must_equal 'e'
71
+ email.header.to_s.must_match(/^Foo: Bar/)
72
+ email.header.to_s.must_match(/^Baz: Quux/)
73
+ end
74
+
75
+ it "requires the :to and :from options" do
76
+ proc{app :from=>nil}.must_raise(Roda::RodaError)
77
+ proc{app :to=>nil}.must_raise(Roda::RodaError)
78
+ end
79
+
80
+ it "works correctly in subclasses" do
81
+ @app = Class.new(app)
82
+ @app.route do |r|
83
+ raise ArgumentError rescue error_mail($!)
84
+ 'e'
85
+ end
86
+ body('rack.input'=>StringIO.new).must_equal 'e'
87
+ email.to.must_equal ['t']
88
+ email.from.must_equal ['f']
89
+ email.header.to_s.must_match(/^Subject: ArgumentError: ArgumentError/)
90
+ email.body.to_s.must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+/m)
91
+ end
92
+ end
93
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.24.0
4
+ version: 2.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-03-15 00:00:00.000000000 Z
11
+ date: 2017-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -204,6 +204,7 @@ extra_rdoc_files:
204
204
  - doc/release_notes/2.22.0.txt
205
205
  - doc/release_notes/2.23.0.txt
206
206
  - doc/release_notes/2.24.0.txt
207
+ - doc/release_notes/2.25.0.txt
207
208
  files:
208
209
  - CHANGELOG
209
210
  - MIT-LICENSE
@@ -232,6 +233,7 @@ files:
232
233
  - doc/release_notes/2.22.0.txt
233
234
  - doc/release_notes/2.23.0.txt
234
235
  - doc/release_notes/2.24.0.txt
236
+ - doc/release_notes/2.25.0.txt
235
237
  - doc/release_notes/2.3.0.txt
236
238
  - doc/release_notes/2.4.0.txt
237
239
  - doc/release_notes/2.5.0.txt
@@ -263,6 +265,7 @@ files:
263
265
  - lib/roda/plugins/environments.rb
264
266
  - lib/roda/plugins/error_email.rb
265
267
  - lib/roda/plugins/error_handler.rb
268
+ - lib/roda/plugins/error_mail.rb
266
269
  - lib/roda/plugins/flash.rb
267
270
  - lib/roda/plugins/h.rb
268
271
  - lib/roda/plugins/halt.rb
@@ -353,6 +356,7 @@ files:
353
356
  - spec/plugin/environments_spec.rb
354
357
  - spec/plugin/error_email_spec.rb
355
358
  - spec/plugin/error_handler_spec.rb
359
+ - spec/plugin/error_mail_spec.rb
356
360
  - spec/plugin/flash_spec.rb
357
361
  - spec/plugin/h_spec.rb
358
362
  - spec/plugin/halt_spec.rb
@@ -450,7 +454,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
450
454
  version: '0'
451
455
  requirements: []
452
456
  rubyforge_project:
453
- rubygems_version: 2.6.8
457
+ rubygems_version: 2.6.11
454
458
  signing_key:
455
459
  specification_version: 4
456
460
  summary: Routing tree web toolkit