jsi 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +6 -1
- data/CHANGELOG.md +33 -0
- data/LICENSE.md +1 -1
- data/README.md +29 -23
- data/jsi.gemspec +29 -0
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +348 -0
- data/lib/jsi/base.rb +497 -339
- data/lib/jsi/jsi_coder.rb +19 -17
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +61 -26
- data/lib/jsi/metaschema_node.rb +161 -133
- data/lib/jsi/ptr.rb +80 -47
- data/lib/jsi/schema/application/child_application/contains.rb +11 -2
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/child_application/items.rb +3 -3
- data/lib/jsi/schema/application/child_application/properties.rb +3 -3
- data/lib/jsi/schema/application/child_application.rb +0 -27
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
- data/lib/jsi/schema/application/inplace_application/ref.rb +2 -2
- data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
- data/lib/jsi/schema/application/inplace_application.rb +0 -32
- data/lib/jsi/schema/draft04.rb +0 -1
- data/lib/jsi/schema/draft06.rb +0 -1
- data/lib/jsi/schema/draft07.rb +0 -1
- data/lib/jsi/schema/ref.rb +46 -19
- data/lib/jsi/schema/schema_ancestor_node.rb +69 -66
- data/lib/jsi/schema/validation/array.rb +3 -3
- data/lib/jsi/schema/validation/const.rb +1 -1
- data/lib/jsi/schema/validation/contains.rb +2 -2
- data/lib/jsi/schema/validation/dependencies.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +8 -6
- data/lib/jsi/schema/validation/draft04.rb +0 -2
- data/lib/jsi/schema/validation/draft06.rb +0 -2
- data/lib/jsi/schema/validation/draft07.rb +0 -2
- data/lib/jsi/schema/validation/enum.rb +1 -1
- data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
- data/lib/jsi/schema/validation/items.rb +7 -7
- data/lib/jsi/schema/validation/not.rb +1 -1
- data/lib/jsi/schema/validation/numeric.rb +5 -5
- data/lib/jsi/schema/validation/object.rb +2 -2
- data/lib/jsi/schema/validation/pattern.rb +2 -2
- data/lib/jsi/schema/validation/properties.rb +7 -7
- data/lib/jsi/schema/validation/property_names.rb +1 -1
- data/lib/jsi/schema/validation/ref.rb +2 -2
- data/lib/jsi/schema/validation/required.rb +1 -1
- data/lib/jsi/schema/validation/someof.rb +3 -3
- data/lib/jsi/schema/validation/string.rb +2 -2
- data/lib/jsi/schema/validation/type.rb +1 -1
- data/lib/jsi/schema/validation.rb +1 -3
- data/lib/jsi/schema.rb +443 -226
- data/lib/jsi/schema_classes.rb +241 -147
- data/lib/jsi/schema_registry.rb +78 -19
- data/lib/jsi/schema_set.rb +114 -28
- data/lib/jsi/simple_wrap.rb +18 -4
- data/lib/jsi/util/private/attr_struct.rb +141 -0
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +185 -0
- data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +79 -105
- data/lib/jsi/util.rb +157 -153
- data/lib/jsi/validation/error.rb +4 -0
- data/lib/jsi/validation/result.rb +18 -32
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +65 -39
- data/lib/schemas/json-schema.org/draft-04/schema.rb +160 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +162 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +189 -3
- metadata +27 -11
- data/lib/jsi/metaschema.rb +0 -7
- data/lib/jsi/pathed_node.rb +0 -116
- data/lib/jsi/schema/validation/core.rb +0 -39
- data/lib/jsi/util/attr_struct.rb +0 -106
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
# JSI::Util::Private classes, modules, constants, and methods are internal, and will be added and removed without warning.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
module Util::Private
|
8
|
+
autoload :AttrStruct, 'jsi/util/private/attr_struct'
|
9
|
+
autoload :MemoMap, 'jsi/util/private/memo_map'
|
10
|
+
|
11
|
+
extend self
|
12
|
+
|
13
|
+
EMPTY_ARY = [].freeze
|
14
|
+
|
15
|
+
EMPTY_HASH = {}.freeze
|
16
|
+
|
17
|
+
EMPTY_SET = Set[].freeze
|
18
|
+
|
19
|
+
CLASSES_ALWAYS_FROZEN = Set[TrueClass, FalseClass, NilClass, Integer, Float, BigDecimal, Rational, Symbol].freeze
|
20
|
+
|
21
|
+
# is a hash as the last argument passed to keyword params? (false in ruby 3; true before - generates
|
22
|
+
# a warning in 2.7 but no way to make 2.7 behave like 3 so the warning is useless)
|
23
|
+
#
|
24
|
+
# TODO remove eventually (keyword argument compatibility)
|
25
|
+
LAST_ARGUMENT_AS_KEYWORD_PARAMETERS = begin
|
26
|
+
if Object.const_defined?(:Warning)
|
27
|
+
warn = ::Warning.instance_method(:warn)
|
28
|
+
::Warning.send(:remove_method, :warn)
|
29
|
+
::Warning.send(:define_method, :warn) { |*, **| }
|
30
|
+
end
|
31
|
+
|
32
|
+
-> (k: ) { k }[{k: nil}]
|
33
|
+
true
|
34
|
+
rescue ArgumentError
|
35
|
+
false
|
36
|
+
ensure
|
37
|
+
if Object.const_defined?(:Warning)
|
38
|
+
::Warning.send(:remove_method, :warn)
|
39
|
+
::Warning.send(:define_method, :warn, warn)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# we won't use #to_json on classes where it is defined by
|
44
|
+
# JSON::Ext::Generator::GeneratorMethods / JSON::Pure::Generator::GeneratorMethods
|
45
|
+
# this is a bit of a kluge and disregards any singleton class to_json, but it will do.
|
46
|
+
USE_TO_JSON_METHOD = Hash.new do |h, klass|
|
47
|
+
h[klass] = klass.method_defined?(:to_json) &&
|
48
|
+
klass.instance_method(:to_json).owner.name !~ /\AJSON:.*:GeneratorMethods\b/
|
49
|
+
end
|
50
|
+
|
51
|
+
RUBY_REJECT_NAME_CODEPOINTS = [
|
52
|
+
0..31, # C0 control chars
|
53
|
+
%q( !"#$%&'()*+,-./:;<=>?@[\\]^`{|}~).each_codepoint, # printable special chars (note: "_" not included)
|
54
|
+
127..159, # C1 control chars
|
55
|
+
].inject(Set[], &:merge).freeze
|
56
|
+
|
57
|
+
RUBY_REJECT_NAME_RE = Regexp.new('[' + Regexp.escape(RUBY_REJECT_NAME_CODEPOINTS.to_a.pack('U*')) + ']+').freeze
|
58
|
+
|
59
|
+
# is the given name ok to use as a ruby method name?
|
60
|
+
def ok_ruby_method_name?(name)
|
61
|
+
# must be a string
|
62
|
+
return false unless name.respond_to?(:to_str)
|
63
|
+
# must not begin with a digit
|
64
|
+
return false if name =~ /\A[0-9]/
|
65
|
+
# must not contain special or control characters
|
66
|
+
return false if name =~ RUBY_REJECT_NAME_RE
|
67
|
+
|
68
|
+
return true
|
69
|
+
end
|
70
|
+
|
71
|
+
def const_name_from_parts(parts, join: '')
|
72
|
+
parts = parts.map do |part|
|
73
|
+
part = part.dup
|
74
|
+
part[/\A[^a-zA-Z]*/] = ''
|
75
|
+
part[0] = part[0].upcase if part[0]
|
76
|
+
part.gsub!(RUBY_REJECT_NAME_RE, '_')
|
77
|
+
part
|
78
|
+
end
|
79
|
+
if !parts.all?(&:empty?)
|
80
|
+
parts.reject(&:empty?).join(join).freeze
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# string or URI → frozen URI
|
87
|
+
# @return [Addressable::URI]
|
88
|
+
def uri(uri)
|
89
|
+
if uri.is_a?(Addressable::URI)
|
90
|
+
if uri.frozen?
|
91
|
+
uri
|
92
|
+
else
|
93
|
+
uri.dup.freeze
|
94
|
+
end
|
95
|
+
else
|
96
|
+
Addressable::URI.parse(uri).freeze
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
|
101
|
+
# to define a recursive function to return the length of an array:
|
102
|
+
#
|
103
|
+
# length = ycomb do |len|
|
104
|
+
# proc { |list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# length.call([0])
|
108
|
+
# # => 1
|
109
|
+
#
|
110
|
+
# see https://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator
|
111
|
+
# and chapter 9 of the little schemer, available as the sample chapter at
|
112
|
+
# https://felleisen.org/matthias/BTLS-index.html
|
113
|
+
def ycomb
|
114
|
+
proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
|
115
|
+
end
|
116
|
+
|
117
|
+
def require_jmespath
|
118
|
+
return if instance_variable_defined?(:@jmespath_required)
|
119
|
+
begin
|
120
|
+
require 'jmespath'
|
121
|
+
rescue ::LoadError => e
|
122
|
+
# :nocov:
|
123
|
+
msg = [
|
124
|
+
"please install and/or add to your Gemfile the `jmespath` gem to use this. jmespath is not a dependency of JSI.",
|
125
|
+
"original error message:",
|
126
|
+
e.message,
|
127
|
+
].join("\n")
|
128
|
+
raise(e.class, msg, e.backtrace)
|
129
|
+
# :nocov:
|
130
|
+
end
|
131
|
+
hashlike = JSI::SchemaSet[].new_jsi({'test' => 0})
|
132
|
+
unless JMESPath.search('test', hashlike) == 0
|
133
|
+
# :nocov:
|
134
|
+
raise(::LoadError, [
|
135
|
+
"the loaded version of jmespath cannot be used with JSI.",
|
136
|
+
"jmespath is compatible with JSI objects as of version 1.5.0",
|
137
|
+
].join("\n"))
|
138
|
+
# :nocov:
|
139
|
+
end
|
140
|
+
@jmespath_required = true
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
# Defines equality methods and #hash (for Hash / Set), based on a method #jsi_fingerprint
|
145
|
+
# implemented by the includer. #jsi_fingerprint is to include the class and any properties
|
146
|
+
# of the instance which constitute its identity.
|
147
|
+
module FingerprintHash
|
148
|
+
# overrides BasicObject#==
|
149
|
+
def ==(other)
|
150
|
+
__id__ == other.__id__ || (other.is_a?(FingerprintHash) && jsi_fingerprint == other.jsi_fingerprint)
|
151
|
+
end
|
152
|
+
|
153
|
+
alias_method :eql?, :==
|
154
|
+
|
155
|
+
# overrides Kernel#hash
|
156
|
+
def hash
|
157
|
+
jsi_fingerprint.hash
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
module FingerprintHash::Immutable
|
162
|
+
include FingerprintHash
|
163
|
+
|
164
|
+
def ==(other)
|
165
|
+
return true if __id__ == other.__id__
|
166
|
+
return false unless other.is_a?(FingerprintHash)
|
167
|
+
# FingerprintHash::Immutable#hash being memoized, comparing that is basically free.
|
168
|
+
# not done with FingerprintHash, its #hash can be expensive.
|
169
|
+
return false if other.is_a?(FingerprintHash::Immutable) && hash != other.hash
|
170
|
+
jsi_fingerprint == other.jsi_fingerprint
|
171
|
+
end
|
172
|
+
|
173
|
+
alias_method :eql?, :==
|
174
|
+
|
175
|
+
def hash
|
176
|
+
@jsi_fingerprint_hash ||= jsi_fingerprint.hash
|
177
|
+
end
|
178
|
+
|
179
|
+
def freeze
|
180
|
+
hash
|
181
|
+
super
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -1,102 +1,60 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# a module relating to objects that act like Hash or Array instances
|
5
|
-
module Typelike
|
6
|
-
# yields the content of the given param `object`. for objects which have a #jsi_modified_copy
|
7
|
-
# method of their own (JSI::Base, JSI::MetaschemaNode) that method is invoked with the given
|
8
|
-
# block. otherwise the given object itself is yielded.
|
9
|
-
#
|
10
|
-
# the given block must result in a modified copy of its block parameter
|
11
|
-
# (not destructively modifying the yielded content).
|
12
|
-
#
|
13
|
-
# @yield [Object] the content of the given object. the block should result
|
14
|
-
# in a (nondestructively) modified copy of this.
|
15
|
-
# @return [object.class] modified copy of the given object
|
16
|
-
def self.modified_copy(object, &block)
|
17
|
-
if object.respond_to?(:jsi_modified_copy)
|
18
|
-
object.jsi_modified_copy(&block)
|
19
|
-
else
|
20
|
-
return yield(object)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
# recursive method to express the given argument object in json-compatible
|
25
|
-
# types of Hash, Array, and basic types of String/boolean/numeric/nil. this
|
26
|
-
# will raise TypeError if an object is given that is not a type that seems
|
27
|
-
# to be expressable as json.
|
28
|
-
#
|
29
|
-
# similar effect could be achieved by requiring 'json/add/core' and using #as_json,
|
30
|
-
# but I don't much care for how it represents classes that are
|
31
|
-
# not naturally expressable in JSON, and prefer not to load its
|
32
|
-
# monkey-patching.
|
33
|
-
#
|
34
|
-
# @param object [Object] the object to be converted to jsonifiability
|
35
|
-
# @return [Array, Hash, String, Boolean, NilClass, Numeric] jsonifiable
|
36
|
-
# expression of param object
|
37
|
-
# @raise [TypeError] when the object (or an object nested with a hash or
|
38
|
-
# array of object) cannot be expressed as json
|
39
|
-
def self.as_json(object, *opt)
|
40
|
-
if object.is_a?(JSI::PathedNode)
|
41
|
-
as_json(object.jsi_node_content, *opt)
|
42
|
-
elsif object.respond_to?(:to_hash)
|
43
|
-
(object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
|
44
|
-
unless k.is_a?(Symbol) || k.respond_to?(:to_str)
|
45
|
-
raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
|
46
|
-
end
|
47
|
-
{k.to_s => as_json(v, *opt)}
|
48
|
-
end.inject({}, &:update)
|
49
|
-
elsif object.respond_to?(:to_ary)
|
50
|
-
(object.respond_to?(:map) ? object : object.to_ary).map { |e| as_json(e, *opt) }
|
51
|
-
elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| object.is_a?(c) }
|
52
|
-
object
|
53
|
-
elsif object.is_a?(Symbol)
|
54
|
-
object.to_s
|
55
|
-
elsif object.is_a?(Set)
|
56
|
-
as_json(object.to_a, *opt)
|
57
|
-
elsif object.respond_to?(:as_json)
|
58
|
-
as_json(object.as_json(*opt), *opt)
|
59
|
-
else
|
60
|
-
raise(TypeError, "cannot express object as json: #{object.pretty_inspect.chomp}")
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
4
|
# a module of methods for objects which behave like Hash but are not Hash.
|
66
5
|
#
|
67
6
|
# this module is intended to be internal to JSI. no guarantees or API promises
|
68
7
|
# are made for non-JSI classes including this module.
|
69
|
-
module Hashlike
|
8
|
+
module Util::Hashlike
|
9
|
+
include(Enumerable)
|
10
|
+
|
70
11
|
# safe methods which can be delegated to #to_hash (which the includer is assumed to have defined).
|
71
12
|
# 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
|
72
13
|
|
73
14
|
# methods which do not need to access the value.
|
74
|
-
SAFE_KEY_ONLY_METHODS = %w(each_key empty? has_key? include? key? keys length member? size)
|
75
|
-
SAFE_KEY_VALUE_METHODS = %w(< <= > >= any? assoc compact dig each_pair each_value fetch fetch_values has_value? invert key merge rassoc reject select to_h to_proc transform_values value? values values_at)
|
76
|
-
DESTRUCTIVE_METHODS = %w(clear delete delete_if keep_if reject! replace select! shift)
|
15
|
+
SAFE_KEY_ONLY_METHODS = %w(each_key empty? has_key? include? key? keys length member? size).map(&:freeze).freeze
|
16
|
+
SAFE_KEY_VALUE_METHODS = %w(< <= > >= any? assoc compact dig each_pair each_value fetch fetch_values has_value? invert key merge rassoc reject select to_h to_proc transform_values value? values values_at).map(&:freeze).freeze
|
17
|
+
DESTRUCTIVE_METHODS = %w(clear delete delete_if keep_if reject! replace select! shift).map(&:freeze).freeze
|
77
18
|
# these return a modified copy
|
78
19
|
safe_modified_copy_methods = %w(compact)
|
79
20
|
# select and reject will return a modified copy but need the yielded block variable value from #[]
|
80
21
|
safe_kv_block_modified_copy_methods = %w(select reject)
|
81
22
|
SAFE_METHODS = SAFE_KEY_ONLY_METHODS | SAFE_KEY_VALUE_METHODS
|
82
|
-
|
23
|
+
custom_methods = %w(merge) # defined below
|
24
|
+
safe_to_hash_methods = SAFE_METHODS -
|
25
|
+
safe_modified_copy_methods -
|
26
|
+
safe_kv_block_modified_copy_methods -
|
27
|
+
custom_methods
|
83
28
|
safe_to_hash_methods.each do |method_name|
|
84
|
-
|
29
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
30
|
+
define_method(method_name) { |*a, &b| to_hash.public_send(method_name, *a, &b) }
|
31
|
+
else
|
32
|
+
define_method(method_name) { |*a, **kw, &b| to_hash.public_send(method_name, *a, **kw, &b) }
|
33
|
+
end
|
85
34
|
end
|
86
35
|
safe_modified_copy_methods.each do |method_name|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
36
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
37
|
+
define_method(method_name) do |*a, &b|
|
38
|
+
jsi_modified_copy do |object_to_modify|
|
39
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
40
|
+
responsive_object.public_send(method_name, *a, &b)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
else
|
44
|
+
define_method(method_name) do |*a, **kw, &b|
|
45
|
+
jsi_modified_copy do |object_to_modify|
|
46
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
47
|
+
responsive_object.public_send(method_name, *a, **kw, &b)
|
48
|
+
end
|
91
49
|
end
|
92
50
|
end
|
93
51
|
end
|
94
52
|
safe_kv_block_modified_copy_methods.each do |method_name|
|
95
|
-
define_method(method_name) do
|
53
|
+
define_method(method_name) do |**kw, &b|
|
96
54
|
jsi_modified_copy do |object_to_modify|
|
97
55
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
98
56
|
responsive_object.public_send(method_name) do |k, _v|
|
99
|
-
b.call(k, self[k,
|
57
|
+
b.call(k, self[k, **kw])
|
100
58
|
end
|
101
59
|
end
|
102
60
|
end
|
@@ -112,9 +70,8 @@ module JSI
|
|
112
70
|
unless other.respond_to?(:to_hash)
|
113
71
|
raise(TypeError, "cannot update with argument that does not respond to #to_hash: #{other.pretty_inspect.chomp}")
|
114
72
|
end
|
115
|
-
self_respondingto_key = self.respond_to?(:key?) ? self : to_hash
|
116
73
|
other.to_hash.each_pair do |key, value|
|
117
|
-
if block &&
|
74
|
+
if block && key?(key)
|
118
75
|
value = yield(key, self[key], value)
|
119
76
|
end
|
120
77
|
self[key] = value
|
@@ -131,27 +88,30 @@ module JSI
|
|
131
88
|
# @return duplicate of this hash with the other hash merged in
|
132
89
|
# @raise [TypeError] when `other` does not respond to #to_hash
|
133
90
|
def merge(other, &block)
|
134
|
-
|
91
|
+
jsi_modified_copy do |instance|
|
92
|
+
instance.merge(other.is_a?(Base) ? other.jsi_node_content : other, &block)
|
93
|
+
end
|
135
94
|
end
|
136
95
|
|
137
96
|
# basically the same #inspect as Hash, but has the class name and, if responsive,
|
138
97
|
# self's #jsi_object_group_text
|
139
98
|
# @return [String]
|
140
99
|
def inspect
|
141
|
-
object_group_str = (respond_to?(:jsi_object_group_text) ?
|
142
|
-
"\#{<#{object_group_str}>#{
|
100
|
+
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
101
|
+
-"\#{<#{object_group_str}>#{map { |k, v| " #{k.inspect} => #{v.inspect}" }.join(',')}}"
|
143
102
|
end
|
144
103
|
|
145
|
-
|
104
|
+
def to_s
|
105
|
+
inspect
|
106
|
+
end
|
146
107
|
|
147
108
|
# pretty-prints a representation of this hashlike to the given printer
|
148
109
|
# @return [void]
|
149
110
|
def pretty_print(q)
|
150
|
-
object_group_str = (respond_to?(:jsi_object_group_text) ? jsi_object_group_text : [self.class]).join(' ')
|
111
|
+
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
151
112
|
q.text "\#{<#{object_group_str}>"
|
152
|
-
q.
|
153
|
-
|
154
|
-
q.breakable(empty? ? '' : ' ')
|
113
|
+
q.group(2) {
|
114
|
+
q.breakable ' ' if !empty?
|
155
115
|
q.seplist(self, nil, :each_pair) { |k, v|
|
156
116
|
q.group {
|
157
117
|
q.pp k
|
@@ -159,9 +119,8 @@ module JSI
|
|
159
119
|
q.pp v
|
160
120
|
}
|
161
121
|
}
|
162
|
-
}
|
163
122
|
}
|
164
|
-
q.breakable ''
|
123
|
+
q.breakable '' if !empty?
|
165
124
|
q.text '}'
|
166
125
|
end
|
167
126
|
end
|
@@ -170,15 +129,17 @@ module JSI
|
|
170
129
|
#
|
171
130
|
# this module is intended to be internal to JSI. no guarantees or API promises
|
172
131
|
# are made for non-JSI classes including this module.
|
173
|
-
module Arraylike
|
132
|
+
module Util::Arraylike
|
133
|
+
include(Enumerable)
|
134
|
+
|
174
135
|
# safe methods which can be delegated to #to_ary (which the includer is assumed to have defined).
|
175
136
|
# 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
|
176
137
|
|
177
138
|
# methods which do not need to access the element.
|
178
|
-
SAFE_INDEX_ONLY_METHODS = %w(each_index empty? length size)
|
139
|
+
SAFE_INDEX_ONLY_METHODS = %w(each_index empty? length size).map(&:freeze).freeze
|
179
140
|
# there are some ambiguous ones that are omitted, like #sort, #map / #collect.
|
180
|
-
SAFE_INDEX_ELEMENT_METHODS = %w(| & * + - <=> abbrev at bsearch bsearch_index combination compact count cycle dig drop drop_while fetch find_index first include? index join last pack permutation product reject repeated_combination repeated_permutation reverse reverse_each rindex rotate sample select shelljoin shuffle slice sort take take_while transpose uniq values_at zip)
|
181
|
-
DESTRUCTIVE_METHODS = %w(<< clear collect! compact! concat delete delete_at delete_if fill flatten! insert keep_if map! pop push reject! replace reverse! rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift)
|
141
|
+
SAFE_INDEX_ELEMENT_METHODS = %w(| & * + - <=> abbrev at bsearch bsearch_index combination compact count cycle dig drop drop_while fetch find_index first include? index join last pack permutation product reject repeated_combination repeated_permutation reverse reverse_each rindex rotate sample select shelljoin shuffle slice sort take take_while transpose uniq values_at zip).map(&:freeze).freeze
|
142
|
+
DESTRUCTIVE_METHODS = %w(<< clear collect! compact! concat delete delete_at delete_if fill flatten! insert keep_if map! pop push reject! replace reverse! rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift).map(&:freeze).freeze
|
182
143
|
|
183
144
|
# methods (well, method) that returns a modified copy and doesn't need any handling of block variable(s)
|
184
145
|
safe_modified_copy_methods = %w(compact)
|
@@ -189,23 +150,36 @@ module JSI
|
|
189
150
|
SAFE_METHODS = SAFE_INDEX_ONLY_METHODS | SAFE_INDEX_ELEMENT_METHODS
|
190
151
|
safe_to_ary_methods = SAFE_METHODS - safe_modified_copy_methods - safe_el_block_methods
|
191
152
|
safe_to_ary_methods.each do |method_name|
|
192
|
-
|
153
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
154
|
+
define_method(method_name) { |*a, &b| to_ary.public_send(method_name, *a, &b) }
|
155
|
+
else
|
156
|
+
define_method(method_name) { |*a, **kw, &b| to_ary.public_send(method_name, *a, **kw, &b) }
|
157
|
+
end
|
193
158
|
end
|
194
159
|
safe_modified_copy_methods.each do |method_name|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
160
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
161
|
+
define_method(method_name) do |*a, &b|
|
162
|
+
jsi_modified_copy do |object_to_modify|
|
163
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
164
|
+
responsive_object.public_send(method_name, *a, &b)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
else
|
168
|
+
define_method(method_name) do |*a, **kw, &b|
|
169
|
+
jsi_modified_copy do |object_to_modify|
|
170
|
+
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
171
|
+
responsive_object.public_send(method_name, *a, **kw, &b)
|
172
|
+
end
|
199
173
|
end
|
200
174
|
end
|
201
175
|
end
|
202
176
|
safe_el_block_methods.each do |method_name|
|
203
|
-
define_method(method_name) do
|
177
|
+
define_method(method_name) do |**kw, &b|
|
204
178
|
jsi_modified_copy do |object_to_modify|
|
205
179
|
i = 0
|
206
180
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
207
181
|
responsive_object.public_send(method_name) do |_e|
|
208
|
-
b.call(self[i,
|
182
|
+
b.call(self[i, **kw]).tap { i += 1 }
|
209
183
|
end
|
210
184
|
end
|
211
185
|
end
|
@@ -229,26 +203,26 @@ module JSI
|
|
229
203
|
# self's #jsi_object_group_text
|
230
204
|
# @return [String]
|
231
205
|
def inspect
|
232
|
-
object_group_str = (respond_to?(:jsi_object_group_text) ? jsi_object_group_text : [self.class]).join(' ')
|
233
|
-
"\#[<#{object_group_str}>#{
|
206
|
+
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
207
|
+
-"\#[<#{object_group_str}>#{map { |e| ' ' + e.inspect }.join(',')}]"
|
234
208
|
end
|
235
209
|
|
236
|
-
|
210
|
+
def to_s
|
211
|
+
inspect
|
212
|
+
end
|
237
213
|
|
238
214
|
# pretty-prints a representation of this arraylike to the given printer
|
239
215
|
# @return [void]
|
240
216
|
def pretty_print(q)
|
241
|
-
object_group_str = (respond_to?(:jsi_object_group_text) ? jsi_object_group_text : [self.class]).join(' ')
|
217
|
+
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
242
218
|
q.text "\#[<#{object_group_str}>"
|
243
|
-
q.
|
244
|
-
|
245
|
-
q.breakable(empty? ? '' : ' ')
|
219
|
+
q.group(2) {
|
220
|
+
q.breakable ' ' if !empty?
|
246
221
|
q.seplist(self, nil, :each) { |e|
|
247
222
|
q.pp e
|
248
223
|
}
|
249
|
-
}
|
250
224
|
}
|
251
|
-
q.breakable ''
|
225
|
+
q.breakable '' if !empty?
|
252
226
|
q.text ']'
|
253
227
|
end
|
254
228
|
end
|