Skip to content

Commit

Permalink
Make Configuration objects more Ruby-like
Browse files Browse the repository at this point in the history
Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
  • Loading branch information
jimtng committed Aug 24, 2023
1 parent 829149b commit b1e1344
Show file tree
Hide file tree
Showing 2 changed files with 359 additions and 0 deletions.
239 changes: 239 additions & 0 deletions lib/openhab/core/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
# frozen_string_literal: true

require "forwardable"

module OpenHAB
module Core
java_import org.openhab.core.config.core.Configuration

# {Configuration} represents openHAB's {org.openhab.core.config.core.Configuration} data
# with the full interface of {::Hash} and stringifies symbolic keys.
class Configuration
include Enumerable
extend Forwardable

# Make it act like a Hash
def_delegators :get_properties,
:any?,
:compact,
:compare_by_identity?,
:deconstruct_keys,
:default,
:default_proc,
:each,
:each_key,
:each_pair,
:each_value,
:empty?,
:filter,
:flatten,
:has_value?,
:invert,
:key,
:length,
:rassoc,
:reject,
:select,
:shift,
:size,
:to_a,
:to_h,
:to_hash,
:transform_keys,
:transform_values,
:values,
:value?

def_delegator :to_h, :inspect

# @!visibility private
alias_method :to_s, :inspect

# @!visibility private
def [](key)
get(key.to_s)
end

# @!visibility private
def []=(key, value)
put(key.to_s, value)
end
alias_method :store, :[]=

# @!visibility private
def delete(key)
remove(key.to_s)
end

# @!visibility private
def key?(key)
contains_key(key.to_s)
end
alias_method :has_key?, :key?
alias_method :include?, :key?

# @!visibility private
def replace(new_pairs)
set_properties(new_pairs.to_h.transform_keys(&:to_s))
self
end

# @!visibility private
alias_method :keys, :key_set

# @!visibility private
alias_method :hash, :hash_code

# @!visibility private
def dup
new(self)
end

# @!visibility private
def <(other)
to_hash < other.to_hash.transform_keys(&:to_s)
end

# @!visibility private
def <=(other)
to_hash <= other.to_hash.transform_keys(&:to_s)
end

# @!visibility private
def ==(other)
return to_hash == other.transform_keys(&:to_s) if other.is_a?(Hash)

equals(other)
end

# @!visibility private
def >(other)
to_hash > other.to_hash.transform_keys(&:to_s)
end

# @!visibility private
def >=(other)
to_hash >= other.to_hash.transform_keys(&:to_s)
end

# @!visibility private
def assoc(key)
to_h.assoc(key.to_s)
end

# @!visibility private
def clear
replace({})
end

# @!visibility private
def compact!
replace(compact)
end

# @!visibility private
def compare_by_identity
raise NotImplementedError
end

# @!visibility private
def default=(*)
raise NotImplementedError
end

# @!visibility private
def default_proc=(*)
raise NotImplementedError
end

# @!visibility private
def delete_if(&block)
raise NotImplementedError unless block

replace(to_h.delete_if(&block))
end

# @!visibility private
def except(*keys)
to_h.except(*keys.map(&:to_s))
end

# @!visibility private
def dig(*keys)
to_h.dig(*keys.map(&:to_s))
end

# @!visibility private
def fetch(key, default = nil)
get(key.to_s) || default || (block_given? && yield)
end

# @!visibility private
def fetch_values(*keys, &block)
to_h.fetch_values(*keys.map(&:to_s), &block)
end

# @!visibility private
def keep_if(&block)
select!(&block)
self
end

# @!visibility private
def merge!(*others, &block)
return self if others.empty?

new_config = to_h
others.each do |h|
new_config.merge!(h.transform_keys(&:to_s), &block)
end
replace(new_config)
end
alias_method :update, :merge!

# @!visibility private
def reject!(&block)
raise NotImplementedError unless block

r = to_h.reject!(&block)
replace(r) if r
end

# @!visibility private
def select!(&block)
raise NotImplementedError unless block?

r = to_h.select!(&block)
replace(r) if r
end
alias_method :filter!, :select!

# @!visibility private
def slice(*keys)
to_h.slice(*keys.map(&:to_s))
end

# @!visibility private
def to_proc
->(k) { self[k] }
end

# @!visibility private
def transform_keys!(*args, &block)
replace(transform_keys(*args, &block))
end

# @!visibility private
def transform_values!(&block)
raise NotImplementedError unless block

replace(transform_values(&block))
end

# @!visibility private
def values_at(*keys)
to_h.values_at(*keys.map(&:to_s))
end
end
end
end
120 changes: 120 additions & 0 deletions spec/openhab/core/configuration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# frozen_string_literal: true

RSpec.describe OpenHAB::Core::Configuration do
let(:contents) do
{ "key1" => "value1", "key2" => "value2" }.freeze
end

let(:configuration) do
org.openhab.core.config.core.Configuration.new(contents)
end

describe "#inspect" do
it "logs the full configuration" do
expect(configuration.inspect).to eql contents.inspect
end
end

describe "#to_h" do
it "works" do
expect(configuration.to_h).to eql contents
end
end

describe "#[]" do
it "works" do
expect(configuration["key1"]).to eql "value1"
end

it "returns nil for nonexistent key" do
expect(configuration["nonexistent"]).to be_nil
end

it "stringifies config keys" do
expect(configuration[:key1]).to eql "value1"
end
end

describe "#[]=" do
it "works" do
configuration["key1"] = "newvalue"
expect(configuration["key1"]).to eql "newvalue"
end

it "stringifies config keys" do
configuration[:key1] = "stringified"
expect(configuration["key1"]).to eql "stringified"
end

it "can be added via hash" do
configuration["newkey"] = "corge"
expect(configuration["newkey"]).to eql "corge"
end
end

describe "#dig" do
it "works" do
expect(configuration.dig("key1")).to eql "value1" # rubocop:disable Style/SingleArgumentDig
end

it "stringifies keys" do
expect(configuration.dig(:key1)).to eql "value1" # rubocop:disable Style/SingleArgumentDig
end
end

describe "#==" do
it "can compare against a ::Hash" do
expect(configuration).to eq(contents)
end

it "can compare against another Configuration object" do
expect(configuration).to eq(described_class.new(contents))
end
end

describe "#replace" do
it "works" do
configuration.replace("x" => "y")
expect(configuration.to_h).to eql("x" => "y")
end

it "stringifies keys" do
configuration.replace(symkey: "ruby")
expect(configuration.to_h).to eql("symkey" => "ruby")
end

it "accepts another Configuration" do
configuration.replace(described_class.new("ma" => "goo"))
expect(configuration.to_h).to eql("ma" => "goo")
end
end

describe "#delete" do
it "works" do
configuration.delete("key1")
expect(configuration.to_h).to eql("key2" => "value2")
end
end

describe "#key?" do
it "works" do
expect(configuration.key?("key1")).to be true
expect(configuration.key?("nonexistent")).to be false
end

it "stringifies the given key" do
expect(configuration.key?(:key1)).to be true
end
end

describe "#include?" do
it "works" do
expect(configuration.include?("key1")).to be true
expect(configuration.include?("nonexistent")).to be false
end

it "stringifies the given key" do
expect(configuration.include?(:key1)).to be true
end
end
end

0 comments on commit b1e1344

Please sign in to comment.