openhab-scripting 4.1.3 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/entity_lookup.rb +1 -57
  3. data/lib/openhab/dsl/actions.rb +2 -3
  4. data/lib/openhab/dsl/dsl.rb +8 -12
  5. data/lib/openhab/dsl/group.rb +1 -5
  6. data/lib/openhab/dsl/items/comparable_item.rb +49 -0
  7. data/lib/openhab/dsl/items/contact_item.rb +41 -0
  8. data/lib/openhab/dsl/items/date_time_item.rb +64 -0
  9. data/lib/openhab/dsl/items/dimmer_item.rb +59 -0
  10. data/lib/openhab/dsl/items/ensure.rb +93 -0
  11. data/lib/openhab/dsl/items/generic_item.rb +174 -0
  12. data/lib/openhab/dsl/items/group_item.rb +121 -89
  13. data/lib/openhab/dsl/items/image_item.rb +5 -41
  14. data/lib/openhab/dsl/items/item_equality.rb +36 -0
  15. data/lib/openhab/dsl/items/item_registry.rb +49 -0
  16. data/lib/openhab/dsl/items/items.rb +80 -35
  17. data/lib/openhab/dsl/items/metadata.rb +325 -0
  18. data/lib/openhab/dsl/items/number_item.rb +6 -312
  19. data/lib/openhab/dsl/items/numeric_item.rb +68 -0
  20. data/lib/openhab/dsl/items/persistence.rb +122 -0
  21. data/lib/openhab/dsl/items/player_item.rb +49 -40
  22. data/lib/openhab/dsl/items/rollershutter_item.rb +25 -77
  23. data/lib/openhab/dsl/items/string_item.rb +16 -58
  24. data/lib/openhab/dsl/items/switch_item.rb +62 -0
  25. data/lib/openhab/dsl/lazy_array.rb +8 -6
  26. data/lib/openhab/dsl/monkey_patch/events/events.rb +2 -2
  27. data/lib/openhab/dsl/monkey_patch/events/item_command.rb +67 -24
  28. data/lib/openhab/dsl/monkey_patch/events/item_event.rb +5 -5
  29. data/lib/openhab/dsl/monkey_patch/events/item_state.rb +10 -11
  30. data/lib/openhab/dsl/monkey_patch/events/item_state_changed.rb +10 -11
  31. data/lib/openhab/dsl/monkey_patch/ruby/number.rb +25 -2
  32. data/lib/openhab/dsl/monkey_patch/ruby/ruby.rb +0 -3
  33. data/lib/openhab/dsl/monkey_patch/ruby/string.rb +24 -24
  34. data/lib/openhab/dsl/rules/terse.rb +24 -0
  35. data/lib/openhab/dsl/states.rb +1 -1
  36. data/lib/openhab/dsl/time_of_day.rb +3 -5
  37. data/lib/openhab/dsl/types/comparable_type.rb +21 -0
  38. data/lib/openhab/dsl/types/date_time_type.rb +334 -0
  39. data/lib/openhab/dsl/types/decimal_type.rb +187 -0
  40. data/lib/openhab/dsl/types/increase_decrease_type.rb +23 -0
  41. data/lib/openhab/dsl/types/next_previous_type.rb +23 -0
  42. data/lib/openhab/dsl/types/numeric_type.rb +39 -0
  43. data/lib/openhab/dsl/types/on_off_type.rb +29 -0
  44. data/lib/openhab/dsl/types/open_closed_type.rb +29 -0
  45. data/lib/openhab/dsl/types/percent_type.rb +68 -0
  46. data/lib/openhab/dsl/types/play_pause_type.rb +27 -0
  47. data/lib/openhab/dsl/types/quantity_type.rb +275 -0
  48. data/lib/openhab/dsl/types/refresh_type.rb +18 -0
  49. data/lib/openhab/dsl/types/rewind_fastforward_type.rb +33 -0
  50. data/lib/openhab/dsl/types/stop_move_type.rb +23 -0
  51. data/lib/openhab/dsl/types/string_type.rb +88 -0
  52. data/lib/openhab/dsl/types/type.rb +72 -0
  53. data/lib/openhab/dsl/types/types.rb +77 -0
  54. data/lib/openhab/dsl/types/un_def_type.rb +22 -0
  55. data/lib/openhab/dsl/types/up_down_type.rb +32 -0
  56. data/lib/openhab/dsl/units.rb +11 -6
  57. data/lib/openhab/version.rb +1 -1
  58. data/lib/openhab.rb +0 -1
  59. metadata +34 -28
  60. data/lib/openhab/dsl/items/datetime_item.rb +0 -75
  61. data/lib/openhab/dsl/items/item_command.rb +0 -90
  62. data/lib/openhab/dsl/items/item_delegate.rb +0 -125
  63. data/lib/openhab/dsl/monkey_patch/items/contact_item.rb +0 -51
  64. data/lib/openhab/dsl/monkey_patch/items/dimmer_item.rb +0 -140
  65. data/lib/openhab/dsl/monkey_patch/items/items.rb +0 -142
  66. data/lib/openhab/dsl/monkey_patch/items/metadata.rb +0 -328
  67. data/lib/openhab/dsl/monkey_patch/items/persistence.rb +0 -123
  68. data/lib/openhab/dsl/monkey_patch/items/switch_item.rb +0 -71
  69. data/lib/openhab/dsl/monkey_patch/ruby/range.rb +0 -47
  70. data/lib/openhab/dsl/monkey_patch/ruby/time.rb +0 -32
  71. data/lib/openhab/dsl/monkey_patch/types/decimal_type.rb +0 -97
  72. data/lib/openhab/dsl/monkey_patch/types/increase_decrease_type.rb +0 -23
  73. data/lib/openhab/dsl/monkey_patch/types/next_previous_type.rb +0 -23
  74. data/lib/openhab/dsl/monkey_patch/types/on_off_type.rb +0 -79
  75. data/lib/openhab/dsl/monkey_patch/types/open_closed_type.rb +0 -71
  76. data/lib/openhab/dsl/monkey_patch/types/percent_type.rb +0 -77
  77. data/lib/openhab/dsl/monkey_patch/types/play_pause_type.rb +0 -23
  78. data/lib/openhab/dsl/monkey_patch/types/quantity_type.rb +0 -69
  79. data/lib/openhab/dsl/monkey_patch/types/refresh_type.rb +0 -23
  80. data/lib/openhab/dsl/monkey_patch/types/rewind_fastforward_type.rb +0 -23
  81. data/lib/openhab/dsl/monkey_patch/types/stop_move_type.rb +0 -23
  82. data/lib/openhab/dsl/monkey_patch/types/types.rb +0 -15
  83. data/lib/openhab/dsl/monkey_patch/types/up_down_type.rb +0 -72
  84. data/lib/openhab/dsl/types/datetime.rb +0 -338
  85. data/lib/openhab/dsl/types/quantity.rb +0 -300
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'java'
3
+ require_relative 'item_event'
4
4
 
5
5
  module OpenHAB
6
6
  module DSL
@@ -9,32 +9,75 @@ module OpenHAB
9
9
  # Patches OpenHAB events
10
10
  #
11
11
  module Events
12
- java_import Java::OrgOpenhabCoreItemsEvents::ItemCommandEvent
13
-
14
- #
15
- # Monkey patch with ruby style accesors
16
- #
17
- class ItemCommandEvent
18
- include Log
12
+ java_import org.openhab.core.items.events.ItemCommandEvent
19
13
 
14
+ # Adds methods to core OpenHAB ItemCommandEvent to make it more natural in Ruby
15
+ class ItemCommandEvent < ItemEvent
16
+ # @return [Type]
20
17
  alias command item_command
21
18
 
22
- #
23
- # For every value in the supplied enumeration create a corresponding method mapped to the lowercase
24
- # string representation of the enum value For example, an enum with values of STOP and START
25
- # would create methods stop? and start? that check if the command is corresponding STOP and START type
26
- #
27
- # @param [Java::JavaLang::Enum] command_enum Enumeration to create commands for
28
- #
29
- def self.def_enum_predicates(command_enum)
30
- command_enum.values.each do |command| # rubocop:disable Style/HashEachMethods : Java enum does not have each_value
31
- command_method = "#{command.to_s.downcase}?"
32
- logger.trace("Creating predicate method (#{command_method}) for #{self}")
33
- define_method(command_method) do
34
- self.command == command
35
- end
36
- end
37
- end
19
+ # @!method refresh?
20
+ # Check if == +REFRESH+
21
+ # @return [Boolean]
22
+
23
+ # @!method on?
24
+ # Check if == +ON+
25
+ # @return [Boolean]
26
+
27
+ # @!method off?
28
+ # Check if == +OFF+
29
+ # @return [Boolean]
30
+
31
+ # @!method up?
32
+ # Check if == +UP+
33
+ # @return [Boolean]
34
+
35
+ # @!method down?
36
+ # Check if == +DOWN+
37
+ # @return [Boolean]
38
+
39
+ # @!method stop?
40
+ # Check if == +STOP+
41
+ # @return [Boolean]
42
+
43
+ # @!method move?
44
+ # Check if == +MOVE+
45
+ # @return [Boolean]
46
+
47
+ # @!method increase?
48
+ # Check if == +INCREASE+
49
+ # @return [Boolean]
50
+
51
+ # @!method decrease?
52
+ # Check if == +DECREASE+
53
+ # @return [Boolean]
54
+
55
+ # @!method play?
56
+ # Check if == +PLAY+
57
+ # @return [Boolean]
58
+
59
+ # @!method pause?
60
+ # Check if == +PAUSE+
61
+ # @return [Boolean]
62
+
63
+ # @!method rewind?
64
+ # Check if == +REWIND+
65
+ # @return [Boolean]
66
+
67
+ # @!method fast_forward?
68
+ # Check if == +FASTFORWARD+
69
+ # @return [Boolean]
70
+
71
+ # @deprecated
72
+ # @!parse alias fastforward? fast_forward?
73
+
74
+ # @!method next?
75
+ # Check if == +NEXT+
76
+ # @return [Boolean]
77
+
78
+ # @!method previous?
79
+ # Check if == +PREVIOUS+
80
+ # @return [Boolean]
38
81
  end
39
82
  end
40
83
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'java'
4
-
5
3
  module OpenHAB
6
4
  module DSL
7
5
  module MonkeyPatch
@@ -9,14 +7,16 @@ module OpenHAB
9
7
  # Patches OpenHAB events
10
8
  #
11
9
  module Events
12
- java_import Java::OrgOpenhabCoreItemsEvents::ItemEvent
10
+ java_import org.openhab.core.items.events.ItemEvent
13
11
 
14
12
  #
15
- # MonkeyPatch to add item
13
+ # Adds methods to core OpenHAB ItemEvent to make it more natural in Ruby
16
14
  #
17
15
  class ItemEvent
18
16
  #
19
- # Return a decorated item
17
+ # The triggering item
18
+ #
19
+ # @return [GenericItem]
20
20
  #
21
21
  def item
22
22
  OpenHAB::Core::EntityLookup.lookup_item(item_name)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'java'
3
+ require 'openhab/dsl/types/un_def_type'
4
4
 
5
5
  module OpenHAB
6
6
  module DSL
@@ -9,18 +9,17 @@ module OpenHAB
9
9
  # Patches OpenHAB events
10
10
  #
11
11
  module Events
12
- java_import Java::OrgOpenhabCoreItemsEvents::ItemStateEvent
13
- java_import Java::OrgOpenhabCoreTypes::UnDefType
12
+ java_import org.openhab.core.items.events.ItemStateEvent
14
13
 
15
14
  # Helpers common to ItemStateEvent and ItemStateChangedEvent
16
- module ItemStateUnDefTypeHelpers
15
+ module ItemState
17
16
  #
18
17
  # Check if the state == UNDEF
19
18
  #
20
19
  # @return [Boolean] True if the state is UNDEF, false otherwise
21
20
  #
22
21
  def undef?
23
- item_state == UnDefType::UNDEF
22
+ item_state == UNDEF
24
23
  end
25
24
 
26
25
  #
@@ -28,7 +27,7 @@ module OpenHAB
28
27
  #
29
28
  # @return [Boolean] True if the state is NULL, false otherwise
30
29
  def null?
31
- item_state == UnDefType::NULL
30
+ item_state == NULL
32
31
  end
33
32
 
34
33
  #
@@ -37,13 +36,13 @@ module OpenHAB
37
36
  # @return [Boolean] True if state is not UNDEF or NULL
38
37
  #
39
38
  def state?
40
- undef? == false && null? == false
39
+ !item_state.is_a?(Types::UnDefType)
41
40
  end
42
41
 
43
42
  #
44
43
  # Get the item state
45
44
  #
46
- # @return [State] OpenHAB state if state is not UNDEF or NULL, nil otherwise
45
+ # @return [Types::PrimitiveState] OpenHAB state if state is not UNDEF or NULL, nil otherwise
47
46
  #
48
47
  def state
49
48
  item_state if state?
@@ -51,10 +50,10 @@ module OpenHAB
51
50
  end
52
51
 
53
52
  #
54
- # MonkeyPatch with ruby style accessors
53
+ # Adds methods to core OpenHAB ItemStateEvent to make it more natural in Ruby
55
54
  #
56
- class ItemStateEvent
57
- include ItemStateUnDefTypeHelpers
55
+ class ItemStateEvent < ItemEvent
56
+ include ItemState
58
57
  end
59
58
  end
60
59
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'java'
3
+ require 'openhab/dsl/types/un_def_type'
4
4
 
5
5
  module OpenHAB
6
6
  module DSL
@@ -9,14 +9,13 @@ module OpenHAB
9
9
  # Patches OpenHAB events
10
10
  #
11
11
  module Events
12
- java_import Java::OrgOpenhabCoreItemsEvents::ItemStateChangedEvent
13
- java_import Java::OrgOpenhabCoreTypes::UnDefType
12
+ java_import org.openhab.core.items.events.ItemStateChangedEvent
14
13
 
15
14
  #
16
- # MonkeyPatch with ruby style accessors
15
+ # Adds methods to core OpenHAB ItemStateChangedEvent to make it more natural in Ruby
17
16
  #
18
- class ItemStateChangedEvent
19
- include ItemStateUnDefTypeHelpers
17
+ class ItemStateChangedEvent < ItemEvent
18
+ include ItemState
20
19
 
21
20
  #
22
21
  # Check if state was == UNDEF
@@ -24,7 +23,7 @@ module OpenHAB
24
23
  # @return [Boolean] True if the state is UNDEF, false otherwise
25
24
  #
26
25
  def was_undef?
27
- old_item_state == UnDefType::UNDEF
26
+ old_item_state == UNDEF
28
27
  end
29
28
 
30
29
  #
@@ -32,7 +31,7 @@ module OpenHAB
32
31
  #
33
32
  # @return [Boolean] True if the state is NULL, false otherwise
34
33
  def was_null?
35
- old_item_state == UnDefType::NULL
34
+ old_item_state == NULL
36
35
  end
37
36
 
38
37
  #
@@ -41,18 +40,18 @@ module OpenHAB
41
40
  # @return [Boolean] True if state is not UNDEF or NULL
42
41
  #
43
42
  def was?
44
- was_undef? == false && was_null? == false
43
+ !old_item_state.is_a?(Types::UnDefType)
45
44
  end
46
45
 
47
46
  #
48
47
  # Get the previous item state
49
48
  #
50
- # @return [State] OpenHAB state if state was not UNDEF or NULL, nil otherwise
49
+ # @return [Types::Type] OpenHAB state if state was not UNDEF or NULL, nil otherwise
51
50
  #
52
51
  def was
53
52
  old_item_state if was?
54
53
  end
55
-
54
+ # @deprecated
56
55
  alias last was
57
56
  end
58
57
  end
@@ -16,7 +16,7 @@ module OpenHAB
16
16
  # @return [Java::JavaTime::Duration] Duration with number of units from self
17
17
  #
18
18
  %w[millis seconds minutes hours].each do |unit|
19
- define_method(unit) { Java::JavaTime::Duration.public_send("of_#{unit}", self) }
19
+ define_method(unit) { java.time.Duration.public_send("of_#{unit}", self) }
20
20
  end
21
21
 
22
22
  alias second seconds
@@ -37,7 +37,7 @@ module OpenHAB
37
37
  # @return [Java::JavaTime::Duration] Duration truncated to an integral number of milliseconds from self
38
38
  #
39
39
  def millis
40
- Java::JavaTime::Duration.of_millis(to_i)
40
+ java.time.Duration.of_millis(to_i)
41
41
  end
42
42
 
43
43
  #
@@ -74,6 +74,26 @@ module OpenHAB
74
74
  alias minute minutes
75
75
  alias hour hours
76
76
  end
77
+
78
+ #
79
+ # Extend numeric to create quantity object
80
+ #
81
+ module NumericExtensions
82
+ #
83
+ # Convert Numeric to a QuantityType
84
+ #
85
+ # @param [Object] other String or Unit representing an OpenHAB Unit
86
+ #
87
+ # @return [Types::QuantityType] +self+ as a {Types::QuantityType} of the supplied Unit
88
+ #
89
+ def |(other)
90
+ other = org.openhab.core.types.util.UnitUtils.parse_unit(other.to_str) if other.respond_to?(:to_str)
91
+
92
+ return super unless other.is_a?(javax.measure.Unit)
93
+
94
+ QuantityType.new(to_d.to_java, other)
95
+ end
96
+ end
77
97
  end
78
98
  end
79
99
  end
@@ -81,3 +101,6 @@ end
81
101
 
82
102
  Integer.prepend(OpenHAB::DSL::MonkeyPatch::Ruby::IntegerExtensions)
83
103
  Float.prepend(OpenHAB::DSL::MonkeyPatch::Ruby::FloatExtensions)
104
+ Numeric.include(OpenHAB::DSL::MonkeyPatch::Ruby::NumericExtensions)
105
+ # Integer already has #|, so we have to prepend it here
106
+ Integer.prepend(OpenHAB::DSL::MonkeyPatch::Ruby::NumericExtensions)
@@ -1,8 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Monkey patch ruby
4
- require 'openhab/dsl/monkey_patch/ruby/range'
5
3
  require 'openhab/dsl/monkey_patch/ruby/number'
6
4
  require 'openhab/dsl/monkey_patch/ruby/string'
7
- require 'openhab/dsl/monkey_patch/ruby/time'
8
5
  require 'bigdecimal/util'
@@ -1,44 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'openhab/dsl/types/quantity'
4
- require 'openhab/dsl/types/datetime'
5
- require 'openhab/dsl/items/datetime_item'
6
-
7
3
  module OpenHAB
8
4
  module DSL
5
+ # monkey patches
9
6
  module MonkeyPatch
7
+ # extensions to core Ruby objects
10
8
  module Ruby
11
- #
12
- # Extend String class
13
- #
9
+ # extend String class so that it will do semantic comparisons against
10
+ # DateTimeType and QuantityType, instead of converting the latter to
11
+ # String and doing an exact match
14
12
  module StringExtensions
15
- include OpenHAB::Core
16
-
17
- #
18
- # Compares String to another object
19
- #
20
- # @param [Object] other object to compare to
21
- #
22
- # @return [Boolean] true if the two objects contain the same value, false otherwise
23
- #
13
+ # {include:StringExtensions}
24
14
  def ==(other)
25
15
  case other
26
- when OpenHAB::DSL::Types::Quantity, QuantityType, Java::OrgOpenhabCoreLibraryTypes::StringType,
27
- OpenHAB::DSL::Types::DateTime, OpenHAB::DSL::Items::DateTimeItem
16
+ when Types::QuantityType,
17
+ Types::DateTimeType,
18
+ Items::DateTimeItem,
19
+ Items::NumericItem
28
20
  other == self
29
21
  else
30
22
  super
31
23
  end
32
24
  end
25
+
26
+ # {include:StringExtensions}
27
+ def <=>(other)
28
+ case other
29
+ when Types::QuantityType,
30
+ Types::DateTimeType,
31
+ Items::DateTimeItem,
32
+ Items::NumericItem
33
+ (other <=> self)&.-@()
34
+ else
35
+ super
36
+ end
37
+ end
33
38
  end
34
39
  end
35
40
  end
36
41
  end
37
42
  end
38
43
 
39
- #
40
- # Prepend String class with comparison extensions
41
- #
42
- class String
43
- prepend OpenHAB::DSL::MonkeyPatch::Ruby::StringExtensions
44
- end
44
+ String.prepend(OpenHAB::DSL::MonkeyPatch::Ruby::StringExtensions)
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ module Rules
6
+ # Module containing terse rule stubs
7
+ module Terse
8
+ %i[changed channel cron every updated received_command].each do |trigger|
9
+ class_eval(<<~RUBY, __FILE__, __LINE__ + 1)
10
+ def #{trigger}(*args, name: nil, **kwargs, &block) # def changed(*args, name: nil, **kwargs, &block)
11
+ # if no name is given, just default to the name of the rules file # # if no name is given, just default to the name of the rules file
12
+ name ||= File.basename(caller_locations.last.path) # name ||= File.basename(caller_locations.last.path)
13
+ rule name do # rule name do
14
+ #{trigger}(*args, **kwargs) # changed(*args, **kwargs)
15
+ run(&block) # run(&block)
16
+ end # end
17
+ end # end
18
+ module_function #{trigger.inspect} # module_function :changed
19
+ RUBY
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -48,7 +48,7 @@ module OpenHAB
48
48
  # @return [StateStorage] item states
49
49
  #
50
50
  def store_states(*items)
51
- items = items.flatten.map { |item| item.respond_to?(:oh_item) ? item.oh_item : item }
51
+ items = items.flatten
52
52
  states = StateStorage.new(BusEvent.storeStates(*items).to_h)
53
53
  if block_given?
54
54
  yield
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'java'
4
3
  require 'openhab/log/logger'
5
- require 'openhab/dsl/items/datetime_item'
6
- require 'openhab/dsl/types/datetime'
4
+ require 'openhab/dsl/types/date_time_type'
7
5
  require 'time'
8
6
 
9
7
  module OpenHAB
@@ -173,7 +171,7 @@ module OpenHAB
173
171
  case object
174
172
  when TimeOfDay then adjust_second_of_day(object.local_time.to_second_of_day)
175
173
  when String then adjust_second_of_day(TimeOfDay.parse(object).local_time.to_second_of_day)
176
- when Time, OpenHAB::DSL::Types::DateTime, OpenHAB::DSL::Items::DateTimeItem
174
+ when Time, OpenHAB::DSL::Types::DateTimeType, OpenHAB::DSL::Items::DateTimeItem
177
175
  adjust_second_of_day(TimeOfDay.new(h: object.hour, m: object.min, s: object.sec)
178
176
  .local_time.to_second_of_day)
179
177
  when TimeOfDayRangeElement then object.sod
@@ -218,7 +216,7 @@ module OpenHAB
218
216
  private_class_method def to_time_of_day(object)
219
217
  case object
220
218
  when String then TimeOfDay.parse(object)
221
- when Time, OpenHAB::DSL::Types::DateTime, OpenHAB::DSL::Items::DateTimeItem
219
+ when Time, OpenHAB::DSL::Types::DateTimeType, OpenHAB::DSL::Items::DateTimeItem
222
220
  TimeOfDay.new(h: object.hour, m: object.min, s: object.sec)
223
221
  else object
224
222
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ # Comparable#== is overwritten by Type, because DecimalType etc.
6
+ # inherits from Comparable on the Java side, so it's in the wrong place
7
+ # in the ancestor list
8
+ # @!visibility private
9
+ module ComparableType
10
+ # re-implement
11
+ # @!visibility private
12
+ def ==(other)
13
+ r = self <=> other
14
+
15
+ return false if r.nil?
16
+
17
+ r.zero?
18
+ end
19
+ end
20
+ end
21
+ end