openhab-jrubyscripting 5.0.0.rc10 → 5.0.0.rc12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/lib/openhab/core/actions/audio.rb +47 -0
  3. data/lib/openhab/core/actions/ephemeris.rb +39 -0
  4. data/lib/openhab/core/actions/exec.rb +51 -0
  5. data/lib/openhab/core/actions/http.rb +80 -0
  6. data/lib/openhab/core/actions/ping.rb +30 -0
  7. data/lib/openhab/core/actions/transformation.rb +32 -0
  8. data/lib/openhab/core/actions/voice.rb +36 -0
  9. data/lib/openhab/core/actions.rb +23 -120
  10. data/lib/openhab/core/{events → dto}/item_channel_link.rb +1 -4
  11. data/lib/openhab/core/{events → dto}/thing.rb +10 -12
  12. data/lib/openhab/core/dto.rb +11 -0
  13. data/lib/openhab/core/entity_lookup.rb +1 -1
  14. data/lib/openhab/core/events/abstract_event.rb +1 -0
  15. data/lib/openhab/core/events/abstract_item_registry_event.rb +36 -0
  16. data/lib/openhab/core/events/abstract_thing_registry_event.rb +40 -0
  17. data/lib/openhab/core/events/item_command_event.rb +1 -1
  18. data/lib/openhab/core/events/item_state_changed_event.rb +6 -6
  19. data/lib/openhab/core/events/item_state_event.rb +6 -6
  20. data/lib/openhab/core/events/thing_status_info_event.rb +8 -6
  21. data/lib/openhab/core/items/date_time_item.rb +3 -2
  22. data/lib/openhab/core/items/generic_item.rb +92 -1
  23. data/lib/openhab/core/items/item.rb +9 -8
  24. data/lib/openhab/core/items/metadata/hash.rb +1 -1
  25. data/lib/openhab/core/items/metadata/namespace_hash.rb +10 -2
  26. data/lib/openhab/core/items/metadata/provider.rb +2 -2
  27. data/lib/openhab/core/items/persistence.rb +99 -21
  28. data/lib/openhab/core/items/player_item.rb +1 -1
  29. data/lib/openhab/core/items/proxy.rb +20 -14
  30. data/lib/openhab/core/items/registry.rb +12 -1
  31. data/lib/openhab/core/items/state_storage.rb +2 -2
  32. data/lib/openhab/core/items.rb +3 -3
  33. data/lib/openhab/core/profile_factory.rb +3 -1
  34. data/lib/openhab/core/proxy.rb +130 -0
  35. data/lib/openhab/core/registry.rb +12 -2
  36. data/lib/openhab/core/rules.rb +1 -1
  37. data/lib/openhab/core/things/links/provider.rb +39 -1
  38. data/lib/openhab/core/things/proxy.rb +8 -0
  39. data/lib/openhab/core/things/registry.rb +4 -0
  40. data/lib/openhab/core/timer.rb +3 -19
  41. data/lib/openhab/core/types/date_time_type.rb +3 -2
  42. data/lib/openhab/core/types/decimal_type.rb +1 -1
  43. data/lib/openhab/core/types/un_def_type.rb +2 -2
  44. data/lib/openhab/core/value_cache.rb +1 -1
  45. data/lib/openhab/core.rb +3 -3
  46. data/lib/openhab/core_ext/ephemeris.rb +53 -0
  47. data/lib/openhab/core_ext/java/class.rb +1 -1
  48. data/lib/openhab/core_ext/java/duration.rb +27 -1
  49. data/lib/openhab/core_ext/java/local_date.rb +17 -7
  50. data/lib/openhab/core_ext/java/local_time.rb +13 -3
  51. data/lib/openhab/core_ext/java/month.rb +1 -1
  52. data/lib/openhab/core_ext/java/month_day.rb +15 -3
  53. data/lib/openhab/core_ext/java/period.rb +1 -1
  54. data/lib/openhab/core_ext/java/temporal_amount.rb +1 -1
  55. data/lib/openhab/core_ext/java/time.rb +5 -1
  56. data/lib/openhab/core_ext/java/zoned_date_time.rb +100 -2
  57. data/lib/openhab/core_ext/ruby/date.rb +4 -2
  58. data/lib/openhab/core_ext/ruby/date_time.rb +1 -0
  59. data/lib/openhab/core_ext/ruby/numeric.rb +6 -1
  60. data/lib/openhab/core_ext/ruby/time.rb +1 -0
  61. data/lib/openhab/dsl/debouncer.rb +259 -0
  62. data/lib/openhab/dsl/items/builder.rb +29 -14
  63. data/lib/openhab/dsl/items/timed_command.rb +31 -13
  64. data/lib/openhab/dsl/rules/automation_rule.rb +30 -44
  65. data/lib/openhab/dsl/rules/builder.rb +404 -39
  66. data/lib/openhab/dsl/rules/guard.rb +12 -54
  67. data/lib/openhab/dsl/rules/name_inference.rb +11 -0
  68. data/lib/openhab/dsl/rules/property.rb +3 -4
  69. data/lib/openhab/dsl/rules/terse.rb +4 -1
  70. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +5 -6
  71. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +1 -0
  72. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +19 -31
  73. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +1 -0
  74. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +22 -30
  75. data/lib/openhab/dsl/things/builder.rb +1 -1
  76. data/lib/openhab/dsl/thread_local.rb +1 -0
  77. data/lib/openhab/dsl/version.rb +1 -1
  78. data/lib/openhab/dsl.rb +251 -14
  79. data/lib/openhab/rspec/helpers.rb +3 -2
  80. data/lib/openhab/rspec/hooks.rb +6 -2
  81. data/lib/openhab/rspec/karaf.rb +7 -0
  82. data/lib/openhab/rspec/mocks/instance_method_stasher.rb +22 -0
  83. data/lib/openhab/rspec/mocks/space.rb +23 -0
  84. data/lib/openhab/rspec/mocks/timer.rb +33 -0
  85. data/lib/openhab/rspec/openhab/core/actions.rb +16 -4
  86. data/lib/openhab/rspec/openhab/core/items/proxy.rb +1 -13
  87. data/lib/openhab/rspec/suspend_rules.rb +1 -14
  88. data/lib/openhab/rspec.rb +9 -0
  89. data/lib/openhab/yard/base_helper.rb +19 -0
  90. data/lib/openhab/yard/code_objects/group_object.rb +9 -3
  91. data/lib/openhab/yard/coderay.rb +17 -0
  92. data/lib/openhab/yard/handlers/jruby/base.rb +10 -1
  93. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +3 -0
  94. data/lib/openhab/yard/html_helper.rb +49 -15
  95. data/lib/openhab/yard/markdown_helper.rb +135 -0
  96. data/lib/openhab/yard.rb +6 -0
  97. metadata +36 -4
data/lib/openhab/dsl.rb CHANGED
@@ -37,6 +37,13 @@ module OpenHAB
37
37
  public_class_method(*mod.private_instance_methods)
38
38
  end
39
39
 
40
+ class << self
41
+ # @!visibility private
42
+ attr_reader :debouncers
43
+ end
44
+
45
+ @debouncers = java.util.concurrent.ConcurrentHashMap.new
46
+
40
47
  module_function
41
48
 
42
49
  # @!group Rule Creation
@@ -53,6 +60,8 @@ module OpenHAB
53
60
 
54
61
  # @!group Rule Support
55
62
 
63
+ # rubocop:disable Layout/LineLength
64
+
56
65
  #
57
66
  # Defines a new profile that can be applied to item channel links.
58
67
  #
@@ -79,8 +88,8 @@ module OpenHAB
79
88
  # @see org.openhab.thing.Profile
80
89
  # @see org.openhab.thing.StateProfile
81
90
  #
82
- # @example
83
- # profile(:veto_closing_shades) do |event, item:, command: nil|
91
+ # @example Vetoing a command
92
+ # profile(:veto_closing_shades) do |event, item:, command:|
84
93
  # next false if command&.down?
85
94
  #
86
95
  # true
@@ -94,9 +103,37 @@ module OpenHAB
94
103
  # # can also be referenced from an `.items` file:
95
104
  # # Rollershutter MyShade { channel="thing:rollershutter"[profile="ruby:veto_closing_shades"] }
96
105
  #
106
+ # @example Overriding units from a binding
107
+ # profile(:set_uom) do |event, configuration:, state:, command:|
108
+ # unless configuration["unit"]
109
+ # logger.warn("Unit configuration not provided for set_uom profile")
110
+ # next true
111
+ # end
112
+ #
113
+ # case event
114
+ # when :state_from_handler
115
+ # next true unless state.is_a?(DecimalType) || state.is_a?(QuantityType) # what is it then?!
116
+ #
117
+ # state = state.to_d if state.is_a?(QuantityType) # ignore the units if QuantityType was given
118
+ # callback.send_update(state | configuration["unit"])
119
+ # false
120
+ # when :command_from_item
121
+ # # strip the unit from the command, as the binding likely can't handle it
122
+ # next true unless command.is_a?(QuantityType)
123
+ #
124
+ # callback.send_command(DecimalType.new(command.to_d))
125
+ # false
126
+ # else
127
+ # true # pass other events through as normal
128
+ # end
129
+ # end
130
+ # # can also be referenced from an `.items` file:
131
+ # # Number:Temperature MyTempWithNonUnitValueFromBinding "I prefer Celsius [%d °C]" { channel="something_that_returns_F"[profile="ruby:set_uom", unit="°F"] }
132
+ #
97
133
  def profile(id, &block)
98
134
  raise ArgumentError, "Block is required" unless block
99
135
 
136
+ id = id.to_s
100
137
  uid = org.openhab.core.thing.profiles.ProfileTypeUID.new("ruby", id)
101
138
 
102
139
  ThreadLocal.thread_local(openhab_rule_type: "profile", openhab_rule_uid: id) do
@@ -104,6 +141,8 @@ module OpenHAB
104
141
  end
105
142
  end
106
143
 
144
+ # rubocop:enable Layout/LineLength
145
+
107
146
  # @!group Object Access
108
147
 
109
148
  #
@@ -113,8 +152,6 @@ module OpenHAB
113
152
  #
114
153
  # @see Core::ValueCache ValueCache
115
154
  #
116
- # @since openHAB 3.4.0
117
- #
118
155
  def shared_cache
119
156
  $sharedCache
120
157
  end
@@ -335,6 +372,133 @@ module OpenHAB
335
372
  Range.new(start, finish, range.exclude_end?)
336
373
  end
337
374
 
375
+ #
376
+ # Limits the frequency of calls to the given block within a given amount of time.
377
+ #
378
+ # It can be useful to throttle certain actions even when a rule is triggered too often.
379
+ # This can be used to debounce triggers in a UI rule.
380
+ #
381
+ # @param (see Debouncer#initialize)
382
+ # @param [Object] id ID to associate with this debouncer.
383
+ # @param [Block] block The block to be debounced.
384
+ #
385
+ # @return [void]
386
+ #
387
+ # @example Run at most once per second even when being called more frequently
388
+ # (1..100).each do
389
+ # debounce for: 1.second do
390
+ # logger.info "This will not be logged more frequently than every 1 second"
391
+ # end
392
+ # sleep 0.1
393
+ # end
394
+ #
395
+ # @see Debouncer Debouncer class
396
+ # @see Rules::BuilderDSL#debounce debounce rule guard
397
+ #
398
+ # @!visibility private
399
+ def debounce(for:, leading: false, idle_time: nil, id: nil, &block)
400
+ interval = binding.local_variable_get(:for)
401
+ id ||= block.source_location
402
+ DSL.debouncers.compute(id) do |_key, debouncer|
403
+ debouncer ||= Debouncer.new(for: interval, leading: leading, idle_time: idle_time)
404
+ debouncer.call(&block)
405
+ debouncer
406
+ end
407
+ end
408
+
409
+ #
410
+ # Waits until calls to this method have stopped firing for a period of time
411
+ # before executing the block.
412
+ #
413
+ # This method acts as a guard for the given block to ensure that it doesn't get executed
414
+ # too frequently. The debounce_for method can be called as frequently as possible.
415
+ # The given block, however, will only be executed once the `debounce_time` has passed
416
+ # since the last call to debounce_for.
417
+ #
418
+ # This method can be used from within a UI rule as well as from a file-based rule.
419
+ #
420
+ # @param (see Rules::BuilderDSL#debounce_for)
421
+ # @param [Object] id ID to associate with this call.
422
+ # @param [Block] block The block to be debounced.
423
+ #
424
+ # @return [void]
425
+ #
426
+ # @example Run a block of code only after an item has stopped changing
427
+ # # This can be placed inside a UI rule with an Item Change trigger for
428
+ # # a Door contact sensor.
429
+ # # If the door state has stopped changing state for 10 minutes,
430
+ # # execute the block.
431
+ # debounce_for(10.minutes) do
432
+ # if DoorState.open?
433
+ # Voice.say("The door has been left open!")
434
+ # end
435
+ # end
436
+ #
437
+ # @see Rules::BuilderDSL#debounce_for Rule builder's debounce_for for a detailed description
438
+ #
439
+ def debounce_for(debounce_time, id: nil, &block)
440
+ idle_time = debounce_time.is_a?(Range) ? debounce_time.begin : debounce_time
441
+ debounce(for: debounce_time, idle_time: idle_time, id: id, &block)
442
+ end
443
+
444
+ #
445
+ # Rate-limits block executions by delaying calls and only executing the last
446
+ # call within the given duration.
447
+ #
448
+ # When throttle_for is called, it will hold from executing the block and start
449
+ # a fixed timer for the given duration. Should more calls occur during
450
+ # this time, keep holding and once the wait time is over, execute the block.
451
+ #
452
+ # {throttle_for} will execute the block after it had waited for the given duration,
453
+ # regardless of how frequently `throttle_for` was called.
454
+ # In contrast, {debounce_for} will wait until there is a minimum interval
455
+ # between two triggers.
456
+ #
457
+ # {throttle_for} is ideal in situations where regular status updates need to be made
458
+ # for frequently changing values. It is also useful when a rule responds to triggers
459
+ # from multiple related items that are updated at around the same time. Instead of
460
+ # executing the rule multiple times, {throttle_for} will wait for a pre-set amount
461
+ # of time since the first group of triggers occurred before executing the rule.
462
+ #
463
+ # @param (see Rules::BuilderDSL#throttle_for)
464
+ # @param [Object] id ID to associate with this call.
465
+ # @param [Block] block The block to be throttled.
466
+ #
467
+ # @return [void]
468
+ #
469
+ # @see Rules::BuilderDSL#debounce_for Rule builder's debounce_for for a detailed description
470
+ # @see Rules::BuilderDSL#throttle_for
471
+ #
472
+ def throttle_for(duration, id: nil, &block)
473
+ debounce(for: duration, id: id, &block)
474
+ end
475
+
476
+ #
477
+ # Limit how often the given block executes to the specified interval.
478
+ #
479
+ # {only_every} will execute the given block but prevents further executions
480
+ # until the given interval has passed. In contrast, {throttle_for} will not
481
+ # execute the block immediately, and will wait until the end of the interval.
482
+ #
483
+ # @param (see Rules::BuilderDSL#only_every)
484
+ # @param [Object] id ID to associate with this call.
485
+ # @param [Block] block The block to be throttled.
486
+ #
487
+ # @return [void]
488
+ #
489
+ # @example Prevent door bell from ringing repeatedly
490
+ # # This can be called from a UI rule.
491
+ # # For file based rule, use the `only_every` rule guard
492
+ # only_every(30.seconds) do { Audio.play_sound("doorbell.mp3") }
493
+ #
494
+ # @see Rules::BuilderDSL#debounce_for Rule builder's debounce_for for a detailed description
495
+ # @see Rules::BuilderDSL#only_every
496
+ #
497
+ def only_every(interval, id: nil, &block)
498
+ interval = 1.send(interval) if %i[second minute hour day].include?(interval)
499
+ debounce(for: interval, leading: true, id: id, &block)
500
+ end
501
+
338
502
  #
339
503
  # Store states of supplied items
340
504
  #
@@ -357,9 +521,6 @@ module OpenHAB
357
521
  # end # the states will be restored here
358
522
  #
359
523
  def store_states(*items)
360
- items = items.flatten.map do |item|
361
- item.respond_to?(:__getobj__) ? item.__getobj__ : item
362
- end
363
524
  states = Core::Items::StateStorage.from_items(*items)
364
525
  if block_given?
365
526
  yield
@@ -438,18 +599,39 @@ module OpenHAB
438
599
  # Item1.average_since(12.hours)
439
600
  # end
440
601
  #
441
- # @see OpenHAB::Core::Items::Persistence
442
- #
443
- # @param [Object] service service either as a String or a Symbol
602
+ # @param [Object] service Persistence service either as a String or a Symbol
444
603
  # @yield [] Block executed in context of the supplied persistence service
445
604
  # @return [Object] The return value from the block.
446
605
  #
606
+ # @see persistence!
607
+ # @see OpenHAB::Core::Items::Persistence
608
+ #
447
609
  def persistence(service)
448
- old = Thread.current[:openhab_persistence_service]
449
- Thread.current[:openhab_persistence_service] = service
610
+ old = persistence!(service)
450
611
  yield
451
612
  ensure
452
- Thread.current[:openhab_persistence_service] = old
613
+ persistence!(old)
614
+ end
615
+
616
+ #
617
+ # Permanently sets the default persistence service for the current thread
618
+ #
619
+ # @note This method is only intended for use at the top level of rule
620
+ # scripts. If it's used within library methods, or hap-hazardly within
621
+ # rules, things can get very confusing because the prior state won't be
622
+ # properly restored.
623
+ #
624
+ # @param [Object] service Persistence service either as a String or a Symbol. When nil, use
625
+ # the system's default persistence service.
626
+ # @return [Object,nil] The previous persistence service settings, or nil when using the system's default.
627
+ #
628
+ # @see persistence
629
+ # @see OpenHAB::Core::Items::Persistence
630
+ #
631
+ def persistence!(service = nil)
632
+ old = Thread.current[:openhab_persistence_service]
633
+ Thread.current[:openhab_persistence_service] = service
634
+ old
453
635
  end
454
636
 
455
637
  #
@@ -624,7 +806,7 @@ module OpenHAB
624
806
  #
625
807
  # @overload provider!(things: nil, items: nil, metadata: nil, links: nil, **metadata_items)
626
808
  #
627
- # @param [Core::Provider, org.openhab.core.registry.common.ManagedProvider, :persistent, :transient, Proc] providers
809
+ # @param [Core::Provider, org.openhab.core.common.registry.ManagedProvider, :persistent, :transient, Proc] providers
628
810
  # An explicit provider to use. If it's a {Core::Provider}, the type will be inferred automatically.
629
811
  # Otherwise it's applied to all types.
630
812
  # @param [Hash] providers_by_type
@@ -703,6 +885,61 @@ module OpenHAB
703
885
  old_providers
704
886
  end
705
887
 
888
+ #
889
+ # @see CoreExt::Ephemeris
890
+ #
891
+ # @overload holiday_file(file)
892
+ #
893
+ # Sets a thread local variable to use a specific holiday file
894
+ # for {CoreExt::Ephemeris ephemeris calls} inside the block.
895
+ #
896
+ # @param [String, nil] file Path to a file defining holidays;
897
+ # `nil` to reset to default.
898
+ # @yield [] Block executed in context of the supplied holiday file
899
+ # @return [Object] The return value from the block.
900
+ #
901
+ # @example Set a specific holiday configuration file temporarily
902
+ # holiday_file("/home/cody/holidays.xml") do
903
+ # Time.now.next_holiday
904
+ # end
905
+ #
906
+ # @see holiday_file!
907
+ #
908
+ # @overload holiday_file
909
+ #
910
+ # Returns the current thread local value for the holiday file.
911
+ #
912
+ # @return [String, nil] the current holiday file
913
+ #
914
+ def holiday_file(*args)
915
+ raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0..1)" if args.length > 1
916
+
917
+ old = Thread.current[:openhab_holiday_file]
918
+ return old if args.empty?
919
+
920
+ holiday_file!(args.first)
921
+ yield
922
+ ensure
923
+ holiday_file!(old)
924
+ end
925
+
926
+ #
927
+ # Sets a thread local variable to set the default holiday file.
928
+ #
929
+ # @see https://github.com/svendiedrichsen/jollyday/tree/master/src/main/resources/holidays Example data files from the source
930
+ #
931
+ # @example
932
+ # holiday_file!("/home/cody/holidays.xml")
933
+ # Time.now.next_holiday
934
+ #
935
+ # @param [String, nil] file Path to a file defining holidays;
936
+ # `nil` to reset to default.
937
+ # @return [Symbol, nil] the new holiday file
938
+ #
939
+ def holiday_file!(file = nil)
940
+ Thread.current[:openhab_holiday_file] = file
941
+ end
942
+
706
943
  # @!visibility private
707
944
  def try_parse_time_like(string)
708
945
  return string unless string.is_a?(String)
@@ -265,12 +265,13 @@ module OpenHAB
265
265
  # @return [void]
266
266
  #
267
267
  def load_rules
268
- automation_path = "#{org.openhab.core.OpenHAB.config_folder}/automation/ruby"
268
+ automation_paths = Array(::RSpec.configuration.openhab_automation_search_paths)
269
+
269
270
  lib_dirs = rubylib_dirs.map { |d| File.join(d, "") }
270
271
  lib_dirs << File.join(gem_home, "")
271
272
 
272
273
  SuspendRules.suspend_rules do
273
- files = Dir["#{automation_path}/**/*.rb"]
274
+ files = automation_paths.map { |p| Dir["#{p}/**/*.rb"] }.flatten
274
275
  files.reject! do |f|
275
276
  lib_dirs.any? { |l| f.start_with?(l) }
276
277
  end
@@ -32,6 +32,11 @@ module OpenHAB
32
32
  config.include ExampleGroup
33
33
 
34
34
  config.before(:suite) do
35
+ if config.mock_framework.framework_name == :rspec
36
+ require_relative "mocks/instance_method_stasher"
37
+ require_relative "mocks/space"
38
+ end
39
+
35
40
  Helpers.autorequires unless Configuration.private_confdir
36
41
  Helpers.send(:set_up_autoupdates)
37
42
  Helpers.load_transforms
@@ -86,8 +91,6 @@ module OpenHAB
86
91
  end
87
92
 
88
93
  config.after do
89
- Core::Items::Proxy.reset_cache
90
- Core::Things::Proxy.reset_cache
91
94
  @profile_factory.unregister
92
95
  timers.cancel_all
93
96
  # timers and rules have already been canceled, so we can safely just
@@ -97,6 +100,7 @@ module OpenHAB
97
100
  restore_autoupdate_items
98
101
  Mocks::PersistenceService.instance.reset
99
102
  Hooks.cache_script_extension.sharedCache.clear if DSL.shared_cache
103
+ DSL.persistence!(nil)
100
104
  end
101
105
  end
102
106
  end
@@ -280,6 +280,13 @@ module OpenHAB
280
280
  props = cfg.properties || java.util.Hashtable.new
281
281
  props.put("default", "default")
282
282
  cfg.update(props)
283
+
284
+ # configure ephemeris with basic values
285
+ cfg = ca.get_configuration("org.openhab.ephemeris", nil)
286
+ props = cfg.properties || java.util.Hashtable.new
287
+ props.put("country", "us")
288
+ props.put("dayset-weekend", %w[SATURDAY SUNDAY])
289
+ cfg.update(props)
283
290
  end
284
291
  wait_for_service("org.openhab.core.automation.RuleManager") do |re|
285
292
  require_relative "mocks/synchronous_executor"
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module RSpec
5
+ module Mocks
6
+ module InstanceMethodStasher
7
+ ::RSpec::Mocks::InstanceMethodStasher.prepend(self)
8
+
9
+ # Disable "singleton on non-persistent Java type"
10
+ # it doesn't matter during specs
11
+ def initialize(*)
12
+ original_verbose = $VERBOSE
13
+ $VERBOSE = nil
14
+
15
+ super
16
+ ensure
17
+ $VERBOSE = original_verbose
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module RSpec
5
+ module Mocks
6
+ module Space
7
+ ::RSpec::Mocks::Space.prepend(self)
8
+
9
+ #
10
+ # When setting expectations on {Items::Proxy proxies}, set them against the item
11
+ # themselves, so that it will catch calls against `self` from within the instance.
12
+ #
13
+ def proxy_for(object)
14
+ return super unless ::RSpec.current_example&.example_group&.consistent_proxies?
15
+
16
+ object = object.__getobj__ if object.is_a?(Core::Items::Proxy)
17
+
18
+ super
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -17,6 +17,39 @@ module OpenHAB
17
17
  end
18
18
  ZonedDateTime.singleton_class.prepend(MockedZonedDateTime)
19
19
 
20
+ # @!visibility private
21
+ module MockedLocalDate
22
+ def now
23
+ mocked_time_stack_item = Timecop.top_stack_item
24
+ return super unless mocked_time_stack_item
25
+
26
+ mocked_time_stack_item.time.to_zoned_date_time.to_local_date
27
+ end
28
+ end
29
+ LocalDate.singleton_class.prepend(MockedLocalDate)
30
+
31
+ # @!visibility private
32
+ module MockedLocalTime
33
+ def now
34
+ mocked_time_stack_item = Timecop.top_stack_item
35
+ return super unless mocked_time_stack_item
36
+
37
+ mocked_time_stack_item.time.to_zoned_date_time.to_local_time
38
+ end
39
+ end
40
+ LocalTime.singleton_class.prepend(MockedLocalTime)
41
+
42
+ # @!visibility private
43
+ module MockedMonthDay
44
+ def now
45
+ mocked_time_stack_item = Timecop.top_stack_item
46
+ return super unless mocked_time_stack_item
47
+
48
+ mocked_time_stack_item.time.to_zoned_date_time.to_month_day
49
+ end
50
+ end
51
+ MonthDay.singleton_class.prepend(MockedMonthDay)
52
+
20
53
  # extend Timecop to support Java time classes
21
54
  # @!visibility private
22
55
  module TimeCopStackItem
@@ -12,12 +12,24 @@ module OpenHAB
12
12
  logger.debug("notify: #{msg}")
13
13
  end
14
14
 
15
- def say(text, voice: nil, sink: nil, volume: nil)
16
- logger.debug("say: #{text}")
15
+ class Voice
16
+ class << self
17
+ def say(text, voice: nil, sink: nil, volume: nil)
18
+ logger.debug("say: #{text}")
19
+ end
20
+ end
17
21
  end
18
22
 
19
- def play_sound(filename, sink: nil, volume: nil)
20
- logger.debug("play_sound: #{filename}")
23
+ class Audio
24
+ class << self
25
+ def play_sound(filename, sink: nil, volume: nil)
26
+ logger.debug("play_sound: #{filename}")
27
+ end
28
+
29
+ def play_stream(url, sink: nil)
30
+ logger.debug("play_stream: #{url}")
31
+ end
32
+ end
21
33
  end
22
34
 
23
35
  # rubocop:enable Lint/UnusedMethodArgument
@@ -4,21 +4,9 @@ module OpenHAB
4
4
  module Core
5
5
  module Items
6
6
  class Proxy
7
- @proxies = {}
8
-
9
7
  class << self
10
- # ensure each item only has a single proxy, so that
11
- # expect(item).to receive(:method) works
12
- def new(item)
13
- return super unless defined?(::RSpec) && ::RSpec.current_example&.example_group&.consistent_proxies?
14
-
15
- @proxies.fetch(item.name) do
16
- @proxies[item.name] = super
17
- end
18
- end
19
-
20
8
  def reset_cache
21
- @proxies = {}
9
+ @proxies.clear
22
10
  end
23
11
  end
24
12
  end
@@ -12,20 +12,7 @@ module OpenHAB
12
12
  logger.trace("Skipping execution of #{uid} because rules are suspended.")
13
13
  return
14
14
  end
15
-
16
- # super
17
- ::OpenHAB::DSL::ThreadLocal.thread_local(**@thread_locals) do
18
- logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs.inspect})" }
19
- logger.trace { "Event details #{inputs["event"].inspect}" } if inputs&.key?("event")
20
- trigger_conditions(inputs).process(mod: mod, inputs: inputs) do
21
- event = extract_event(inputs)
22
- process_queue(create_queue(event), mod, event)
23
- end
24
- rescue Exception => e
25
- raise if defined?(::RSpec) && ::RSpec.current_example.example_group.propagate_exceptions?
26
-
27
- @run_context.send(:logger).log_exception(e)
28
- end
15
+ execute!(mod, inputs)
29
16
  end
30
17
  end
31
18
  # private_constant :AutomationRule
data/lib/openhab/rspec.rb CHANGED
@@ -15,3 +15,12 @@ require_relative "rspec/configuration"
15
15
  require_relative "rspec/helpers"
16
16
  require_relative "rspec/karaf"
17
17
  require_relative "rspec/hooks"
18
+
19
+ return unless defined?(RSpec)
20
+
21
+ RSpec.configure do |c|
22
+ c.add_setting :openhab_automation_search_paths, default: [
23
+ "#{org.openhab.core.OpenHAB.config_folder}/automation/ruby",
24
+ "#{org.openhab.core.OpenHAB.config_folder}/automation/jsr223"
25
+ ]
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module YARD
5
+ # @!visibility private
6
+ module BaseHelper
7
+ def link_object(obj, title = nil, *)
8
+ ::YARD::Handlers::JRuby::Base.infer_java_class(obj) if obj.is_a?(String)
9
+ obj = ::YARD::Registry.resolve(object, obj, true, true) if obj.is_a?(String)
10
+ if obj.is_a?(::YARD::CodeObjects::Java::Base) && (see = obj.docstring.tag(:see))
11
+ # link to the first see tag
12
+ return linkify(see.name, title&.to_s || see.text)
13
+ end
14
+
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end
@@ -4,13 +4,19 @@ module OpenHAB
4
4
  module YARD
5
5
  module CodeObjects
6
6
  class GroupObject < ::YARD::CodeObjects::Base
7
+ attr_reader :full_name
8
+
9
+ def initialize(namespace, name)
10
+ @full_name = name
11
+ name = name.delete(%(.?"')).tr(" ", "-")
12
+ super
13
+ end
14
+
7
15
  def path
8
16
  "group#{sep}#{super}"
9
17
  end
10
18
 
11
- def to_s
12
- name
13
- end
19
+ alias_method :to_s, :full_name
14
20
  end
15
21
  end
16
22
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "coderay"
4
+
5
+ module OpenHAB
6
+ module YARD
7
+ module CodeRay
8
+ module HtmlHelper
9
+ ::CodeRay::Scanners.list.each do |scanner|
10
+ define_method("html_syntax_highlight_#{scanner}") do |source|
11
+ ::CodeRay.scan(source, scanner).html
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -7,9 +7,18 @@ module YARD
7
7
  class << self
8
8
  def infer_java_class(klass, inferred_type = nil, comments = nil, statement = nil)
9
9
  components = klass.split(".")
10
+ components.pop if components.last == "freeze"
11
+
10
12
  class_first_char = components.last[0]
11
13
  is_field = components.last == components.last.upcase
12
- is_package = !is_field && class_first_char != class_first_char.upcase
14
+ container_first_char = components[-2]&.[](0)
15
+ is_method = container_first_char &&
16
+ class_first_char != class_first_char.upcase &&
17
+ container_first_char == container_first_char.upcase
18
+ is_package = !is_method && !is_field && class_first_char != class_first_char.upcase
19
+
20
+ # methods aren't supported right now
21
+ return if is_method
13
22
 
14
23
  javadocs = YARD::Config.options.dig(:jruby, "javadocs") || {}
15
24
 
@@ -14,6 +14,9 @@ module YARD
14
14
  obj = infer_java_class(klass)
15
15
  next unless obj
16
16
 
17
+ # don't overwrite an already-extant object with the same name
18
+ next if ::YARD::Registry.at("#{namespace.path}::#{obj.simple_name}")
19
+
17
20
  # then we create a new constant in the current namespace
18
21
  register CodeObjects::ConstantObject.new(namespace, obj.simple_name) { |o|
19
22
  o.source = statement