phlexing 0.3.0 → 0.4.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
  SHA256:
3
- metadata.gz: 36dca58e17556b8b085edeb24dbe3fc5f8c8cb976056a6f739a655c5d94d7a36
4
- data.tar.gz: 21f2b6dbec24a29a4dbfe52bb25d77887e52c72d29098e41c5a80a2173e8cc66
3
+ metadata.gz: 0a1f00bbf43b734cd75f369ace78ec7a8e782192fc65b9729c345b16dfb25dd6
4
+ data.tar.gz: 10985bfa447d6e052b2bfe932ff796bc2465458ca5a7b8f0e77bb089f0372371
5
5
  SHA512:
6
- metadata.gz: e52e0c6836f3e701c24f6ccf2338f823b5507497863e3af93d089712a2503213d2e86d5f3516f1f9d3e60bffd907de7ffbfc3661610141dd0f917f43a7cc2008
7
- data.tar.gz: 00730543e38df6d87c1f5a1263ea964bfc27024de00dea885df86a5d60626afd665485899aad1d8cf350fba3df7e914879fda76040953125a4e9842e387c4b7b
6
+ metadata.gz: b9ad394751b8749495198b624ee0b0df792225d62664ef760b98f29ec18f1bc09aeb02b9cd3727e386bcf75fd11409658679e2a69b12709644cfeefc7aea3b3c
7
+ data.tar.gz: '009f8d61b78789d5f78641c85ef37307661bdcdc5bcf3591032766d3de63a9bb8c6fb4b63c74500b94b8084ebccd6e977f1d7abb883a0e5417e2a915edcad9d3'
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source "https://rubygems.org"
4
4
 
5
5
  gemspec
6
6
 
7
+ gem "maxitest", "~> 4.4"
7
8
  gem "minitest", "~> 5.0"
8
9
  gem "rake", "~> 13.0"
9
10
  gem "rubocop", require: false, github: "joeldrapper/rubocop", branch: "rubocop-user-agent"
data/Gemfile.lock CHANGED
@@ -17,63 +17,215 @@ GIT
17
17
  PATH
18
18
  remote: .
19
19
  specs:
20
- phlexing (0.2.0)
21
- erb_parser (~> 0.0.2)
20
+ phlexing (0.4.0)
21
+ deface (~> 1.9)
22
22
  html_press (~> 0.8.2)
23
23
  nokogiri (~> 1.0)
24
- rufo (~> 0.13.0)
24
+ phlex (~> 1.6)
25
+ phlex-rails (~> 0.9)
26
+ syntax_tree (~> 6.0)
25
27
 
26
28
  GEM
27
29
  remote: https://rubygems.org/
28
30
  specs:
31
+ actioncable (7.0.4.3)
32
+ actionpack (= 7.0.4.3)
33
+ activesupport (= 7.0.4.3)
34
+ nio4r (~> 2.0)
35
+ websocket-driver (>= 0.6.1)
36
+ actionmailbox (7.0.4.3)
37
+ actionpack (= 7.0.4.3)
38
+ activejob (= 7.0.4.3)
39
+ activerecord (= 7.0.4.3)
40
+ activestorage (= 7.0.4.3)
41
+ activesupport (= 7.0.4.3)
42
+ mail (>= 2.7.1)
43
+ net-imap
44
+ net-pop
45
+ net-smtp
46
+ actionmailer (7.0.4.3)
47
+ actionpack (= 7.0.4.3)
48
+ actionview (= 7.0.4.3)
49
+ activejob (= 7.0.4.3)
50
+ activesupport (= 7.0.4.3)
51
+ mail (~> 2.5, >= 2.5.4)
52
+ net-imap
53
+ net-pop
54
+ net-smtp
55
+ rails-dom-testing (~> 2.0)
56
+ actionpack (7.0.4.3)
57
+ actionview (= 7.0.4.3)
58
+ activesupport (= 7.0.4.3)
59
+ rack (~> 2.0, >= 2.2.0)
60
+ rack-test (>= 0.6.3)
61
+ rails-dom-testing (~> 2.0)
62
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
63
+ actiontext (7.0.4.3)
64
+ actionpack (= 7.0.4.3)
65
+ activerecord (= 7.0.4.3)
66
+ activestorage (= 7.0.4.3)
67
+ activesupport (= 7.0.4.3)
68
+ globalid (>= 0.6.0)
69
+ nokogiri (>= 1.8.5)
70
+ actionview (7.0.4.3)
71
+ activesupport (= 7.0.4.3)
72
+ builder (~> 3.1)
73
+ erubi (~> 1.4)
74
+ rails-dom-testing (~> 2.0)
75
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
76
+ activejob (7.0.4.3)
77
+ activesupport (= 7.0.4.3)
78
+ globalid (>= 0.3.6)
79
+ activemodel (7.0.4.3)
80
+ activesupport (= 7.0.4.3)
81
+ activerecord (7.0.4.3)
82
+ activemodel (= 7.0.4.3)
83
+ activesupport (= 7.0.4.3)
84
+ activestorage (7.0.4.3)
85
+ actionpack (= 7.0.4.3)
86
+ activejob (= 7.0.4.3)
87
+ activerecord (= 7.0.4.3)
88
+ activesupport (= 7.0.4.3)
89
+ marcel (~> 1.0)
90
+ mini_mime (>= 1.1.0)
91
+ activesupport (7.0.4.3)
92
+ concurrent-ruby (~> 1.0, >= 1.0.2)
93
+ i18n (>= 1.6, < 2)
94
+ minitest (>= 5.1)
95
+ tzinfo (~> 2.0)
29
96
  ast (2.4.2)
97
+ builder (3.2.4)
98
+ cgi (0.3.6)
99
+ concurrent-ruby (1.2.2)
100
+ crass (1.0.6)
30
101
  css_press (0.3.2)
31
102
  csspool-st (= 3.1.2)
32
103
  json
33
104
  csspool-st (3.1.2)
34
- erb_parser (0.0.2)
35
- treetop
105
+ date (3.3.3)
106
+ deface (1.9.0)
107
+ actionview (>= 5.2)
108
+ nokogiri (>= 1.6)
109
+ polyglot
110
+ railties (>= 5.2)
111
+ rainbow (>= 2.1.0)
112
+ erb (4.0.2)
113
+ cgi (>= 0.3.3)
114
+ erubi (1.12.0)
36
115
  execjs (2.8.1)
116
+ globalid (1.1.0)
117
+ activesupport (>= 5.0)
37
118
  html_press (0.8.2)
38
119
  htmlentities
39
120
  multi_css (>= 0.1.0)
40
121
  multi_js (>= 0.1.0)
41
122
  htmlentities (4.3.4)
123
+ i18n (1.12.0)
124
+ concurrent-ruby (~> 1.0)
42
125
  json (2.6.2)
43
- minitest (5.16.3)
126
+ loofah (2.19.1)
127
+ crass (~> 1.0.2)
128
+ nokogiri (>= 1.5.9)
129
+ mail (2.8.1)
130
+ mini_mime (>= 0.1.1)
131
+ net-imap
132
+ net-pop
133
+ net-smtp
134
+ marcel (1.0.2)
135
+ maxitest (4.4.0)
136
+ minitest (>= 5.0.0, < 5.18.0)
137
+ method_source (1.0.0)
138
+ mini_mime (1.1.2)
139
+ minitest (5.17.0)
44
140
  multi_css (0.1.0)
45
141
  css_press
46
142
  multi_js (0.1.0)
47
143
  uglifier (~> 2)
48
- nokogiri (1.13.10-x86_64-darwin)
144
+ net-imap (0.3.4)
145
+ date
146
+ net-protocol
147
+ net-pop (0.1.2)
148
+ net-protocol
149
+ net-protocol (0.2.1)
150
+ timeout
151
+ net-smtp (0.3.3)
152
+ net-protocol
153
+ nio4r (2.5.8)
154
+ nokogiri (1.14.2-x86_64-darwin)
49
155
  racc (~> 1.4)
50
- nokogiri (1.13.10-x86_64-linux)
156
+ nokogiri (1.14.2-x86_64-linux)
51
157
  racc (~> 1.4)
52
158
  parallel (1.22.1)
53
- parser (3.1.2.1)
159
+ parser (3.2.0.0)
54
160
  ast (~> 2.4.1)
161
+ phlex (1.6.1)
162
+ concurrent-ruby (~> 1.2)
163
+ erb (>= 4)
164
+ zeitwerk (~> 2.6)
165
+ phlex-rails (0.9.0)
166
+ phlex (~> 1.6)
167
+ rails (>= 6.1, < 8)
168
+ zeitwerk (~> 2.6)
55
169
  polyglot (0.3.5)
170
+ prettier_print (1.2.1)
56
171
  racc (1.6.2)
172
+ rack (2.2.6.4)
173
+ rack-test (2.1.0)
174
+ rack (>= 1.3)
175
+ rails (7.0.4.3)
176
+ actioncable (= 7.0.4.3)
177
+ actionmailbox (= 7.0.4.3)
178
+ actionmailer (= 7.0.4.3)
179
+ actionpack (= 7.0.4.3)
180
+ actiontext (= 7.0.4.3)
181
+ actionview (= 7.0.4.3)
182
+ activejob (= 7.0.4.3)
183
+ activemodel (= 7.0.4.3)
184
+ activerecord (= 7.0.4.3)
185
+ activestorage (= 7.0.4.3)
186
+ activesupport (= 7.0.4.3)
187
+ bundler (>= 1.15.0)
188
+ railties (= 7.0.4.3)
189
+ rails-dom-testing (2.0.3)
190
+ activesupport (>= 4.2.0)
191
+ nokogiri (>= 1.6)
192
+ rails-html-sanitizer (1.5.0)
193
+ loofah (~> 2.19, >= 2.19.1)
194
+ railties (7.0.4.3)
195
+ actionpack (= 7.0.4.3)
196
+ activesupport (= 7.0.4.3)
197
+ method_source
198
+ rake (>= 12.2)
199
+ thor (~> 1.0)
200
+ zeitwerk (~> 2.5)
57
201
  rainbow (3.1.1)
58
202
  rake (13.0.6)
59
- regexp_parser (2.6.0)
203
+ regexp_parser (2.6.2)
60
204
  rexml (3.2.5)
61
- rubocop-ast (1.23.0)
205
+ rubocop-ast (1.24.1)
62
206
  parser (>= 3.1.1.0)
63
207
  ruby-progressbar (1.11.0)
64
- rufo (0.13.0)
65
- treetop (1.6.12)
66
- polyglot (~> 0.3)
208
+ syntax_tree (6.1.1)
209
+ prettier_print (>= 1.2.0)
210
+ thor (1.2.1)
211
+ timeout (0.3.2)
212
+ tzinfo (2.0.6)
213
+ concurrent-ruby (~> 1.0)
67
214
  uglifier (2.7.2)
68
215
  execjs (>= 0.3.0)
69
216
  json (>= 1.8.0)
70
- unicode-display_width (2.3.0)
217
+ unicode-display_width (2.4.2)
218
+ websocket-driver (0.7.5)
219
+ websocket-extensions (>= 0.1.0)
220
+ websocket-extensions (0.1.5)
221
+ zeitwerk (2.6.7)
71
222
 
72
223
  PLATFORMS
73
224
  x86_64-darwin-19
74
225
  x86_64-linux
75
226
 
76
227
  DEPENDENCIES
228
+ maxitest (~> 4.4)
77
229
  minitest (~> 5.0)
78
230
  phlexing!
79
231
  rake (~> 13.0)
data/Rakefile CHANGED
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
3
  require "rake/testtask"
5
4
 
6
5
  Rake::TestTask.new(:test) do |t|
@@ -9,8 +8,4 @@ Rake::TestTask.new(:test) do |t|
9
8
  t.test_files = FileList["test/**/*_test.rb"]
10
9
  end
11
10
 
12
- require "rubocop/rake_task"
13
-
14
- RuboCop::RakeTask.new
15
-
16
- task default: %i[test rubocop]
11
+ task default: %i[test]
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexing
4
+ class ComponentGenerator
5
+ include Helpers
6
+
7
+ attr_accessor :converter, :analyzer
8
+
9
+ def self.call(converter)
10
+ new(converter).call
11
+ end
12
+
13
+ def initialize(converter)
14
+ @converter = converter
15
+ @analyzer = RubyAnalyzer.new
16
+ @analyzer.analyze(converter.source)
17
+ end
18
+
19
+ def call
20
+ out = StringIO.new
21
+
22
+ out << "class "
23
+ out << options.component_name
24
+ out << " < "
25
+ out << options.parent_component
26
+ out << newline
27
+
28
+ analyzer.includes.sort.each do |inklude|
29
+ out << "include "
30
+ out << inklude
31
+ out << newline
32
+ end
33
+
34
+ out << newline if analyzer.includes.any?
35
+
36
+ if analyzer.locals.any?
37
+ out << "attr_accessor "
38
+ out << build_accessors
39
+ out << newline
40
+ out << newline
41
+ end
42
+
43
+ converter.custom_elements.sort.each do |element|
44
+ out << "register_element :"
45
+ out << element
46
+ out << newline
47
+ end
48
+
49
+ out << newline if converter.custom_elements.any?
50
+
51
+ if kwargs.any?
52
+ out << "def initialize("
53
+ out << build_kwargs
54
+ out << ")"
55
+ out << newline
56
+
57
+ kwargs.each do |dep|
58
+ out << "@#{dep} = #{dep}\n"
59
+ end
60
+
61
+ out << "end"
62
+ out << newline
63
+ out << newline
64
+ end
65
+
66
+ out << "def template"
67
+ out << newline
68
+ out << converter.template_code
69
+ out << newline
70
+ out << "end"
71
+
72
+ if analyzer.instance_methods.any?
73
+ out << newline
74
+ out << newline
75
+ out << "private"
76
+ out << newline
77
+ out << newline
78
+
79
+ analyzer.instance_methods.sort.each do |instance_method|
80
+ out << "def "
81
+ out << instance_method
82
+ out << "(*args, **kwargs)"
83
+ out << newline
84
+ out << "# TODO: Implement me"
85
+ out << newline
86
+ out << "end"
87
+ out << newline
88
+ out << newline
89
+ end
90
+
91
+ out << newline
92
+ end
93
+
94
+ out << newline
95
+ out << "end"
96
+ out << newline
97
+
98
+ Formatter.call(out.string.strip)
99
+ rescue StandardError
100
+ out.string.strip
101
+ end
102
+
103
+ private
104
+
105
+ def kwargs
106
+ Set.new(analyzer.ivars + analyzer.locals).sort
107
+ end
108
+
109
+ def build_kwargs
110
+ kwargs.map { |kwarg| arg(kwarg) }.join(", ")
111
+ end
112
+
113
+ def build_accessors
114
+ analyzer.locals.sort.map { |local| symbol(local) }.join(", ")
115
+ end
116
+
117
+ def options
118
+ converter.options
119
+ end
120
+ end
121
+ end
@@ -1,253 +1,42 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "nokogiri"
4
- require "ostruct"
5
- require "rufo"
6
- require "html_press"
7
- require "erb_parser"
8
-
9
3
  module Phlexing
10
4
  class Converter
11
- include Helpers
12
-
13
- using ::Phlexing::Refinements::StringRefinements
14
-
15
- attr_accessor :html, :custom_elements, :ivars, :locals, :idents
5
+ attr_accessor :source, :custom_elements, :options
16
6
 
17
- def self.convert(html, **options)
18
- new(html, **options).output
7
+ def self.call(source, **options)
8
+ new(**options).call(source)
19
9
  end
20
10
 
21
- def initialize(html, **options)
22
- @html = html
23
- @buffer = StringIO.new
24
- @custom_elements = Set.new
25
- @ivars = Set.new
26
- @locals = Set.new
27
- @includes = Set.new
28
- @idents = Set.new
29
- @options = options
30
- analyze_ruby
31
- handle_node
11
+ def self.convert(source, **options)
12
+ new(**options).call(source)
32
13
  end
33
14
 
34
- def handle_text(node, level, newline: true)
35
- text = node.text
36
-
37
- if text.squish.empty? && text.length.positive?
38
- @buffer << indent(level)
39
- @buffer << whitespace(@options)
40
-
41
- text.strip!
42
- end
15
+ def call(source)
16
+ @source = source
43
17
 
44
- if text.length.positive?
45
- @buffer << indent(level)
46
-
47
- if siblings?(node)
48
- @buffer << "text "
49
- end
50
-
51
- @buffer << quote(text)
52
- @buffer << "\n" if newline
53
- end
18
+ code
54
19
  end
55
20
 
56
- def handle_erb_element(node, level, newline: true)
57
- if erb_safe_output?(node)
58
- @buffer << "unsafe_raw "
59
- @buffer << node.text.from(1)
60
- @buffer << "\n" if newline
61
-
62
- return
63
- end
64
-
65
- if erb_interpolation?(node) && node.parent.children.count > 1
66
- if node.text.length >= 24
67
- @buffer << "text("
68
- @buffer << node.text
69
- @buffer << ")"
70
- else
71
- @buffer << "text "
72
- @buffer << node.text
73
- end
74
- elsif erb_comment?(node)
75
- @buffer << "#"
76
- @buffer << node.text
77
- else
78
- @buffer << node.text
79
- end
80
-
81
- @buffer << "\n" if newline
82
- end
83
-
84
- def handle_element(node, level)
85
- @buffer << (indent(level) + node_name(node) + handle_attributes(node))
86
-
87
- if node.children.any?
88
- if node.children.one? && text_node?(node.children.first) && node.text.length <= 32
89
- single_line_block {
90
- handle_text(node.children.first, 0, newline: false)
91
- }
92
- elsif node.children.one? && erb_interpolation?(node.children.first) && node.text.length <= 32
93
- single_line_block {
94
- handle_erb_element(node.children.first, 0, newline: false)
95
- }
96
- else
97
- multi_line_block(level) {
98
- handle_children(node, level)
99
- }
100
- end
101
- else
102
- @buffer << "\n"
103
- end
104
- end
105
-
106
- def handle_comment_node(node, level)
107
- @buffer << indent(level)
108
- @buffer << "comment "
109
- @buffer << quote(node.text.strip)
110
- @buffer << "\n"
111
- end
112
-
113
- def handle_children(node, level)
114
- node.children.each do |child|
115
- handle_node(child, level + 1)
116
- end
117
- end
118
-
119
- def handle_attributes(node)
120
- return "" if node.attributes.keys.none?
121
-
122
- b = StringIO.new
123
-
124
- node.attributes.each_value do |attribute|
125
- b << attribute.name.gsub("-", "_")
126
- b << ": "
127
- b << double_quote(attribute.value)
128
- b << ", " if node.attributes.values.last != attribute
129
- end
130
-
131
- if node.children.any?
132
- "(#{b.string.strip}) "
133
- else
134
- " #{b.string.strip}"
135
- end
136
- end
137
-
138
- def handle_node(node = parsed, level = 0)
139
- case node
140
- when Nokogiri::XML::Text
141
- handle_text(node, level)
142
- when Nokogiri::XML::Element
143
- if erb_node?(node)
144
- handle_erb_element(node, level)
145
- else
146
- handle_element(node, level)
147
- end
148
-
149
- @buffer << "\n" if level == 1
150
- when Nokogiri::HTML4::DocumentFragment
151
- handle_children(node, level)
152
- when Nokogiri::XML::Comment
153
- handle_comment_node(node, level)
154
- else
155
- @buffer << ("UNKNOWN#{node.class}")
156
- end
157
-
158
- @buffer.string
159
- end
160
-
161
- def parsed
162
- @parsed ||= Nokogiri::HTML.fragment(minified_erb)
163
- end
21
+ def initialize(source = nil, **options)
22
+ @custom_elements = Set.new
23
+ @options = Options.new(**options)
164
24
 
165
- def buffer
166
- Rufo::Formatter.format(@buffer.string.strip)
167
- rescue Rufo::SyntaxError
168
- @buffer.string.strip
25
+ call(source)
169
26
  end
170
27
 
171
- def output
172
- out = StringIO.new
173
-
174
- if @options.fetch(:phlex_class, false)
175
- component_name = @options.fetch(:component_name, "Component")
176
- component_name = "A#{component_name}" if component_name[0] == "0" || component_name[0].to_i != 0
177
-
178
- parent_component = @options.fetch(:parent_component, "Phlex::HTML")
179
- parent_component = "A#{parent_component}" if parent_component[0] == "0" || parent_component[0].to_i != 0
180
-
181
- out << "class #{component_name}"
182
- out << "< #{parent_component}\n"
183
-
184
- if locals.any?
185
- out << indent(1)
186
- out << "attr_accessor "
187
- out << locals.sort.map { |local| ":#{local}" }.join(", ")
188
- out << "\n\n"
189
- end
190
-
191
- @custom_elements.sort.each do |element|
192
- out << indent(1)
193
- out << "register_element :#{element}\n"
194
- end
195
-
196
- kwargs = Set.new(ivars + locals).sort
197
-
198
- if kwargs.any?
199
- out << indent(1)
200
- out << "def initialize("
201
- out << kwargs.map { |kwarg| "#{kwarg}: " }.join(", ")
202
- out << ")\n"
203
-
204
- kwargs.each do |dep|
205
- out << indent(2)
206
- out << "@#{dep} = #{dep}\n"
207
- end
208
-
209
- out << indent(1)
210
- out << "end\n"
211
- end
212
-
213
- out << indent(1)
214
- out << "def template\n"
215
-
216
- out << indent(2)
217
- out << @buffer.string
218
-
219
- out << indent(1)
220
- out << "end\n"
221
- out << "end\n"
222
- else
223
- out << @buffer.string
224
- end
225
-
226
- Rufo::Formatter.format(out.string.strip)
227
- rescue Rufo::SyntaxError
228
- out.string.strip
28
+ def code
29
+ options.component? ? component_code : template_code
229
30
  end
230
31
 
231
- def analyze_ruby
232
- ruby_code = ErbParser.parse(html).tokens.map { |tag| tag.is_a?(ErbParser::ErbTag) && !tag.to_s.start_with?("<%#") ? tag.ruby_code.delete_prefix("=") : nil }.join("\n")
233
- visitor = Phlexing::Visitor.new(self)
234
- program = SyntaxTree.parse(ruby_code)
235
- # puts program.construct_keys
236
- visitor.visit(program)
237
- rescue StandardError => e
238
- puts e.inspect
239
- end
32
+ # private
240
33
 
241
- def converted_erb
242
- ErbParser.transform_xml(html).gsub("\n", "").gsub("\r", "")
243
- rescue StandardError
244
- html
34
+ def template_code
35
+ TemplateGenerator.call(self, source)
245
36
  end
246
37
 
247
- def minified_erb
248
- HtmlPress.press(converted_erb)
249
- rescue StandardError
250
- converted_erb
38
+ def component_code
39
+ ComponentGenerator.call(self)
251
40
  end
252
41
  end
253
42
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "deface"
4
+
5
+ module Phlexing
6
+ # Takes ERB and transforms it to Nokogiri-compatible HTML.
7
+ class ERBTransformer
8
+ def self.call(...)
9
+ new(...).call
10
+ end
11
+
12
+ def initialize(source)
13
+ @source = source.to_s.dup
14
+ end
15
+
16
+ def call
17
+ remove_newlines
18
+ strip_whitespace
19
+ transform_erb_tags
20
+ transform_template_tags
21
+
22
+ @source
23
+ end
24
+
25
+ private
26
+
27
+ def remove_newlines
28
+ @source.tr!("\n\r", "")
29
+ end
30
+
31
+ def strip_whitespace
32
+ @source.strip!
33
+ end
34
+
35
+ # Replace ERB tags with Nokogiri-compatible HTML.
36
+ def transform_erb_tags
37
+ @source = Deface::Parser.erb_markup!(@source)
38
+ end
39
+
40
+ # Phlex uses `template_tag` in place of `template` for `<template>` tags.
41
+ def transform_template_tags
42
+ @source.gsub!(/<template/i, "<template-tag")
43
+ @source.gsub!(%r{</template}i, "</template-tag")
44
+ end
45
+ end
46
+ end