• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

notEthan / jsi / 9708111060

24 Jun 2024 04:43AM UTC coverage: 98.097% (+0.7%) from 97.348%
9708111060

push

github

notEthan
v0.8.0

5877 of 5991 relevant lines covered (98.1%)

148855.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.89
/lib/jsi/base/node.rb
1
# frozen_string_literal: true
2

3
module JSI
56✔
4
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
5
  # is a Hash (or responds to `#to_hash`)
6
  module Base::HashNode
56✔
7
    # instantiates and yields each property name (hash key) as a JSI described by any `propertyNames` schemas.
8
    #
9
    # @yield [JSI::Base]
10
    # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
11
    def jsi_each_propertyName
56✔
12
      return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block_given?
574✔
13

14
      property_schemas = SchemaSet.build do |schemas|
434✔
15
        jsi_schemas.each do |s|
434✔
16
          if s.keyword?('propertyNames') && s['propertyNames'].is_a?(Schema)
448✔
17
            schemas << s['propertyNames']
252✔
18
          end
19
        end
20
      end
21
      jsi_node_content_hash_pubsend(:each_key) do |key|
434✔
22
        yield property_schemas.new_jsi(key)
826✔
23
      end
24

25
      nil
180✔
26
    end
27

28
    # See {Base#jsi_hash?}. Always true for HashNode.
29
    def jsi_hash?
56✔
30
      true
448✔
31
    end
32

33
    # Yields each key - see {Base#jsi_each_child_token}
34
    def jsi_each_child_token(&block)
56✔
35
      return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
120,678✔
36
      jsi_node_content_hash_pubsend(:each_key, &block)
120,678✔
37
      nil
51,976✔
38
    end
39

40
    # See {Base#jsi_child_token_in_range?}
41
    def jsi_child_token_in_range?(token)
56✔
42
      jsi_node_content_hash_pubsend(:key?, token)
×
43
    end
44

45
    # See {Base#jsi_node_content_child}
46
    def jsi_node_content_child(token)
56✔
47
      # I could check token_in_range? and return nil here (as ArrayNode does).
48
      # without that check, if the instance defines Hash#default or #default_proc, that result is returned.
49
      # the preferred mechanism for a JSI's default value should be its schema.
50
      # but there's no compelling reason not to support both, so I'll return what #[] returns.
51
      jsi_node_content_hash_pubsend(:[], token)
497,946✔
52
    end
53

54
    # See {Base#[]}
55
    def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
56✔
56
      if jsi_node_content_hash_pubsend(:key?, token)
280,130✔
57
        jsi_child(token, as_jsi: as_jsi)
277,904✔
58
      else
59
        if use_default
2,226✔
60
          jsi_default_child(token, as_jsi: as_jsi)
112✔
61
        else
62
          nil
906✔
63
        end
64
      end
65
    end
66

67
    # yields each Hash key (JSON object property name) and value of this node.
68
    #
69
    # each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
70
    #
71
    # @param kw keyword arguments are passed to {Base#[]}
72
    # @yield [Object, Object] each key and value of this hash node
73
    # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
74
    def each(**kw, &block)
56✔
75
      return to_enum(__method__, **kw) { jsi_node_content_hash_pubsend(:size) } unless block
13,356✔
76
      if block.arity > 1
13,356✔
77
        jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, **kw] }
33,326✔
78
      else
79
        jsi_node_content_hash_pubsend(:each_key) { |k| yield [k, self[k, **kw]] }
6,014✔
80
      end
81
      self
13,356✔
82
    end
83

84
    # a hash in which each key is a key of the instance hash and each value is the result of {Base#[]}
85
    # @param kw keyword arguments are passed to {Base#[]}
86
    # @return [Hash]
87
    def to_hash(**kw)
56✔
88
      hash = {}
4,800✔
89
      jsi_node_content_hash_pubsend(:each_key) { |k| hash[k] = self[k, **kw] }
10,754✔
90
      hash.freeze
4,800✔
91
    end
92

93
    # See {Base#as_json}
94
    def as_json(options = {})
56✔
95
      hash = {}
224✔
96
      each_key do |k|
224✔
97
        ks = k.is_a?(String) ? k :
434✔
98
          k.is_a?(Symbol) ? k.to_s :
38✔
99
          k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
28✔
100
          raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
4✔
101
        hash[ks] = jsi_child_node(k).as_json(**options)
300✔
102
      end
103
      hash
210✔
104
    end
105

106
    include Util::Hashlike
56✔
107

108
    if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
56✔
109
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
110
      # @param method_name [String, Symbol]
111
      # @param a positional arguments are passed to the invocation of method_name
112
      # @param b block is passed to the invocation of method_name
113
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
114
      def jsi_node_content_hash_pubsend(method_name, *a, &b)
16✔
115
        if jsi_node_content.respond_to?(method_name)
281,974✔
116
          jsi_node_content.public_send(method_name, *a, &b)
281,614✔
117
        else
118
          jsi_node_content.to_hash.public_send(method_name, *a, &b)
360✔
119
        end
120
      end
121
    else
122
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
123
      # @param method_name [String, Symbol]
124
      # @param a positional arguments are passed to the invocation of method_name
125
      # @param kw keyword arguments are passed to the invocation of method_name
126
      # @param b block is passed to the invocation of method_name
127
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
128
      def jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
40✔
129
        if jsi_node_content.respond_to?(method_name)
708,502✔
130
          jsi_node_content.public_send(method_name, *a, **kw, &b)
707,602✔
131
        else
132
          jsi_node_content.to_hash.public_send(method_name, *a, **kw, &b)
900✔
133
        end
134
      end
135
    end
136

137
    # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
138
    SAFE_KEY_ONLY_METHODS.each do |method_name|
56✔
139
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
504✔
140
        define_method(method_name) do |*a, &b|
144✔
141
          jsi_node_content_hash_pubsend(method_name, *a, &b)
20,912✔
142
        end
143
      else
144
        define_method(method_name) do |*a, **kw, &b|
360✔
145
          jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
52,220✔
146
        end
147
      end
148
    end
149
  end
150

151
  # module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
152
  # is an Array (or responds to `#to_ary`)
153
  module Base::ArrayNode
56✔
154
    # See {Base#jsi_array?}. Always true for ArrayNode.
155
    def jsi_array?
56✔
156
      true
406✔
157
    end
158

159
    # Yields each index - see {Base#jsi_each_child_token}
160
    def jsi_each_child_token(&block)
56✔
161
      return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
15,824✔
162
      jsi_node_content_ary_pubsend(:each_index, &block)
15,824✔
163
      nil
6,806✔
164
    end
165

166
    # See {Base#jsi_child_token_in_range?}
167
    def jsi_child_token_in_range?(token)
56✔
168
      token.is_a?(Integer) && token >= 0 && token < jsi_node_content_ary_pubsend(:size)
116,660✔
169
    end
170

171
    # See {Base#jsi_node_content_child}
172
    def jsi_node_content_child(token)
56✔
173
      # we check token_in_range? here (unlike HashNode) because we do not want to pass
174
      # negative indices, Ranges, or non-Integers to Array#[]
175
      if jsi_child_token_in_range?(token)
116,660✔
176
        jsi_node_content_ary_pubsend(:[], token)
116,618✔
177
      else
178
        nil
18✔
179
      end
180
    end
181

182
    # See {Base#[]}
183
    def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
56✔
184
      size = jsi_node_content_ary_pubsend(:size)
91,236✔
185
      if token.is_a?(Integer)
91,236✔
186
        if token < 0
85,326✔
187
          if token < -size
70✔
188
            nil
18✔
189
          else
190
            jsi_child(token + size, as_jsi: as_jsi)
28✔
191
          end
192
        else
193
          if token < size
85,256✔
194
            jsi_child(token, as_jsi: as_jsi)
85,186✔
195
          else
196
            if use_default
70✔
197
              jsi_default_child(token, as_jsi: as_jsi)
42✔
198
            else
199
              nil
12✔
200
            end
201
          end
202
        end
203
      elsif token.is_a?(Range)
5,908✔
204
        type_err = proc do
5,868✔
205
          raise(TypeError, [
×
206
            "given range does not contain Integers",
207
            "range: #{token.inspect}",
208
          ].join("\n"))
209
        end
210

211
        start_idx = token.begin
5,868✔
212
        if start_idx.is_a?(Integer)
5,868✔
213
          start_idx += size if start_idx < 0
5,292✔
214
          return Util::EMPTY_ARY if start_idx == size
5,292✔
215
          return nil if start_idx < 0 || start_idx > size
4,632✔
216
        elsif start_idx.nil?
574✔
217
          start_idx = 0
576✔
218
        else
219
          type_err.call
×
220
        end
221

222
        end_idx = token.end
3,552✔
223
        if end_idx.is_a?(Integer)
3,552✔
224
          end_idx += size if end_idx < 0
3,192✔
225
          end_idx += 1 unless token.exclude_end?
3,192✔
226
          end_idx = size if end_idx > size
3,192✔
227
          return Util::EMPTY_ARY if start_idx >= end_idx
3,192✔
228
        elsif end_idx.nil?
358✔
229
          end_idx = size
360✔
230
        else
231
          type_err.call
×
232
        end
233

234
        (start_idx...end_idx).map { |i| jsi_child(i, as_jsi: as_jsi) }.freeze
7,836✔
235
      else
236
        raise(TypeError, [
48✔
237
          "expected `token` param to be an Integer or Range",
238
          "token: #{token.inspect}",
12✔
239
        ].join("\n"))
240
      end
241
    end
242

243
    # yields each array element of this node.
244
    #
245
    # each yielded element is the result of {Base#[]} for each index of the instance array.
246
    #
247
    # @param kw keyword arguments are passed to {Base#[]}
248
    # @yield [Object] each element of this array node
249
    # @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
250
    def each(**kw, &block)
56✔
251
      return to_enum(__method__, **kw) { jsi_node_content_ary_pubsend(:size) } unless block
15,624✔
252
      jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, **kw]) }
72,086✔
253
      self
15,596✔
254
    end
255

256
    # an array, the same size as the instance array, in which the element at each index is the
257
    # result of {Base#[]}.
258
    # @param kw keyword arguments are passed to {Base#[]}
259
    # @return [Array]
260
    def to_ary(**kw)
56✔
261
      to_a(**kw)
1,092✔
262
    end
263

264
    # See {Base#as_json}
265
    def as_json(options = {})
56✔
266
      each_index.map { |i| jsi_child_node(i).as_json(**options) }
294✔
267
    end
268

269
    include Util::Arraylike
56✔
270

271
    if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
56✔
272
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
273
      # @param method_name [String, Symbol]
274
      # @param a positional arguments are passed to the invocation of method_name
275
      # @param b block is passed to the invocation of method_name
276
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
277
      def jsi_node_content_ary_pubsend(method_name, *a, &b)
16✔
278
        if jsi_node_content.respond_to?(method_name)
104,786✔
279
          jsi_node_content.public_send(method_name, *a, &b)
104,366✔
280
        else
281
          jsi_node_content.to_ary.public_send(method_name, *a, &b)
420✔
282
        end
283
      end
284
    else
285
      # invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
286
      # @param method_name [String, Symbol]
287
      # @param a positional arguments are passed to the invocation of method_name
288
      # @param kw keyword arguments are passed to the invocation of method_name
289
      # @param b block is passed to the invocation of method_name
290
      # @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
291
      def jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
40✔
292
        if jsi_node_content.respond_to?(method_name)
267,968✔
293
          jsi_node_content.public_send(method_name, *a, **kw, &b)
266,918✔
294
        else
295
          jsi_node_content.to_ary.public_send(method_name, *a, **kw, &b)
1,050✔
296
        end
297
      end
298
    end
299

300
    # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
301
    # we override these methods from Arraylike
302
    SAFE_INDEX_ONLY_METHODS.each do |method_name|
56✔
303
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
224✔
304
        define_method(method_name) do |*a, &b|
64✔
305
          jsi_node_content_ary_pubsend(method_name, *a, &b)
4,800✔
306
        end
307
      else
308
        define_method(method_name) do |*a, **kw, &b|
160✔
309
          jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
11,992✔
310
        end
311
      end
312
    end
313
  end
314

315
  module Base::StringNode
56✔
316
    delegate_methods = %w(% * + << =~ [] []=
56✔
317
      ascii_only? b byteindex byterindex bytes bytesize byteslice bytesplice capitalize capitalize!
318
      casecmp casecmp? center chars chomp chomp! chop chop! chr clear codepoints concat count delete delete!
319
      delete_prefix delete_prefix! delete_suffix delete_suffix! downcase downcase!
320
      each_byte each_char each_codepoint each_grapheme_cluster each_line
321
      empty? encode encode! encoding end_with? force_encoding getbyte grapheme_clusters gsub gsub! hex
322
      include? index insert intern length lines ljust lstrip lstrip! match match? next next! oct ord
323
      partition prepend replace reverse reverse! rindex rjust rpartition rstrip rstrip! scan scrub scrub!
324
      setbyte size slice slice! split squeeze squeeze! start_with? strip strip! sub sub! succ succ! sum
325
      swapcase swapcase! to_c to_f to_i to_r to_s to_str to_sym tr tr! tr_s tr_s!
326
      unicode_normalize unicode_normalize! unicode_normalized? unpack unpack1 upcase upcase! upto valid_encoding?
327
    )
328
    delegate_methods.each do |method_name|
56✔
329
      if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
6,664✔
330
        define_method(method_name) do |*a, &b|
1,904✔
331
          if jsi_node_content.respond_to?(method_name)
4✔
332
            jsi_node_content.public_send(method_name, *a, &b)
4✔
333
          else
334
            jsi_node_content.to_str.public_send(method_name, *a, &b)
×
335
          end
336
        end
337
      else
338
        define_method(method_name) do |*a, **kw, &b|
4,760✔
339
          if jsi_node_content.respond_to?(method_name)
14✔
340
            jsi_node_content.public_send(method_name, *a, **kw, &b)
14✔
341
          else
342
            jsi_node_content.to_str.public_send(method_name, *a, **kw, &b)
×
343
          end
344
        end
345
      end
346
    end
347
  end
348
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc