openhab-scripting 4.1.4 → 4.5.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.
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