hamlit 2.9.5-java → 2.13.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +17 -11
  4. data/CHANGELOG.md +52 -0
  5. data/Gemfile +2 -8
  6. data/LICENSE.txt +26 -23
  7. data/REFERENCE.md +13 -4
  8. data/benchmark/dynamic_merger/benchmark.rb +25 -0
  9. data/benchmark/dynamic_merger/hello.haml +50 -0
  10. data/benchmark/dynamic_merger/hello.string +50 -0
  11. data/bin/bench +3 -3
  12. data/bin/update-haml +125 -0
  13. data/ext/hamlit/hamlit.c +0 -1
  14. data/hamlit.gemspec +3 -1
  15. data/lib/hamlit/attribute_builder.rb +2 -2
  16. data/lib/hamlit/attribute_compiler.rb +3 -3
  17. data/lib/hamlit/compiler/children_compiler.rb +18 -4
  18. data/lib/hamlit/compiler/comment_compiler.rb +8 -5
  19. data/lib/hamlit/compiler/tag_compiler.rb +0 -4
  20. data/lib/hamlit/dynamic_merger.rb +67 -0
  21. data/lib/hamlit/engine.rb +5 -6
  22. data/lib/hamlit/filters/escaped.rb +1 -1
  23. data/lib/hamlit/filters/markdown.rb +1 -0
  24. data/lib/hamlit/filters/plain.rb +0 -4
  25. data/lib/hamlit/filters/preserve.rb +1 -1
  26. data/lib/hamlit/filters/text_base.rb +1 -1
  27. data/lib/hamlit/filters/tilt_base.rb +1 -1
  28. data/lib/hamlit/html.rb +8 -0
  29. data/lib/hamlit/parser.rb +6 -2
  30. data/lib/hamlit/parser/haml_attribute_builder.rb +164 -0
  31. data/lib/hamlit/parser/haml_buffer.rb +20 -130
  32. data/lib/hamlit/parser/haml_compiler.rb +1 -553
  33. data/lib/hamlit/parser/haml_error.rb +29 -25
  34. data/lib/hamlit/parser/haml_escapable.rb +1 -0
  35. data/lib/hamlit/parser/haml_generator.rb +1 -0
  36. data/lib/hamlit/parser/haml_helpers.rb +41 -59
  37. data/lib/hamlit/parser/{haml_xss_mods.rb → haml_helpers/xss_mods.rb} +20 -15
  38. data/lib/hamlit/parser/haml_options.rb +53 -66
  39. data/lib/hamlit/parser/haml_parser.rb +103 -71
  40. data/lib/hamlit/parser/haml_temple_engine.rb +123 -0
  41. data/lib/hamlit/parser/haml_util.rb +10 -40
  42. data/lib/hamlit/rails_template.rb +1 -1
  43. data/lib/hamlit/string_splitter.rb +10 -78
  44. data/lib/hamlit/template.rb +1 -1
  45. data/lib/hamlit/temple_line_counter.rb +31 -0
  46. data/lib/hamlit/version.rb +1 -1
  47. metadata +58 -24
  48. data/lib/hamlit/hamlit.su +0 -0
  49. data/lib/hamlit/parser/MIT-LICENSE +0 -20
  50. data/lib/hamlit/parser/README.md +0 -30
@@ -152,7 +152,6 @@ hamlit_build_multi_class(VALUE escape_attrs, VALUE values)
152
152
  }
153
153
  }
154
154
 
155
- rb_ary_sort_bang(buf);
156
155
  rb_funcall(buf, id_uniq_bang, 0);
157
156
 
158
157
  return escape_attribute(escape_attrs, rb_ary_join(buf, str_space()));
@@ -26,10 +26,11 @@ Gem::Specification.new do |spec|
26
26
  spec.required_ruby_version = '>= 2.1.0'
27
27
  end
28
28
 
29
- spec.add_dependency 'temple', '>= 0.8.0'
29
+ spec.add_dependency 'temple', '>= 0.8.2'
30
30
  spec.add_dependency 'thor'
31
31
  spec.add_dependency 'tilt'
32
32
 
33
+ spec.add_development_dependency 'benchmark_driver'
33
34
  spec.add_development_dependency 'bundler'
34
35
  spec.add_development_dependency 'coffee-script'
35
36
  spec.add_development_dependency 'erubi'
@@ -41,5 +42,6 @@ Gem::Specification.new do |spec|
41
42
  spec.add_development_dependency 'rake-compiler'
42
43
  spec.add_development_dependency 'sass'
43
44
  spec.add_development_dependency 'slim'
45
+ spec.add_development_dependency 'string_template'
44
46
  spec.add_development_dependency 'unindent'
45
47
  end
@@ -47,7 +47,7 @@ module Hamlit::AttributeBuilder
47
47
  when value.is_a?(String)
48
48
  # noop
49
49
  when value.is_a?(Array)
50
- value = value.flatten.select { |v| v }.map(&:to_s).sort.uniq.join(' ')
50
+ value = value.flatten.select { |v| v }.map(&:to_s).uniq.join(' ')
51
51
  when value
52
52
  value = value.to_s
53
53
  else
@@ -67,7 +67,7 @@ module Hamlit::AttributeBuilder
67
67
  classes << value.to_s
68
68
  end
69
69
  end
70
- escape_html(escape_attrs, classes.map(&:to_s).sort.uniq.join(' '))
70
+ escape_html(escape_attrs, classes.map(&:to_s).uniq.join(' '))
71
71
  end
72
72
 
73
73
  def build_data(escape_attrs, quote, *hashes)
@@ -17,7 +17,7 @@ module Hamlit
17
17
  if node.value[:object_ref] != :nil || !Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
18
18
  return runtime_compile(node)
19
19
  end
20
- node.value[:attributes_hashes].each do |attribute_str|
20
+ [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_str|
21
21
  hash = AttributeParser.parse(attribute_str)
22
22
  return runtime_compile(node) unless hash
23
23
  hashes << hash
@@ -28,11 +28,11 @@ module Hamlit
28
28
  private
29
29
 
30
30
  def runtime_compile(node)
31
- attrs = node.value[:attributes_hashes]
31
+ attrs = []
32
32
  attrs.unshift(node.value[:attributes].inspect) if node.value[:attributes] != {}
33
33
 
34
34
  args = [@escape_attrs.inspect, "#{@quote.inspect}.freeze", @format.inspect].push(node.value[:object_ref]) + attrs
35
- [:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')})"]]
35
+ [:html, :attrs, [:dynamic, "::Hamlit::AttributeBuilder.build(#{args.join(', ')}, #{node.value[:dynamic_attributes].to_literal})"]]
36
36
  end
37
37
 
38
38
  def static_compile(static_hash, dynamic_hashes)
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ require 'hamlit/temple_line_counter'
3
+
2
4
  module Hamlit
3
5
  class Compiler
4
6
  class ChildrenCompiler
@@ -14,7 +16,7 @@ module Hamlit
14
16
  node.children.each do |n|
15
17
  rstrip_whitespace!(temple) if nuke_prev_whitespace?(n)
16
18
  insert_newlines!(temple, n)
17
- temple << yield(n)
19
+ temple << moving_lineno(n) { block.call(n) }
18
20
  temple << :whitespace if insert_whitespace?(n)
19
21
  end
20
22
  rstrip_whitespace!(temple) if nuke_inner_whitespace?(node)
@@ -27,19 +29,31 @@ module Hamlit
27
29
  (node.line - @lineno).times do
28
30
  temple << [:newline]
29
31
  end
32
+
30
33
  @lineno = node.line
34
+ end
31
35
 
36
+ def moving_lineno(node, &block)
37
+ # before: As they may have children, we need to increment lineno before compilation.
32
38
  case node.type
33
39
  when :script, :silent_script
34
40
  @lineno += 1
35
- when :filter
36
- @lineno += (node.value[:text] || '').split("\n").size
37
41
  when :tag
38
- node.value[:attributes_hashes].each do |attribute_hash|
42
+ [node.value[:dynamic_attributes].new, node.value[:dynamic_attributes].old].compact.each do |attribute_hash|
39
43
  @lineno += attribute_hash.count("\n")
40
44
  end
41
45
  @lineno += 1 if node.children.empty? && node.value[:parse]
42
46
  end
47
+
48
+ temple = block.call # compile
49
+
50
+ # after: filter may not have children, and for some dynamic filters we can't predict the number of lines.
51
+ case node.type
52
+ when :filter
53
+ @lineno += TempleLineCounter.count_lines(temple)
54
+ end
55
+
56
+ temple
43
57
  end
44
58
 
45
59
  def confirm_whitespace(temple)
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Hamlit
2
3
  class Compiler
3
4
  class CommentCompiler
@@ -25,11 +26,13 @@ module Hamlit
25
26
  condition = $1
26
27
  end
27
28
 
28
- if node.children.empty?
29
- [:html, :condcomment, condition, [:static, " #{node.value[:text]} "]]
30
- else
31
- [:html, :condcomment, condition, yield(node)]
32
- end
29
+ content =
30
+ if node.children.empty?
31
+ [:static, " #{node.value[:text]} "]
32
+ else
33
+ yield(node)
34
+ end
35
+ [:html, :condcomment, condition, content, node.value[:revealed]]
33
36
  end
34
37
  end
35
38
  end
@@ -55,10 +55,6 @@ module Hamlit
55
55
 
56
56
  # We should handle interpolation here to escape only interpolated values.
57
57
  def compile_interpolated_plain(node)
58
- unless Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
59
- return [:multi, [:escape, node.value[:escape_interpolation], [:dynamic, "%Q[#{node.value[:value]}]"]], [:newline]]
60
- end
61
-
62
58
  temple = [:multi]
63
59
  StringSplitter.compile(node.value[:value]).each do |type, value|
64
60
  case type
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+ module Hamlit
3
+ # Compile [:multi, [:static, 'foo'], [:dynamic, 'bar']] to [:dynamic, '"foo#{bar}"']
4
+ class DynamicMerger < Temple::Filter
5
+ def on_multi(*exps)
6
+ exps = exps.dup
7
+ result = [:multi]
8
+ buffer = []
9
+
10
+ until exps.empty?
11
+ type, arg = exps.first
12
+ if type == :dynamic && arg.count("\n") == 0
13
+ buffer << exps.shift
14
+ elsif type == :static && exps.size > (count = arg.count("\n")) &&
15
+ exps[1, count].all? { |e| e == [:newline] }
16
+ (1 + count).times { buffer << exps.shift }
17
+ elsif type == :newline && exps.size > (count = count_newline(exps)) &&
18
+ exps[count].first == :static && count == exps[count].last.count("\n")
19
+ (count + 1).times { buffer << exps.shift }
20
+ else
21
+ result.concat(merge_dynamic(buffer))
22
+ buffer = []
23
+ result << compile(exps.shift)
24
+ end
25
+ end
26
+ result.concat(merge_dynamic(buffer))
27
+
28
+ result.size == 2 ? result[1] : result
29
+ end
30
+
31
+ private
32
+
33
+ def merge_dynamic(exps)
34
+ # Merge exps only when they have both :static and :dynamic
35
+ unless exps.any? { |type,| type == :static } && exps.any? { |type,| type == :dynamic }
36
+ return exps
37
+ end
38
+
39
+ strlit_body = String.new
40
+ exps.each do |type, arg|
41
+ case type
42
+ when :static
43
+ strlit_body << arg.dump.sub!(/\A"/, '').sub!(/"\z/, '').gsub('\n', "\n")
44
+ when :dynamic
45
+ strlit_body << "\#{#{arg}}"
46
+ when :newline
47
+ # newline is added by `gsub('\n', "\n")`
48
+ else
49
+ raise "unexpected type #{type.inspect} is given to #merge_dynamic"
50
+ end
51
+ end
52
+ [[:dynamic, "%Q\0#{strlit_body}\0"]]
53
+ end
54
+
55
+ def count_newline(exps)
56
+ count = 0
57
+ exps.each do |exp|
58
+ if exp == [:newline]
59
+ count += 1
60
+ else
61
+ return count
62
+ end
63
+ end
64
+ return count
65
+ end
66
+ end
67
+ end
@@ -2,10 +2,10 @@
2
2
  require 'temple'
3
3
  require 'hamlit/parser'
4
4
  require 'hamlit/compiler'
5
+ require 'hamlit/html'
5
6
  require 'hamlit/escapable'
6
7
  require 'hamlit/force_escapable'
7
- require 'hamlit/html'
8
- require 'hamlit/string_splitter'
8
+ require 'hamlit/dynamic_merger'
9
9
 
10
10
  module Hamlit
11
11
  class Engine < Temple::Engine
@@ -25,15 +25,14 @@ module Hamlit
25
25
  use Parser
26
26
  use Compiler
27
27
  use HTML
28
- if Ripper.respond_to?(:lex) # No Ripper.lex in truffleruby
29
- use StringSplitter
30
- filter :StaticAnalyzer
31
- end
28
+ filter :StringSplitter
29
+ filter :StaticAnalyzer
32
30
  use Escapable
33
31
  use ForceEscapable
34
32
  filter :ControlFlow
35
33
  filter :MultiFlattener
36
34
  filter :StaticMerger
35
+ use DynamicMerger
37
36
  use :Generator, -> { options[:generator] }
38
37
  end
39
38
  end
@@ -12,7 +12,7 @@ module Hamlit
12
12
 
13
13
  def compile_text(text)
14
14
  if ::Hamlit::HamlUtil.contains_interpolation?(text)
15
- [:dynamic, ::Hamlit::HamlUtil.slow_unescape_interpolation(text)]
15
+ [:dynamic, ::Hamlit::HamlUtil.unescape_interpolation(text)]
16
16
  else
17
17
  [:static, text]
18
18
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Hamlit
2
3
  class Filters
3
4
  class Markdown < TiltBase
@@ -14,10 +14,6 @@ module Hamlit
14
14
 
15
15
  def compile_plain(text)
16
16
  string_literal = ::Hamlit::HamlUtil.unescape_interpolation(text)
17
- unless Ripper.respond_to?(:lex) # truffleruby doesn't have Ripper.lex
18
- return [[:escape, false, [:dynamic, string_literal]]]
19
- end
20
-
21
17
  StringSplitter.compile(string_literal).map do |temple|
22
18
  type, str = temple
23
19
  case type
@@ -12,7 +12,7 @@ module Hamlit
12
12
 
13
13
  def compile_text(text)
14
14
  if ::Hamlit::HamlUtil.contains_interpolation?(text)
15
- [:dynamic, ::Hamlit::HamlUtil.slow_unescape_interpolation(text)]
15
+ [:dynamic, ::Hamlit::HamlUtil.unescape_interpolation(text)]
16
16
  else
17
17
  [:static, text]
18
18
  end
@@ -6,7 +6,7 @@ module Hamlit
6
6
  text = node.value[:text].rstrip.gsub(/^/, prefix)
7
7
  if ::Hamlit::HamlUtil.contains_interpolation?(node.value[:text])
8
8
  # original: Haml::Filters#compile
9
- text = ::Hamlit::HamlUtil.slow_unescape_interpolation(text).gsub(/(\\+)n/) do |s|
9
+ text = ::Hamlit::HamlUtil.unescape_interpolation(text).gsub(/(\\+)n/) do |s|
10
10
  escapes = $1.size
11
11
  next s if escapes % 2 == 0
12
12
  "#{'\\' * (escapes - 1)}\n"
@@ -35,7 +35,7 @@ module Hamlit
35
35
 
36
36
  def dynamic_compile(node, name, indent_width: 0)
37
37
  # original: Haml::Filters#compile
38
- text = ::Hamlit::HamlUtil.slow_unescape_interpolation(node.value[:text]).gsub(/(\\+)n/) do |s|
38
+ text = ::Hamlit::HamlUtil.unescape_interpolation(node.value[:text]).gsub(/(\\+)n/) do |s|
39
39
  escapes = $1.size
40
40
  next s if escapes % 2 == 0
41
41
  "#{'\\' * (escapes - 1)}\n"
@@ -10,5 +10,13 @@ module Hamlit
10
10
  end
11
11
  super(opts)
12
12
  end
13
+
14
+ # This dispatcher supports Haml's "revealed" conditional comment.
15
+ def on_html_condcomment(condition, content, revealed = false)
16
+ on_html_comment [:multi,
17
+ [:static, "[#{condition}]>#{'<!-->' if revealed}"],
18
+ content,
19
+ [:static, "#{'<!--' if revealed}<![endif]"]]
20
+ end
13
21
  end
14
22
  end
@@ -2,13 +2,17 @@
2
2
  # Hamlit::Parser uses original Haml::Parser to generate Haml AST.
3
3
  # hamlit/parser/haml_* are modules originally in haml gem.
4
4
 
5
+ require 'hamlit/parser/haml_attribute_builder'
5
6
  require 'hamlit/parser/haml_error'
6
7
  require 'hamlit/parser/haml_util'
8
+ require 'hamlit/parser/haml_helpers'
7
9
  require 'hamlit/parser/haml_buffer'
8
10
  require 'hamlit/parser/haml_compiler'
9
11
  require 'hamlit/parser/haml_parser'
10
- require 'hamlit/parser/haml_helpers'
11
12
  require 'hamlit/parser/haml_options'
13
+ require 'hamlit/parser/haml_escapable'
14
+ require 'hamlit/parser/haml_generator'
15
+ require 'hamlit/parser/haml_temple_engine'
12
16
 
13
17
  module Hamlit
14
18
  class Parser
@@ -29,7 +33,7 @@ module Hamlit
29
33
  template = Hamlit::HamlUtil.check_haml_encoding(template) do |msg, line|
30
34
  raise Hamlit::Error.new(msg, line)
31
35
  end
32
- HamlParser.new(template, HamlOptions.new(@options)).parse
36
+ HamlParser.new(HamlOptions.new(@options)).call(template)
33
37
  rescue ::Hamlit::HamlError => e
34
38
  error_with_lineno(e)
35
39
  end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hamlit
4
+ module HamlAttributeBuilder
5
+ # https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
6
+ INVALID_ATTRIBUTE_NAME_REGEX = /[ \0"'>\/=]/
7
+
8
+ class << self
9
+ def build_attributes(is_html, attr_wrapper, escape_attrs, hyphenate_data_attrs, attributes = {})
10
+ # @TODO this is an absolutely ridiculous amount of arguments. At least
11
+ # some of this needs to be moved into an instance method.
12
+ join_char = hyphenate_data_attrs ? '-' : '_'
13
+
14
+ attributes.each do |key, value|
15
+ if value.is_a?(Hash)
16
+ data_attributes = attributes.delete(key)
17
+ data_attributes = flatten_data_attributes(data_attributes, '', join_char)
18
+ data_attributes = build_data_keys(data_attributes, hyphenate_data_attrs, key)
19
+ verify_attribute_names!(data_attributes.keys)
20
+ attributes = data_attributes.merge(attributes)
21
+ end
22
+ end
23
+
24
+ result = attributes.collect do |attr, value|
25
+ next if value.nil?
26
+
27
+ value = filter_and_join(value, ' ') if attr == 'class'
28
+ value = filter_and_join(value, '_') if attr == 'id'
29
+
30
+ if value == true
31
+ next " #{attr}" if is_html
32
+ next " #{attr}=#{attr_wrapper}#{attr}#{attr_wrapper}"
33
+ elsif value == false
34
+ next
35
+ end
36
+
37
+ value =
38
+ if escape_attrs == :once
39
+ Hamlit::HamlHelpers.escape_once_without_haml_xss(value.to_s)
40
+ elsif escape_attrs
41
+ Hamlit::HamlHelpers.html_escape_without_haml_xss(value.to_s)
42
+ else
43
+ value.to_s
44
+ end
45
+ " #{attr}=#{attr_wrapper}#{value}#{attr_wrapper}"
46
+ end
47
+ result.compact!
48
+ result.sort!
49
+ result.join
50
+ end
51
+
52
+ # @return [String, nil]
53
+ def filter_and_join(value, separator)
54
+ return '' if (value.respond_to?(:empty?) && value.empty?)
55
+
56
+ if value.is_a?(Array)
57
+ value = value.flatten
58
+ value.map! {|item| item ? item.to_s : nil}
59
+ value.compact!
60
+ value = value.join(separator)
61
+ else
62
+ value = value ? value.to_s : nil
63
+ end
64
+ !value.nil? && !value.empty? && value
65
+ end
66
+
67
+ # Merges two attribute hashes.
68
+ # This is the same as `to.merge!(from)`,
69
+ # except that it merges id, class, and data attributes.
70
+ #
71
+ # ids are concatenated with `"_"`,
72
+ # and classes are concatenated with `" "`.
73
+ # data hashes are simply merged.
74
+ #
75
+ # Destructively modifies `to`.
76
+ #
77
+ # @param to [{String => String,Hash}] The attribute hash to merge into
78
+ # @param from [{String => Object}] The attribute hash to merge from
79
+ # @return [{String => String,Hash}] `to`, after being merged
80
+ def merge_attributes!(to, from)
81
+ from.keys.each do |key|
82
+ to[key] = merge_value(key, to[key], from[key])
83
+ end
84
+ to
85
+ end
86
+
87
+ # Merge multiple values to one attribute value. No destructive operation.
88
+ #
89
+ # @param key [String]
90
+ # @param values [Array<Object>]
91
+ # @return [String,Hash]
92
+ def merge_values(key, *values)
93
+ values.inject(nil) do |to, from|
94
+ merge_value(key, to, from)
95
+ end
96
+ end
97
+
98
+ def verify_attribute_names!(attribute_names)
99
+ attribute_names.each do |attribute_name|
100
+ if attribute_name =~ INVALID_ATTRIBUTE_NAME_REGEX
101
+ raise HamlInvalidAttributeNameError.new("Invalid attribute name '#{attribute_name}' was rendered")
102
+ end
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ # Merge a couple of values to one attribute value. No destructive operation.
109
+ #
110
+ # @param to [String,Hash,nil]
111
+ # @param from [Object]
112
+ # @return [String,Hash]
113
+ def merge_value(key, to, from)
114
+ if from.kind_of?(Hash) || to.kind_of?(Hash)
115
+ from = { nil => from } if !from.is_a?(Hash)
116
+ to = { nil => to } if !to.is_a?(Hash)
117
+ to.merge(from)
118
+ elsif key == 'id'
119
+ merged_id = filter_and_join(from, '_')
120
+ if to && merged_id
121
+ merged_id = "#{to}_#{merged_id}"
122
+ elsif to || merged_id
123
+ merged_id ||= to
124
+ end
125
+ merged_id
126
+ elsif key == 'class'
127
+ merged_class = filter_and_join(from, ' ')
128
+ if to && merged_class
129
+ merged_class = (to.split(' ') | merged_class.split(' ')).join(' ')
130
+ elsif to || merged_class
131
+ merged_class ||= to
132
+ end
133
+ merged_class
134
+ else
135
+ from
136
+ end
137
+ end
138
+
139
+ def build_data_keys(data_hash, hyphenate, attr_name="data")
140
+ Hash[data_hash.map do |name, value|
141
+ if name == nil
142
+ [attr_name, value]
143
+ elsif hyphenate
144
+ ["#{attr_name}-#{name.to_s.tr('_', '-')}", value]
145
+ else
146
+ ["#{attr_name}-#{name}", value]
147
+ end
148
+ end]
149
+ end
150
+
151
+ def flatten_data_attributes(data, key, join_char, seen = [])
152
+ return {key => data} unless data.is_a?(Hash)
153
+
154
+ return {key => nil} if seen.include? data.object_id
155
+ seen << data.object_id
156
+
157
+ data.sort {|x, y| x[0].to_s <=> y[0].to_s}.inject({}) do |hash, (k, v)|
158
+ joined = key == '' ? k : [key, k].join(join_char)
159
+ hash.merge! flatten_data_attributes(v, joined, join_char, seen)
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end