From 797650a78dce692ebec24e0c30e268f7feba59c5 Mon Sep 17 00:00:00 2001 From: Sylvain Joyeux Date: Tue, 31 Mar 2026 11:13:03 -0300 Subject: [PATCH] chore: make the overall API a little bit more uniform Defined each_direct_* versions on classes that have only direct enumerators, for compatibility with Model Created find_model_by_name on relevant classes Added enumeration method for plugins on Sensor --- lib/sdf/element.rb | 3 ++- lib/sdf/model.rb | 4 ++++ lib/sdf/sensor.rb | 10 ++++++++++ lib/sdf/world.rb | 20 ++++++++++++++++++-- test/test_model.rb | 26 ++++++++++++++++++++++++++ test/test_sensor.rb | 18 ++++++++++++++++++ test/test_world.rb | 11 +++++++++++ 7 files changed, 89 insertions(+), 3 deletions(-) diff --git a/lib/sdf/element.rb b/lib/sdf/element.rb index 7d70baa..efb1bba 100644 --- a/lib/sdf/element.rb +++ b/lib/sdf/element.rb @@ -93,7 +93,8 @@ def self.wrap(xml, parent = nil) "model" => Model, "sdf" => Root, "link" => Link, - "joint" => Joint] + "joint" => Joint, + "plugin" => Plugin] if (klass = xml_to_class[xml.name]) return klass.new(xml, parent) diff --git a/lib/sdf/model.rb b/lib/sdf/model.rb index d1b0aee..d123b8e 100644 --- a/lib/sdf/model.rb +++ b/lib/sdf/model.rb @@ -87,6 +87,10 @@ def initialize(xml = REXML::Element.new("model"), parent = nil) # The link that is used to represent the pose of the model itself attr_accessor :canonical_link + def find_model_by_name(name) + @models[name] + end + # (see Element#find_by_name) def find_by_name(name) @models[name] || @links[name] || @joints[name] diff --git a/lib/sdf/sensor.rb b/lib/sdf/sensor.rb index 034117b..139e808 100644 --- a/lib/sdf/sensor.rb +++ b/lib/sdf/sensor.rb @@ -18,6 +18,16 @@ def initialize(xml, parent = nil) @sensor_info = xml.elements[type] end + def each_direct_plugin(&block) + each_plugin(&block) + end + + def each_plugin + xml.elements.each do |plugin_xml| + yield(Plugin.new(plugin_xml, self)) + end + end + # The sensor type def type xml.attributes["type"] diff --git a/lib/sdf/world.rb b/lib/sdf/world.rb index c5ea331..5e8b55e 100644 --- a/lib/sdf/world.rb +++ b/lib/sdf/world.rb @@ -16,10 +16,19 @@ def self.empty(name: "world", version: nil) xml_tag_name "world" + def find_model_by_name(name) + each_model.find { |m| m.name == name } + end + + # @deprecated use {#each_direct_model} instead + def each_model(&block) + each_direct_model(&block) + end + # Enumerates the models from this world # # @yieldparam [Model] model - def each_model + def each_direct_model return enum_for(__method__) unless block_given? xml.elements.each do |element| @@ -38,8 +47,15 @@ def spherical_coordinates ) end + # @deprecated use {#each_direct_plugin} instead + def each_plugin(&block) + each_direct_plugin(&block) + end + # Enumerate the world-level plugins - def each_plugin + # + # @yieldparam [Plugin] + def each_direct_plugin return enum_for(__method__) unless block_given? xml.elements.each do |element| diff --git a/test/test_model.rb b/test/test_model.rb index 473c343..83b560f 100644 --- a/test/test_model.rb +++ b/test/test_model.rb @@ -61,6 +61,32 @@ def models_dir end end + describe "#find_model_by_name" do + before do + xml_s = <<~XML + + + + + XML + @xml = REXML::Document.new(xml_s).root + @root = SDF::Model.new(@xml) + end + it "returns nil if the model has no " do + assert_nil @root.find_model_by_name("does_not_exist") + end + it "returns a direct model" do + model = @root.find_model_by_name("1") + assert_kind_of SDF::Model, model + assert_equal @xml.elements["model[@name=\"1\"]"], model.xml + end + it "returns models recursively" do + model = @root.find_model_by_name("0::a") + assert_kind_of SDF::Model, model + assert_equal @xml.elements["//model[@name=\"a\"]"], model.xml + end + end + describe "#each_model" do it "does not yield anything if the model has no models" do root = SDF::Model.new(REXML::Document.new("").root) diff --git a/test/test_sensor.rb b/test/test_sensor.rb index 13b1e18..d22ff02 100644 --- a/test/test_sensor.rb +++ b/test/test_sensor.rb @@ -28,5 +28,23 @@ module SDF assert_nil sensor.update_period end end + + describe "#each_plugin" do + it "does not yield anything if the sensor has no plugin" do + root = SDF::Sensor.new(REXML::Document.new("").root) + assert root.enum_for(:each_plugin).to_a.empty? + end + it "yields the plugins otherwise" do + root = SDF::Sensor.new(REXML::Document.new("").root) + + plugin = root.enum_for(:each_plugin).to_a + assert_equal 2, plugin.size + plugin.each do |l| + assert_kind_of SDF::Plugin, l + assert_same root, l.parent + assert_equal root.xml.elements.to_a("plugin[@name=\"#{l.name}\"]"), [l.xml] + end + end + end end end diff --git a/test/test_world.rb b/test/test_world.rb index 7327cfb..464ce0a 100644 --- a/test/test_world.rb +++ b/test/test_world.rb @@ -20,6 +20,17 @@ module SDF end end end + describe "#find_model_by_name" do + it "returns nil if the model does not exist" do + root = SDF::World.new(REXML::Document.new("").root) + assert_nil root.find_model_by_name("does_not_exist") + end + it "yields the models otherwise" do + xml = REXML::Document.new("").root + world = SDF::World.new(xml) + assert_equal xml.elements["model[@name=\"1\"]"], world.find_model_by_name("1").xml + end + end describe ".empty" do it "creates a world with no models" do world = World.empty(name: "test")