ryo.rb 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/specs.yml +23 -0
- data/.gitignore +6 -0
- data/.gitlab-ci.yml +9 -0
- data/.rubocop.yml +56 -0
- data/.yardoc-template/default/fulldoc/html/css/0x1eef.css +15 -0
- data/.yardoc-template/default/layout/html/setup.rb +5 -0
- data/.yardoc-template/default/module/setup.rb +7 -0
- data/.yardopts +4 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +373 -0
- data/Rakefile +3 -0
- data/lib/ryo/basic_object.rb +58 -0
- data/lib/ryo/builder.rb +106 -0
- data/lib/ryo/enumerable.rb +214 -0
- data/lib/ryo/function.rb +68 -0
- data/lib/ryo/keywords.rb +67 -0
- data/lib/ryo/lazy.rb +4 -0
- data/lib/ryo/object.rb +58 -0
- data/lib/ryo/reflect.rb +379 -0
- data/lib/ryo/version.rb +5 -0
- data/lib/ryo.rb +197 -0
- data/ryo.rb.gemspec +21 -0
- data/share/ryo.rb/examples/1.0_prototypes_point_object.rb +12 -0
- data/share/ryo.rb/examples/1.1_prototypes_ryo_fn.rb +14 -0
- data/share/ryo.rb/examples/2.0_iteration_each.rb +13 -0
- data/share/ryo.rb/examples/2.1_iteration_map.rb +16 -0
- data/share/ryo.rb/examples/2.2_iteration_ancestors.rb +13 -0
- data/share/ryo.rb/examples/3.0_recursion_ryo_from.rb +13 -0
- data/share/ryo.rb/examples/3.1_recursion_ryo_from_with_array.rb +19 -0
- data/share/ryo.rb/examples/3.2_recursion_ryo_from_with_openstruct.rb +14 -0
- data/share/ryo.rb/examples/4.0_basicobject_ryo_basicobject.rb +12 -0
- data/share/ryo.rb/examples/4.1_basicobject_ryo_basicobject_from.rb +13 -0
- data/share/ryo.rb/examples/5_collisions_resolution_strategy.rb +8 -0
- data/share/ryo.rb/examples/6_beyond_hash_objects.rb +20 -0
- data/share/ryo.rb/examples/7_ryo_lazy.rb +14 -0
- data/share/ryo.rb/examples/setup.rb +3 -0
- data/spec/readme_spec.rb +79 -0
- data/spec/ryo_basic_object_spec.rb +60 -0
- data/spec/ryo_enumerable_spec.rb +197 -0
- data/spec/ryo_keywords_spec.rb +86 -0
- data/spec/ryo_object_spec.rb +71 -0
- data/spec/ryo_prototypes_spec.rb +45 -0
- data/spec/ryo_reflect_spec.rb +175 -0
- data/spec/ryo_spec.rb +130 -0
- data/spec/setup.rb +5 -0
- metadata +173 -0
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
            require "ryo"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class Point
         | 
| 7 | 
            +
              def initialize
         | 
| 8 | 
            +
                @x = 5
         | 
| 9 | 
            +
                @y = 10
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              def each_pair
         | 
| 13 | 
            +
                yield("x", @x)
         | 
| 14 | 
            +
                yield("y", @y)
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            option = Ryo(Point.new)
         | 
| 19 | 
            +
            p option.x # => 5
         | 
| 20 | 
            +
            p option.y # => 10
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require_relative "setup"
         | 
| 2 | 
            +
            require "ryo"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            point_x = Ryo(x: Ryo.lazy { 5 })
         | 
| 5 | 
            +
            point_y = Ryo({y: Ryo.lazy { 10 }}, point_x)
         | 
| 6 | 
            +
            point = Ryo({sum: Ryo.lazy { x + y }}, point_y)
         | 
| 7 | 
            +
            print "point.x = ", point.x, "\n"
         | 
| 8 | 
            +
            print "point.y = ", point.y, "\n"
         | 
| 9 | 
            +
            print "point.sum = ", point.sum, "\n"
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            ##
         | 
| 12 | 
            +
            # point.x = 5
         | 
| 13 | 
            +
            # point.y = 10
         | 
| 14 | 
            +
            # point.sum = 15
         | 
    
        data/spec/readme_spec.rb
    ADDED
    
    | @@ -0,0 +1,79 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
            require "test-cmd"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            RSpec.describe "README.md examples" do
         | 
| 7 | 
            +
              run_example = ->(file) do
         | 
| 8 | 
            +
                Test::Cmd.cmd("ruby share/ryo.rb/examples/#{file}")
         | 
| 9 | 
            +
              end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
              subject do
         | 
| 12 | 
            +
                run_example.(file).stdout.chomp
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              context "when given 1.0_prototypes_point_object.rb" do
         | 
| 16 | 
            +
                let(:file) { "1.0_prototypes_point_object.rb" }
         | 
| 17 | 
            +
                it { is_expected.to eq("[5, 10]") }
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              context "when given 1.1_prototypes_ryo_fn.rb" do
         | 
| 21 | 
            +
                let(:file) { "1.1_prototypes_ryo_fn.rb" }
         | 
| 22 | 
            +
                it { is_expected.to eq("[10, 20]") }
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              context "when given 2.0_iteration_each.rb" do
         | 
| 26 | 
            +
                let(:file) { "2.0_iteration_each.rb" }
         | 
| 27 | 
            +
                it { is_expected.to eq(%(["x", 10]\n["y", 20])) }
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              context "when given 2.1_iteration_map.rb" do
         | 
| 31 | 
            +
                let(:file) { "2.1_iteration_map.rb" }
         | 
| 32 | 
            +
                it { is_expected.to eq("[4, 8]\n[4, 8]") }
         | 
| 33 | 
            +
              end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              context "when given 2.2_iteration_ancestors.rb" do
         | 
| 36 | 
            +
                let(:file) { "2.2_iteration_ancestors.rb" }
         | 
| 37 | 
            +
                it { is_expected.to eq("nil\nnil\n5\n5") }
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
              context "when given 3.0_recursion_ryo_from.rb" do
         | 
| 41 | 
            +
                let(:file) { "3.0_recursion_ryo_from.rb" }
         | 
| 42 | 
            +
                it { is_expected.to eq("[0, 10]") }
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              context "when given 3.1_recursion_ryo_from_with_array.rb" do
         | 
| 46 | 
            +
                let(:file) { "3.1_recursion_ryo_from_with_array.rb" }
         | 
| 47 | 
            +
                it { is_expected.to eq(%(2\n"foobar"\n4)) }
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              context "when given 3.2_recursion_ryo_from_with_openstruct.rb" do
         | 
| 51 | 
            +
                let(:file) { "3.2_recursion_ryo_from_with_openstruct.rb" }
         | 
| 52 | 
            +
                it { is_expected.to eq("[5, 10]") }
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              context "when given 4.0_basicobject_ryo_basicobject.rb" do
         | 
| 56 | 
            +
                let(:file) { "4.0_basicobject_ryo_basicobject.rb" }
         | 
| 57 | 
            +
                it { is_expected.to eq("[0, 0]") }
         | 
| 58 | 
            +
              end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
              context "when given 4.1_basicobject_ryo_basicobject_from.rb" do
         | 
| 61 | 
            +
                let(:file) { "4.1_basicobject_ryo_basicobject_from.rb" }
         | 
| 62 | 
            +
                it { is_expected.to eq("[2, 4]") }
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              context "when given 5_collisions_resolution_strategy.rb" do
         | 
| 66 | 
            +
                let(:file) { "5_collisions_resolution_strategy.rb" }
         | 
| 67 | 
            +
                it { is_expected.to eq("12\n34") }
         | 
| 68 | 
            +
              end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
              context "when given 6_beyond_hash_objects.rb" do
         | 
| 71 | 
            +
                let(:file) { "6_beyond_hash_objects.rb" }
         | 
| 72 | 
            +
                it { is_expected.to eq("5\n10") }
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              context "when given 7_ryo_lazy.rb" do
         | 
| 76 | 
            +
                let(:file) { "7_ryo_lazy.rb" }
         | 
| 77 | 
            +
                it { is_expected.to eq("point.x = 5\npoint.y = 10\npoint.sum = 15") }
         | 
| 78 | 
            +
              end
         | 
| 79 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
            require "ostruct"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            RSpec.describe Ryo::BasicObject do
         | 
| 7 | 
            +
              describe "Ryo::BasicObject()" do
         | 
| 8 | 
            +
                context "when given a Hash" do
         | 
| 9 | 
            +
                  subject { [point.x, point.y] }
         | 
| 10 | 
            +
                  let(:point) { Ryo::BasicObject(x: 0, y: 0) }
         | 
| 11 | 
            +
                  it { is_expected.to eq([0, 0]) }
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                context "when given an OpenStruct" do
         | 
| 15 | 
            +
                  subject { [point.x, point.y] }
         | 
| 16 | 
            +
                  let(:point) { Ryo::BasicObject(OpenStruct.new(x: 0, y: 0)) }
         | 
| 17 | 
            +
                  it { is_expected.to eq([0, 0]) }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  context "when verifying the object is a Ryo object" do
         | 
| 20 | 
            +
                    subject { Ryo === point }
         | 
| 21 | 
            +
                    it { is_expected.to be(true) }
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              describe ".from" do
         | 
| 27 | 
            +
                context "when given an instance of Ryo::BasicObject" do
         | 
| 28 | 
            +
                  subject { Ryo.from(point) }
         | 
| 29 | 
            +
                  let(:point) { Ryo::BasicObject(x: 5, y: 10) }
         | 
| 30 | 
            +
                  it { is_expected.to eq(point) }
         | 
| 31 | 
            +
                end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                context "when given nested Hash objects" do
         | 
| 34 | 
            +
                  subject { point.x.to_i }
         | 
| 35 | 
            +
                  let(:point) { Ryo::BasicObject.from({x: {to_i: 4}}) }
         | 
| 36 | 
            +
                  it { is_expected.to eq(4) }
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                context "when given an Array that contains nested Hash objects" do
         | 
| 40 | 
            +
                  subject { points[0].x.to_i }
         | 
| 41 | 
            +
                  let(:points) { Ryo::BasicObject.from([{x: {to_i: 4}}]) }
         | 
| 42 | 
            +
                  it { is_expected.to eq(4) }
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                context "with a prototype" do
         | 
| 46 | 
            +
                  let(:point_a) { Ryo::BasicObject.from(x: {to_i: 0}) }
         | 
| 47 | 
            +
                  let(:point_b) { Ryo::BasicObject.from({y: {to_i: 2}}, point_a) }
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  context "when traversing to the prototype (point_a)" do
         | 
| 50 | 
            +
                    subject { point_b.x.to_i }
         | 
| 51 | 
            +
                    it { is_expected.to eq(0) }
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  context "when verifying a nested Hash doesn't inherit the prototype (point_a)" do
         | 
| 55 | 
            +
                    subject { point_b.y.x }
         | 
| 56 | 
            +
                    it { is_expected.to eq(nil) }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,197 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            RSpec.describe Ryo::Enumerable do
         | 
| 6 | 
            +
              describe ".each" do
         | 
| 7 | 
            +
                context "when verifying each traverses through the prototype chain" do
         | 
| 8 | 
            +
                  subject { Ryo.each(point_c).map { [_1, _2] } }
         | 
| 9 | 
            +
                  let(:point_a) { Ryo(x: 0) }
         | 
| 10 | 
            +
                  let(:point_b) { Ryo({y: 5}, point_a) }
         | 
| 11 | 
            +
                  let(:point_c) { Ryo({}, point_b) }
         | 
| 12 | 
            +
                  it { is_expected.to eq([["y", 5], ["x", 0]]) }
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              describe ".map" do
         | 
| 17 | 
            +
                subject(:point_c) { Ryo.map(point_b) { _2 * 2 } }
         | 
| 18 | 
            +
                let(:point_a) { Ryo::BasicObject(x: 4, y: 4) }
         | 
| 19 | 
            +
                let(:point_b) { Ryo::BasicObject({x: 2, y: 2}, point_a) }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                context "when verifying the map operation" do
         | 
| 22 | 
            +
                  it { is_expected.to eq({x: 4, y: 4}) }
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                context "when verifying the map operation on the prototype" do
         | 
| 26 | 
            +
                  subject { point_a }
         | 
| 27 | 
            +
                  before { Ryo.map!(point_b) { _2 * 2 } }
         | 
| 28 | 
            +
                  it { is_expected.to eq({x: 8, y: 8}) }
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                context "when verifying the map operation returns a new object" do
         | 
| 32 | 
            +
                  subject { Ryo.kernel(:equal?).bind_call(point_b, point_c) }
         | 
| 33 | 
            +
                  it { is_expected.to be(false) }
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              describe ".select" do
         | 
| 38 | 
            +
                context "with prototype chain traversal" do
         | 
| 39 | 
            +
                  subject { Ryo.select(point_b) { _1 == "y" and _2 == 4 } }
         | 
| 40 | 
            +
                  let(:point_a) { Ryo::BasicObject(x: 1, y: 2) }
         | 
| 41 | 
            +
                  let(:point_b) { Ryo::BasicObject({x: 3, y: 4}, point_a) }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  context "when verifying the filter operation" do
         | 
| 44 | 
            +
                    it { is_expected.to eq(y: 4) }
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  context "when verifying the filter operation on the prototype" do
         | 
| 48 | 
            +
                    subject { point_a.y }
         | 
| 49 | 
            +
                    before { Ryo.select!(point_b) { _1 == "x" } }
         | 
| 50 | 
            +
                    it { is_expected.to eq(nil) }
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              describe ".reject" do
         | 
| 56 | 
            +
                context "with prototype chain traversal" do
         | 
| 57 | 
            +
                  subject { Ryo.reject(point_b) { _1 == "x" } }
         | 
| 58 | 
            +
                  let(:point_a) { Ryo::BasicObject(x: 1, y: 2) }
         | 
| 59 | 
            +
                  let(:point_b) { Ryo::BasicObject({x: 3, y: 4}, point_a) }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  context "when verifying the filter operation" do
         | 
| 62 | 
            +
                    it { is_expected.to eq(y: 4) }
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  context "when verifying the filter operation on the prototype" do
         | 
| 66 | 
            +
                    subject { point_a.y }
         | 
| 67 | 
            +
                    before { Ryo.reject!(point_b) { _1 == "y" } }
         | 
| 68 | 
            +
                    it { is_expected.to eq(nil) }
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
              end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
              describe ".any?" do
         | 
| 74 | 
            +
                let(:point_a) { Ryo::BasicObject(y: 10) }
         | 
| 75 | 
            +
                let(:point_b) { Ryo::BasicObject({x: 5}, point_a) }
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                context "when an iteration returns a truthy value" do
         | 
| 78 | 
            +
                  subject { Ryo.any?(point_b) { _2 > 5 } }
         | 
| 79 | 
            +
                  it { is_expected.to be(true) }
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                context "when an iteration fails to return a truthy value" do
         | 
| 83 | 
            +
                  subject { Ryo.any?(point_b) { _2 > 20 } }
         | 
| 84 | 
            +
                  it { is_expected.to be(false) }
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
              end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
              describe ".all?" do
         | 
| 89 | 
            +
                let(:point_a) { Ryo::BasicObject(y: 10) }
         | 
| 90 | 
            +
                let(:point_b) { Ryo::BasicObject({x: 5}, point_a) }
         | 
| 91 | 
            +
                let(:point_c) { Ryo::BasicObject({z: 0}, point_b) }
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                context "when every iteration returns a truthy value" do
         | 
| 94 | 
            +
                  subject { Ryo.all?(point_c) { _2 < 11 } }
         | 
| 95 | 
            +
                  it { is_expected.to be(true) }
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                context "when an iteration fails to return a truthy value" do
         | 
| 99 | 
            +
                  subject { Ryo.all?(point_c) { _2 < 5 } }
         | 
| 100 | 
            +
                  it { is_expected.to be(false) }
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
              end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
              describe ".find" do
         | 
| 105 | 
            +
                let(:point_a) { Ryo::BasicObject(x: 5) }
         | 
| 106 | 
            +
                let(:point_b) { Ryo::BasicObject({y: 10}, point_a) }
         | 
| 107 | 
            +
                let(:point_c) { Ryo::BasicObject({z: 15}, point_b) }
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                context "when an iteration yields true on point_a" do
         | 
| 110 | 
            +
                  subject { Ryo.find(point_c) { _2 == 5 } }
         | 
| 111 | 
            +
                  it { is_expected.to be(point_a) }
         | 
| 112 | 
            +
                end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                context "when an iteration yields true on point_b" do
         | 
| 115 | 
            +
                  subject { Ryo.find(point_c) { _2 == 10 } }
         | 
| 116 | 
            +
                  it { is_expected.to be(point_b) }
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                context "when an iteration yields true on point_c" do
         | 
| 120 | 
            +
                  subject { Ryo.find(point_c) { _2 == 15 } }
         | 
| 121 | 
            +
                  it { is_expected.to be(point_c) }
         | 
| 122 | 
            +
                end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                context "when an iteration never yields true" do
         | 
| 125 | 
            +
                  subject { Ryo.find(point_c) { _2 == 20 } }
         | 
| 126 | 
            +
                  it { is_expected.to eq(nil) }
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                context "when ancestors is set to zero" do
         | 
| 130 | 
            +
                  context "when the condition matches for point_a" do
         | 
| 131 | 
            +
                    subject { Ryo.find(point_c, ancestors: 0) { _2 == 5 } }
         | 
| 132 | 
            +
                    it { is_expected.to be_nil }
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  context "when the condition matches for point_b" do
         | 
| 136 | 
            +
                    subject { Ryo.find(point_c, ancestors: 0) { _2 == 10 } }
         | 
| 137 | 
            +
                    it { is_expected.to be_nil }
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  context "when the condition matches for point_c" do
         | 
| 141 | 
            +
                    subject { Ryo.find(point_c, ancestors: 0) { _2 == 15 } }
         | 
| 142 | 
            +
                    it { is_expected.to be(point_c) }
         | 
| 143 | 
            +
                  end
         | 
| 144 | 
            +
                end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                context "when ancestors is set to one" do
         | 
| 147 | 
            +
                  context "when the condition matches for point_a" do
         | 
| 148 | 
            +
                    subject { Ryo.find(point_c, ancestors: 1) { _2 == 5 } }
         | 
| 149 | 
            +
                    it { is_expected.to be_nil }
         | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  context "when the condition matches for point_b" do
         | 
| 153 | 
            +
                    subject { Ryo.find(point_c, ancestors: 1) { _2 == 10 } }
         | 
| 154 | 
            +
                    it { is_expected.to be(point_b) }
         | 
| 155 | 
            +
                  end
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                  context "when the condition matches for point_c" do
         | 
| 158 | 
            +
                    subject { Ryo.find(point_c, ancestors: 1) { _2 == 15 } }
         | 
| 159 | 
            +
                    it { is_expected.to be(point_c) }
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                context "when ancestors is set to two" do
         | 
| 164 | 
            +
                  context "when the condition matches for point_a" do
         | 
| 165 | 
            +
                    subject { Ryo.find(point_c, ancestors: 2) { _2 == 5 } }
         | 
| 166 | 
            +
                    it { is_expected.to be(point_a) }
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                  context "when the condition matches for point_b" do
         | 
| 170 | 
            +
                    subject { Ryo.find(point_c, ancestors: 2) { _2 == 10 } }
         | 
| 171 | 
            +
                    it { is_expected.to be(point_b) }
         | 
| 172 | 
            +
                  end
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  context "when the condition matches for point_c" do
         | 
| 175 | 
            +
                    subject { Ryo.find(point_c, ancestors: 2) { _2 == 15 } }
         | 
| 176 | 
            +
                    it { is_expected.to be(point_c) }
         | 
| 177 | 
            +
                  end
         | 
| 178 | 
            +
                end
         | 
| 179 | 
            +
             | 
| 180 | 
            +
                context "when ancestors is set to three (or higher)" do
         | 
| 181 | 
            +
                  context "when the condition matches for point_a" do
         | 
| 182 | 
            +
                    subject { Ryo.find(point_c, ancestors: 3) { _2 == 5 } }
         | 
| 183 | 
            +
                    it { is_expected.to be(point_a) }
         | 
| 184 | 
            +
                  end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                  context "when the condition matches for point_b" do
         | 
| 187 | 
            +
                    subject { Ryo.find(point_c, ancestors: 3) { _2 == 10 } }
         | 
| 188 | 
            +
                    it { is_expected.to be(point_b) }
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
             | 
| 191 | 
            +
                  context "when the condition matches for point_c" do
         | 
| 192 | 
            +
                    subject { Ryo.find(point_c, ancestors: 3) { _2 == 15 } }
         | 
| 193 | 
            +
                    it { is_expected.to be(point_c) }
         | 
| 194 | 
            +
                  end
         | 
| 195 | 
            +
                end
         | 
| 196 | 
            +
              end
         | 
| 197 | 
            +
            end
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            RSpec.describe Ryo::Keywords do
         | 
| 6 | 
            +
              describe ".function" do
         | 
| 7 | 
            +
                let(:point) { Ryo(move: Ryo.fn { |x, y| [x, y] }) }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                context "when the function requires argument(s)" do
         | 
| 10 | 
            +
                  context "when the required argument is not given" do
         | 
| 11 | 
            +
                    subject { point.move.() }
         | 
| 12 | 
            +
                    it { expect { is_expected }.to raise_error(ArgumentError) }
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  context "when the required argument is given" do
         | 
| 16 | 
            +
                    subject { point.move.(30, 50) }
         | 
| 17 | 
            +
                    it { is_expected.to eq([30, 50]) }
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                context "when the function receives a block" do
         | 
| 22 | 
            +
                  subject { point.move.() { "block" } }
         | 
| 23 | 
            +
                  let(:point) { Ryo(move: Ryo.fn { |&b| b.() }) }
         | 
| 24 | 
            +
                  it { is_expected.to eq("block") }
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              describe ".delete" do
         | 
| 29 | 
            +
                let(:point_a) { Ryo(x: 0) }
         | 
| 30 | 
            +
                let(:point_b) { Ryo({x: 1}, point_a) }
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                context "with no prototype" do
         | 
| 33 | 
            +
                  context "when a property is deleted" do
         | 
| 34 | 
            +
                    subject { point_a.x }
         | 
| 35 | 
            +
                    before { Ryo.delete(point_a, "x") }
         | 
| 36 | 
            +
                    it { is_expected.to be_nil }
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                context "with a prototype" do
         | 
| 41 | 
            +
                  context "when a property is deleted from point_a" do
         | 
| 42 | 
            +
                    subject { point_b.x }
         | 
| 43 | 
            +
                    before { Ryo.delete(point_a, "x") }
         | 
| 44 | 
            +
                    it { is_expected.to eq(1) }
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  context "when a property is deleted from point_b" do
         | 
| 48 | 
            +
                    subject { point_b.x }
         | 
| 49 | 
            +
                    before { Ryo.delete(point_b, "x") }
         | 
| 50 | 
            +
                    it { is_expected.to eq(0) }
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  context "when a property is deleted from both point_a / point_b" do
         | 
| 54 | 
            +
                    subject { point_b.x }
         | 
| 55 | 
            +
                    before { [point_a, point_b].each { Ryo.delete(_1, "x") } }
         | 
| 56 | 
            +
                    it { is_expected.to be(nil) }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              describe ".in?" do
         | 
| 62 | 
            +
                let(:point_a) { Ryo(x: 0) }
         | 
| 63 | 
            +
                let(:point_b) { Ryo({y: 1}, point_a) }
         | 
| 64 | 
            +
                let(:point_c) { Ryo({z: 2}, point_b) }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                context "when given 'x' as a property name" do
         | 
| 67 | 
            +
                  subject { Ryo.in?(point_c, "x") }
         | 
| 68 | 
            +
                  it { is_expected.to be(true) }
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                context "when given 'y' as a property name" do
         | 
| 72 | 
            +
                  subject { Ryo.in?(point_c, "y") }
         | 
| 73 | 
            +
                  it { is_expected.to be(true) }
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                context "when given 'z' as a property name" do
         | 
| 77 | 
            +
                  subject { Ryo.in?(point_c, "z") }
         | 
| 78 | 
            +
                  it { is_expected.to be(true) }
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                context "when given 'w' as a property name" do
         | 
| 82 | 
            +
                  subject { Ryo.in?(point_c, "w") }
         | 
| 83 | 
            +
                  it { is_expected.to be(false) }
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            RSpec.describe "Ryo objects" do
         | 
| 6 | 
            +
              let(:car) { Ryo(name: "Car") }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "#respond_to?" do
         | 
| 9 | 
            +
                context "when a property is defined" do
         | 
| 10 | 
            +
                  subject { car.respond_to?(:name) }
         | 
| 11 | 
            +
                  it { is_expected.to be(true) }
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                context "when a property is not defined" do
         | 
| 15 | 
            +
                  subject { car.respond_to?(:foobar) }
         | 
| 16 | 
            +
                  it { is_expected.to be(true) }
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              describe "#method_missing" do
         | 
| 21 | 
            +
                context "when a property doesn't exist" do
         | 
| 22 | 
            +
                  subject { car.foobar }
         | 
| 23 | 
            +
                  it { is_expected.to eq(nil) }
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              describe "#eql?" do
         | 
| 28 | 
            +
                context "when two objects are equal" do
         | 
| 29 | 
            +
                  subject { car == car_2 }
         | 
| 30 | 
            +
                  let(:car_2) { Ryo(name: "Car") }
         | 
| 31 | 
            +
                  it { is_expected.to be(true) }
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                context "when an object and a Hash are equal" do
         | 
| 35 | 
            +
                  subject { car == {"name" => "Car"} }
         | 
| 36 | 
            +
                  it { is_expected.to be(true) }
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                context "when an object and symbol-key Hash are equal" do
         | 
| 40 | 
            +
                  subject { car == {name: "Car"} }
         | 
| 41 | 
            +
                  it { is_expected.to be(true) }
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                context "when an object and nested symbol-key Hash are equal" do
         | 
| 45 | 
            +
                  subject { car == {name: "Car", wheels: {quantity: 4, weight: {lbs: "50"}}} }
         | 
| 46 | 
            +
                  let(:car) { Ryo.from(name: "Car", wheels: {quantity: 4, weight: {lbs: "50"}}) }
         | 
| 47 | 
            +
                  it { is_expected.to be(true) }
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                context "when an object is compared against nil" do
         | 
| 51 | 
            +
                  subject { car == nil }
         | 
| 52 | 
            +
                  it { is_expected.to be(false) }
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
              end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
              describe "when a property overshadows a method" do
         | 
| 57 | 
            +
                let(:car) do
         | 
| 58 | 
            +
                  Ryo(tap: "property")
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                context "when a block is not given" do
         | 
| 62 | 
            +
                  subject { car.tap }
         | 
| 63 | 
            +
                  it { is_expected.to eq("property") }
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                context "when a block is given" do
         | 
| 67 | 
            +
                  subject { car.tap {} }
         | 
| 68 | 
            +
                  it { is_expected.to eq(car) }
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require_relative "setup"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            RSpec.describe "Prototypes" do
         | 
| 6 | 
            +
              context "when there is one prototype" do
         | 
| 7 | 
            +
                let(:root) { Ryo(name: "root") }
         | 
| 8 | 
            +
                let(:node) { Ryo({}, root) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                context "when traversing to a property on the root prototype" do
         | 
| 11 | 
            +
                  subject { node.name }
         | 
| 12 | 
            +
                  it { is_expected.to eq("root") }
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                context "when a property is deleted from the root prototype" do
         | 
| 16 | 
            +
                  subject { node.name }
         | 
| 17 | 
            +
                  before { Ryo.delete(root, "name") }
         | 
| 18 | 
            +
                  it { is_expected.to eq(nil) }
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              context "when there are two prototypes" do
         | 
| 23 | 
            +
                let(:root) { Ryo(name: "root") }
         | 
| 24 | 
            +
                let(:node_1) { Ryo({}, root) }
         | 
| 25 | 
            +
                let(:node_2) { Ryo({}, node_1) }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                context "when traversing to a property on the root prototype" do
         | 
| 28 | 
            +
                  subject { node_2.name }
         | 
| 29 | 
            +
                  it { is_expected.to eq("root") }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                context "when traversing to a property on the middle prototype" do
         | 
| 33 | 
            +
                  subject { node_2.name }
         | 
| 34 | 
            +
                  let(:node_1) { Ryo({name: "Node 1"}, root) }
         | 
| 35 | 
            +
                  it { is_expected.to eq("Node 1") }
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                context "when a property is deleted from the middle prototype" do
         | 
| 39 | 
            +
                  subject { node_2.name }
         | 
| 40 | 
            +
                  let(:node_1) { Ryo({name: "Node 1"}, root) }
         | 
| 41 | 
            +
                  before { Ryo.delete(node_1, "name") }
         | 
| 42 | 
            +
                  it { is_expected.to eq("root") }
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         |