openhab-scripting 4.1.4 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) 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/color_item.rb +60 -0
  7. data/lib/openhab/dsl/items/comparable_item.rb +49 -0
  8. data/lib/openhab/dsl/items/contact_item.rb +41 -0
  9. data/lib/openhab/dsl/items/date_time_item.rb +64 -0
  10. data/lib/openhab/dsl/items/dimmer_item.rb +59 -0
  11. data/lib/openhab/dsl/items/ensure.rb +93 -0
  12. data/lib/openhab/dsl/items/generic_item.rb +174 -0
  13. data/lib/openhab/dsl/items/group_item.rb +121 -89
  14. data/lib/openhab/dsl/items/image_item.rb +5 -41
  15. data/lib/openhab/dsl/items/item_equality.rb +36 -0
  16. data/lib/openhab/dsl/items/item_registry.rb +49 -0
  17. data/lib/openhab/dsl/items/items.rb +81 -35
  18. data/lib/openhab/dsl/items/metadata.rb +325 -0
  19. data/lib/openhab/dsl/items/number_item.rb +6 -312
  20. data/lib/openhab/dsl/items/numeric_item.rb +68 -0
  21. data/lib/openhab/dsl/items/persistence.rb +122 -0
  22. data/lib/openhab/dsl/items/player_item.rb +49 -40
  23. data/lib/openhab/dsl/items/rollershutter_item.rb +25 -77
  24. data/lib/openhab/dsl/items/string_item.rb +16 -58
  25. data/lib/openhab/dsl/items/switch_item.rb +62 -0
  26. data/lib/openhab/dsl/lazy_array.rb +8 -6
  27. data/lib/openhab/dsl/monkey_patch/events/events.rb +2 -2
  28. data/lib/openhab/dsl/monkey_patch/events/item_command.rb +67 -24
  29. data/lib/openhab/dsl/monkey_patch/events/item_event.rb +5 -5
  30. data/lib/openhab/dsl/monkey_patch/events/item_state.rb +10 -11
  31. data/lib/openhab/dsl/monkey_patch/events/item_state_changed.rb +10 -11
  32. data/lib/openhab/dsl/monkey_patch/ruby/number.rb +25 -2
  33. data/lib/openhab/dsl/monkey_patch/ruby/ruby.rb +0 -3
  34. data/lib/openhab/dsl/monkey_patch/ruby/string.rb +24 -24
  35. data/lib/openhab/dsl/rules/terse.rb +24 -0
  36. data/lib/openhab/dsl/states.rb +1 -1
  37. data/lib/openhab/dsl/time_of_day.rb +3 -5
  38. data/lib/openhab/dsl/types/comparable_type.rb +21 -0
  39. data/lib/openhab/dsl/types/date_time_type.rb +334 -0
  40. data/lib/openhab/dsl/types/decimal_type.rb +187 -0
  41. data/lib/openhab/dsl/types/hsb_type.rb +201 -0
  42. data/lib/openhab/dsl/types/increase_decrease_type.rb +23 -0
  43. data/lib/openhab/dsl/types/next_previous_type.rb +23 -0
  44. data/lib/openhab/dsl/types/numeric_type.rb +39 -0
  45. data/lib/openhab/dsl/types/on_off_type.rb +29 -0
  46. data/lib/openhab/dsl/types/open_closed_type.rb +29 -0
  47. data/lib/openhab/dsl/types/percent_type.rb +70 -0
  48. data/lib/openhab/dsl/types/play_pause_type.rb +27 -0
  49. data/lib/openhab/dsl/types/quantity_type.rb +275 -0
  50. data/lib/openhab/dsl/types/refresh_type.rb +18 -0
  51. data/lib/openhab/dsl/types/rewind_fastforward_type.rb +33 -0
  52. data/lib/openhab/dsl/types/stop_move_type.rb +23 -0
  53. data/lib/openhab/dsl/types/string_type.rb +88 -0
  54. data/lib/openhab/dsl/types/type.rb +72 -0
  55. data/lib/openhab/dsl/types/types.rb +78 -0
  56. data/lib/openhab/dsl/types/un_def_type.rb +22 -0
  57. data/lib/openhab/dsl/types/up_down_type.rb +32 -0
  58. data/lib/openhab/dsl/units.rb +11 -6
  59. data/lib/openhab/version.rb +1 -1
  60. data/lib/openhab.rb +0 -1
  61. metadata +36 -28
  62. data/lib/openhab/dsl/items/datetime_item.rb +0 -75
  63. data/lib/openhab/dsl/items/item_command.rb +0 -90
  64. data/lib/openhab/dsl/items/item_delegate.rb +0 -125
  65. data/lib/openhab/dsl/monkey_patch/items/contact_item.rb +0 -51
  66. data/lib/openhab/dsl/monkey_patch/items/dimmer_item.rb +0 -140
  67. data/lib/openhab/dsl/monkey_patch/items/items.rb +0 -142
  68. data/lib/openhab/dsl/monkey_patch/items/metadata.rb +0 -328
  69. data/lib/openhab/dsl/monkey_patch/items/persistence.rb +0 -123
  70. data/lib/openhab/dsl/monkey_patch/items/switch_item.rb +0 -71
  71. data/lib/openhab/dsl/monkey_patch/ruby/range.rb +0 -47
  72. data/lib/openhab/dsl/monkey_patch/ruby/time.rb +0 -32
  73. data/lib/openhab/dsl/monkey_patch/types/decimal_type.rb +0 -97
  74. data/lib/openhab/dsl/monkey_patch/types/increase_decrease_type.rb +0 -23
  75. data/lib/openhab/dsl/monkey_patch/types/next_previous_type.rb +0 -23
  76. data/lib/openhab/dsl/monkey_patch/types/on_off_type.rb +0 -79
  77. data/lib/openhab/dsl/monkey_patch/types/open_closed_type.rb +0 -71
  78. data/lib/openhab/dsl/monkey_patch/types/percent_type.rb +0 -77
  79. data/lib/openhab/dsl/monkey_patch/types/play_pause_type.rb +0 -23
  80. data/lib/openhab/dsl/monkey_patch/types/quantity_type.rb +0 -69
  81. data/lib/openhab/dsl/monkey_patch/types/refresh_type.rb +0 -23
  82. data/lib/openhab/dsl/monkey_patch/types/rewind_fastforward_type.rb +0 -23
  83. data/lib/openhab/dsl/monkey_patch/types/stop_move_type.rb +0 -23
  84. data/lib/openhab/dsl/monkey_patch/types/types.rb +0 -15
  85. data/lib/openhab/dsl/monkey_patch/types/up_down_type.rb +0 -72
  86. data/lib/openhab/dsl/types/datetime.rb +0 -338
  87. data/lib/openhab/dsl/types/quantity.rb +0 -300
@@ -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
@@ -0,0 +1,334 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'time'
5
+
6
+ module OpenHAB
7
+ module DSL
8
+ module Types
9
+ java_import org.openhab.core.library.types.DateTimeType
10
+
11
+ # global alias
12
+ ::DateTimeType = DateTimeType
13
+
14
+ # @deprecated
15
+ # Backwards-compatible alias
16
+ DateTime = DateTimeType
17
+
18
+ # rubocop: disable Metrics/ClassLength class has a single responsibility
19
+
20
+ #
21
+ # Add methods to core OpenHAB DateTimeType to make it behave as a Ruby
22
+ # Time object
23
+ #
24
+ # Any method not explicitly defined is forwarded to the +ZonedDateTime+
25
+ # or +Time+ representation of this object as appropriate.
26
+ #
27
+ class DateTimeType
28
+ # @!parse include Type
29
+
30
+ # remove the JRuby default == so that we can inherit the Ruby method
31
+ remove_method :==
32
+
33
+ extend Forwardable
34
+ include Comparable
35
+
36
+ #
37
+ # Regex expression to identify strings defining a time in hours, minutes and optionally seconds
38
+ #
39
+ TIME_ONLY_REGEX = /\A(?<hours>\d\d):(?<minutes>\d\d)(?<seconds>:\d\d)?\Z/.freeze
40
+
41
+ #
42
+ # Regex expression to identify strings defining a time in year, month, and day
43
+ #
44
+ DATE_ONLY_REGEX = /\A\d{4}-\d\d-\d\d\Z/.freeze
45
+ private_constant :TIME_ONLY_REGEX, :DATE_ONLY_REGEX
46
+
47
+ class << self
48
+ #
49
+ # Parses a String representing a time into an OpenHAB DateTimeType. First tries to parse it
50
+ # using java's DateTimeType's parser, then falls back to the Ruby Time.parse
51
+ #
52
+ # @param [String] time_string
53
+ #
54
+ # @return [DateTimeType]
55
+ #
56
+ def parse(time_string)
57
+ time_string = "#{time_string}Z" if TIME_ONLY_REGEX.match?(time_string)
58
+ DateTimeType.new(time_string)
59
+ rescue java.lang.StringIndexOutOfBoundsException, java.lang.IllegalArgumentException
60
+ # Try ruby's Time.parse if OpenHAB's DateTimeType parser fails
61
+ begin
62
+ DateTimeType.new(Time.parse(time_string))
63
+ rescue ArgumentError
64
+ raise ArgumentError, "Unable to parse #{time_string} into a DateTimeType"
65
+ end
66
+ end
67
+
68
+ # parses a String representing a duration
69
+ #
70
+ # for internal use
71
+ #
72
+ # @return [java.time.Duration]
73
+ #
74
+ # @!visibility private
75
+ def parse_duration(time_string)
76
+ # convert from common HH:MM to ISO8601 for parsing
77
+ if (match = time_string.match(TIME_ONLY_REGEX))
78
+ time_string = "PT#{match[:hours]}H#{match[:minutes]}M#{match[:seconds] || 0}S"
79
+ end
80
+ java.time.Duration.parse(time_string)
81
+ end
82
+ end
83
+
84
+ # act like a ruby Time
85
+ def_delegator :zoned_date_time, :month_value, :month
86
+ def_delegator :zoned_date_time, :day_of_month, :mday
87
+ def_delegator :zoned_date_time, :day_of_year, :yday
88
+ def_delegator :zoned_date_time, :minute, :min
89
+ def_delegator :zoned_date_time, :second, :sec
90
+ def_delegator :zoned_date_time, :nano, :nsec
91
+ def_delegator :zoned_date_time, :to_epoch_second, :to_i
92
+
93
+ alias day mday
94
+
95
+ #
96
+ # Create a new instance of DateTimeType
97
+ #
98
+ # @param value [ZonedDateTime, Time, String, Numeric]
99
+ #
100
+ def initialize(value = nil) # rubocop:disable Metrics
101
+ if value.respond_to?(:to_time)
102
+ time = value.to_time
103
+ instant = java.time.Instant.ofEpochSecond(time.to_i, time.nsec)
104
+ zone_id = java.time.ZoneId.of_offset('UTC', java.time.ZoneOffset.of_total_seconds(time.utc_offset))
105
+ super(ZonedDateTime.ofInstant(instant, zone_id))
106
+ return
107
+ elsif value.respond_to?(:to_str)
108
+ # strings respond_do?(:to_d), but we want to avoid that conversion
109
+ super(value.to_str)
110
+ return
111
+ elsif value.respond_to?(:to_d)
112
+ time = value.to_d
113
+ super(ZonedDateTime.ofInstant(
114
+ java.time.Instant.ofEpochSecond(time.to_i,
115
+ ((time % 1) * 1_000_000_000).to_i),
116
+ java.time.ZoneId.systemDefault
117
+ ))
118
+ return
119
+ end
120
+
121
+ super
122
+ end
123
+
124
+ #
125
+ # Check equality without type conversion
126
+ #
127
+ # @return [Boolean] if the same value is represented, without type
128
+ # conversion
129
+ def eql?(other)
130
+ return false unless other.instance_of?(self.class)
131
+
132
+ zoned_date_time.compare_to(other.zoned_date_time).zero?
133
+ end
134
+
135
+ #
136
+ # Comparison
137
+ #
138
+ # @param [DateTimeType, Items::DateTimeItem, Time,
139
+ # String] other object to compare to
140
+ #
141
+ # @return [Integer, nil] -1, 0, +1 depending on whether +other+ is
142
+ # less than, equal to, or greater than self
143
+ #
144
+ # nil is returned if the two values are incomparable
145
+ #
146
+ def <=>(other) # rubocop:disable Metrics
147
+ logger.trace("(#{self.class}) #{self} <=> #{other} (#{other.class})")
148
+ if other.is_a?(self.class)
149
+ zoned_date_time.to_instant.compare_to(other.zoned_date_time.to_instant)
150
+ elsif other.is_a?(Items::DateTimeItem) ||
151
+ (other.is_a?(Items::GroupItem) && other.base_item.is_a?(Items::DateTimeItem))
152
+ return nil unless other.state?
153
+
154
+ zoned_date_time.to_instant.compare_to(other.state.zoned_date_time.to_instant)
155
+ elsif other.is_a?(TimeOfDay::TimeOfDay) || other.is_a?(TimeOfDay::TimeOfDayRangeElement)
156
+ to_tod <=> other
157
+ elsif other.respond_to?(:to_time)
158
+ to_time <=> other.to_time
159
+ elsif other.respond_to?(:to_str)
160
+ time_string = other.to_str
161
+ time_string = "#{time_string}T00:00:00#{zone}" if DATE_ONLY_REGEX.match?(time_string)
162
+ self <=> DateTimeType.parse(time_string)
163
+ elsif other.respond_to?(:coerce)
164
+ lhs, rhs = other.coerce(self)
165
+ lhs <=> rhs
166
+ end
167
+ end
168
+
169
+ #
170
+ # Type Coercion
171
+ #
172
+ # Coerce object to a DateTimeType
173
+ #
174
+ # @param [Items::DateTimeItem, Time] other object to coerce to a
175
+ # DateTimeType
176
+ #
177
+ # @return [[DateTimeType, DateTimeType]]
178
+ #
179
+ def coerce(other)
180
+ logger.trace("Coercing #{self} as a request from #{other.class}")
181
+ if other.is_a?(Items::DateTimeItem)
182
+ raise TypeError, "can't convert #{UnDefType} into #{self.class}" unless other.state?
183
+
184
+ [other.state, self]
185
+ elsif other.respond_to?(:to_time)
186
+ [DateTimeType.new(other), self]
187
+ else
188
+ raise TypeError, "can't convert #{other.class} into #{self.class}"
189
+ end
190
+ end
191
+
192
+ #
193
+ # Convert this DateTimeType to a ruby Time object
194
+ #
195
+ # @return [Time] A Time object representing the same instant and timezone
196
+ #
197
+ def to_time
198
+ Time.at(to_i, nsec, :nsec).localtime(utc_offset)
199
+ end
200
+
201
+ #
202
+ # Convert the time part of this DateTimeType to a TimeOfDay object
203
+ #
204
+ # @return [TimeOfDay] A TimeOfDay object representing the time
205
+ #
206
+ def to_time_of_day
207
+ TimeOfDay::TimeOfDay.new(h: hour, m: minute, s: second)
208
+ end
209
+
210
+ alias to_tod to_time_of_day
211
+
212
+ #
213
+ # Returns the value of time as a floating point number of seconds since the Epoch
214
+ #
215
+ # @return [Float] Number of seconds since the Epoch, with nanosecond presicion
216
+ #
217
+ def to_f
218
+ zoned_date_time.to_epoch_second + (zoned_date_time.nano / 1_000_000_000)
219
+ end
220
+
221
+ #
222
+ # The offset in seconds from UTC
223
+ #
224
+ # @return [Integer] The offset from UTC, in seconds
225
+ #
226
+ def utc_offset
227
+ zoned_date_time.offset.total_seconds
228
+ end
229
+
230
+ #
231
+ # Returns true if time represents a time in UTC (GMT)
232
+ #
233
+ # @return [Boolean] true if utc_offset == 0, false otherwise
234
+ #
235
+ def utc?
236
+ utc_offset.zero?
237
+ end
238
+
239
+ #
240
+ # Returns an integer representing the day of the week, 0..6, with Sunday == 0.
241
+ #
242
+ # @return [Integer] The day of week
243
+ #
244
+ def wday
245
+ zoned_date_time.day_of_week.value % 7
246
+ end
247
+
248
+ #
249
+ # The timezone
250
+ #
251
+ # @return [String] The timezone in `[+-]hh:mm(:ss)` format (`Z` for UTC)
252
+ #
253
+ def zone
254
+ zoned_date_time.zone.id
255
+ end
256
+
257
+ # @!visibility private
258
+ def respond_to_missing?(method, _include_private = false)
259
+ return true if zoned_date_time.respond_to?(method)
260
+ return true if Time.instance_methods.include?(method.to_sym)
261
+
262
+ super
263
+ end
264
+
265
+ #
266
+ # Forward missing methods to the +ZonedDateTime+ object or a ruby +Time+
267
+ # object representing the same instant
268
+ #
269
+ def method_missing(method, *args, &block)
270
+ return zoned_date_time.send(method, *args, &block) if zoned_date_time.respond_to?(method)
271
+ return to_time.send(method, *args, &block) if Time.instance_methods.include?(method.to_sym)
272
+
273
+ super
274
+ end
275
+
276
+ # Add other to self
277
+ #
278
+ # @param other [java.time.Duration, String, Numeric]
279
+ #
280
+ # @return [DateTimeType]
281
+ def +(other) # rubocop:disable Metrics
282
+ if other.is_a?(java.time.Duration)
283
+ DateTimeType.new(zoned_date_time.plus(other))
284
+ elsif other.respond_to?(:to_str)
285
+ other = self.class.parse_duration(other.to_str)
286
+ self + other
287
+ elsif other.respond_to?(:to_d)
288
+ DateTimeType.new(zoned_date_time.plusNanos((other.to_d * 1_000_000_000).to_i))
289
+ elsif other.respond_to?(:coerce)
290
+ lhs, rhs = other.coerce(to_d)
291
+ lhs + rhs
292
+ else
293
+ raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
294
+ end
295
+ end
296
+
297
+ # Subtract other from self
298
+ #
299
+ # if other is a Duration-like object, the result is a new
300
+ # {DateTimeType} of duration seconds earlier in time.
301
+ #
302
+ # if other is a DateTime-like object, the result is a Duration
303
+ # representing how long between the two instants in time.
304
+ #
305
+ # @param other [java.time.Duration, Time, String, Numeric]
306
+ #
307
+ # @return [DateTimeType, java.Time.Duration]
308
+ def -(other) # rubocop:disable Metrics
309
+ if other.is_a?(java.time.Duration)
310
+ DateTimeType.new(zoned_date_time.minus(other))
311
+ elsif other.respond_to?(:to_time)
312
+ to_time - other.to_time
313
+ elsif other.respond_to?(:to_str)
314
+ time_string = other.to_str
315
+ other = if TIME_ONLY_REGEX.match?(time_string)
316
+ self.class.parse_duration(time_string)
317
+ else
318
+ DateTimeType.parse(time_string)
319
+ end
320
+ self - other
321
+ elsif other.respond_to?(:to_d)
322
+ DateTimeType.new(zoned_date_time.minusNanos((other.to_d * 1_000_000_000).to_i))
323
+ elsif other.respond_to?(:coerce)
324
+ lhs, rhs = other.coerce(to_d)
325
+ lhs - rhs
326
+ else
327
+ raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
328
+ end
329
+ end
330
+ end
331
+ # rubocop: enable Metrics/ClassLength
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'comparable_type'
4
+ require_relative 'numeric_type'
5
+
6
+ module OpenHAB
7
+ module DSL
8
+ module Types
9
+ java_import org.openhab.core.library.types.DecimalType
10
+
11
+ #
12
+ # Add methods to core OpenHAB DecimalType to make it behave as a Ruby
13
+ # BigDecimal object
14
+ #
15
+ #
16
+ # Any method not explicitly defined is forwarded to the +BigDecimal+
17
+ # representation of this object.
18
+ #
19
+ class DecimalType
20
+ # @!parse include Type
21
+ include NumericType
22
+ include ComparableType
23
+
24
+ #
25
+ # Create a new instance of DateTimeType
26
+ #
27
+ # @param value [java.math.BigDecimal, Items::NumericItem, Numeric]
28
+ def initialize(*args) # rubocop:disable Metrics
29
+ unless args.length == 1
30
+ super
31
+ return
32
+ end
33
+
34
+ value = args.first
35
+ if value.is_a?(java.math.BigDecimal)
36
+ super
37
+ elsif value.is_a?(BigDecimal)
38
+ super(value.to_java)
39
+ elsif value.is_a?(DecimalType)
40
+ super(value.to_big_decimal)
41
+ elsif value.is_a?(Items::NumericItem) ||
42
+ (value.is_a?(Items::GroupItem) && value.base_item.is_a?(Items::NumericItem))
43
+ super(value.state)
44
+ elsif value.respond_to?(:to_d)
45
+ super(value.to_d.to_java)
46
+ else # rubocop:disable Lint/DuplicateBranch
47
+ # duplicates the Java BigDecimal branch, but that needs to go first
48
+ # in order to avoid unnecessary conversions
49
+ super
50
+ end
51
+ end
52
+
53
+ #
54
+ # Convert DecimalType to a QuantityType
55
+ #
56
+ # @param [Object] other String or Unit representing an OpenHAB Unit
57
+ #
58
+ # @return [QuantityType] +self+ as a {QuantityType} of the supplied Unit
59
+ #
60
+ def |(other)
61
+ other = org.openhab.core.types.util.UnitUtils.parse_unit(other.to_str) if other.respond_to?(:to_str)
62
+ QuantityType.new(to_big_decimal, other)
63
+ end
64
+
65
+ #
66
+ # Comparison
67
+ #
68
+ # @param [NumericType, Items::NumericItem, Numeric]
69
+ # other object to compare to
70
+ #
71
+ # @return [Integer, nil] -1, 0, +1 depending on whether +other+ is
72
+ # less than, equal to, or greater than self
73
+ #
74
+ # nil is returned if the two values are incomparable
75
+ #
76
+ def <=>(other) # rubocop:disable Metrics
77
+ logger.trace("(#{self.class}) #{self} <=> #{other} (#{other.class})")
78
+ if other.is_a?(QuantityType)
79
+ (other <=> self)&.-@
80
+ elsif other.is_a?(self.class)
81
+ compare_to(other)
82
+ elsif other.is_a?(Items::NumericItem) ||
83
+ (other.is_a?(Items::GroupItem) && other.base_item.is_a?(NumericItem))
84
+ return nil unless other.state?
85
+
86
+ self <=> other.state
87
+ elsif other.respond_to?(:to_d)
88
+ to_d <=> other.to_d
89
+ elsif other.respond_to?(:coerce)
90
+ lhs, rhs = other.coerce(self)
91
+ lhs <=> rhs
92
+ end
93
+ end
94
+
95
+ #
96
+ # Type Coercion
97
+ #
98
+ # Coerce object to a DecimalType
99
+ #
100
+ # @param [Items::NumericItem, Numeric, Type] other object to
101
+ # coerce to a {DecimalType}
102
+ #
103
+ # if +other+ is a {Type}, +self+ will instead be coerced
104
+ # to that type to accomodate comparison with things such as {OnOffType}
105
+ #
106
+ # @return [[DecimalType, DecimalType]]
107
+ #
108
+ def coerce(other) # rubocop:disable Metrics
109
+ logger.trace("Coercing #{self} as a request from #{other.class}")
110
+ if other.is_a?(Items::NumericItem) ||
111
+ (other.is_a?(Items::GroupItem) && other.base_item.is_a?(Items::NumericItem))
112
+ raise TypeError, "can't convert #{UnDefType} into #{self.class}" unless other.state?
113
+
114
+ [other.state, self]
115
+ elsif other.is_a?(Type)
116
+ [other, as(other.class)]
117
+ elsif other.respond_to?(:to_d)
118
+ [self.class.new(other.to_d), self]
119
+ else
120
+ raise TypeError, "can't convert #{other.class} into #{self.class}"
121
+ end
122
+ end
123
+
124
+ #
125
+ # Unary minus
126
+ #
127
+ # Negates self
128
+ #
129
+ # @return [DecimalType]
130
+ def -@
131
+ self.class.new(to_big_decimal.negate)
132
+ end
133
+
134
+ {
135
+ :add => :+,
136
+ :subtract => :-,
137
+ :multiply => :*,
138
+ :divide => :/,
139
+ :remainder => :%,
140
+ :pow => :**
141
+ }.each do |java_op, ruby_op|
142
+ class_eval( # rubocop:disable Style/DocumentDynamicEvalDefinition https://github.com/rubocop/rubocop/issues/10179
143
+ # def +(other)
144
+ # if other.is_a?(DecimalType)
145
+ # self.class.new(to_big_decimal.add(other.to_big_decimal))
146
+ # elsif other.is_a?(java.math.BigDecimal)
147
+ # self.class.new(to_big_decimal.add(other))
148
+ # elsif other.respond_to?(:to_d)
149
+ # result = to_d + other
150
+ # # result could already be a QuantityType
151
+ # result = self.class.new(result) unless result.is_a?(NumericType)
152
+ # result
153
+ # elsif other.respond_to?(:coerce)
154
+ # lhs, rhs = other.coerce(to_d)
155
+ # lhs + rhs
156
+ # else
157
+ # raise TypeError, "#{other.class} can't be coerced into #{self.class}"
158
+ # end
159
+ # end
160
+ <<~RUBY, __FILE__, __LINE__ + 1
161
+ def #{ruby_op}(other)
162
+ if other.is_a?(DecimalType)
163
+ self.class.new(to_big_decimal.#{java_op}(other.to_big_decimal))
164
+ elsif other.is_a?(java.math.BigDecimal)
165
+ self.class.new(to_big_decimal.#{java_op}(other))
166
+ elsif other.respond_to?(:to_d)
167
+ result = to_d #{ruby_op} other
168
+ # result could already be a QuantityType
169
+ result = self.class.new(result) unless result.is_a?(NumericType)
170
+ result
171
+ elsif other.respond_to?(:coerce)
172
+ lhs, rhs = other.coerce(to_d)
173
+ lhs #{ruby_op} rhs
174
+ else
175
+ raise TypeError, "\#{other.class} can't be coerced into \#{self.class}"
176
+ end
177
+ end
178
+ RUBY
179
+ )
180
+ end
181
+
182
+ # any method that exists on BigDecimal gets forwarded to to_d
183
+ delegate (BigDecimal.instance_methods - instance_methods) => :to_d
184
+ end
185
+ end
186
+ end
187
+ end