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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/specs.yml +23 -0
  3. data/.gitignore +6 -0
  4. data/.gitlab-ci.yml +9 -0
  5. data/.rubocop.yml +56 -0
  6. data/.yardoc-template/default/fulldoc/html/css/0x1eef.css +15 -0
  7. data/.yardoc-template/default/layout/html/setup.rb +5 -0
  8. data/.yardoc-template/default/module/setup.rb +7 -0
  9. data/.yardopts +4 -0
  10. data/Gemfile +5 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +373 -0
  13. data/Rakefile +3 -0
  14. data/lib/ryo/basic_object.rb +58 -0
  15. data/lib/ryo/builder.rb +106 -0
  16. data/lib/ryo/enumerable.rb +214 -0
  17. data/lib/ryo/function.rb +68 -0
  18. data/lib/ryo/keywords.rb +67 -0
  19. data/lib/ryo/lazy.rb +4 -0
  20. data/lib/ryo/object.rb +58 -0
  21. data/lib/ryo/reflect.rb +379 -0
  22. data/lib/ryo/version.rb +5 -0
  23. data/lib/ryo.rb +197 -0
  24. data/ryo.rb.gemspec +21 -0
  25. data/share/ryo.rb/examples/1.0_prototypes_point_object.rb +12 -0
  26. data/share/ryo.rb/examples/1.1_prototypes_ryo_fn.rb +14 -0
  27. data/share/ryo.rb/examples/2.0_iteration_each.rb +13 -0
  28. data/share/ryo.rb/examples/2.1_iteration_map.rb +16 -0
  29. data/share/ryo.rb/examples/2.2_iteration_ancestors.rb +13 -0
  30. data/share/ryo.rb/examples/3.0_recursion_ryo_from.rb +13 -0
  31. data/share/ryo.rb/examples/3.1_recursion_ryo_from_with_array.rb +19 -0
  32. data/share/ryo.rb/examples/3.2_recursion_ryo_from_with_openstruct.rb +14 -0
  33. data/share/ryo.rb/examples/4.0_basicobject_ryo_basicobject.rb +12 -0
  34. data/share/ryo.rb/examples/4.1_basicobject_ryo_basicobject_from.rb +13 -0
  35. data/share/ryo.rb/examples/5_collisions_resolution_strategy.rb +8 -0
  36. data/share/ryo.rb/examples/6_beyond_hash_objects.rb +20 -0
  37. data/share/ryo.rb/examples/7_ryo_lazy.rb +14 -0
  38. data/share/ryo.rb/examples/setup.rb +3 -0
  39. data/spec/readme_spec.rb +79 -0
  40. data/spec/ryo_basic_object_spec.rb +60 -0
  41. data/spec/ryo_enumerable_spec.rb +197 -0
  42. data/spec/ryo_keywords_spec.rb +86 -0
  43. data/spec/ryo_object_spec.rb +71 -0
  44. data/spec/ryo_prototypes_spec.rb +45 -0
  45. data/spec/ryo_reflect_spec.rb +175 -0
  46. data/spec/ryo_spec.rb +130 -0
  47. data/spec/setup.rb +5 -0
  48. metadata +173 -0
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # {Ryo::Builder Ryo::Builder} is a module that's used underneath Ryo's public
5
+ # interface when creating instances of {Ryo::Object Ryo::Object}, and
6
+ # {Ryo::BasicObject Ryo::BasicObject}. This module is not intended to be
7
+ # used directly.
8
+ #
9
+ # @api private
10
+ module Ryo::Builder
11
+ ##
12
+ # @param [<Ryo::Object, Ryo::BasicObject>] buildee
13
+ # The class of the object to build.
14
+ #
15
+ # @param [<Hash, Ryo::Object, Ryo::BasicObject, #each_pair>] props
16
+ # A Hash object, an object that implements "#each_pair", or a Ryo object.
17
+ #
18
+ # @param [<Ryo::Object, Ryo::BasicObject>, nil] prototype
19
+ # The prototype, or nil for none.
20
+ #
21
+ # @return [<Ryo::Object, Ryo::BasicObject>]
22
+ # Returns a Ryo object.
23
+ #
24
+ # @note
25
+ # When "props" is given as a Ryo object, a duplicate Ryo object is
26
+ # returned in its place.
27
+ def self.build(buildee, props, prototype = nil)
28
+ if Ryo.ryo?(props)
29
+ build(builedee, Ryo.table_of(props), prototype || Ryo.prototype_of(props))
30
+ else
31
+ ryo = buildee.new
32
+ Ryo.set_prototype_of(ryo, prototype)
33
+ Ryo.set_table_of(ryo, {})
34
+ Ryo.extend!(ryo, Ryo)
35
+ props.each_pair { ryo[_1] = _2 }
36
+ ryo
37
+ end
38
+ end
39
+
40
+ ##
41
+ # Creates a Ryo object by recursively walking a Hash object, or an Array of Hash objects.
42
+ #
43
+ # @example
44
+ # objects = Ryo.from([{x: 0, y: 0}, "foo", {point: {x: 0, y: 0}}])
45
+ # objects[0].x # => 0
46
+ # objects[1] # => "foo"
47
+ # objects[2].point.x # => 0
48
+ #
49
+ # @param buildee (see Ryo::Builder.build)
50
+ #
51
+ # @param [<Hash, Ryo::Object, Ryo::BasicObject, Array, #each_pair, #each> ] props
52
+ # A Hash object, a Ryo object, or an array composed of either Hash / Ryo objects.
53
+ #
54
+ # @param prototype (see Ryo::Builder.build)
55
+ #
56
+ # @return (see Ryo::Builder.build)
57
+ #
58
+ # @note
59
+ # When "props" is given as a Ryo object, a duplicate Ryo object is
60
+ # returned in its place.
61
+ def self.recursive_build(buildee, props, prototype = nil)
62
+ if Ryo.ryo?(props)
63
+ recursive_build(buildee, Ryo.table_of(props), prototype || Ryo.prototype_of(props))
64
+ elsif eachless?(props)
65
+ raise TypeError, "The provided object does not implement #each / #each_pair"
66
+ elsif !props.respond_to?(:each_pair)
67
+ map(props) do
68
+ noop = Ryo.ryo?(_1) || !_1.respond_to?(:each_pair)
69
+ noop ? _1 : recursive_build(buildee, _1)
70
+ end
71
+ else
72
+ visited = {}
73
+ props.each_pair { visited[_1] = map_value(buildee, _2) }
74
+ build(buildee, visited, prototype)
75
+ end
76
+ end
77
+
78
+ ##
79
+ # @private
80
+ def self.map_value(buildee, value)
81
+ if Ryo.ryo?(value) || eachless?(value)
82
+ value
83
+ elsif value.respond_to?(:each_pair)
84
+ recursive_build(buildee, value)
85
+ elsif value.respond_to?(:each)
86
+ map(value) { map_value(buildee, _1) }
87
+ end
88
+ end
89
+ private_class_method :map_value
90
+
91
+ ##
92
+ # @private
93
+ def self.eachless?(value)
94
+ !value.respond_to?(:each) && !value.respond_to?(:each_pair)
95
+ end
96
+ private_class_method :eachless?
97
+
98
+ ##
99
+ # @private
100
+ def self.map(obj)
101
+ ary = []
102
+ obj.each { ary.push(yield(_1)) }
103
+ ary
104
+ end
105
+ private_class_method :map
106
+ end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The {Ryo::Enumerable Ryo::Enumerable} module implements methods
5
+ # for iterating through and performing operations on Ryo objects.
6
+ # The methods implemented by this module are available as singleton
7
+ # methods on the {Ryo} module.
8
+ module Ryo::Enumerable
9
+ ##
10
+ # The {#each} methods iterates a Ryo object, and yields a key / value pair.
11
+ # When a block is not given, {#each} returns an Enumerator.
12
+ #
13
+ # @example
14
+ # point_a = Ryo(x: 1, y: 2)
15
+ # point_b = Ryo(y: 1)
16
+ # Ryo.each(point_b) { p [_1, _2] }
17
+ # # ["y", 1]
18
+ # # ["y", 2]
19
+ # # ["x", 1]
20
+ #
21
+ # @param [Ryo::Object, Ryo::BasicObject] ryo
22
+ # A Ryo object.
23
+ #
24
+ # @param ancestors (see #each_ryo)
25
+ #
26
+ # @return [<Enumerator, Array>]
27
+ # Returns an Enumerator when a block is not given, otherwise returns an Array.
28
+ def each(ryo, ancestors: nil)
29
+ return enum_for(:each, ryo) unless block_given?
30
+ each_ryo(ryo, ancestors: ancestors) do |_, key, value|
31
+ yield(key, value)
32
+ end
33
+ end
34
+
35
+ ##
36
+ # The {#each_ryo} method iterates through a Ryo object, and its prototypes.
37
+ # {#each_ryo} yields three parameters: a Ryo object, a key, and a value.
38
+ #
39
+ # @example
40
+ # point_a = Ryo(x: 1, y: 2)
41
+ # point_b = Ryo({y: 3}, point_a)
42
+ # Ryo.each_ryo(point_b) { |ryo, key, value| p [ryo, key, value] }
43
+ # # [point_b, "y", 3]
44
+ # # [point_a, "x", 1]
45
+ # # [point_a, "y", 2]
46
+ #
47
+ # @param [<Ryo::BasicObject, Ryo::Object>] ryo
48
+ # A Ryo object.
49
+ #
50
+ # @param [Integer] ancestors
51
+ # `ancestors` is an integer that determines how far up the prototype chain a
52
+ # {Ryo::Enumerable Ryo::Enumerable} method can go. 0 covers a Ryo object,
53
+ # and none of the prototypes in its prototype chain. 1 covers a Ryo object,
54
+ # and one of the prototypes in its prototype chain - and so on. The default
55
+ # behavior is to traverse the entire prototype chain.
56
+ #
57
+ # @return [<Ryo::BasicObject, Ryo::Object>]
58
+ def each_ryo(ryo, ancestors: nil)
59
+ proto_chain = [ryo, *prototype_chain_of(ryo)]
60
+ ancestors ||= -1
61
+ proto_chain[0..ancestors].each do |ryo|
62
+ properties_of(ryo).each do |key|
63
+ yield(ryo, key, ryo[key])
64
+ end
65
+ end
66
+ ryo
67
+ end
68
+
69
+ ##
70
+ # The {#map} method creates a copy of a Ryo object, and then performs a map operation
71
+ # on the copy and its prototypes.
72
+ #
73
+ # @param (see #map!)
74
+ # @return (see #map!)
75
+ def map(ryo, ancestors: nil, &b)
76
+ map!(Ryo.dup(ryo), ancestors: ancestors, &b)
77
+ end
78
+
79
+ ##
80
+ # The {#map!} method performs an in-place map operation on a Ryo object, and its prototypes.
81
+ #
82
+ # @example
83
+ # point = Ryo(x: 2, y: 4)
84
+ # Ryo.map!(point) { _2 * 2 }
85
+ # [point.x, point.y]
86
+ # # => [4, 8]
87
+ #
88
+ # @param [<Ryo::Object, Ryo::BasicObject>] ryo
89
+ # A Ryo object.
90
+ #
91
+ # @param ancestors (see #each_ryo)
92
+ #
93
+ # @return [<Ryo::Object, Ryo::BasicObject>]
94
+ def map!(ryo, ancestors: nil)
95
+ each_ryo(ryo, ancestors: ancestors) do |ryo, key, value|
96
+ ryo[key] = yield(key, value)
97
+ end
98
+ end
99
+
100
+ ##
101
+ # The {#select} method creates a copy of a Ryo object, and then performs a filter operation
102
+ # on the copy and its prototypes.
103
+ #
104
+ # @param (see #select!)
105
+ # @return (see #select!)
106
+ def select(ryo, ancestors: nil, &b)
107
+ select!(Ryo.dup(ryo), ancestors: ancestors, &b)
108
+ end
109
+
110
+ ##
111
+ # The {#select!} method performs an in-place filter operation on a Ryo object, and
112
+ # its prototypes.
113
+ #
114
+ # @example
115
+ # point_a = Ryo(x: 1, y: 2, z: 3)
116
+ # point_b = Ryo({z: 4}, point_a)
117
+ # Ryo.select!(point_b) { |key, value| %w(x y).include?(key) }
118
+ # [point_b.x, point_b.y, point_b.z]
119
+ # # => [1, 2, nil]
120
+ #
121
+ # @param [<Ryo::Object, Ryo::BasicObject>] ryo
122
+ # A Ryo object.
123
+ #
124
+ # @param ancestors (see #each_ryo)
125
+ #
126
+ # @return [<Ryo::Object, Ryo::BasicObject>]
127
+ def select!(ryo, ancestors: nil)
128
+ each_ryo(ryo, ancestors: ancestors) do |ryo, key, value|
129
+ delete(ryo, key) unless yield(key, value)
130
+ end
131
+ end
132
+
133
+ ##
134
+ # The {#reject} method creates a copy of a Ryo object, and then performs a filter operation
135
+ # on the copy and its prototypes.
136
+ #
137
+ # @param (see #reject!)
138
+ # @return (see #reject!)
139
+ def reject(ryo, ancestors: nil, &b)
140
+ reject!(Ryo.dup(ryo), ancestors: ancestors, &b)
141
+ end
142
+
143
+ ##
144
+ # The {#reject!} method performs an in-place filter operation on a Ryo object, and
145
+ # its prototypes.
146
+ #
147
+ # @example
148
+ # point_a = Ryo(x: 1, y: 2, z: 3)
149
+ # point_b = Ryo({z: 4}, point_a)
150
+ # Ryo.reject!(point_b) { |key, value| value > 2 }
151
+ # [point_b.x, point_b.y, point_b.z]
152
+ # # => [1, 2, nil]
153
+ #
154
+ # @param [<Ryo::Object, Ryo::BasicObject>] ryo
155
+ # A Ryo object.
156
+ #
157
+ # @param ancestors (see #each_ryo)
158
+ #
159
+ # @return [<Ryo::Object, Ryo::BasicObject>]
160
+ def reject!(ryo, ancestors: nil)
161
+ each_ryo(ryo, ancestors: ancestors) do |ryo, key, value|
162
+ delete(ryo, key) if yield(key, value)
163
+ end
164
+ end
165
+
166
+ ##
167
+ # The {#any?} method iterates through a Ryo object, and its prototypes - yielding a
168
+ # key / value pair to a block. If the block ever returns a truthy value, {#any?} will
169
+ # break from the iteration and return true - otherwise false will be returned.
170
+ #
171
+ # @param ancestors (see #each_ryo)
172
+ # @return [Boolean]
173
+ def any?(ryo, ancestors: nil)
174
+ each_ryo(ryo, ancestors: ancestors) do |_, key, value|
175
+ return true if yield(key, value)
176
+ end
177
+ false
178
+ end
179
+
180
+ ##
181
+ # The {#all?} method iterates through a Ryo object, and its prototypes - yielding a
182
+ # key / value pair to a block. If the block ever returns a falsey value, {#all?} will
183
+ # break from the iteration and return false - otherwise true will be returned.
184
+ #
185
+ # @param ancestors (see #each_ryo)
186
+ # @return [Boolean]
187
+ def all?(ryo, ancestors: nil)
188
+ each_ryo(ryo, ancestors: ancestors) do |_, key, value|
189
+ return false unless yield(key, value)
190
+ end
191
+ true
192
+ end
193
+
194
+ ##
195
+ # The {#find} method iterates through a Ryo object, and its prototypes - yielding a
196
+ # key / value pair to a block. If the block ever returns a truthy value, {#find} will
197
+ # break from the iteration and return a Ryo object - otherwise nil will be returned.
198
+ #
199
+ # @example
200
+ # point_a = Ryo(x: 5)
201
+ # point_b = Ryo({y: 10}, point_a)
202
+ # point_c = Ryo({z: 15}, point_b)
203
+ # ryo = Ryo.find(point_c) { |key, value| value == 5 }
204
+ # ryo == point_a # => true
205
+ #
206
+ # @param ancestors (see #each_ryo)
207
+ # @return [<Ryo::Object, Ryo::BasicObject>, nil]
208
+ def find(ryo, ancestors: nil)
209
+ each_ryo(ryo, ancestors: ancestors) do |ryo, key, value|
210
+ return ryo if yield(key, value)
211
+ end
212
+ nil
213
+ end
214
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The {Ryo::Function Ryo::Function} class represents a Ryo
5
+ # function. The class is usually not used directly but through
6
+ # {Ryo::Keywords#fn Ryo.fn}. A Ryo function has a special relationship
7
+ # with Ryo objects: when a Ryo function is assigned as a property
8
+ # on a Ryo object - the "self" of the function becomes bound to the
9
+ # Ryo object.
10
+ class Ryo::Function
11
+ ##
12
+ # @param [Proc] body
13
+ # The body of the function as a block.
14
+ #
15
+ # @return [Ryo::Function]
16
+ # Returns an instance of {Ryo::Function Ryo::Function}.
17
+ def initialize(&body)
18
+ @body = body
19
+ @ryo = nil
20
+ @to_proc = nil
21
+ end
22
+
23
+ ##
24
+ # @return [<Ryo::Object, Ryo::BasicObject>]
25
+ # Returns the object that the function's "self" is bound to.
26
+ def receiver
27
+ @ryo
28
+ end
29
+ alias_method :self, :receiver
30
+
31
+ ##
32
+ # Change the receiver (self) of the function.
33
+ #
34
+ # @param [<Ryo::Object, Ryo::BasicObject>] ryo
35
+ # A Ryo object.
36
+ #
37
+ # @return [nil]
38
+ def bind!(ryo)
39
+ @ryo = ryo
40
+ @to_proc = nil
41
+ end
42
+
43
+ ##
44
+ # Call the function.
45
+ def call(...)
46
+ to_proc.call(...)
47
+ end
48
+
49
+ ##
50
+ # @return [Proc]
51
+ # Returns the function as a lambda bound to {#receiver}.
52
+ def to_proc
53
+ @to_proc ||= lambda!(@body)
54
+ end
55
+
56
+ private
57
+
58
+ def lambda!(body)
59
+ ryo, lambda = @ryo, nil
60
+ Module.new do
61
+ define_method(:__function__, &body)
62
+ lambda = instance_method(:__function__)
63
+ .bind(ryo)
64
+ .to_proc
65
+ end
66
+ lambda
67
+ end
68
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # The {Ryo::Keywords Ryo::Keywords} module implements Ryo equivalents
5
+ # to some of JavaScript's keywords - for example: the **in** and **delete**
6
+ # operators. The instance methods of this module are available as singleton
7
+ # methods on the {Ryo} module.
8
+ module Ryo::Keywords
9
+ ##
10
+ # @example
11
+ # point = Ryo(x: 0, y: 0, print: Ryo.fn { print x, y, "\n" })
12
+ # point.print.()
13
+ #
14
+ # @param [Proc] b
15
+ # The function's body.
16
+ #
17
+ # @return [Ryo::Function]
18
+ # Returns a Ryo function.
19
+ #
20
+ # @see Ryo::Function Ryo::Function
21
+ def fn(&b)
22
+ Ryo::Function.new(&b)
23
+ end
24
+ alias_method :function, :fn
25
+
26
+ ##
27
+ # Equivalent to JavaScript's **in** operator.
28
+ #
29
+ # @param [<Ryo::Object, Ryo::BasicObject>] ryo
30
+ # A Ryo object.
31
+ #
32
+ # @param [<String, #to_s>] property
33
+ # A property name.
34
+ #
35
+ # @return [Boolean]
36
+ # Returns true when **property** is a member of **ryo**, or its prototype chain.
37
+ def in?(ryo, property)
38
+ return false unless ryo
39
+ property?(ryo, property) || in?(prototype_of(ryo), property)
40
+ end
41
+
42
+ ##
43
+ # The {#delete} method deletes a property from a Ryo object.
44
+ # More or less equivalent to JavaScript's "delete" operator.
45
+ #
46
+ # @note
47
+ # This method does not delete properties from the prototype(s)
48
+ # of a Ryo object. <br>
49
+ # For that - see {Ryo::Reflect#delete! Ryo::Reflect#delete!}.
50
+ #
51
+ # @param [<Ryo::Object, Ryo::BasicObject>] ryo
52
+ # A Ryo object.
53
+ #
54
+ # @param [<String, #to_s>] property
55
+ # A property name.
56
+ #
57
+ # @return [void]
58
+ def delete(ryo, property)
59
+ property = property.to_s
60
+ if property?(ryo, property)
61
+ table_of(ryo).delete(property)
62
+ else
63
+ return if getter_defined?(ryo, property)
64
+ define_method!(ryo, property) { ryo[property] }
65
+ end
66
+ end
67
+ end
data/lib/ryo/lazy.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Ryo::Lazy < Ryo::Function
4
+ end
data/lib/ryo/object.rb ADDED
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # {Ryo::Object Ryo::Object} is a Ryo object and subclass of
5
+ # Ruby's Object class that can be created by using
6
+ # [Ryo()](https://0x1eef.github.io/x/ryo.rb/top-level-namespace.html#Ryo-instance_method),
7
+ # {Ryo.Object Ryo.Object()}, {Ryo::Object.from Ryo::Object.from},
8
+ # or {Ryo::Object.create Ryo::Object.create}.
9
+ class Ryo::Object
10
+ ##
11
+ # @param props (see Ryo::Builder.build)
12
+ # @param prototype (see Ryo::Builder.build)
13
+ #
14
+ # @return [Ryo::Object]
15
+ # Returns an instance of {Ryo::Object Ryo::Object}.
16
+ def self.create(props, prototype = nil)
17
+ Ryo::Builder.build(self, props, prototype)
18
+ end
19
+
20
+ ##
21
+ # Creates a Ryo object by recursively walking a Hash object.
22
+ #
23
+ # @param props (see Ryo::Builder.recursive_build)
24
+ # @param prototype (see Ryo::Builder.recursive_build)
25
+ #
26
+ # @return [Ryo::Object]
27
+ # Returns an instance of {Ryo::Object Ryo::Object}.
28
+ def self.from(props, prototype = nil)
29
+ Ryo::Builder.recursive_build(self, props, prototype)
30
+ end
31
+
32
+ ##
33
+ # Duplicates the internals of a Ryo object.
34
+ #
35
+ # @param [Ryo::Object] ryo
36
+ # A Ryo object.
37
+ #
38
+ # @return [Ryo::Object]
39
+ # Returns a Ryo object.
40
+ def initialize_dup(ryo)
41
+ Ryo.set_table_of(self, Ryo.table_of(ryo).dup)
42
+ Ryo.extend!(self, Ryo)
43
+ end
44
+ end
45
+
46
+ ##
47
+ # @example
48
+ # point = Ryo::Object(x: 0, y: 0)
49
+ # p [point.x, point.y] # => [0, 0]
50
+ #
51
+ # @param props (see Ryo::Builder.build)
52
+ # @param prototype (see Ryo::Builder.build)
53
+ #
54
+ # @return [Ryo::Object]
55
+ # Returns an instance of {Ryo::Object Ryo::Object}.
56
+ def Ryo.Object(props, prototype = nil)
57
+ Ryo::Object.create(props, prototype)
58
+ end