openhab-jrubyscripting 5.0.0.rc2 → 5.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
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