roda 2.22.0 → 2.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +14 -0
- data/doc/conventions.rdoc +2 -2
- data/doc/release_notes/2.23.0.txt +29 -0
- data/lib/roda.rb +10 -0
- data/lib/roda/plugins/error_email.rb +30 -7
- data/lib/roda/plugins/multi_route.rb +2 -2
- data/lib/roda/plugins/render.rb +13 -6
- data/lib/roda/plugins/static_routing.rb +1 -1
- data/lib/roda/version.rb +1 -1
- data/spec/plugin/error_email_spec.rb +23 -2
- data/spec/plugin/render_spec.rb +70 -6
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c091dcf6b8bb44694c2ea16a6244e3534662e2ed
|
4
|
+
data.tar.gz: cff4ce05dd629994658d4470bf2d3f339b6c11c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4571d9602f0a17a330110bfcb4c319ff1c36476b16f8a488c0c081c5e651ddcbd8fe2df7c6d01f667dcc30445946f91131820e48fc22340c32a3465e21d5b756
|
7
|
+
data.tar.gz: d7d6fc717405752f1b4eabd14fc23587f4cf2d9c25db32ab6f9425f1a67cda19ea0cb84a0c42173f07cdf722b0c8ab0b962d74fb898fc47fd5ae9382406c7995
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
= 2.23.0 (2017-02-24)
|
2
|
+
|
3
|
+
* Add :inherit_cache render plugin option, to create a copy of the cache for subclasses, instead of using an empty cache (jeremyevans)
|
4
|
+
|
5
|
+
* In development mode, default to :explicit_cache=>true, :cache=>true instead of :cache=>false (jeremyevans)
|
6
|
+
|
7
|
+
* Add :explicit_cache render plugin option, to only cache templates if the :cache option is given to render/view (jeremyevans)
|
8
|
+
|
9
|
+
* Add error_email_content method to error_email plugin (jeremyevans)
|
10
|
+
|
11
|
+
* Make error_email method in error_email plugin support non-exception arguments (jeremyevans)
|
12
|
+
|
13
|
+
* Make Roda.freeze in the static_routing plugin return self (jeremyevans)
|
14
|
+
|
1
15
|
= 2.22.0 (2017-01-20)
|
2
16
|
|
3
17
|
* Add support for :verbatim_string_matcher option, for making all string matchers match verbatim (jeremyevans)
|
data/doc/conventions.rdoc
CHANGED
@@ -23,11 +23,11 @@ For a small application, the following directory layout is recommended:
|
|
23
23
|
views/
|
24
24
|
|
25
25
|
+app_name.rb+ should contain the Roda application, and should reflect the name of your application.
|
26
|
-
So if your application is named +FooBar+, you should use +foo_bar.rb+.
|
26
|
+
So, if your application is named +FooBar+, you should use +foo_bar.rb+.
|
27
27
|
|
28
28
|
+views/+ should contain your template files. This assumes you are using the +render+ plugin
|
29
29
|
and server-side rendering. If you are creating a single page application and just serving
|
30
|
-
JSON, then you won't need a +views+ directory. For small applications, all view files should
|
30
|
+
JSON, then you won't need a +views+ directory. For small applications, all view files should be
|
31
31
|
in the +views+ directory.
|
32
32
|
|
33
33
|
+public/+ should contain any static files that should be served directly by the webserver.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* An :explicit_cache option has been added to the render plugin.
|
4
|
+
This is similar to the :cache=>false option, but instead of
|
5
|
+
disabling caching completely, this disables caching by default but
|
6
|
+
allows for explicit caching of templates by providing the :cache
|
7
|
+
option to view/render.
|
8
|
+
|
9
|
+
In development mode, Roda now defaults to :explicit_cache=>true
|
10
|
+
instead of :cache=>false.
|
11
|
+
|
12
|
+
* An :inherit_cache option has been added to the render plugin,
|
13
|
+
making subclasses of that class start with a dup of the template
|
14
|
+
cache, instead of starting with an empty template cache. This can
|
15
|
+
result in less memory used.
|
16
|
+
|
17
|
+
* Roda#error_email in the error_email plugin now accepts non-Exception
|
18
|
+
arguments (such as strings). This can be useful in conditions that
|
19
|
+
are errors you may want to notify about, where an exception hasn't
|
20
|
+
been raised.
|
21
|
+
|
22
|
+
* Roda#error_email_content has been added to the error_email plugin.
|
23
|
+
This can be used to create the email message, which can be delivered
|
24
|
+
via another mechanism, and may make testing easier.
|
25
|
+
|
26
|
+
= Other Improvements
|
27
|
+
|
28
|
+
* Roda.freeze in the static_routing plugin now returns self, fixing
|
29
|
+
code such as Roda.freeze.app.
|
data/lib/roda.rb
CHANGED
@@ -29,6 +29,16 @@ class Roda
|
|
29
29
|
def []=(key, value)
|
30
30
|
@mutex.synchronize{@hash[key] = value}
|
31
31
|
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Create a copy of the cache with a separate mutex.
|
36
|
+
def initialize_copy(other)
|
37
|
+
@mutex = Mutex.new
|
38
|
+
other.instance_variable_get(:@mutex).synchronize do
|
39
|
+
@hash = other.instance_variable_get(:@hash).dup
|
40
|
+
end
|
41
|
+
end
|
32
42
|
end
|
33
43
|
|
34
44
|
# Base class used for Roda requests. The instance methods for this
|
@@ -25,6 +25,8 @@ class Roda
|
|
25
25
|
# The subject of the error email shows the exception class and message.
|
26
26
|
# The body of the error email shows the backtrace of the error and the
|
27
27
|
# request environment, as well the request params and session variables (if any).
|
28
|
+
# You can also call error_email with a plain string instead of an exception,
|
29
|
+
# in which case the string is used as the subject, and no backtrace is included.
|
28
30
|
#
|
29
31
|
# Note that emailing on every error as shown above is only appropriate
|
30
32
|
# for low traffic web applications. For high traffic web applications,
|
@@ -38,7 +40,12 @@ class Roda
|
|
38
40
|
:emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
|
39
41
|
# :nocov:
|
40
42
|
:default_headers=>lambda do |h, e|
|
41
|
-
|
43
|
+
subject = if e.respond_to?(:message)
|
44
|
+
"#{e.class}: #{e.message}"
|
45
|
+
else
|
46
|
+
e.to_s
|
47
|
+
end
|
48
|
+
{'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{subject}"}
|
42
49
|
end,
|
43
50
|
:body=>lambda do |s, e|
|
44
51
|
format = lambda{|h| h.map{|k, v| "#{k.inspect} => #{v.inspect}"}.sort.join("\n")}
|
@@ -47,14 +54,21 @@ class Roda
|
|
47
54
|
message << <<END
|
48
55
|
Path: #{s.request.path}
|
49
56
|
|
57
|
+
END
|
58
|
+
if e.respond_to?(:backtrace)
|
59
|
+
message << <<END
|
50
60
|
Backtrace:
|
51
61
|
|
52
62
|
#{e.backtrace.join("\n")}
|
63
|
+
END
|
64
|
+
end
|
53
65
|
|
66
|
+
message << <<END
|
54
67
|
ENV:
|
55
68
|
|
56
69
|
#{format[s.env]}
|
57
70
|
END
|
71
|
+
|
58
72
|
unless s.request.params.empty?
|
59
73
|
message << <<END
|
60
74
|
|
@@ -91,15 +105,24 @@ END
|
|
91
105
|
end
|
92
106
|
|
93
107
|
module InstanceMethods
|
94
|
-
# Send an email for the given error.
|
95
|
-
|
108
|
+
# Send an email for the given error. +exception+ is usually an exception
|
109
|
+
# instance, but it can be a plain string which is used as the subject for
|
110
|
+
# the email.
|
111
|
+
def error_email(exception)
|
96
112
|
email_opts = self.class.opts[:error_email].dup
|
97
|
-
|
113
|
+
email_opts[:message] = error_email_content(exception)
|
114
|
+
email_opts[:emailer].call(email_opts)
|
115
|
+
end
|
116
|
+
|
117
|
+
# The content of the email to send, include the headers and the body.
|
118
|
+
# Takes the same argument as #error_email.
|
119
|
+
def error_email_content(exception)
|
120
|
+
email_opts = self.class.opts[:error_email]
|
121
|
+
headers = email_opts[:default_headers].call(email_opts, exception)
|
98
122
|
headers = Hash[headers].merge!(email_opts[:headers])
|
99
123
|
headers = headers.map{|k,v| "#{k}: #{v.gsub(/\r?\n/m, "\r\n ")}"}.sort.join("\r\n")
|
100
|
-
body = email_opts[:body].call(self,
|
101
|
-
|
102
|
-
email_opts[:emailer].call(email_opts)
|
124
|
+
body = email_opts[:body].call(self, exception)
|
125
|
+
"#{headers}\r\n\r\n#{body}"
|
103
126
|
end
|
104
127
|
end
|
105
128
|
end
|
@@ -9,8 +9,8 @@ class Roda
|
|
9
9
|
# and if the named route does handle the request, the response returned by
|
10
10
|
# the named route will be returned.
|
11
11
|
#
|
12
|
-
# In addition, this
|
13
|
-
#
|
12
|
+
# In addition, this plugin adds the +r.multi_route+ method, which will check
|
13
|
+
# if the first segment in the path matches a named route, and dispatch
|
14
14
|
# to that named route.
|
15
15
|
#
|
16
16
|
# Example:
|
data/lib/roda/plugins/render.rb
CHANGED
@@ -48,9 +48,7 @@ class Roda
|
|
48
48
|
#
|
49
49
|
# :allowed_paths :: Set the template paths to allow if +:check_paths+ is true.
|
50
50
|
# Defaults to the +:views+ directory.
|
51
|
-
# :cache :: nil/false to
|
52
|
-
# to true unless RACK_ENV is development to automatically use the
|
53
|
-
# default template cache.
|
51
|
+
# :cache :: nil/false to disallow template caching completely.
|
54
52
|
# :cache_class :: A class to use as the template cache instead of the default.
|
55
53
|
# :check_paths :: Check template paths start with one of the entries in +:allowed_paths+,
|
56
54
|
# and raise a RodaError if the path doesn't.
|
@@ -64,6 +62,11 @@ class Roda
|
|
64
62
|
# :escaper :: Object used for escaping output of <tt><%= %></tt>, when :escape=>true is used,
|
65
63
|
# overriding the default. If given, object should respond to +escape_xml+ with
|
66
64
|
# a single argument and return an output string.
|
65
|
+
# :explicit_cache :: Only use the template cache if the :cache option is provided when rendering
|
66
|
+
# (useful for development). Defaults to true if RACK_ENV is development, allowing explicit
|
67
|
+
# caching of specific templates, but not caching by default.
|
68
|
+
# :inherit_cache :: Whether to create a dup of the cache in subclasses. The default is false, which
|
69
|
+
# starts subclasses with an empty cache.
|
67
70
|
# :layout :: The base name of the layout file, defaults to 'layout'. This can be provided as a hash
|
68
71
|
# with the :template or :inline options.
|
69
72
|
# :layout_opts :: The options to use when rendering the layout, if different
|
@@ -165,7 +168,7 @@ class Roda
|
|
165
168
|
opts[:allowed_paths] ||= [opts[:views]].freeze
|
166
169
|
opts[:allowed_paths] = opts[:allowed_paths].map{|f| ::File.expand_path(f)}.uniq.freeze
|
167
170
|
|
168
|
-
if opts.fetch(:cache,
|
171
|
+
if opts.fetch(:cache, true)
|
169
172
|
if cache_class = opts[:cache_class]
|
170
173
|
opts[:cache] = cache_class.new
|
171
174
|
else
|
@@ -173,6 +176,8 @@ class Roda
|
|
173
176
|
end
|
174
177
|
end
|
175
178
|
|
179
|
+
opts[:explicit_cache] = ENV['RACK_ENV'] == 'development' unless opts.has_key?(:explicit_cache)
|
180
|
+
|
176
181
|
opts[:layout_opts] = (opts[:layout_opts] || {}).dup
|
177
182
|
opts[:layout_opts][:_is_layout] = true
|
178
183
|
|
@@ -231,7 +236,9 @@ class Roda
|
|
231
236
|
opts = subclass.opts[:render] = subclass.opts[:render].dup
|
232
237
|
|
233
238
|
if opts[:cache]
|
234
|
-
|
239
|
+
opts[:cache] = if opts[:inherit_cache]
|
240
|
+
opts[:cache] = opts[:cache].dup
|
241
|
+
elsif cache_class = opts[:cache_class]
|
235
242
|
opts[:cache] = cache_class.new
|
236
243
|
else
|
237
244
|
opts[:cache] = thread_safe_cache
|
@@ -285,7 +292,7 @@ class Roda
|
|
285
292
|
# If caching templates, attempt to retrieve the template from the cache. Otherwise, just yield
|
286
293
|
# to get the template.
|
287
294
|
def cached_template(opts, &block)
|
288
|
-
if (cache = render_opts[:cache]) && (key = opts[:cache_key])
|
295
|
+
if (!render_opts[:explicit_cache] || opts[:cache]) && (cache = render_opts[:cache]) && (key = opts[:cache_key])
|
289
296
|
unless template = cache[key]
|
290
297
|
template = cache[key] = yield
|
291
298
|
end
|
@@ -56,9 +56,9 @@ class Roda
|
|
56
56
|
module ClassMethods
|
57
57
|
# Freeze the static route metadata when freezing the app.
|
58
58
|
def freeze
|
59
|
-
super
|
60
59
|
opts[:static_routes].freeze
|
61
60
|
opts[:static_routes].each_value(&:freeze)
|
61
|
+
super
|
62
62
|
end
|
63
63
|
|
64
64
|
# Duplicate static route metadata in subclass.
|
data/lib/roda/version.rb
CHANGED
@@ -7,7 +7,8 @@ describe "error_email plugin" do
|
|
7
7
|
plugin :error_email, {:to=>'t', :from=>'f', :emailer=>lambda{|h| emails << h}}.merge(opts)
|
8
8
|
|
9
9
|
route do |r|
|
10
|
-
|
10
|
+
r.get('noerror'){error_email("Problem"); 'g'}
|
11
|
+
raise ArgumentError, 'bad foo' rescue error_email($!)
|
11
12
|
'e'
|
12
13
|
end
|
13
14
|
end
|
@@ -23,10 +24,30 @@ describe "error_email plugin" do
|
|
23
24
|
email[:to].must_equal 't'
|
24
25
|
email[:from].must_equal 'f'
|
25
26
|
email[:host].must_equal 'localhost'
|
26
|
-
email[:message].must_match(/^Subject: ArgumentError/)
|
27
|
+
email[:message].must_match(/^Subject: ArgumentError: bad foo/)
|
27
28
|
email[:message].must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
|
28
29
|
end
|
29
30
|
|
31
|
+
it "have error_email method support string arguments" do
|
32
|
+
app
|
33
|
+
body('/noerror', 'rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'}).must_equal 'g'
|
34
|
+
email[:to].must_equal 't'
|
35
|
+
email[:from].must_equal 'f'
|
36
|
+
email[:host].must_equal 'localhost'
|
37
|
+
email[:message].must_match(/^Subject: Problem/)
|
38
|
+
email[:message].must_match(/^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
|
39
|
+
email[:message].wont_include('Backtrace')
|
40
|
+
end
|
41
|
+
|
42
|
+
it "supports error_email_content for the content of the email" do
|
43
|
+
app.route do |r|
|
44
|
+
raise ArgumentError, 'bad foo' rescue error_email_content($!)
|
45
|
+
end
|
46
|
+
b = body('rack.input'=>StringIO.new, 'QUERY_STRING'=>'b=c', 'rack.session'=>{'d'=>'e'})
|
47
|
+
b.must_match(/^Subject: ArgumentError: bad foo/)
|
48
|
+
b.must_match(/^Backtrace:$.+^ENV:$.+^"rack\.input" => .+^Params:$\s+^"b" => "c"$\s+^Session:$\s+^"d" => "e"$/m)
|
49
|
+
end
|
50
|
+
|
30
51
|
it "uses :host option" do
|
31
52
|
app(:host=>'foo.bar.com')
|
32
53
|
body('rack.input'=>StringIO.new).must_equal 'e'
|
data/spec/plugin/render_spec.rb
CHANGED
@@ -353,13 +353,13 @@ describe "render plugin" do
|
|
353
353
|
body('/c').must_equal "3"
|
354
354
|
end
|
355
355
|
|
356
|
-
it "Default to :
|
356
|
+
it "Default to :explicit_cache=>true in development mode" do
|
357
357
|
with_rack_env('development') do
|
358
358
|
app(:render){}
|
359
359
|
end
|
360
|
-
app.render_opts[:
|
360
|
+
app.render_opts[:explicit_cache].must_equal true
|
361
361
|
app(:render){}
|
362
|
-
app.render_opts[:
|
362
|
+
app.render_opts[:explicit_cache].must_equal false
|
363
363
|
end
|
364
364
|
|
365
365
|
it "Support :cache=>false option to disable template caching" do
|
@@ -379,6 +379,57 @@ describe "render plugin" do
|
|
379
379
|
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].wont_equal nil
|
380
380
|
end
|
381
381
|
|
382
|
+
it "Support :cache=>false option to disable template caching even when :cache_key is given" do
|
383
|
+
app(:bare) do
|
384
|
+
plugin :render, :views=>"./spec/views"
|
385
|
+
|
386
|
+
route do |r|
|
387
|
+
@a = 'a'
|
388
|
+
r.is('a'){render('iv', :cache=>false, :cache_key=>:foo)}
|
389
|
+
render('iv', :cache_key=>:foo)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
body('/a').strip.must_equal "a"
|
394
|
+
app.render_opts[:cache][:foo].must_be_nil
|
395
|
+
body('/b').strip.must_equal "a"
|
396
|
+
app.render_opts[:cache][:foo].wont_equal nil
|
397
|
+
end
|
398
|
+
|
399
|
+
it "Support :explicit_cache option to disable caching by default, but still allow caching on a per-call basis" do
|
400
|
+
app(:bare) do
|
401
|
+
plugin :render, :views=>"./spec/views", :explicit_cache=>true
|
402
|
+
|
403
|
+
route do |r|
|
404
|
+
@a = 'a'
|
405
|
+
r.is('a'){render('iv')}
|
406
|
+
render('iv', :cache=>true)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
body('/a').strip.must_equal "a"
|
411
|
+
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].must_be_nil
|
412
|
+
body('/b').strip.must_equal "a"
|
413
|
+
app.render_opts[:cache][File.expand_path('spec/views/iv.erb')].wont_equal nil
|
414
|
+
end
|
415
|
+
|
416
|
+
it "Support :explicit_cache plugin option with :cache_key render option" do
|
417
|
+
app(:bare) do
|
418
|
+
plugin :render, :views=>"./spec/views", :explicit_cache=>true
|
419
|
+
|
420
|
+
route do |r|
|
421
|
+
@a = 'a'
|
422
|
+
r.is('a'){render('iv', :cache_key=>:foo)}
|
423
|
+
render('iv', :cache=>true, :cache_key=>:foo)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
|
427
|
+
body('/a').strip.must_equal "a"
|
428
|
+
app.render_opts[:cache][:foo].must_be_nil
|
429
|
+
body('/b').strip.must_equal "a"
|
430
|
+
app.render_opts[:cache][:foo].wont_equal nil
|
431
|
+
end
|
432
|
+
|
382
433
|
it "Support :cache=>true option to enable template caching when :template_block is used" do
|
383
434
|
c = Class.new do
|
384
435
|
def initialize(path, *, &block)
|
@@ -437,23 +488,36 @@ describe "render plugin" do
|
|
437
488
|
body.must_equal "1-2"
|
438
489
|
end
|
439
490
|
|
440
|
-
it "render_opts
|
491
|
+
it "should dup render_opts when subclasses, including an empty cache" do
|
441
492
|
c = Class.new(Roda)
|
442
493
|
c.plugin :render
|
494
|
+
c.render_opts[:cache][:foo] = 1
|
495
|
+
sc = Class.new(c)
|
496
|
+
|
497
|
+
c.render_opts.wont_be_same_as(sc.render_opts)
|
498
|
+
c.render_opts[:cache].wont_be_same_as(sc.render_opts[:cache])
|
499
|
+
sc.render_opts[:cache][:foo].must_be_nil
|
500
|
+
end
|
501
|
+
|
502
|
+
it "should use same render_opts as superclass when inheriting if :inherit_cache option is used" do
|
503
|
+
c = Class.new(Roda)
|
504
|
+
c.plugin :render, :inherit_cache=>true
|
505
|
+
c.render_opts[:cache][:foo] = 1
|
443
506
|
sc = Class.new(c)
|
444
507
|
|
445
508
|
c.render_opts.wont_be_same_as(sc.render_opts)
|
446
509
|
c.render_opts[:cache].wont_be_same_as(sc.render_opts[:cache])
|
510
|
+
sc.render_opts[:cache][:foo].must_equal 1
|
447
511
|
end
|
448
512
|
|
449
|
-
it "render plugin call should not override options" do
|
513
|
+
it "render plugin call should not override existing options" do
|
450
514
|
c = Class.new(Roda)
|
451
515
|
c.plugin :render, :layout=>:foo
|
452
516
|
c.plugin :render
|
453
517
|
c.render_opts[:layout].must_equal :foo
|
454
518
|
end
|
455
519
|
|
456
|
-
it "
|
520
|
+
it "should not use cache in subclass if caching disabled in superclass" do
|
457
521
|
app(:bare) do
|
458
522
|
plugin :render, :views=>"./spec/views", :cache=>false
|
459
523
|
|
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.
|
4
|
+
version: 2.23.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-
|
11
|
+
date: 2017-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -202,6 +202,7 @@ extra_rdoc_files:
|
|
202
202
|
- doc/release_notes/2.20.0.txt
|
203
203
|
- doc/release_notes/2.21.0.txt
|
204
204
|
- doc/release_notes/2.22.0.txt
|
205
|
+
- doc/release_notes/2.23.0.txt
|
205
206
|
files:
|
206
207
|
- CHANGELOG
|
207
208
|
- MIT-LICENSE
|
@@ -228,6 +229,7 @@ files:
|
|
228
229
|
- doc/release_notes/2.20.0.txt
|
229
230
|
- doc/release_notes/2.21.0.txt
|
230
231
|
- doc/release_notes/2.22.0.txt
|
232
|
+
- doc/release_notes/2.23.0.txt
|
231
233
|
- doc/release_notes/2.3.0.txt
|
232
234
|
- doc/release_notes/2.4.0.txt
|
233
235
|
- doc/release_notes/2.5.0.txt
|