openhab-jrubyscripting 5.0.0.rc2 → 5.0.0.rc3

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/items/generic_item.rb +13 -5
  3. data/lib/openhab/core/items/metadata/hash.rb +17 -34
  4. data/lib/openhab/core/items/persistence.rb +2 -0
  5. data/lib/openhab/core/items/semantics/enumerable.rb +6 -4
  6. data/lib/openhab/core/profile_factory.rb +2 -0
  7. data/lib/openhab/core/provider.rb +8 -1
  8. data/lib/openhab/core/rules/provider.rb +25 -0
  9. data/lib/openhab/core/rules/registry.rb +76 -0
  10. data/lib/openhab/core/rules/rule.rb +150 -0
  11. data/lib/openhab/core/rules.rb +25 -0
  12. data/lib/openhab/core/timer.rb +5 -7
  13. data/lib/openhab/core/types.rb +1 -1
  14. data/lib/openhab/core.rb +0 -16
  15. data/lib/openhab/core_ext/java/list.rb +436 -0
  16. data/lib/openhab/core_ext/java/map.rb +66 -0
  17. data/lib/openhab/core_ext/java/zoned_date_time.rb +1 -2
  18. data/lib/openhab/core_ext/ruby/date.rb +2 -0
  19. data/lib/openhab/core_ext/ruby/date_time.rb +53 -0
  20. data/lib/openhab/core_ext/ruby/time.rb +88 -86
  21. data/lib/openhab/dsl/events/watch_event.rb +1 -1
  22. data/lib/openhab/dsl/items/builder.rb +8 -3
  23. data/lib/openhab/dsl/items/ensure.rb +6 -2
  24. data/lib/openhab/dsl/items/timed_command.rb +10 -11
  25. data/lib/openhab/dsl/rules/automation_rule.rb +36 -13
  26. data/lib/openhab/dsl/rules/builder.rb +99 -8
  27. data/lib/openhab/dsl/rules/name_inference.rb +0 -5
  28. data/lib/openhab/dsl/rules/terse.rb +1 -2
  29. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +17 -53
  30. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +0 -3
  31. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +1 -1
  32. data/lib/openhab/dsl/rules.rb +0 -21
  33. data/lib/openhab/dsl/thread_local.rb +2 -2
  34. data/lib/openhab/dsl/timer_manager.rb +3 -1
  35. data/lib/openhab/dsl/version.rb +1 -1
  36. data/lib/openhab/dsl.rb +12 -105
  37. data/lib/openhab/log.rb +2 -2
  38. data/lib/openhab/rspec/example_group.rb +42 -0
  39. data/lib/openhab/rspec/helpers.rb +31 -8
  40. data/lib/openhab/rspec/hooks.rb +3 -6
  41. data/lib/openhab/rspec/karaf.rb +45 -27
  42. data/lib/openhab/rspec/mocks/synchronous_executor.rb +11 -4
  43. data/lib/openhab/rspec/mocks/timer.rb +2 -1
  44. data/lib/openhab/rspec/suspend_rules.rb +4 -2
  45. metadata +23 -2
@@ -6,19 +6,12 @@ module OpenHAB
6
6
  module Triggers
7
7
  # @!visibility private
8
8
  module Conditions
9
- #
10
- # this is a no-op condition which simply executes the provided block
11
- #
12
-
13
9
  #
14
10
  # Struct capturing data necessary for a conditional trigger
15
11
  #
16
- # TriggerDelay = Struct.new(:to, :from, :duration, :timer, :tracking_to, keyword_init: true) do
17
- # def timer_active?
18
- # timer&.active?
19
- # end
20
- # end
21
12
  class Duration
13
+ attr_accessor :rule
14
+
22
15
  #
23
16
  # Create a new duration condition
24
17
  # @param [Object] to optional condition on to state
@@ -39,7 +32,17 @@ module OpenHAB
39
32
  # @param [Hash] inputs inputs from trigger
40
33
  #
41
34
  def process(mod:, inputs:, &block)
42
- process_trigger_delay(mod, inputs, &block)
35
+ if @timer&.active?
36
+ process_active_timer(inputs, mod, &block)
37
+ elsif check_trigger_guards(inputs)
38
+ logger.trace("Trigger Guards Matched for #{self}, delaying rule execution")
39
+ # Add timer and attach timer to delay object, and also state being tracked to so
40
+ # timer can be cancelled if state changes
41
+ # Also another timer should not be created if changed to same value again but instead rescheduled
42
+ create_trigger_delay_timer(inputs, mod, &block)
43
+ else
44
+ logger.trace("Trigger Guards did not match for #{self}, ignoring trigger.")
45
+ end
43
46
  end
44
47
 
45
48
  # Cleanup any resources from the condition
@@ -51,13 +54,6 @@ module OpenHAB
51
54
 
52
55
  private
53
56
 
54
- #
55
- # Checks if there is an active timer
56
- # @return [true, false] true if the timer exists and is active, false otherwise
57
- def timer_active?
58
- @timer&.active?
59
- end
60
-
61
57
  #
62
58
  # Check if trigger guards prevent rule execution
63
59
  #
@@ -79,44 +75,12 @@ module OpenHAB
79
75
  # @return [Array] An array of the values for [newState, oldState] or [newStatus, oldStatus]
80
76
  #
81
77
  def retrieve_states(inputs)
82
- new_state = inputs["newState"] || thing_status_to_sym(inputs["newStatus"])
83
- old_state = inputs["oldState"] || thing_status_to_sym(inputs["oldStatus"])
78
+ new_state = inputs["newState"] || inputs["newStatus"]&.to_s&.downcase&.to_sym
79
+ old_state = inputs["oldState"] || inputs["oldStatus"]&.to_s&.downcase&.to_sym
84
80
 
85
81
  [new_state, old_state]
86
82
  end
87
83
 
88
- #
89
- # Converts a ThingStatus object to a ruby Symbol
90
- #
91
- # @param [org.openhab.core.thing.ThingStatus] status A ThingStatus instance
92
- #
93
- # @return [Symbol] A corresponding symbol, in lower case
94
- #
95
- def thing_status_to_sym(status)
96
- status&.to_s&.downcase&.to_sym
97
- end
98
-
99
- #
100
- # Process any matching trigger delays
101
- #
102
- # @param [Map] mod OpenHAB map object describing rule trigger
103
- # @param [Map] inputs OpenHAB map object describing rule trigger
104
- #
105
- #
106
- def process_trigger_delay(mod, inputs, &block)
107
- if timer_active?
108
- process_active_timer(inputs, mod, &block)
109
- elsif check_trigger_guards(inputs)
110
- logger.trace("Trigger Guards Matched for #{self}, delaying rule execution")
111
- # Add timer and attach timer to delay object, and also state being tracked to so
112
- # timer can be cancelled if state changes
113
- # Also another timer should not be created if changed to same value again but instead rescheduled
114
- create_trigger_delay_timer(inputs, mod, &block)
115
- else
116
- logger.trace("Trigger Guards did not match for #{self}, ignoring trigger.")
117
- end
118
- end
119
-
120
84
  #
121
85
  # Creates a timer for trigger delays
122
86
  #
@@ -131,6 +95,7 @@ module OpenHAB
131
95
  @timer = nil
132
96
  yield
133
97
  end
98
+ rule.on_removal(self)
134
99
  @tracking_to, = retrieve_states(inputs)
135
100
  end
136
101
 
@@ -140,7 +105,6 @@ module OpenHAB
140
105
  # @param [Hash] inputs rule trigger inputs
141
106
  # @param [Hash] mod rule trigger mods
142
107
  #
143
- #
144
108
  def process_active_timer(inputs, mod, &block)
145
109
  state, = retrieve_states(inputs)
146
110
  if state == @tracking_to
@@ -150,7 +114,7 @@ module OpenHAB
150
114
  logger.trace("Item changed to #{state} for #{self}, canceling timer.")
151
115
  @timer.cancel
152
116
  # Reprocess trigger delay after canceling to track new state (if guards matched, etc)
153
- process_trigger_delay(mod, inputs, &block)
117
+ process(mod: mod, inputs: inputs, &block)
154
118
  end
155
119
  end
156
120
  end
@@ -84,9 +84,6 @@ module OpenHAB
84
84
  yield if check_procs(inputs: inputs)
85
85
  end
86
86
 
87
- # Cleanup any resources from the condition
88
- def cleanup; end
89
-
90
87
  #
91
88
  # Check if command condition match the proc
92
89
  # @param [Hash] inputs from trigger must be supplied if state is not supplied
@@ -98,8 +98,8 @@ module OpenHAB
98
98
  #
99
99
  def watch_event_handler(glob)
100
100
  lambda { |watch_event|
101
- logger.trace("Received event(#{watch_event})")
102
101
  if watch_event.path.fnmatch?(glob)
102
+ logger.trace("Received event(#{watch_event})")
103
103
  @rule_engine_callback&.triggered(@trigger, { "event" => watch_event })
104
104
  else
105
105
  logger.trace("Event #{watch_event} did not match glob(#{glob})")
@@ -1,29 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "script_handling"
4
-
5
3
  module OpenHAB
6
4
  module DSL
7
5
  module Rules
8
- @script_rules = {}
9
-
10
- @scripted_rule_provider = OSGi.service(
11
- "org.openhab.core.automation.module.script.rulesupport.shared.ScriptedRuleProvider"
12
- )
13
- class << self
14
- # @!visibility private
15
- attr_reader :script_rules, :scripted_rule_provider
16
-
17
- #
18
- # Cleanup rules in this script file
19
- #
20
- # @return [void]
21
- #
22
- def cleanup_rules
23
- script_rules.each_value(&:cleanup)
24
- end
25
- end
26
- Core::ScriptHandling.script_unloaded { cleanup_rules }
27
6
  end
28
7
  end
29
8
  end
@@ -11,12 +11,12 @@ module OpenHAB
11
11
 
12
12
  # Keys to persist
13
13
  KNOWN_KEYS = %i[
14
+ openhab_ensure_states
15
+ openhab_persistence_service
14
16
  openhab_providers
15
17
  openhab_rule_uid
16
18
  openhab_rule_type
17
19
  openhab_units
18
- openhab_persistence_service
19
- openhab_ensure_states
20
20
  ].freeze
21
21
 
22
22
  #
@@ -185,9 +185,11 @@ module OpenHAB
185
185
  # @return [void]
186
186
  #
187
187
  def cancel_all
188
- logger.trace("Canceling #{@timers.length} timers")
188
+ logged = false
189
189
  # don't use #each, in case timers are scheduling more timers
190
190
  until @timers.empty?
191
+ logger.trace("Canceling #{@timers.length} timers") unless logged
192
+ logged = true
191
193
  timer = @timers.keys.first
192
194
  timer.cancel
193
195
  end
@@ -4,6 +4,6 @@ module OpenHAB
4
4
  module DSL
5
5
  # Version of OpenHAB helper libraries
6
6
  # @return [String]
7
- VERSION = "5.0.0.rc2"
7
+ VERSION = "5.0.0.rc3"
8
8
  end
9
9
  end
data/lib/openhab/dsl.rb CHANGED
@@ -41,80 +41,14 @@ module OpenHAB
41
41
 
42
42
  # @!group Rule Creation
43
43
 
44
- #
45
- # Create a new rule
46
- #
47
- # @param [String] name The rule name
48
- # @yield Block executed in the context of a {Rules::Builder}
49
- # @yieldparam [Rules::Builder] rule
50
- # Optional parameter to access the rule configuration from within execution blocks and guards.
51
- # @return [org.openhab.core.automation.Rule] The OpenHAB Rule object
52
- #
53
- # @see OpenHAB::DSL::Rules::Builder Rule builder for details on rule triggers, guards and execution blocks
54
- # @see Rules::Terse Terse Rules
55
- #
56
- # @example
57
- # require "openhab/dsl"
58
- #
59
- # rule "name" do
60
- # <zero or more triggers>
61
- # <zero or more execution blocks>
62
- # <zero or more guards>
63
- # end
64
- #
65
- def rule(name = nil, id: nil, script: nil, binding: nil, &block)
66
- raise ArgumentError, "Block is required" unless block
67
-
68
- id ||= Rules::NameInference.infer_rule_id_from_name(name) if name
69
- id ||= Rules::NameInference.infer_rule_id_from_block(block)
70
- script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
71
-
72
- builder = nil
73
-
74
- ThreadLocal.thread_local(openhab_rule_type: "rule", openhab_rule_uid: id) do
75
- builder = Rules::Builder.new(binding || block.binding)
76
- builder.uid(id)
77
- builder.instance_exec(builder, &block)
78
- builder.guard = Rules::Guard.new(run_context: builder.caller, only_if: builder.only_if,
79
- not_if: builder.not_if)
80
-
81
- name ||= Rules::NameInference.infer_rule_name(builder)
82
- name ||= id
83
-
84
- builder.name(name)
85
- logger.trace { builder.inspect }
86
- builder.build(script)
87
- end
44
+ # (see Rules::Builder#rule)
45
+ def rule(name = nil, **kwargs, &block)
46
+ rules.build { rule(name, **kwargs, &block) }
88
47
  end
89
48
 
90
- #
91
- # Create a new script
92
- #
93
- # A script is a rule with no triggers. It can be called by various other actions,
94
- # such as the Run Rules action.
95
- #
96
- # @param [String] id The script's ID
97
- # @param [String] name A descriptive name
98
- # @yield [] Block executed when the script is executed.
99
- #
100
- def script(name = nil, id: nil, script: nil, &block)
101
- raise ArgumentError, "Block is required" unless block
102
-
103
- id ||= Rules::NameInference.infer_rule_id_from_name(name) if name
104
- id ||= Rules::NameInference.infer_rule_id_from_block(block)
105
- name ||= id
106
- script ||= block.source rescue nil # rubocop:disable Style/RescueModifier
107
-
108
- builder = nil
109
- ThreadLocal.thread_local(openhab_rule_type: "script", openhab_rule_uid: id) do
110
- builder = Rules::Builder.new(block.binding)
111
- builder.uid(id)
112
- builder.tags(["Script"])
113
- builder.name(name)
114
- builder.script(&block)
115
- logger.trace { builder.inspect }
116
- builder.build(script)
117
- end
49
+ # (see Rules::Builder#script)
50
+ def script(name = nil, id: nil, **kwargs, &block)
51
+ rules.build { script(name, id: id, **kwargs, &block) }
118
52
  end
119
53
 
120
54
  # @!group Rule Support
@@ -170,44 +104,17 @@ module OpenHAB
170
104
  end
171
105
  end
172
106
 
173
- #
174
- # Remove a rule
175
- #
176
- # @param [String, org.openhab.core.automation.Rule] uid The rule UID or the Rule object to remove.
177
- # @return [void]
178
- #
179
- # @example
180
- # my_rule = rule do
181
- # every :day
182
- # run { nil }
183
- # end
184
- #
185
- # remove_rule(my_rule)
186
- #
187
- def remove_rule(uid)
188
- uid = uid.uid if uid.respond_to?(:uid)
189
- automation_rule = Rules.script_rules.delete(uid)
190
- raise "Rule #{uid} doesn't exist to remove" unless automation_rule
191
-
192
- automation_rule.cleanup
193
- # automation_manager doesn't have a remove method, so just have to
194
- # remove it directly from the provider
195
- Rules.scripted_rule_provider.remove_rule(uid)
196
- end
107
+ # @!group Object Access
197
108
 
198
109
  #
199
- # Manually trigger a rule by ID
110
+ # Fetches all rules from the rule registry.
200
111
  #
201
- # @param [String] uid The rule ID
202
- # @param [Object, nil] event The event to pass to the rule's execution blocks.
203
- # @return [void]
112
+ # @return [Core::Rules::Registry]
204
113
  #
205
- def trigger_rule(uid, event = nil)
206
- Rules.script_rules.fetch(uid).execute(nil, { "event" => event })
114
+ def rules
115
+ Core::Rules::Registry.instance
207
116
  end
208
117
 
209
- # @!group Object Access
210
-
211
118
  #
212
119
  # Fetches all items from the item registry
213
120
  #
@@ -296,7 +203,7 @@ module OpenHAB
296
203
  # different files.
297
204
  #
298
205
  # @see timers
299
- # @see Rules::Builder#changed
206
+ # @see Rules::BuilderDSL#changed
300
207
  # @see Items::TimedCommand
301
208
  #
302
209
  # @param [java.time.temporal.TemporalAmount, #to_zoned_date_time, Proc] duration
data/lib/openhab/log.rb CHANGED
@@ -332,8 +332,8 @@ module OpenHAB
332
332
  rule_type = Thread.current[:openhab_rule_type]
333
333
  full_id = "#{rule_type}:#{rule_uid}"
334
334
 
335
- self.class.rule_loggers[full_id] ||= Logger.new("#{Logger::PREFIX}.#{rule_type}.#{rule_uid.tr_s(":", "_")
336
- .gsub(/[^A-Za-z0-9_.-]/, "")}")
335
+ self.class.rule_loggers[full_id] ||= Logger.new("#{Logger::PREFIX}.#{rule_type}.#{rule_uid
336
+ .gsub(/[^A-Za-z0-9_.:-]/, "")}")
337
337
  end
338
338
 
339
339
  extend Forwardable
@@ -79,6 +79,48 @@ module OpenHAB
79
79
 
80
80
  true
81
81
  end
82
+
83
+ # @!attribute [w] propagate_exceptions
84
+ #
85
+ # Set if exceptions in rules should be propagated in specs, instead of just logged.
86
+ #
87
+ # @param value [true, false, nil]
88
+ # @return [true, false, nil]
89
+ #
90
+ # @example
91
+ # describe "my_rule" do
92
+ # self.propagate_exceptions = false
93
+ #
94
+ # it "logs exceptions in rule execution" do
95
+ # expect(self.class.propagate_exceptions?).to be false
96
+ # rule do
97
+ # on_start
98
+ # run { raise "exception is logged" }
99
+ # end
100
+ # expect(spec_log_lines).to include(match(/exception is logged/))
101
+ # end
102
+ # end
103
+ #
104
+ def propagate_exceptions=(value)
105
+ @propagate_exceptions = value
106
+ end
107
+
108
+ #
109
+ # If timers are mocked for this example group
110
+ #
111
+ # It will search through parent groups until it finds one where it's
112
+ # explicitly defined, or defaults to `true` if none are.
113
+ #
114
+ # @return [true, false]
115
+ #
116
+ def propagate_exceptions?
117
+ if instance_variable_defined?(:@propagate_exceptions) && !@propagate_exceptions.nil?
118
+ return @propagate_exceptions
119
+ end
120
+ return superclass.propagate_exceptions? if superclass.is_a?(ClassMethods)
121
+
122
+ true
123
+ end
82
124
  end
83
125
 
84
126
  # @!visibility private
@@ -86,11 +86,16 @@ module OpenHAB
86
86
  end
87
87
 
88
88
  @autoupdated_items = []
89
+ @spec_metadata_provider = Core::Items::Metadata::Provider.current
89
90
 
90
91
  $ir.for_each do |_provider, item|
91
- if (hash = item.metadata.delete("autoupdate"))
92
- @autoupdated_items << hash
93
- item.metadata["autoupdate"] = "true"
92
+ if (hash = item.metadata["autoupdate"])
93
+ provider = Core::Items::Metadata::Provider.registry.provider_for(hash.uid)
94
+ provider.remove(hash.uid)
95
+ @autoupdated_items << [provider, hash]
96
+ provider(@spec_metadata_provider) do
97
+ item.metadata["autoupdate"] = "true"
98
+ end
94
99
  end
95
100
  end
96
101
  end
@@ -142,13 +147,13 @@ module OpenHAB
142
147
  # @yield
143
148
  # @return [void]
144
149
  def wait(how_long = 2.seconds)
145
- now = Time.now
150
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
146
151
 
147
152
  begin
148
153
  yield
149
154
  rescue ::RSpec::Expectations::ExpectationNotMetError,
150
155
  ::RSpec::Mocks::MockExpectationError
151
- raise if Time.now > now + how_long
156
+ raise if Process.clock_gettime(Process::CLOCK_MONOTONIC) > start + how_long.to_f
152
157
 
153
158
  sleep 0.1
154
159
  retry
@@ -177,6 +182,17 @@ module OpenHAB
177
182
  # @return [void]
178
183
  #
179
184
  def autorequires
185
+ ENV["RUBYLIB"] ||= ""
186
+ ENV["RUBYLIB"] += ":" unless ENV["RUBYLIB"].empty?
187
+ ENV["RUBYLIB"] += rubylib_dir
188
+
189
+ $LOAD_PATH.unshift(*ENV["RUBYLIB"]
190
+ .split(File::PATH_SEPARATOR)
191
+ .reject(&:empty?)
192
+ .reject do |path|
193
+ $LOAD_PATH.include?(path)
194
+ end)
195
+
180
196
  requires = jrubyscripting_config&.get("require") || ""
181
197
  requires.split(",").each do |f|
182
198
  require f.strip
@@ -202,9 +218,6 @@ module OpenHAB
202
218
  karaf.use_root_instance = use_root_instance
203
219
  main = karaf.launch
204
220
 
205
- ENV["RUBYLIB"] ||= ""
206
- ENV["RUBYLIB"] += ":" unless ENV["RUBYLIB"].empty?
207
- ENV["RUBYLIB"] += rubylib_dir
208
221
  require "openhab/dsl"
209
222
 
210
223
  require_relative "mocks/persistence_service"
@@ -411,6 +424,16 @@ module OpenHAB
411
424
  autoupdate_provider.add(metadata)
412
425
  end
413
426
  end
427
+
428
+ def restore_autoupdate_items
429
+ return unless instance_variable_defined?(:@autoupdated_items)
430
+
431
+ @autoupdated_items.each do |(provider, hash)|
432
+ @spec_metadata_provider.remove(hash.uid)
433
+ provider.add(hash.instance_variable_get(:@metadata))
434
+ end
435
+ @autoupdated_items = nil
436
+ end
414
437
  end
415
438
 
416
439
  if defined?(::RSpec)
@@ -38,12 +38,12 @@ module OpenHAB
38
38
  item.state = NULL unless item.raw_state == NULL
39
39
  end
40
40
  end
41
- @known_rules = Core.rule_registry.all.map(&:uid)
42
41
  end
43
42
 
44
43
  # Each spec gets temporary providers
45
44
  [Core::Items::Provider,
46
45
  Core::Items::Metadata::Provider,
46
+ Core::Rules::Provider,
47
47
  Core::Things::Provider,
48
48
  Core::Things::Links::Provider].each do |klass|
49
49
  config.around do |example|
@@ -53,7 +53,7 @@ module OpenHAB
53
53
 
54
54
  config.before do |example|
55
55
  # clear persisted thing status
56
- tm = OSGi.service("org.openhab.core.thing.ThingManager")
56
+ tm = Core::Things.manager
57
57
  tm.class.field_reader :storage
58
58
  tm.storage.keys.each { |k| tm.storage.remove(k) } # rubocop:disable Style/HashEachMethods not a hash
59
59
 
@@ -70,10 +70,6 @@ module OpenHAB
70
70
  end
71
71
 
72
72
  config.after do
73
- # remove rules created during the spec
74
- (Core.rule_registry.all.map(&:uid) - @known_rules).each do |uid|
75
- remove_rule(uid) if defined?(remove_rule)
76
- end
77
73
  Core::Items::Proxy.reset_cache
78
74
  Core::Things::Proxy.reset_cache
79
75
  @profile_factory_registration.unregister
@@ -82,6 +78,7 @@ module OpenHAB
82
78
  # wipe this
83
79
  DSL::Items::TimedCommand.timed_commands.clear
84
80
  Timecop.return
81
+ restore_autoupdate_items
85
82
  Mocks::PersistenceService.instance.reset
86
83
  end
87
84
  end