recursive-open-struct-1.1.3/0000755000004100000410000000000013761545507016051 5ustar www-datawww-datarecursive-open-struct-1.1.3/recursive-open-struct.gemspec0000644000004100000410000000261313761545507023710 0ustar www-datawww-data# -*- encoding: utf-8 -*- require './lib/recursive_open_struct/version' Gem::Specification.new do |s| s.name = "recursive-open-struct" s.version = RecursiveOpenStruct::VERSION s.authors = ["William (B.J.) Snow Orvis"] s.email = "aetherknight@gmail.com" s.date = Time.now.utc.strftime("%Y-%m-%d") s.homepage = "https://github.com/aetherknight/recursive-open-struct" s.licenses = ["MIT"] s.summary = "OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs" s.description = <<-QUOTE .gsub(/^ /,'') RecursiveOpenStruct is a subclass of OpenStruct. It differs from OpenStruct in that it allows nested hashes to be treated in a recursive fashion. For example: ros = RecursiveOpenStruct.new({ :a => { :b => 'c' } }) ros.a.b # 'c' Also, nested hashes can still be accessed as hashes: ros.a_as_a_hash # { :b => 'c' } QUOTE s.files = `git ls-files`.split("\n") s.test_files = `git ls-files spec`.split("\n") s.require_paths = ["lib"] s.extra_rdoc_files = [ "CHANGELOG.md", "LICENSE.txt", "README.md" ] s.add_development_dependency('bundler', [">= 0"]) s.add_development_dependency('pry', [">= 0"]) s.add_development_dependency('rake', [">= 0"]) s.add_development_dependency('rdoc', [">= 0"]) s.add_development_dependency('rspec', "~> 3.2") s.add_development_dependency('simplecov', [">= 0"]) end recursive-open-struct-1.1.3/.travis.yml0000644000004100000410000000104113761545507020156 0ustar www-datawww-data--- language: ruby rvm: # No longer supported (but test anyways) - 2.0.0 - 2.1.10 - 2.2.10 - jruby-19mode - 2.3.8 - 2.4.10 # Current stable supported by Travis - 2.5.8 - 2.6.6 - 2.7.1 - jruby-9.1.9.0 # Future - ruby-head - jruby-head - truffleruby-head sudo: false matrix: allow_failures: # No longer supported - rvm: 2.0.0 - rvm: 2.1.10 - rvm: 2.2.10 - rvm: 2.3.8 - rvm: 2.4.10 - rvm: jruby-19mode # Future - rvm: ruby-head - rvm: jruby-head - rvm: truffleruby-head recursive-open-struct-1.1.3/.rspec0000644000004100000410000000001013761545507017155 0ustar www-datawww-data--color recursive-open-struct-1.1.3/README.md0000644000004100000410000000541413761545507017334 0ustar www-datawww-data# recursive-open-struct OpenStruct subclass that returns nested hash attributes as RecursiveOpenStructs. ## Usage It allows for hashes within hashes to be called in a chain of methods: ```ruby ros = RecursiveOpenStruct.new( { wha: { tagoo: 'siam' } } ) ros.wha.tagoo # => 'siam' ``` Also, if needed, nested hashes can still be accessed as hashes: ```ruby ros.wha_as_a_hash # { tagoo: 'siam' } ``` ### Optional: Recurse Over Arrays RecursiveOpenStruct can also optionally recurse across arrays, although you have to explicitly enable it. Default behavior: ```ruby h = { :somearr => [ { name: 'a'}, { name: 'b' } ] } ros = RecursiveOpenStruct.new(h) ros.somearr # => [ { name: 'a'}, { name: 'b' } ] ``` Enabling `recurse_over_arrays`: ```ruby ros = RecursiveOpenStruct.new(h, recurse_over_arrays: true ) ros.somearr[0].name # => 'a' ros.somearr[1].name # => 'b' ``` ### Optional: Preserve Original Keys Also, by default it will turn all hash keys into symbols internally: ```ruby h = { 'fear' => 'is', 'the' => 'mindkiller' } } ros = RecursiveOpenStruct.new(h) ros.to_h # => { fear: 'is', the: 'mindkiller' } ``` You can preserve the original keys by enabling `:preserve_original_keys`: ```ruby h = { 'fear' => 'is', 'the' => 'mindkiller' } } ros = RecursiveOpenStruct.new(h, preserve_original_keys: true) ros.to_h # => { 'fear' => 'is', 'the' => 'mindkiller' } ``` ## Installation Available as a gem in rubygems, the default gem repository. If you use bundler, just add recursive-open-struct to your gemfile : ```ruby gem 'recursive-open-struct' ``` You may also install the gem manually: gem install recursive-open-struct ## Contributing If you would like to file or fix a bug, or propose a new feature, please review [CONTRIBUTING](CONTRIBUTING.md) first. ## Supported Ruby Versions Recursive-open-struct attempts to support just the versions of Ruby that are still actively maintained. Once a given major/minor version of Ruby no longer receives patches, they will no longer be supported (but recursive-open-struct may still work). I usually update the travis.yml file to reflect this when preparing for a new release or do some other work on recursive-open-struct. I also try to update recursive-open-struct to support new features in OpenStruct itself as new versions of Ruby are released. However, I don't actively monitor the status of this, so a newer feature might not work. If you encounter such a feature, please file a bug or a PR to fix it, and I will try to cut a new release of recursive-open-struct quickly. ## SemVer Compliance Rescursive-open-struct follows [SemVer 2.0](https://semver.org/spec/v2.0.0.html) for its versioning. ## Copyright Copyright (c) 2009-2018, The Recursive-open-struct developers (given in the file AUTHORS.txt). See LICENSE.txt for details. recursive-open-struct-1.1.3/spec/0000755000004100000410000000000013761545507017003 5ustar www-datawww-datarecursive-open-struct-1.1.3/spec/spec_helper.rb0000644000004100000410000000105513761545507021622 0ustar www-datawww-data$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(File.dirname(__FILE__)) require 'rspec' require 'pry' if ENV['COVERAGE'] == 'true' require 'simplecov' SimpleCov.start end # Requires supporting files with custom matchers and macros, etc, # in ./support/ and its subdirectories. Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} RSpec.configure do |config| config.run_all_when_everything_filtered = true config.filter_run :focus # config.expect_with(:rspec) { |c| c.syntax = :should } end recursive-open-struct-1.1.3/spec/recursive_open_struct/0000755000004100000410000000000013761545507023437 5ustar www-datawww-datarecursive-open-struct-1.1.3/spec/recursive_open_struct/ostruct_2_0_0_spec.rb0000644000004100000410000000477713761545507027377 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do let(:hash) { {:foo => 'foo', 'bar' => :bar} } subject(:ros) { RecursiveOpenStruct.new(hash) } describe "OpenStruct 2.0+ methods" do context "Hash style setter" do it "method exists" do expect(ros.respond_to?('[]=')).to be_truthy end it "changes the value" do ros[:foo] = :foo ros.foo = :foo end end context "delete_field" do before(:each) { ros.delete_field :foo } it "removes the value" do expect(ros.foo).to be_nil expect(ros.to_h).to_not include(:foo) end it "removes the getter method" do is_expected.to_not respond_to :foo end it "removes the setter method" do expect(ros.respond_to? 'foo=').to be_falsey end it "works with indifferent access" do expect(ros.delete_field :bar).to eq :bar is_expected.to_not respond_to :bar is_expected.to_not respond_to 'bar=' expect(ros.to_h).to be_empty end end context "eql?" do subject(:new_ros) { ros.dup } context "with identical ROS" do subject { ros } it { is_expected.to be_eql ros } end context "with similar ROS" do subject { RecursiveOpenStruct.new(hash) } it { is_expected.to be_eql ros } end context "with same Hash" do subject { RecursiveOpenStruct.new(hash, recurse_over_arrays: true) } it { is_expected.to be_eql ros } end context "with duplicated ROS" do subject { ros.dup } it "fails on different value" do subject.foo = 'bar' is_expected.not_to be_eql ros end it "fails on missing field" do subject.delete_field :bar is_expected.not_to be_eql ros end it "fails on added field" do subject.baz = :baz is_expected.not_to be_eql ros end end end context "hash" do it "calculates table hash" do expect(ros.hash).to be ros.instance_variable_get('@table').hash end end context "each_pair" do it "iterates over hash keys, with keys as symbol" do ros_pairs = [] ros.each_pair {|k,v| ros_pairs << [k,v]} hash_pairs = [] {:foo => 'foo', :bar => :bar}.each_pair {|k,v| hash_pairs << [k,v]} expect(ros_pairs).to match (hash_pairs) end end end # describe OpenStruct 2.0+ methods end recursive-open-struct-1.1.3/spec/recursive_open_struct/recursion_spec.rb0000644000004100000410000002511313761545507027011 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "recursive behavior" do let(:h) { { :blah => { :another => 'value' } } } subject(:ros) { RecursiveOpenStruct.new(h) } it "can convert the entire hash tree back into a hash" do blank_obj = Object.new h = {:asdf => 'John Smith', :foo => [{:bar => blank_obj}, {:baz => nil}]} ros = RecursiveOpenStruct.new(h) expect(ros.to_h).to eq h expect(ros.to_hash).to eq h end it "returns accessed hashes as RecursiveOpenStructs instead of hashes" do expect(subject.blah.another).to eq 'value' end it "handles subscript notation the same way as dotted notation" do expect(subject.blah.another).to eq subject[:blah].another end it "uses #key_as_a_hash to return key as a Hash" do expect(subject.blah_as_a_hash).to eq({ :another => 'value' }) end it "handles sub-element replacement with dotted notation before member setup" do expect(ros[:blah][:another]).to eql 'value' expect(ros.methods).not_to include(:blah) ros.blah = { changed: 'backing' } expect(ros.blah.changed).to eql 'backing' end describe "handling loops in the original Hashes" do let(:h1) { { :a => 'a'} } let(:h2) { { :a => 'b', :h1 => h1 } } before(:each) { h1[:h2] = h2 } subject { RecursiveOpenStruct.new(h2) } it { expect(subject.h1.a).to eq 'a' } it { expect(subject.h1.h2.a).to eq 'b' } it { expect(subject.h1.h2.h1.a).to eq 'a' } it { expect(subject.h1.h2.h1.h2.a).to eq 'b' } it { expect(subject.h1).to eq subject.h1.h2.h1 } it { expect(subject.h1).to_not eq subject.h1.h2 } end # describe handling loops in the origin Hashes it "can modify a key of a sub-element" do h = { :blah => { :blargh => 'Brad' } } ros = RecursiveOpenStruct.new(h) ros.blah.blargh = "Janet" expect(ros.blah.blargh).to eq "Janet" end describe 'subscript mutation notation' do it 'handles the basic case' do subject[:blah] = 12345 expect(subject.blah).to eql 12345 end it 'recurses properly' do subject[:blah][:another] = 'abc' expect(subject.blah.another).to eql 'abc' expect(subject.blah_as_a_hash).to eql({ :another => 'abc' }) end let(:diff){ { :different => 'thing' } } it 'can replace the entire hash' do expect(subject.to_h).to eql(h) subject[:blah] = diff expect(subject.to_h).to eql({ :blah => diff }) end it 'updates sub-element cache' do expect(subject.blah.different).to be_nil subject[:blah] = diff expect(subject.blah.different).to eql 'thing' expect(subject.blah_as_a_hash).to eql(diff) end end context "after a sub-element has been modified" do let(:hash) do { :blah => { :blargh => "Brad" }, :some_array => [ 1, 2, 3] } end let(:updated_hash) do { :blah => { :blargh => "Janet" }, :some_array => [ 1, 2, 3] } end subject { RecursiveOpenStruct.new(hash) } before(:each) { subject.blah.blargh = "Janet" } describe ".to_h" do it "returns a hash tree that contains those modifications" do expect(subject.to_h).to eq updated_hash end specify "modifying the returned hash tree does not modify the ROS" do subject.to_h[:blah][:blargh] = "Dr Scott" expect(subject.blah.blargh).to eq "Janet" end end it "does not mutate the original hash tree passed to the constructor" do expect(hash[:blah][:blargh]).to eq 'Brad' end it "limits the deep-copy to the initial hash tree" do subject.some_array[0] = 4 expect(hash[:some_array][0]).to eq 4 end describe "#dup" do let(:duped_subject) { subject.dup } it "preserves sub-element modifications" do expect(duped_subject.blah.blargh).to eq subject.blah.blargh end it "allows the copy's sub-elements to be modified independently from the original's" do expect(subject.blah.blargh).to eq "Janet" duped_subject.blah.blargh = "Dr. Scott" expect(subject.blah.blargh).to eq "Janet" expect(duped_subject.blah.blargh).to eq "Dr. Scott" end end end context "when memoizing and then modifying entire recursive structures" do subject do RecursiveOpenStruct.new( { :blah => original_blah }, :recurse_over_arrays => true) end before(:each) { subject.blah } # enforce memoization context "when modifying an entire Hash" do let(:original_blah) { { :a => 'A', :b => 'B' } } let(:new_blah) { { :something_new => "C" } } before(:each) { subject.blah = new_blah } it "returns the modified value instead of the memoized one" do expect(subject.blah.something_new).to eq "C" end specify "the old value no longer exists" do expect(subject.blah.a).to be_nil end end context "when modifying an entire Array" do let(:original_blah) { [1, 2, 3] } it "returns the modified value instead of the memoized one" do new_blah = [4, 5, 6] subject.blah = new_blah expect(subject.blah).to eq new_blah end end end describe 'recursing over arrays' do let(:blah_list) { [ { :foo => '1' }, { :foo => '2' }, 'baz' ] } let(:h) { { :blah => blah_list } } context "when recursing over arrays is enabled" do subject { RecursiveOpenStruct.new(h, :recurse_over_arrays => true) } it { expect(subject.blah.length).to eq 3 } it { expect(subject.blah[0].foo).to eq '1' } it { expect(subject.blah[1].foo).to eq '2' } it { expect(subject.blah_as_a_hash).to eq blah_list } it { expect(subject.blah[2]).to eq 'baz' } context "when an inner value changes" do let(:updated_blah_list) { [ { :foo => '1' }, { :foo => 'Dr Scott' }, 'baz' ] } let(:updated_h) { { :blah => updated_blah_list } } before(:each) { subject.blah[1].foo = "Dr Scott" } it "Retains changes across Array lookups" do expect(subject.blah[1].foo).to eq "Dr Scott" end it "propagates the changes through to .to_h across Array lookups" do expect(subject.to_h).to eq({ :blah => [ { :foo => '1' }, { :foo => "Dr Scott" }, 'baz' ] }) end it "deep-copies hashes within Arrays" do subject.to_h[:blah][1][:foo] = "Rocky" expect(subject.blah[1].foo).to eq "Dr Scott" end it "does not mutate the input hash passed to the constructor" do expect(h[:blah][1][:foo]).to eq '2' end it "the deep copy recurses over Arrays as well" do expect(h[:blah][1][:foo]).to eq '2' end describe "#dup" do let(:duped_subject) { subject.dup } it "preserves sub-element modifications" do expect(duped_subject.blah[1].foo).to eq subject.blah[1].foo end it "allows the copy's sub-elements to be modified independently from the original's" do duped_subject.blah[1].foo = "Rocky" expect(duped_subject.blah[1].foo).to eq "Rocky" expect(subject.blah[1].foo).to eq "Dr Scott" end end end context "when array is nested deeper" do let(:deep_hash) { { :foo => { :blah => blah_list } } } subject { RecursiveOpenStruct.new(deep_hash, :recurse_over_arrays => true) } it { expect(subject.foo.blah.length).to eq 3 } it "Retains changes across Array lookups" do subject.foo.blah[1].foo = "Dr Scott" expect(subject.foo.blah[1].foo).to eq "Dr Scott" end end context "when array is in an array" do let(:haah) { { :blah => [ blah_list ] } } subject { RecursiveOpenStruct.new(haah, :recurse_over_arrays => true) } it { expect(subject.blah.length).to eq 1 } it { expect(subject.blah[0].length).to eq 3 } it "Retains changes across Array lookups" do subject.blah[0][1].foo = "Dr Scott" expect(subject.blah[0][1].foo).to eq "Dr Scott" end end end # when recursing over arrays is enabled context "when recursing over arrays is disabled" do subject { RecursiveOpenStruct.new(h) } it { expect(subject.blah.length).to eq 3 } it { expect(subject.blah[0]).to eq({ :foo => '1' }) } it { expect(subject.blah[0][:foo]).to eq '1' } end # when recursing over arrays is disabled describe 'modifying an array and recursing over it' do let(:h) { {} } subject { RecursiveOpenStruct.new(h, recurse_over_arrays: true) } context 'when adding an array with hashes into the tree' do before(:each) do subject.mystery = {} subject.mystery.science = [{ theatre: 9000 }] end it "ROS's it" do expect(subject.mystery.science[0].theatre).to eq 9000 end end context 'when appending a hash to an array' do before(:each) do subject.mystery = {} subject.mystery.science = [] subject.mystery.science << { theatre: 9000 } end it "ROS's it" do expect(subject.mystery.science[0].theatre).to eq 9000 end specify "the changes show up in .to_h" do expect(subject.to_h).to eq({ mystery: { science: [{theatre: 9000}]}}) end end context 'after appending a hash to an array' do before(:each) do subject.mystery = {} subject.mystery.science = [] subject.mystery.science[0] = {} end it "can have new values be set" do expect do subject.mystery.science[0].theatre = 9000 end.to_not raise_error expect(subject.mystery.science[0].theatre).to eq 9000 end end end # modifying an array and then recursing end # recursing over arrays describe 'nested nil values' do let(:h) { { foo: { bar: nil }} } it 'returns nil' do expect(subject.foo.bar).to be_nil end it 'returns a hash with the key and a nil value' do expect(subject.to_hash).to eq({ foo: { bar: nil }}) end end # nested nil values end # recursive behavior end recursive-open-struct-1.1.3/spec/recursive_open_struct/indifferent_access_spec.rb0000644000004100000410000001173113761545507030617 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do let(:value) { 'foo' } let(:symbol) { :bar } let(:new_value) { 'bar' } let(:new_symbol) { :foo } describe 'indifferent access' do let(:hash) { {:foo => value, 'bar' => symbol} } let(:hash_ros_opts) { {} } subject(:hash_ros) { RecursiveOpenStruct.new(hash, hash_ros_opts) } context 'setting value with method' do before(:each) do subject.foo = value end it('allows getting with method') { expect(subject.foo).to be value } it('allows getting with symbol') { expect(subject[:foo]).to be value } it('allows getting with string') { expect(subject['foo']).to be value } end context 'setting value with symbol' do before(:each) do subject[:foo] = value end it('allows getting with method') { expect(subject.foo).to be value } it('allows getting with symbol') { expect(subject[:foo]).to be value } it('allows getting with string') { expect(subject['foo']).to be value } end context 'setting value with string' do before(:each) do subject['foo'] = value end it('allows getting with method') { expect(subject.foo).to be value } it('allows getting with symbol') { expect(subject[:foo]).to be value } it('allows getting with string') { expect(subject['foo']).to be value } end context 'overwriting values' do context 'set with method' do before(:each) do subject.foo = value end it('overrides with symbol') do subject[:foo] = new_value expect(subject.foo).to be new_value end it('overrides with string') do subject['foo'] = new_value expect(subject.foo).to be new_value end end context 'set with symbol' do before(:each) do subject[:foo] = value end it('overrides with method') do subject.foo = new_value expect(subject[:foo]).to be new_value end it('overrides with string') do subject['foo'] = new_value expect(subject[:foo]).to be new_value end end context 'set with string' do before(:each) do subject['foo'] = value end it('overrides with method') do subject.foo = new_value expect(subject['foo']).to be new_value end it('overrides with symbol') do subject[:foo] = new_value expect(subject['foo']).to be new_value end end context 'set with hash' do it('overrides with method') do hash_ros.foo = new_value expect(hash_ros[:foo]).to be new_value hash_ros.bar = new_symbol expect(hash_ros['bar']).to be new_symbol end it('overrides with symbol') do hash_ros[:bar] = new_symbol expect(hash_ros['bar']).to be new_symbol end it('overrides with string') do hash_ros['foo'] = new_value expect(hash_ros[:foo]).to be new_value end end context 'when preserve_original_keys is not enabled' do context 'transforms original keys to symbols' do subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true) } let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } let(:symbolized_recursive_hash) { {:foo => [ {:bar => [ { :foo => :bar} ] } ] } } let(:symbolized_modified_hash) { {:foo => [ {:bar => [ { :foo => :foo} ] } ] } } let(:symbolized_hash) { Hash[hash.map{|(k,v)| [k.to_sym,v]}] } specify 'after initialization' do expect(hash_ros.to_h).to eq symbolized_hash end specify 'in recursive hashes' do expect(recursive.to_h).to eq symbolized_recursive_hash end specify 'after resetting value' do recursive.foo.first[:bar].first[:foo] = :foo expect(recursive.to_h).to eq symbolized_modified_hash end end end context 'when preserve_original_keys is enabled' do context 'preserves the original keys' do subject(:recursive) { RecursiveOpenStruct.new(recursive_hash, recurse_over_arrays: true, preserve_original_keys: true) } let(:recursive_hash) { {:foo => [ {'bar' => [ { 'foo' => :bar} ] } ] } } let(:modified_hash) { {:foo => [ {'bar' => [ { 'foo' => :foo} ] } ] } } let(:hash_ros_opts) { { preserve_original_keys: true }} specify 'after initialization' do expect(hash_ros.to_h).to eq hash end specify 'in recursive hashes' do expect(recursive.to_h).to eq recursive_hash end specify 'after resetting value' do recursive.foo.first[:bar].first[:foo] = :foo expect(recursive.to_h).to eq modified_hash end end end end end end recursive-open-struct-1.1.3/spec/recursive_open_struct/open_struct_behavior_spec.rb0000644000004100000410000000720213761545507031223 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do let(:hash) { {} } subject(:ros) { RecursiveOpenStruct.new(hash) } describe "behavior it inherits from OpenStruct" do context 'when not initialized from anything' do subject(:ros) { RecursiveOpenStruct.new } it "can represent arbitrary data objects" do ros.blah = "John Smith" expect(ros.blah).to eq "John Smith" end it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end end context 'when initialized with nil' do let(:hash) { nil } it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end end context 'when initialized with an empty hash' do it 'returns nil for missing attributes' do expect(ros.foo).to be_nil end end context "when initialized from a hash" do let(:hash) { { :asdf => 'John Smith' } } context 'that contains symbol keys' do it "turns those symbol keys into method names" do expect(ros.asdf).to eq "John Smith" end end it "can modify an existing key" do ros.asdf = "George Washington" expect(ros.asdf).to eq "George Washington" end context 'that contains string keys' do let(:hash) { { 'asdf' => 'John Smith' } } it "turns those string keys into method names" do expect(ros.asdf).to eq "John Smith" end end context 'that contains keys that mirror existing private methods' do let(:hash) { { test: :foo, rand: 'not a number' } } # https://github.com/aetherknight/recursive-open-struct/issues/42 it 'handles subscript notation without calling the method name first (#42)' do expect(ros['test']).to eq :foo expect(ros['rand']).to eq 'not a number' expect(ros.test).to eq :foo expect(ros.rand).to eq 'not a number' end end context 'that contains keys that mirror existing public methods inherited from Object' do let(:hash) { { method: :something } } it 'handles subscript notation without calling the existing methods' do expect(ros[:method]).to eq :something expect(ros['method']).to eq :something end end if [/\A([0-9]+)\.([0-9]+)\.([0-9]+)\z/.match(RUBY_VERSION)].tap { |l| m = l[0] ; l[0] = (m[1].to_i >= 2 && m[2].to_i >= 4) }.first context 'when Ruby 2.4.0 or newer' do specify 'new_ostruct_member! is private' do expect { ros.new_ostruct_member!(:bonsoir) }.to raise_error(NoMethodError) # OpenStruct.new().new_ostruct_member!(:foo) end end end end describe "handling of arbitrary attributes" do subject { RecursiveOpenStruct.new } before(:each) do subject.blah = "John Smith" end describe "#respond?" do it { expect(subject).to respond_to :blah } it { expect(subject).to respond_to :blah= } it { expect(subject).to_not respond_to :asdf } it { expect(subject).to_not respond_to :asdf= } end # describe #respond? describe "#methods" do it { expect(subject.methods.map(&:to_sym)).to include :blah } it { expect(subject.methods.map(&:to_sym)).to include :blah= } it { expect(subject.methods.map(&:to_sym)).to_not include :asdf } it { expect(subject.methods.map(&:to_sym)).to_not include :asdf= } end # describe #methods end # describe handling of arbitrary attributes end # describe behavior it inherits from OpenStruct end recursive-open-struct-1.1.3/spec/recursive_open_struct/debug_inspect_spec.rb0000644000004100000410000000441613761545507027616 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "#debug_inspect" do before(:each) do h1 = { :a => 'a'} h2 = { :a => 'b', :h1 => h1 } h1[:h2] = h2 @ros = RecursiveOpenStruct.new(h2) end it "should have a simple way of display" do @output = <<-QUOTE a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. a = "b" h1. a = "a" h2. (recursion limit reached) QUOTE @io = StringIO.new @ros.debug_inspect(@io) expect(@io.string).to match /^a = "b"$/ expect(@io.string).to match /^h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ a = "b"$/ expect(@io.string).to match /^ h1\.$/ expect(@io.string).to match /^ a = "a"$/ expect(@io.string).to match /^ h2\.$/ expect(@io.string).to match /^ \(recursion limit reached\)$/ end end end recursive-open-struct-1.1.3/spec/recursive_open_struct/recursion_and_subclassing_spec.rb0000644000004100000410000000065713761545507032236 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "subclassing RecursiveOpenStruct" do let(:subclass) { Class.new(RecursiveOpenStruct) } subject(:rossc) { subclass.new({ :one => [{:two => :three}] }, recurse_over_arrays: true) } specify "nested objects use the subclass of the parent" do expect(rossc.one.first.class).to eq subclass end end end recursive-open-struct-1.1.3/spec/recursive_open_struct/ostruct_2_3_0_spec.rb0000644000004100000410000000343613761545507027371 0ustar www-datawww-datarequire_relative '../spec_helper' require 'recursive_open_struct' describe RecursiveOpenStruct do describe "OpenStruct 2.3.0+ methods" do describe "#dig" do # We only care when Ruby supports `#dig`. if OpenStruct.public_instance_methods.include? :dig context "recurse_over_arrays: false" do subject { RecursiveOpenStruct.new(a: { b: 2, c: ["doo", "bee", { inner: "one"}]}) } describe "OpenStruct-like behavior" do it { expect(subject.dig(:a, :b)).to eq 2 } it { expect(subject.dig(:a, :c, 0)).to eq "doo" } it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" } end describe "recursive behavior" do it { expect(subject.dig(:a)).to eq RecursiveOpenStruct.new( { b: 2, c: ["doo", "bee", { inner: "one"}]} ) } it { expect(subject.dig(:a, :c, 2)).to eq({inner: "one"}) } end end context "recurse_over_arrays: true" do subject { RecursiveOpenStruct.new({a: { b: 2, c: ["doo", "bee", { inner: "one"}]}}, recurse_over_arrays: true) } describe "OpenStruct-like behavior" do it { expect(subject.dig(:a, :b)).to eq 2 } it { expect(subject.dig(:a, :c, 0)).to eq "doo" } it { expect(subject.dig(:a, :c, 2, :inner)).to eq "one" } end describe "recursive behavior" do it { expect(subject.dig(:a)).to eq RecursiveOpenStruct.new( { b: 2, c: ["doo", "bee", { inner: "one"}]} ) } it { expect(subject.dig(:a, :c, 2)).to eq RecursiveOpenStruct.new(inner: "one") } end end end end # describe #dig end # describe OpenStruct 2.3+ methods end recursive-open-struct-1.1.3/CHANGELOG.md0000644000004100000410000001777313761545507017701 0ustar www-datawww-data1.1.3 / 2020/10/15 ================== * No longer officially supporting Ruby 2.4.x, but compatiblity continues. * [#68](https://github.com/aetherknight/recursive-open-struct/pull/68): Igor Victor: Add truffleruby-head to travis * FIX [#67](https://github.com/aetherknight/recursive-open-struct/pull/67): Jean Boussier: Support upstream changes to OpenStruct in ruby-head (Ruby 3.0.0-dev) 1.1.2 / 2020/06/20 ================== * FIX [#58](https://github.com/aetherknight/recursive-open-struct/pull/58): David Feldman: Fix `[]=` so that it properly updates sub-elements * [#58](https://github.com/aetherknight/recursive-open-struct/pull/58): David Feldman: Make the default options configurable at the class level to simplify adding additional options in subclasses 1.1.1 / 2020/03/10 ================== * FIX [#64](https://github.com/aetherknight/recursive-open-struct/pull/64): Pirate Praveen: Support Ruby 2.7.0. `OpenStruct#modifiable` support was finally dropped, and has to be replaced with `OpenStruct#modifiable?`. * Made some additional changes to continue supporting pre-2.4.x Rubies, including the current stable JRuby (9.1.x.x, which tracks Ruby 2.3.x for features) 1.1.0 / 2018-02-03 ================== * NEW/FIX [#56](https://github.com/aetherknight/recursive-open-struct/issues/56): Add better support for Ruby 2.3+'s `#dig` method (when it exists for the current version of Ruby), so that nested Hashes are properly converted to RecursiveOpenStructs. `OpenStruct#dig`'s implementation was returning Hashes and does not handle `recurse_over_arrays` so ROS needs special support. Thanks to maxp-edcast for reporting the issue. * FIX [#55](https://github.com/aetherknight/recursive-open-struct/pull/55): EdwardBetts: Fixed a typo in the documentation/comment for `#method_missing` 1.0.5 / 2017-06-21 ================== * FIX [#54](https://github.com/aetherknight/recursive-open-struct/pull/54): Beni Cherniavsky-Paskin: Improve performance of `new_ostruct_member` by using `self.singleton_class.method_defined?` instead of `self.methods.include?` 1.0.4 / 2017-04-29 ================== * FIX [#52](https://github.com/aetherknight/recursive-open-struct/pull/52): Joe Rafaniello: Improve performance of DeepDup by using Set instead of an Array to track visited nodes. 1.0.3 / 2017-04-10 ================== * No longer officially supporting Ruby 2.0.0 and Ruby 2.1.x. They are still tested against but are permitted to fail within the Travis configuration. * FIX: Fix subscript notation for keys that collide with existing public methods. Related to [#51](https://github.com/aetherknight/recursive-open-struct/issues/51). * FIX [#49](https://github.com/aetherknight/recursive-open-struct/issues/49): Ensure test suite passes with Ruby 2.4.0-rc1. 1.0.2 / 2016-12-20 ================== * FIX [#46](https://github.com/aetherknight/recursive-open-struct/issues/46): Pedro Sena: fix issues with mutating arrays within an ROS that has `recurse_over_arrays: true` 1.0.1 / 2016-01-18 ================== * FIX [#42](https://github.com/aetherknight/recursive-open-struct/issues/42): `[]` tried to call private methods if they existed instead of triggering the `method_missing` code path. Thanks to @SaltwaterC for reporting. 1.0.0 / 2015-12-11 ================== * API-Breaking Change: Frederico Aloi: Change `to_h` to always return symbol keys. This is more consistent with OpenStruct. * API-Breaking Change: No longer officially supporting Ruby 1.9.3. * NEW/FIX: Kris Dekeyser: Ensure that ROS continues to work with the new version of OpenStruct included in dev versions of Ruby 2.2.x and Ruby 2.3. It now implements lazy attribute creation, which broke ROS. * NEW: Added `preserve_original_keys` option to revert to the 0.x behavior. Set it to true if you want methods like `to_h` to return strings and perhaps other non-symbols. * NEW: Ensuring support for Ruby 2.0.0+ including the upcoming 2.3 release and JRuby 9000. * FIX: Peter Yeremenko: Fix a mistake in one of the examples in the README 0.6.5 / 2015-06-30 ================== * FIX: Fix ROS when initialized with nil instead of a hash. 0.6.4 / 2015-05-20 ================== * FIX: Kris Dekeyser: Fix indifferent subscript access (string or symbol). Also backported several ostruct methods for Ruby 1.9.x. * FIX: Partial fix for allowing an array in a RecursiveOpenStruct tree to be modified. However, methods such as to_hash are still broken. 0.6.3 / 2015-04-11 ================== * FIX: Thiago Guimaraes: Restore being able to create an ROS from a hash that contains strings for keys instead of symbols for keys. 0.6.2 / 2015-04-07 ================== * FIX: fervic: Address a bug in the Ruby 1.9's version of OpenStruct's `dup` * FIX: Victor Guzman: Reset memoized values on assignment in order to force the implementation to re-memoize them. * MAINT: fervic: Simplified `initialize` 0.6.1 / 2015-03-28 ================== * FIX: Actually ensure that the internal @table is properly dependent or independent of the input hash tree. I mistakenly refactored away an important piece of code that fervic added. * FIX: Actually ensure that `#dup` works. * Also refactored how `#to_h` is implemented to use newer plumbing. 0.6.0 / 2015-03-28 ================== * NEW: fervic: Make subscript notation be recursive like dot-method notation * NEW: fervic: Added a new option, `:mutate_input_hash`, that allows the caller to determine whether the original hash is mutated or not when a nested value in the ROS tree is modified. If false (the default), the ROS will not modify the original hash tree. If tree, changes within the ROS tree will also be reflected in the hash tree. * FIX: fervic: Setting/updating a value nested deep in an ROS tree is kept when the top-level ROS object is duped. * MAINT: Extracted `#deep_dup` added by fervic into its own class. This makes it possibly easier to use/copy for others, and it cleans up the main class file. * MAINT: Moved `#debug_inspect` out to its own module. This cleans up the main class file a bit. It is also something I may remove if I ever have a major version bump. * MAINT: Adding MRI 2.2 to Travis-CI 0.5.0 / 2014-06-14 ================== * NEW: Tom Chapin: Added a `#to_hash` alias for `#to_h` * MAINT: Added Travis-CI support. Testing against MRI 1.9.3, MRI 2.0, MRI 2.1, and JRuby in 1.9 mode. Not aiming to support 1.8.7 since it has been nearly a year since it has officially been retired. 0.4.5 / 2013-10-23 ================== * FIX: Matt Culpepper: Allow ROS subclasses to use their own type when creating nested objects in the tree. 0.4.4 / 2013-08-28 ================== * FIX: Ensure proper file permissions when building the gem archive 0.4.3 / 2013-05-30 ================== * FIX: Sebastian Gaul: Make `recurse_over_arrays` option work on more deeply-nested hashes. 0.4.2 / 2013-05-29 ================== * FIX: Setting a value on a nested element, then getting that value should show the updated value * FIX: Calling `#to_h` on the top-level ROS object also reflects changed nested elements. 0.4.1 / 2013-05-28 ================== * FIX: Fixing the `spec:coverage` Rake task 0.4.0 / 2013-05-26 ================== * NEW: Added `#to_h` * MAINT: Stopped using jeweler for gem development/packaging 0.3.1 / 2012-10-23 ================== * FIX: Cédric Felizard: Fix to make it work with MRI 1.8.7 again * MAINT: More spec fixups to improve spec runs on MRI 1.9.3 0.3.0 / 2013-10-23 ================== * NEW: Matthew O'Riordan: Add support for recursion working over Arrays * NEW: Made recursion over Arrays optional with `recurse_over_arrays` option. * NEW: Improving `#debug_inspect` so that it can use any IO object, not just STDOUT. * MAINT: Much cleanup of development dependencies, README file, etc. 0.2.1 / 2011-05-31 ================== * FIX: Offirmo: Slight improvement for `#debug_inspect` 0.2.0 / 2011-05-25 ================== * NEW: Offirmo: Added `debug_inspect` * MAINT: Offirmo: Worked the development files so that it can be built as a gem 0.1.0 / 2010-01-12 ================== * Initial release recursive-open-struct-1.1.3/.gitignore0000644000004100000410000000147413761545507020047 0ustar www-datawww-data# rcov generated coverage # rdoc generated rdoc # yard generated doc .yardoc # bundler .bundle # jeweler generated pkg # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore: # # * Create a file at ~/.gitignore # * Include files you want ignored # * Run: git config --global core.excludesfile ~/.gitignore # # After doing this, these files will be ignored in all your git projects, # saving you from having to 'pollute' every project you touch with them # # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line) # # For MacOS: # .DS_Store # For TextMate *.tmproj tmtags # For emacs: *~ \#* .\#* # For vim: *.swp # For redcar: #.redcar # For rubinius: #*.rbc Gemfile.lock .ruby-version recursive-open-struct-1.1.3/.document0000644000004100000410000000006713761545507017673 0ustar www-datawww-datalib/**/*.rb bin/* - features/**/*.feature LICENSE.txt recursive-open-struct-1.1.3/Rakefile0000644000004100000410000000277613761545507017532 0ustar www-datawww-data# encoding: utf-8 require 'rubygems' require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) do |spec| spec.pattern = FileList['spec/**/*_spec.rb'] end namespace :spec do if RUBY_VERSION =~ /^1\.8/ desc "Rspec code coverage (1.8.7)" RSpec::Core::RakeTask.new(:coverage) do |spec| spec.pattern = 'spec/**/*_spec.rb' spec.rcov = true end else desc "Rspec code coverage (1.9+)" task :coverage do ENV['COVERAGE'] = 'true' Rake::Task["spec"].execute end end end require 'rdoc/task' Rake::RDocTask.new do |rdoc| version = File.exist?('VERSION') ? File.read('VERSION') : "" rdoc.rdoc_dir = 'rdoc' rdoc.title = "recursive-open-struct #{version}" rdoc.rdoc_files.include('README*') rdoc.rdoc_files.include('lib/**/*.rb') end task :default => :spec task :fix_permissions do File.umask 0022 filelist = `git ls-files`.split("\n") FileUtils.chmod 0644, filelist, :verbose => true FileUtils.chmod 0755, ['lib','spec'], :verbose => true end desc "Update the AUTHORS.txt file" task :update_authors do authors = `git log --format="%aN <%aE>"|sort -f|uniq` File.open('AUTHORS.txt', 'w') do |f| f.write("Recursive-open-struct was written by these fine people:\n\n") f.write(authors.split("\n").map { |a| "* #{a}" }.join( "\n" )) f.write("\n") end end task :build => [:update_authors, :fix_permissions] desc "Run an interactive pry shell with ros required" task :pry do sh "pry -I lib -r recursive-open-struct" end recursive-open-struct-1.1.3/lib/0000755000004100000410000000000013761545507016617 5ustar www-datawww-datarecursive-open-struct-1.1.3/lib/recursive-open-struct.rb0000644000004100000410000000004013761545507023426 0ustar www-datawww-datarequire 'recursive_open_struct' recursive-open-struct-1.1.3/lib/recursive_open_struct.rb0000644000004100000410000001402713761545507023604 0ustar www-datawww-datarequire 'ostruct' require 'recursive_open_struct/version' require 'recursive_open_struct/debug_inspect' require 'recursive_open_struct/deep_dup' require 'recursive_open_struct/dig' # TODO: When we care less about Rubies before 2.4.0, match OpenStruct's method # names instead of doing things like aliasing `new_ostruct_member` to # `new_ostruct_member!` # # TODO: `#*_as_a_hash` deprecated. Nested hashes can be referenced using # `#to_h`. class RecursiveOpenStruct < OpenStruct include Dig if OpenStruct.public_instance_methods.include? :dig # TODO: deprecated, possibly remove or make optional an runtime so that it # doesn't normally pollute the public method namespace include DebugInspect def self.default_options { mutate_input_hash: false, recurse_over_arrays: false, preserve_original_keys: false } end def initialize(hash=nil, passed_options={}) hash ||= {} @options = self.class.default_options.merge!(passed_options).freeze @deep_dup = DeepDup.new(@options) @table = @options[:mutate_input_hash] ? hash : @deep_dup.call(hash) @sub_elements = {} end if OpenStruct.public_instance_methods.include?(:initialize_copy) def initialize_copy(orig) super # deep copy the table to separate the two objects @table = @deep_dup.call(@table) # Forget any memoized sub-elements @sub_elements = {} end end def to_h @deep_dup.call(@table) end # TODO: deprecated, unsupported by OpenStruct. OpenStruct does not consider # itself to be a "kind of" Hash. alias_method :to_hash, :to_h # Continue supporting older rubies -- JRuby 9.1.x.x is still considered # stable, but is based on Ruby # 2.3.x and so uses :modifiable instead of :modifiable?. Furthermore, if # :modifiable is private, then make :modifiable? private too. if !OpenStruct.private_instance_methods.include?(:modifiable?) if OpenStruct.private_instance_methods.include?(:modifiable) alias_method :modifiable?, :modifiable elsif OpenStruct.public_instance_methods.include?(:modifiable) alias_method :modifiable?, :modifiable private :modifiable? end end def [](name) key_name = _get_key_from_table_(name) v = @table[key_name] if v.is_a?(Hash) @sub_elements[key_name] ||= _create_sub_element_(v, mutate_input_hash: true) elsif v.is_a?(Array) and @options[:recurse_over_arrays] @sub_elements[key_name] ||= recurse_over_array(v) @sub_elements[key_name] = recurse_over_array(@sub_elements[key_name]) else v end end if private_instance_methods.include?(:modifiable?) || public_instance_methods.include?(:modifiable?) def []=(name, value) key_name = _get_key_from_table_(name) tbl = modifiable? # Ensure we are modifiable @sub_elements.delete(key_name) tbl[key_name] = value end else def []=(name, value) key_name = _get_key_from_table_(name) @table[key_name] = value # raises if self is frozen in Ruby 3.0 @sub_elements.delete(key_name) end end # Makes sure ROS responds as expected on #respond_to? and #method requests def respond_to_missing?(mid, include_private = false) mname = _get_key_from_table_(mid.to_s.chomp('=').chomp('_as_a_hash')) @table.key?(mname) || super end # Adapted implementation of method_missing to accommodate the differences # between ROS and OS. def method_missing(mid, *args) len = args.length if mid =~ /^(.*)=$/ if len != 1 raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1) end # self[$1.to_sym] = args[0] # modifiable?[new_ostruct_member!($1.to_sym)] = args[0] new_ostruct_member!($1.to_sym) public_send(mid, args[0]) elsif len == 0 key = mid key = $1 if key =~ /^(.*)_as_a_hash$/ if @table.key?(_get_key_from_table_(key)) new_ostruct_member!(key) public_send(mid) end else err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args err.set_backtrace caller(1) raise err end end # TODO: Rename to new_ostruct_member! once we care less about Rubies before # 2.4.0. def new_ostruct_member(name) key_name = _get_key_from_table_(name) unless self.singleton_class.method_defined?(name.to_sym) class << self; self; end.class_eval do define_method(name) do self[key_name] end define_method("#{name}=") do |x| self[key_name] = x end define_method("#{name}_as_a_hash") { @table[key_name] } end end key_name end # Support Ruby 2.4.0+'s changes in a way that doesn't require dynamically # modifying ROS. # # TODO: Once we care less about Rubies before 2.4.0, reverse this so that # new_ostruct_member points to our version and not OpenStruct's. alias new_ostruct_member! new_ostruct_member # new_ostruct_member! is private, but new_ostruct_member is not on OpenStruct in 2.4.0-rc1?! private :new_ostruct_member! def delete_field(name) sym = _get_key_from_table_(name) singleton_class.__send__(:remove_method, sym, "#{sym}=") rescue NoMethodError # ignore if methods not yet generated. @sub_elements.delete(sym) @table.delete(sym) end private unless OpenStruct.public_instance_methods.include?(:initialize_copy) def initialize_dup(orig) super # deep copy the table to separate the two objects @table = @deep_dup.call(@table) # Forget any memoized sub-elements @sub_elements = {} end end def _get_key_from_table_(name) return name.to_s if @table.has_key?(name.to_s) return name.to_sym if @table.has_key?(name.to_sym) name end def _create_sub_element_(hash, **overrides) self.class.new(hash, @options.merge(overrides)) end def recurse_over_array(array) array.each_with_index do |a, i| if a.is_a? Hash array[i] = _create_sub_element_(a, mutate_input_hash: true, recurse_over_arrays: true) elsif a.is_a? Array array[i] = recurse_over_array a end end array end end recursive-open-struct-1.1.3/lib/recursive_open_struct/0000755000004100000410000000000013761545507023253 5ustar www-datawww-datarecursive-open-struct-1.1.3/lib/recursive_open_struct/version.rb0000644000004100000410000000025313761545507025265 0ustar www-datawww-data# Necessary since the top-level class/module is a class that inherits from # OpenStruct. require 'ostruct' class RecursiveOpenStruct < OpenStruct VERSION = "1.1.3" end recursive-open-struct-1.1.3/lib/recursive_open_struct/debug_inspect.rb0000644000004100000410000000264113761545507026416 0ustar www-datawww-datamodule RecursiveOpenStruct::DebugInspect def debug_inspect(io = STDOUT, indent_level = 0, recursion_limit = 12) display_recursive_open_struct(io, @table, indent_level, recursion_limit) end def display_recursive_open_struct(io, ostrct_or_hash, indent_level, recursion_limit) if recursion_limit <= 0 then # protection against recursive structure (like in the tests) io.puts ' '*indent_level + '(recursion limit reached)' else #puts ostrct_or_hash.inspect if ostrct_or_hash.is_a?(self.class) then ostrct_or_hash = ostrct_or_hash.marshal_dump end # We'll display the key values like this : key = value # to align display, we look for the maximum key length of the data that will be displayed # (everything except hashes) data_indent = ostrct_or_hash \ .reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) } \ .max {|a,b| a[0].to_s.length <=> b[0].to_s.length}[0].to_s.length # puts "max length = #{data_indent}" ostrct_or_hash.each do |key, value| if (value.is_a?(self.class) || value.is_a?(Hash)) then io.puts ' '*indent_level + key.to_s + '.' display_recursive_open_struct(io, value, indent_level + 1, recursion_limit - 1) else io.puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect end end end true end end recursive-open-struct-1.1.3/lib/recursive_open_struct/deep_dup.rb0000644000004100000410000000160413761545507025366 0ustar www-datawww-datarequire 'set' class RecursiveOpenStruct::DeepDup def initialize(opts={}) @recurse_over_arrays = opts.fetch(:recurse_over_arrays, false) @preserve_original_keys = opts.fetch(:preserve_original_keys, false) end def call(obj) deep_dup(obj) end private def deep_dup(obj, visited=Set.new) if obj.is_a?(Hash) obj.each_with_object({}) do |(key, value), h| h[@preserve_original_keys ? key : key.to_sym] = value_or_deep_dup(value, visited) end elsif obj.is_a?(Array) && @recurse_over_arrays obj.each_with_object([]) do |value, arr| value = value.is_a?(RecursiveOpenStruct) ? value.to_h : value arr << value_or_deep_dup(value, visited) end else obj end end def value_or_deep_dup(value, visited) obj_id = value.object_id visited.include?(obj_id) ? value : deep_dup(value, visited << obj_id) end end recursive-open-struct-1.1.3/lib/recursive_open_struct/dig.rb0000644000004100000410000000102713761545507024343 0ustar www-datawww-dataclass RecursiveOpenStruct < OpenStruct module Dig # Replaces +OpenStruct#dig+ to properly support treating nested values as # RecursiveOpenStructs instead of returning the nested Hashes. def dig(name, *names) begin name = name.to_sym rescue NoMethodError raise TypeError, "#{name} is not a symbol nor a string" end name_val = self[name] if names.length > 0 && name_val.respond_to?(:dig) name_val.dig(*names) else name_val end end end end recursive-open-struct-1.1.3/CONTRIBUTING.md0000644000004100000410000000510513761545507020303 0ustar www-datawww-data# Contributing to recursive-open-struct Thanks for wanting to contribute a bug or code to recursive-open-struct! To help you out with understanding the direction and philosophy of this project with regards to to new features/how it should behave (and whether to file a bug report), please review the following contribution guidelines. ## ROS Feature Philosophy Recursive-open-struct tries to be a minimal extension to the Ruby stdlib's `ostruct`/OpenStruct that allows for a nested set of Hashes (and Arrays) to initialize similarly structured OpenStruct-like objects. This has the benefit of creating arbitrary objects whose values can be accessed with accessor methods, similar to JavaScript Objects' dot-notation. To phrase it another way, RecursiveOpenStruct tries to behave as closely as possible to OpenStruct, except for the recursive functionality that it adds. If Recursive-open-struct were to add additional features (particularly methods) that are not implemented by OpenStruct, then those method names would not be available for use for accessing fields with the dot-notation that OpenStruct and RecursiveOpenStruct provide. For example, OpenStruct is not (at the time this is written) a subclass/specialization of Hash, so several methods implemented by Hash do not work with OpenStruct (and thus Recursive OpenStruct), such as `#fetch`. If you want to add features into RecursiveOpenStruct that would "pollute" the method namespace more than OpenStruct already does, consider creating your own subclass instead of submitting a code change to RecursiveOpenStruct itself. ## Filing/Fixing Bugs and Requesting/Proposing New Features For simple bug fixes, feel free to provide a pull request. This includes bugs in stated features of RecursiveOpenStruct, as well as features added to OpenStruct in a newer version of Ruby that RecursiveOpenStruct needs custom support to handle. For anything else (new features, bugs that you want to report, and bugs that are difficult to fix), I recommend opening an issue first to discuss the feature or bug. I am fairly cautious about adding new features that might cause RecursiveOpenStruct's API to deviate radically from OpenStruct's (since it might introduce new reserved method names), and it is useful to discuss the best way to solve a problem when there are tradeoffs or imperfect solutions. When contributing code that changes behavior or fixes bugs, please include unit tests to cover the new behavior or to provide regression testing for bugs. Also, treat the unit tests as documentation --- make sure they are clean, clear, and concise, and well organized. recursive-open-struct-1.1.3/Gemfile0000644000004100000410000000004613761545507017344 0ustar www-datawww-datasource 'https://rubygems.org' gemspec recursive-open-struct-1.1.3/LICENSE.txt0000644000004100000410000000213713761545507017677 0ustar www-datawww-dataCopyright (c) 2009-2018, The Recursive-open-struct developers (given in the file AUTHORS.txt). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. recursive-open-struct-1.1.3/AUTHORS.txt0000644000004100000410000000174313761545507017744 0ustar www-datawww-dataRecursive-open-struct was written by these fine people: * Ben Langfeld * Beni Cherniavsky-Paskin * Cédric Felizard * David Feldman * Edward Betts * Ewoud Kohl van Wijngaarden * Federico Aloi * fervic * Igor Victor * Jean Boussier * Joe Rafaniello * Kris Dekeyser * Matt Culpepper * Matthew O'Riordan * Offirmo * Pedro Sena * Peter Yeremenko * Pirate Praveen * Sebastian Gaul * Thiago Guimaraes * Tom Chapin * Victor Guzman * William (B.J.) Snow Orvis