trenni 3.8.0 → 3.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/bake/trenni/entities.rb +57 -0
  3. data/bake/trenni/parsers.rb +66 -0
  4. data/ext/tmp/x86_64-linux/lib/trenni/trenni.so +0 -0
  5. data/ext/tmp/x86_64-linux/trenni/2.4.0/Makefile +263 -0
  6. data/ext/tmp/x86_64-linux/trenni/2.4.0/escape.o +0 -0
  7. data/ext/tmp/x86_64-linux/trenni/2.4.0/markup.o +0 -0
  8. data/ext/tmp/x86_64-linux/trenni/2.4.0/mkmf.log +108 -0
  9. data/ext/tmp/x86_64-linux/trenni/2.4.0/tag.o +0 -0
  10. data/ext/tmp/x86_64-linux/trenni/2.4.0/template.o +0 -0
  11. data/ext/tmp/x86_64-linux/trenni/2.4.0/trenni.o +0 -0
  12. data/ext/tmp/x86_64-linux/trenni/2.4.0/trenni.so +0 -0
  13. data/ext/tmp/x86_64-linux/trenni/2.7.0/Makefile +266 -0
  14. data/ext/tmp/x86_64-linux/trenni/2.7.0/escape.o +0 -0
  15. data/ext/tmp/x86_64-linux/trenni/2.7.0/markup.o +0 -0
  16. data/ext/tmp/x86_64-linux/trenni/2.7.0/mkmf.log +113 -0
  17. data/ext/tmp/x86_64-linux/trenni/2.7.0/query.o +0 -0
  18. data/ext/tmp/x86_64-linux/trenni/2.7.0/tag.o +0 -0
  19. data/ext/tmp/x86_64-linux/trenni/2.7.0/template.o +0 -0
  20. data/ext/tmp/x86_64-linux/trenni/2.7.0/trenni.o +0 -0
  21. data/ext/tmp/x86_64-linux/trenni/2.7.0/trenni.so +0 -0
  22. data/ext/tmp/x86_64-linux/trenni/2.7.1/Makefile +266 -0
  23. data/ext/tmp/x86_64-linux/trenni/2.7.1/escape.o +0 -0
  24. data/ext/tmp/x86_64-linux/trenni/2.7.1/markup.o +0 -0
  25. data/ext/tmp/x86_64-linux/trenni/2.7.1/mkmf.log +113 -0
  26. data/ext/tmp/x86_64-linux/trenni/2.7.1/query.o +0 -0
  27. data/ext/tmp/x86_64-linux/trenni/2.7.1/tag.o +0 -0
  28. data/ext/tmp/x86_64-linux/trenni/2.7.1/template.o +0 -0
  29. data/ext/tmp/x86_64-linux/trenni/2.7.1/trenni.o +0 -0
  30. data/ext/tmp/x86_64-linux/trenni/2.7.1/trenni.so +0 -0
  31. data/ext/trenni/markup.c +85 -85
  32. data/ext/trenni/markup.rl +11 -11
  33. data/ext/trenni/query.c +619 -0
  34. data/ext/trenni/query.h +6 -0
  35. data/ext/trenni/query.rl +82 -0
  36. data/ext/trenni/tag.c +8 -6
  37. data/ext/trenni/template.c +57 -57
  38. data/ext/trenni/template.rl +4 -4
  39. data/ext/trenni/trenni.c +9 -1
  40. data/ext/trenni/trenni.h +8 -3
  41. data/lib/trenni.rb +2 -0
  42. data/lib/trenni/builder.rb +79 -15
  43. data/lib/trenni/entities.rb +2082 -2084
  44. data/lib/trenni/entities.trenni +1 -3
  45. data/lib/trenni/{parse_error.rb → error.rb} +4 -1
  46. data/lib/trenni/fallback/markup.rb +1622 -1576
  47. data/lib/trenni/fallback/markup.rl +2 -2
  48. data/lib/trenni/fallback/markup.rl.dot +278 -0
  49. data/lib/trenni/fallback/markup.rl.pdf +0 -0
  50. data/lib/trenni/fallback/query.rb +565 -0
  51. data/lib/trenni/fallback/query.rl +105 -0
  52. data/lib/trenni/fallback/query.rl.dot +54 -0
  53. data/lib/trenni/fallback/query.rl.pdf +0 -0
  54. data/lib/trenni/fallback/template.rb +756 -748
  55. data/lib/trenni/fallback/template.rl +1 -1
  56. data/lib/trenni/fallback/template.rl.dot +270 -0
  57. data/lib/trenni/fallback/template.rl.pdf +0 -0
  58. data/lib/trenni/native.rb +1 -1
  59. data/lib/trenni/parsers.rb +1 -0
  60. data/lib/trenni/query.rb +94 -0
  61. data/lib/trenni/reference.rb +125 -0
  62. data/lib/trenni/strings.rb +15 -4
  63. data/lib/trenni/template.rb +23 -22
  64. data/lib/trenni/trenni.so +0 -0
  65. data/lib/trenni/uri.rb +1 -0
  66. data/lib/trenni/version.rb +1 -1
  67. data/parsers/trenni/query.rl +23 -0
  68. data/spec/trenni/builder_spec.rb +103 -60
  69. data/spec/trenni/markup_performance_spec.rb +14 -2
  70. data/spec/trenni/parsers_performance_spec.rb +31 -0
  71. data/spec/trenni/query_spec.rb +51 -0
  72. data/spec/trenni/reference_spec.rb +87 -0
  73. data/spec/trenni/template_spec/builder.trenni +2 -2
  74. metadata +91 -59
  75. data/.gitignore +0 -19
  76. data/.rspec +0 -5
  77. data/.simplecov +0 -9
  78. data/.travis.yml +0 -23
  79. data/Gemfile +0 -21
  80. data/README.md +0 -312
  81. data/Rakefile +0 -21
  82. data/benchmark/call_vs_yield.rb +0 -52
  83. data/benchmark/interpolation_vs_concat.rb +0 -30
  84. data/benchmark/io_vs_string.rb +0 -91
  85. data/entities.json +0 -2233
  86. data/tasks/entities.rake +0 -34
  87. data/tasks/parsers.rake +0 -44
  88. data/trenni.gemspec +0 -36
@@ -30,7 +30,11 @@ module Trenni
30
30
  end
31
31
 
32
32
  def self.to_quoted_string(string)
33
- '"' + string.gsub('"', '\\"').gsub(/\r/, "\\r").gsub(/\n/, "\\n") + '"'
33
+ string = string.gsub('"', '\\"')
34
+ string.gsub!(/\r/, "\\r")
35
+ string.gsub!(/\n/, "\\n")
36
+
37
+ return "\"#{string}\""
34
38
  end
35
39
 
36
40
  # `value` must already be escaped.
@@ -43,11 +47,18 @@ module Trenni
43
47
  end
44
48
 
45
49
  def self.to_title(string)
46
- string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}.strip
50
+ string = string.gsub(/(^|[ \-_])(.)/){" " + $2.upcase}
51
+ string.strip!
52
+
53
+ return string
47
54
  end
48
-
55
+
49
56
  def self.to_snake(string)
50
- string.gsub("::", "").gsub(/([A-Z]+)/){"_" + $1.downcase}.sub(/^_+/, "")
57
+ string = string.gsub("::", "")
58
+ string.gsub!(/([A-Z]+)/){"_" + $1.downcase}
59
+ string.sub!(/^_+/, "")
60
+
61
+ return string
51
62
  end
52
63
  end
53
64
  end
@@ -29,36 +29,27 @@ module Trenni
29
29
  BINDING = binding
30
30
 
31
31
  class Builder
32
- def >> block
33
- if block
34
- Template.buffer(block.binding) << self
35
- return nil
36
- else
37
- return self
38
- end
39
- end
40
-
41
- def capture(*args, &block)
42
- self.append Template.capture(*args, &block)
32
+ def capture(*arguments, &block)
33
+ Template.capture(*arguments, output: self, &block)
43
34
  end
44
35
  end
45
36
 
46
37
  class Template
47
38
  # Returns the output produced by calling the given block.
48
- def self.capture(*args, &block)
39
+ def self.capture(*arguments, output: nil, &block)
49
40
  scope = block.binding
50
- output_buffer = scope.local_variable_get(OUT)
41
+ previous_output = scope.local_variable_get(OUT)
51
42
 
52
- capture_buffer = String.new.force_encoding(output_buffer.encoding)
53
- scope.local_variable_set(OUT, capture_buffer)
43
+ output ||= previous_output.class.new(encoding: previous_output.encoding)
44
+ scope.local_variable_set(OUT, output)
54
45
 
55
46
  begin
56
- block.call(*args)
47
+ block.call(*arguments)
57
48
  ensure
58
- scope.local_variable_set(OUT, output_buffer)
49
+ scope.local_variable_set(OUT, previous_output)
59
50
  end
60
51
 
61
- return capture_buffer
52
+ return output
62
53
  end
63
54
 
64
55
  # Returns the buffer used for capturing output.
@@ -125,7 +116,7 @@ module Trenni
125
116
  end
126
117
 
127
118
  def to_proc(scope = @binding.dup)
128
- @compiled_proc ||= eval("\# frozen_string_literals: true\nproc{|#{OUT}|;#{code}}", scope, @buffer.path, 0).freeze
119
+ @compiled_proc ||= eval("\# frozen_string_literal: true\nproc{|#{OUT}|;#{code}}", scope, @buffer.path, 0).freeze
129
120
  end
130
121
 
131
122
  protected
@@ -154,9 +145,19 @@ module Trenni
154
145
  class MarkupTemplate < Template
155
146
  class Assembler < Template::Assembler
156
147
  # Output a string interpolation.
157
- def expression(text)
158
- @code << "Trenni::Markup.append(#{OUT},(#{text}));"
148
+ def expression(code)
149
+ @code << "#{OUT}<<(#{code});"
159
150
  end
151
+
152
+ # Output raw text to the template.
153
+ def text(text)
154
+ text = text.gsub("'", "\\\\'")
155
+ @code << "#{OUT}.raw('#{text}');"
156
+ end
157
+ end
158
+
159
+ def to_string(scope = Object.new, output = nil)
160
+ super.output
160
161
  end
161
162
 
162
163
  protected
@@ -168,7 +169,7 @@ module Trenni
168
169
 
169
170
  # The output of the markup template is encoded markup (e.g. with entities, tags, etc).
170
171
  def output_buffer
171
- MarkupString.new.force_encoding(code.encoding)
172
+ Builder.new(encoding: code.encoding)
172
173
  end
173
174
  end
174
175
  end
Binary file
@@ -21,6 +21,7 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Trenni
24
+ # This class is superceeded by `Trenni::Reference`.
24
25
  class URI
25
26
  def initialize(path, query_string, fragment, parameters)
26
27
  @path = path
@@ -21,5 +21,5 @@
21
21
  # THE SOFTWARE.
22
22
 
23
23
  module Trenni
24
- VERSION = "3.8.0"
24
+ VERSION = "3.13.0"
25
25
  end
@@ -0,0 +1,23 @@
1
+ %%{
2
+ machine query;
3
+
4
+ # An application/x-www-form-urlencoded parser based on the definition by WhatWG.
5
+ # Based on https://url.spec.whatwg.org/#application/x-www-form-urlencoded
6
+ pchar = any - [&=\[\]%+];
7
+ echar = pchar | ('+' | '%' xdigit xdigit) >encoded;
8
+
9
+ integer = ([0-9]+) >integer_begin %integer_end;
10
+ string = (echar+ - integer) >string_begin %string_end;
11
+
12
+ value = (echar*) >value_begin %value_end;
13
+
14
+ index = string (
15
+ '[' (integer | string) ']'
16
+ )* ('[]' %append)?;
17
+
18
+ pair = (
19
+ index ('=' value)?
20
+ ) %pair;
21
+
22
+ main := ((pair '&')* pair)?;
23
+ }%%
@@ -21,22 +21,34 @@
21
21
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
22
  # THE SOFTWARE.
23
23
 
24
- require 'trenni'
24
+ require 'trenni/builder'
25
25
 
26
- module Trenni::BuilderSpec
27
- describe 'Trenni::Builder#tag' do
28
- subject {Trenni::Builder.new}
29
-
30
- it "should format nested attributes" do
31
- subject.tag('div', data: {id: 10})
32
-
33
- expect(subject.output).to be == '<div data-id="10"/>'
26
+ RSpec.describe Trenni::Builder do
27
+ it "should produce valid html" do
28
+ subject.doctype
29
+ subject.tag('html') do
30
+ subject.tag('head') do
31
+ subject.inline('title') do
32
+ subject.text('Hello World')
33
+ end
34
+ end
35
+ subject.tag('body') do
36
+ end
34
37
  end
38
+
39
+ expect(subject.output).to be == <<~HTML.chomp
40
+ <!DOCTYPE html>
41
+ <html>
42
+ <head>
43
+ <title>Hello World</title>
44
+ </head>
45
+ <body>
46
+ </body>
47
+ </html>
48
+ HTML
35
49
  end
36
50
 
37
- describe 'Trenni::Builder#fragment' do
38
- let(:builder) {Trenni::Builder.new}
39
-
51
+ describe '.fragment' do
40
52
  it "should use an existing builder" do
41
53
  result = Trenni::Builder.fragment do |builder|
42
54
  end
@@ -47,49 +59,59 @@ module Trenni::BuilderSpec
47
59
  it "should use an existing builder" do
48
60
  expect(Trenni::Builder).to receive(:new).and_call_original
49
61
 
50
- result = Trenni::Builder.fragment(builder) do |builder|
62
+ result = Trenni::Builder.fragment(subject) do |builder|
51
63
  end
52
64
 
53
- expect(result).to_not be_nil
65
+ expect(result).to be_nil
54
66
  end
55
67
  end
56
68
 
57
- describe Trenni::Builder do
58
- it 'should be able to append nil' do
59
- expect{subject.append(nil)}.to_not raise_error
60
- end
61
-
62
- it 'should append existing markup' do
63
- subject.tag("outer") do
64
- subject.append("<inner>\n\t<nested/>\n</inner>")
65
- end
66
-
67
- expect(subject.output).to be == "<outer>\n\t<inner>\n\t\t<nested/>\n\t</inner>\n</outer>"
68
- end
69
-
70
- it "should produce valid html" do
71
- subject.doctype
72
- subject.tag('html') do
73
- subject.tag('head') do
74
- subject.inline('title') do
75
- subject.text('Hello World')
76
- end
77
- end
78
- subject.tag('body') do
79
- end
80
- end
69
+ describe '#tag' do
70
+ it "should format nested attributes" do
71
+ subject.tag('div', data: {id: 10})
81
72
 
82
- expect(subject.output).to be == "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Hello World</title>\n\t</head>\n\t<body>\n\t</body>\n</html>"
73
+ expect(subject.output).to be == '<div data-id="10"/>'
83
74
  end
84
75
 
85
76
  it "should indent self-closing tag correctly" do
86
77
  builder = Trenni::Builder.new
87
78
 
88
- builder.tag('foo') { builder.tag('bar') }
79
+ builder.tag('foo') {builder.tag('bar')}
89
80
 
90
- expect(builder.output).to be == "<foo>\n\t<bar/>\n</foo>"
81
+ expect(builder.output).to be == <<~HTML.chomp
82
+ <foo>
83
+ <bar/>
84
+ </foo>
85
+ HTML
86
+ end
87
+
88
+ it "should support compact attributes" do
89
+ subject.tag :option, :required => true
90
+ expect(subject.output).to be == %Q{<option required/>}
91
91
  end
92
92
 
93
+ it "should output without changing escaped characters" do
94
+ subject.tag "section", :'data-text' => 'foo\nbar'
95
+ expect(subject.output).to be == '<section data-text="foo\nbar"/>'
96
+ end
97
+
98
+ it "should order array attributes as specified" do
99
+ subject.tag :t, [[:a, 10], [:b, 20]]
100
+ expect(subject.output).to be == %Q{<t a="10" b="20"/>}
101
+ end
102
+
103
+ it "should order hash attributes as specified" do
104
+ subject.tag :t, :b => 20, :a => 10
105
+ expect(subject.output).to be == %Q{<t b="20" a="10"/>}
106
+ end
107
+
108
+ it "shouldn't output attributes with nil value" do
109
+ subject.tag :t, [[:a, 10], [:b, nil]]
110
+ expect(subject.output).to be == %Q{<t a="10"/>}
111
+ end
112
+ end
113
+
114
+ describe '#inline' do
93
115
  it "should produce inline html" do
94
116
  subject.inline("div") do
95
117
  subject.tag("strong") do
@@ -102,6 +124,20 @@ module Trenni::BuilderSpec
102
124
  expect(subject.output).to be == "<div><strong>Hello</strong>World!</div>"
103
125
  end
104
126
 
127
+ it "can inline fragments" do
128
+ subject.inline! do
129
+ subject.inline('a') do
130
+ subject << "Hello"
131
+ end
132
+
133
+ subject.inline('a') do
134
+ subject << "World"
135
+ end
136
+ end
137
+
138
+ expect(subject.output).to be == "<a>Hello</a><a>World</a>"
139
+ end
140
+
105
141
  it "escapes attributes and text correctly" do
106
142
  subject.inline :foo, :bar => %Q{"Hello World"} do
107
143
  subject.text %Q{if x < 10}
@@ -109,30 +145,37 @@ module Trenni::BuilderSpec
109
145
 
110
146
  expect(subject.output).to be == %Q{<foo bar="&quot;Hello World&quot;">if x &lt; 10</foo>}
111
147
  end
112
-
113
- it "should support compact attributes" do
114
- subject.tag :option, :required => true
115
- expect(subject.output).to be == %Q{<option required/>}
116
- end
117
-
118
- it "should output without changing escaped characters" do
119
- subject.tag "section", :'data-text' => 'foo\nbar'
120
- expect(subject.output).to be == '<section data-text="foo\nbar"/>'
148
+ end
149
+
150
+ describe '#<<' do
151
+ it 'can append text' do
152
+ subject << 'text'
153
+ expect(subject.output).to be == "text"
121
154
  end
122
155
 
123
- it "should order array attributes as specified" do
124
- subject.tag :t, [[:a, 10], [:b, 20]]
125
- expect(subject.output).to be == %Q{<t a="10" b="20"/>}
156
+ it "doesn't append nil" do
157
+ subject << nil
158
+ expect(subject.output).to be == ""
126
159
  end
127
-
128
- it "should order hash attributes as specified" do
129
- subject.tag :t, :b => 20, :a => 10
130
- expect(subject.output).to be == %Q{<t b="20" a="10"/>}
160
+ end
161
+
162
+ describe '#append' do
163
+ it 'should be able to append nil' do
164
+ expect{subject.append(nil)}.to_not raise_error
131
165
  end
132
166
 
133
- it "shouldn't output attributes with nil value" do
134
- subject.tag :t, [[:a, 10], [:b, nil]]
135
- expect(subject.output).to be == %Q{<t a="10"/>}
167
+ it 'should append existing markup' do
168
+ subject.tag("outer") do
169
+ subject.append("<inner>\n\t<nested/>\n</inner>")
170
+ end
171
+
172
+ expect(subject.output).to be == <<~HTML.chomp
173
+ <outer>
174
+ <inner>
175
+ <nested/>
176
+ </inner>
177
+ </outer>
178
+ HTML
136
179
  end
137
180
  end
138
181
  end
@@ -9,13 +9,25 @@ RSpec.describe Trenni::Markup do
9
9
 
10
10
  it "should be fast to parse large documents" do
11
11
  Benchmark.ips do |x|
12
- x.report("General String") do |times|
12
+ x.report("CGI.escapeHTML(general_string)") do |times|
13
+ while (times -= 1) >= 0
14
+ CGI.escapeHTML(general_string)
15
+ end
16
+ end
17
+
18
+ x.report("CGI.escapeHTML(code_string)") do |times|
19
+ while (times -= 1) >= 0
20
+ CGI.escapeHTML(code_string)
21
+ end
22
+ end
23
+
24
+ x.report("Trenni::Markup.escape_string(general_string)") do |times|
13
25
  while (times -= 1) >= 0
14
26
  Trenni::Markup.escape_string(general_string)
15
27
  end
16
28
  end
17
29
 
18
- x.report("Code String") do |times|
30
+ x.report("Trenni::Markup.escape_string(code_string)") do |times|
19
31
  while (times -= 1) >= 0
20
32
  Trenni::Markup.escape_string(code_string)
21
33
  end
@@ -4,6 +4,9 @@ require 'benchmark/ips'
4
4
  require 'trenni/parsers'
5
5
  require 'trenni/entities'
6
6
 
7
+ require 'trenni/query'
8
+ require 'rack/utils'
9
+
7
10
  require 'nokogiri'
8
11
 
9
12
  RSpec.describe Trenni::Parsers do
@@ -71,4 +74,32 @@ RSpec.describe Trenni::Parsers do
71
74
  end
72
75
  end
73
76
  end
77
+
78
+ describe '#parse_query' do
79
+ let(:string) {"foo=hi%20there&bar[blah]=123&bar[quux][0]=1&bar[quux][1]=2&bar[quux][2]=3"}
80
+
81
+ it "should be fast to parse large query strings" do
82
+ # query = Trenni::Query.new
83
+ # query.parse(Trenni::Buffer.new string)
84
+ # pp query
85
+ #
86
+ # pp Rack::Utils.parse_nested_query(string)
87
+
88
+ Benchmark.ips do |x|
89
+ x.report("Large (Trenni)") do |times|
90
+ while (times -= 1) >= 0
91
+ Trenni::Query.new.parse(Trenni::Buffer.new string)
92
+ end
93
+ end
94
+
95
+ x.report("Large (Rack)") do |times|
96
+ while (times -= 1) >= 0
97
+ Rack::Utils.parse_nested_query(string)
98
+ end
99
+ end
100
+
101
+ x.compare!
102
+ end
103
+ end
104
+ end
74
105
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+ require 'trenni/query'
24
+
25
+ RSpec.describe Trenni::Query do
26
+ def parse(string)
27
+ subject.parse(Trenni::Buffer.new(string))
28
+
29
+ return subject
30
+ end
31
+
32
+ it "can parse query string with integer key" do
33
+ expect(parse "q[0]=0").to be == {q: {0 => "0"}}
34
+ end
35
+
36
+ it "can parse query string with mixed integer/string key" do
37
+ expect(parse "q[2d]=3d").to be == {q: {:'2d' => "3d"}}
38
+ end
39
+
40
+ it "can parse query string appending items to array" do
41
+ expect(parse "q[]=a&q[]=b").to be == {q: ["a", "b"]}
42
+ end
43
+
44
+ it "can decode encoded keys" do
45
+ expect(parse "hello+world=true").to be == {:"hello world" => "true"}
46
+ end
47
+
48
+ it "can decode encoded values" do
49
+ expect(parse "message=hello+world").to be == {message: "hello world"}
50
+ end
51
+ end