From a020e1bd486ac031e40c1d52d55375161cb219a0 Mon Sep 17 00:00:00 2001 From: Cody Cutrer Date: Sat, 28 Dec 2024 07:53:59 -0700 Subject: [PATCH] Allow passing an array of channels to item builder Also add fluent aliases for adding multiple channels, groups and tags, and clarify documentation. Also fixes calling `channel` on a `GroupItemBuilder`, because it was being shadowed by `Terse#channel`, because `DSL` was getting included unecessarily (we only need `EntityLookup`, which is already explicitly included). Fixes #374 Signed-off-by: Cody Cutrer --- lib/openhab/dsl/items/builder.rb | 134 +++++++++++++++++++------ spec/openhab/dsl/items/builder_spec.rb | 24 ++++- 2 files changed, 126 insertions(+), 32 deletions(-) diff --git a/lib/openhab/dsl/items/builder.rb b/lib/openhab/dsl/items/builder.rb index 2e765ab12..54d446605 100644 --- a/lib/openhab/dsl/items/builder.rb +++ b/lib/openhab/dsl/items/builder.rb @@ -98,8 +98,6 @@ def group_item(*args, **kwargs, &block) result end - include DSL - private def item(*args, **kwargs, &block) @@ -159,10 +157,11 @@ def add(builder) # make sure to add the item to the registry before linking it channel_uids = builder.channels.to_set do |(channel, config)| - # fill in partial channel names from group's thing id + channel = channel.to_s + # fill in partial channel names from item's or group's thing id if !channel.include?(":") && - (group = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing }) - thing = group.thing + (thing = builder.thing || + thing = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing }&.thing) channel = "#{thing}:#{channel}" end @@ -243,18 +242,12 @@ class ItemBuilder # The icon to be associated with the item # @return [Symbol, String, nil] attr_accessor :icon - # Groups to which this item should be added - # @return [Array] - attr_reader :groups - # Tags to apply to this item - # @return [Array] - attr_reader :tags # Autoupdate setting # @return [true, false, nil] attr_accessor :autoupdate - # {Core::Things::ChannelUID Channel} to link the item to - # @return [String, Core::Things::ChannelUID, nil] - attr_accessor :channels + # @return [String, Core::Things::Thing, Core::Things::ThingUID, nil] + # {Core::Things::ThingUID Thing} from which to resolve relative channel ids + attr_accessor :thing # @return [Core::Items::Metadata::NamespaceHash] attr_reader :metadata # Initial state @@ -264,6 +257,19 @@ class ItemBuilder # @return [Core::Types::State] attr_reader :state + attr_writer :channels, :groups, :tags + + # @!attribute [rw] channels + # @return [Array] + # {Core::Things::ChannelUID Channel} to link the item to + + # @!attribute [rw] groups + # @return [Array] Groups to which this item should be added + + # @!attribute [rw] tags + # @return [Array] Tags to apply to this item + + # This comment needs to exist otherwise YARD thinks the above attribute should apply to the metaclass class << self # @!visibility private def item_factory @@ -352,6 +358,7 @@ def initialize(type, autoupdate: nil, thing: nil, channel: nil, + channels: nil, expire: nil, alexa: nil, ga: nil, # rubocop:disable Naming/MethodParameterName @@ -408,12 +415,13 @@ def initialize(type, self.state = state self.group(*group) - self.group(*groups) + self.groups(*groups) self.tag(*tag) - self.tag(*tags) + self.tags(*tags) - self.channel(*channel) if channel + self.channel(*channel) + self.channels(*channels) end # @@ -428,20 +436,45 @@ def to_s # # Tag item # - # @param tags [String, Symbol, Semantics::Tag] - # @return [void] + # @return [Array] + # + # @overload tag(tag) + # @param tag [String, Symbol, Semantics::Tag] + # @return [Array] + # + # @overload tags + # @return [Array] + # + # @overload tags(*tags) + # @param tags [String, Symbol, Semantics::Tag] + # @return [Array] # def tag(*tags) - @tags += self.class.normalize_tags(*tags) + return @tags if tags.empty? + + @tags.concat(self.class.normalize_tags(*tags)) end + alias_method :tags, :tag # # Add this item to a group # - # @param groups [String, GroupItemBuilder, GroupItem] - # @return [void] + # @return [Array] + # + # @overload group(group) + # @param group [String, GroupItemBuilder, GroupItem] + # @return [Array] + # + # @overload groups + # @return [Array] + # + # @overload groups(*groups) + # @param groups [String, GroupItemBuilder, GroupItem] + # @return [Array] # def group(*groups) + return @groups if groups.empty? + unless groups.all? do |group| group.is_a?(String) || group.is_a?(Core::Items::GroupItem) || group.is_a?(GroupItemBuilder) end @@ -450,6 +483,7 @@ def group(*groups) @groups.concat(groups) end + alias_method :groups, :group # # @!method alexa(value, config = nil) @@ -494,10 +528,23 @@ def group(*groups) # # Add a channel link to this item. # - # @param channel [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel] - # Channel to link the item to. When thing is set, this can be a relative channel name. - # @param config [Hash] Additional configuration, such as profile - # @return [void] + # @return [Array] + # + # @overload channel(channel, config = {}) + # @param channel [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel] + # Channel to link the item to. When {thing} is set, this can be a relative channel name. + # @param config [Hash] Additional configuration, such as profile + # @return [Array] + # + # @overload channels + # @return [Array] + # + # @overload channels(*channels) + # @param channels [String, Symbol, Core::Things::ChannelUID, Core::Things::Channel, Array] + # Channels to link the item to. When {thing} is set, these can be relative channel names. + # Each array element can also be a two element array with the first element being the + # channel, and the second element being a config hash. + # @return [Array] # # @example # items.build do @@ -511,11 +558,38 @@ def group(*groups) # switch_item Bedroom_Light, thing: "mqtt:topic:bedroom-light", channel: :power # end # - def channel(channel, config = {}) - channel = channel.to_s - channel = "#{@thing}:#{channel}" if @thing && !channel.include?(":") - @channels << [channel, config] + # @example Multiple channels + # items.build do + # dimmer_item DemoDimmer, channels: ["hue:0210:bridge:1:color", "knx:device:bridge:generic:controlDimmer"] + # end + # + # @example Multiple channels with config + # items.build do + # dimmer_item DemoDimmer, channels: [["hue:0210:bridge:1:color", profile: "system:follow"], + # "knx:device:bridge:generic:controlDimmer"] + # end + # + def channel(*channels) + return @channels if channels.empty? + + channels = [channels] if channels.length == 2 && channels[1].is_a?(Hash) + + channels.each do |channel| + orig_channel = channel + channel = channel.first if channel.is_a?(Array) + next if channel.is_a?(String) || + channel.is_a?(Symbol) || + channel.is_a?(Core::Things::ChannelUID) || + channel.is_a?(Core::Things::Channel) + + raise ArgumentError, "channel #{orig_channel.inspect} must be a `String`, `Symbol`, `ChannelUID`, or " \ + "`Channel`, or a two element array with the first element those types, and the " \ + "second element a Hash" + end + + @channels.concat(channels) end + alias_method :channels, :channel # # @!method expire(duration, command: nil, state: nil, ignore_state_updates: nil, ignore_commands: nil) diff --git a/spec/openhab/dsl/items/builder_spec.rb b/spec/openhab/dsl/items/builder_spec.rb index 142d41198..8984b8a05 100644 --- a/spec/openhab/dsl/items/builder_spec.rb +++ b/spec/openhab/dsl/items/builder_spec.rb @@ -675,6 +675,7 @@ def build_and_update(org_config, new_config, item_to_keep: :new_item, &block) install_addon "binding-astro", ready_markers: "openhab.xmlThingTypes" things.build do thing "astro:sun:home", "Astro Sun Data", config: { "geolocation" => "0,0" } + thing "astro:moon:home", "Astro Moon Data", config: { "geolocation" => "0,0" } end end @@ -689,13 +690,32 @@ def build_and_update(org_config, new_config, item_to_keep: :new_item, &block) end items.build do date_time_item "DateTime1" do - channel "astro:sun:home:rise#start" - channel "astro:moon:home:rise#start" + channel "astro:sun:home:rise#start" + channel "astro:moon:home:rise#start" end end expect(DateTime1.things).to match_array([things["astro:sun:home"], things["astro:moon:home"]]) end + it "accepts multiple channels in an immediate argument" do + items.build { string_item "StringItem1", channels: ["astro:sun:home:season#name", "astro:moon:home:season#name"] } + expect(StringItem1.things).to match [things["astro:sun:home"], things["astro:moon:home"]] + end + + it "accepts multiple channels with config in an immediate argument" do + items.build do + string_item "StringItem1", + channels: [["astro:sun:home:season#name", { config: 1 }], "astro:moon:home:season#name"] + end + expect(StringItem1.things).to match [things["astro:sun:home"], things["astro:moon:home"]] + end + + it "rejects invalid channel data" do + expect do + items.build { string_item "StringItem1", channels: 1 } + end.to raise_error(ArgumentError) + end + it "can link to an item channel with a profile" do items.build do date_time_item "LastUpdated", channel: ["astro:sun:home:season#name", { profile: "system:timestamp-update" }]