roda 2.22.0 → 2.23.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 +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
|