jsi 0.6.0 → 0.8.0
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 +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
|