jsi 0.7.0 → 0.8.1
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 +19 -0
- data/README.md +29 -20
- data/jsi.gemspec +2 -3
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +199 -34
- data/lib/jsi/base.rb +412 -228
- data/lib/jsi/jsi_coder.rb +18 -16
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +57 -23
- data/lib/jsi/metaschema_node.rb +138 -107
- data/lib/jsi/ptr.rb +59 -37
- 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.rb +0 -25
- 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/ref.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/someof.rb +1 -1
- data/lib/jsi/schema/application/inplace_application.rb +0 -27
- 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 +44 -18
- data/lib/jsi/schema/schema_ancestor_node.rb +65 -56
- data/lib/jsi/schema/validation/contains.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +2 -0
- 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/items.rb +3 -3
- data/lib/jsi/schema/validation/pattern.rb +1 -1
- data/lib/jsi/schema/validation/properties.rb +4 -4
- data/lib/jsi/schema/validation/ref.rb +1 -1
- data/lib/jsi/schema/validation.rb +0 -2
- data/lib/jsi/schema.rb +408 -198
- data/lib/jsi/schema_classes.rb +196 -127
- data/lib/jsi/schema_registry.rb +66 -17
- data/lib/jsi/schema_set.rb +76 -30
- data/lib/jsi/simple_wrap.rb +2 -7
- data/lib/jsi/util/private/attr_struct.rb +28 -14
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +73 -92
- data/lib/jsi/util/typelike.rb +28 -28
- data/lib/jsi/util.rb +120 -36
- 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 +67 -25
- data/lib/schemas/json-schema.org/draft-04/schema.rb +159 -4
- data/lib/schemas/json-schema.org/draft-06/schema.rb +161 -4
- data/lib/schemas/json-schema.org/draft-07/schema.rb +188 -4
- data/readme.rb +1 -1
- metadata +19 -5
- data/lib/jsi/metaschema.rb +0 -6
- data/lib/jsi/schema/validation/core.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: daf35895a6186a3d3123b037f917912ff65ea03c876da9dd5cb40e2a9b079641
|
4
|
+
data.tar.gz: b14524e9eb8796385fdd2ec96bcbb5129c2e80ad9eabb39d60637b9c6078d1c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 005c27210c96f9b613f7f6959e86e17b41380c5366fe14f69cd047498fb5bfc8256b8404aa6c5f2ced04882b94edd1c9b62ff79c48001b7388b4b4faadf6c5b1
|
7
|
+
data.tar.gz: 9eccdec2038200c340df2365a3b179da83fd900deb8b4f8f21aa5e4430f785d80afa929e8d718e80850d463fd5d2cd0e0982f7b99352b9e4109cf7c781e07c74
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
# v0.8.1
|
2
|
+
|
3
|
+
- JSIs are immutable by default
|
4
|
+
|
5
|
+
# v0.8.0
|
6
|
+
|
7
|
+
- Immutable JSIs with new_jsi param `mutable`
|
8
|
+
- JSIs are still mutable by default, but in the next release they will default to immutable
|
9
|
+
- Base#jsi_indicated_schemas
|
10
|
+
- Base::StringNode
|
11
|
+
- rename metaschema modules /JSONSchemaOrgDraft0X/JSONSchemaDraft0X/
|
12
|
+
- terminology: /Metaschema/Meta-Schema/ and /metaschema/meta-schema/ (where hyphen is allowed)
|
13
|
+
- Base::HashNode#jsi_each_propertyName
|
14
|
+
- new_schema and/or new_jsi params register, schema_registry, stringify_symbol_keys, to_immutable
|
15
|
+
- new_schema block param will module_exec on schema module
|
16
|
+
- Base#[] param use_default default false, overridable
|
17
|
+
- SchemaModule::Connects, SchemaModule::Connection
|
18
|
+
- rm Schema#jsi_schema_instance_modules
|
19
|
+
|
1
20
|
# v0.7.0
|
2
21
|
|
3
22
|
- JSI::Base instances include Array/Hash-like modules on subclasses rather than extending each instance; are only Enumerable when appropriate instead of always
|
data/README.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
JSI offers an Object-Oriented representation for JSON data using JSON Schemas. Given your JSON Schemas, JSI constructs Ruby modules and classes which are used to instantiate your JSON data. These modules let you use JSON with all the niceties of OOP such as property accessors and application-defined instance methods.
|
7
7
|
|
8
|
-
To learn more about JSON Schema see
|
8
|
+
To learn more about JSON Schema see <https://json-schema.org/>.
|
9
9
|
|
10
10
|
JSI marries object-oriented programming with JSON Schemas by associating a module with each schema, and extending every instance described by a schema with that module. When an application adds methods to a schema module, those methods can be used on its instances.
|
11
11
|
|
@@ -15,7 +15,9 @@ Note: The canonical location of this README is on [RubyDoc](http://rubydoc.info/
|
|
15
15
|
|
16
16
|
## Example
|
17
17
|
|
18
|
-
Words are boring, let's code.
|
18
|
+
Words are boring, let's code. You can follow along from the code blocks - install the gem (`gem install jsi`), load an irb (`irb -r jsi`), and copy/paste/hack.
|
19
|
+
|
20
|
+
Here's a schema in yaml:
|
19
21
|
|
20
22
|
```yaml
|
21
23
|
$schema: "http://json-schema.org/draft-07/schema"
|
@@ -61,8 +63,12 @@ nickname: big b
|
|
61
63
|
So, if we construct an instance like:
|
62
64
|
|
63
65
|
```ruby
|
64
|
-
|
65
|
-
|
66
|
+
bill = Contact.new_jsi(
|
67
|
+
# this would typically load JSON or YAML; the schema instance is inlined for copypastability.
|
68
|
+
{"name" => "bill", "phone" => [{"location" => "home", "number" => "555"}], "nickname" => "big b"},
|
69
|
+
# note: bill is mutable to demonstrate setters below; the default is immutable.
|
70
|
+
mutable: true
|
71
|
+
)
|
66
72
|
# => #{<JSI (Contact)>
|
67
73
|
# "name" => "bill",
|
68
74
|
# "phone" => #[<JSI (Contact.properties["phone"])>
|
@@ -91,7 +97,7 @@ bill.phone.map(&:location)
|
|
91
97
|
# => ["home"]
|
92
98
|
```
|
93
99
|
|
94
|
-
We also get validations, as you'd expect given that's largely what
|
100
|
+
We also get validations, as you'd expect given that's largely what JSON Schema exists to do:
|
95
101
|
|
96
102
|
```ruby
|
97
103
|
bill.jsi_valid?
|
@@ -115,7 +121,7 @@ bad.phone.jsi_validate
|
|
115
121
|
# #<Set: {#<JSI::Validation::Error
|
116
122
|
# message: "instance type does not match `type` value",
|
117
123
|
# keyword: "type",
|
118
|
-
# schema: #{<JSI (JSI::
|
124
|
+
# schema: #{<JSI (JSI::JSONSchemaDraft07) Schema> "type" => "string"},
|
119
125
|
# instance_ptr: JSI::Ptr["phone", 0, "number"],
|
120
126
|
# instance_document: {"phone"=>[{"number"=>[5, 5, 5]}]}
|
121
127
|
# >,
|
@@ -140,7 +146,7 @@ There's plenty more JSI has to offer, but this should give you a pretty good ide
|
|
140
146
|
## Terminology and Concepts
|
141
147
|
|
142
148
|
- `JSI::Base` is the base class for each JSI schema class representing instances of JSON Schemas.
|
143
|
-
- a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a
|
149
|
+
- a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a meta-schema (see the section on meta-schemas below). A JSI Schema is an instance of the module `JSI::Schema`.
|
144
150
|
- a "JSI Schema Module" is a module which represents one schema, dynamically created by that Schema. Instances of that schema are extended with its JSI schema module. applications may reopen these modules to add functionality to JSI instances described by a given schema.
|
145
151
|
- a "JSI schema class" is a subclass of `JSI::Base` representing one or more JSON schemas. Instances of such a class are described by all of the represented schemas. A JSI schema class includes the JSI schema module of each represented schema.
|
146
152
|
- "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
|
@@ -154,9 +160,9 @@ JSI supports these JSON Schema specification versions:
|
|
154
160
|
|
155
161
|
| Version | `$schema` URI | JSI Schema Module |
|
156
162
|
| --- | --- | --- |
|
157
|
-
| Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::
|
158
|
-
| Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::
|
159
|
-
| Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::
|
163
|
+
| Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaDraft04} |
|
164
|
+
| Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaDraft06} |
|
165
|
+
| Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaDraft07} |
|
160
166
|
|
161
167
|
## JSI and Object Oriented Programming
|
162
168
|
|
@@ -190,7 +196,7 @@ bill['name']
|
|
190
196
|
|
191
197
|
For `#name` and `#name=`, we're overriding existing accessor methods. note the use of `super` - this invokes the accessor methods defined by JSI which these override. You could alternatively use `self['name']` and `self['name']=` in these methods, with the same effect as `super`.
|
192
198
|
|
193
|
-
Working with subschemas is just about as easy as with root schemas.
|
199
|
+
Working with subschemas to add methods is just about as easy as with root schemas.
|
194
200
|
|
195
201
|
You can subscript or use property accessors on a JSI schema module to refer to the schema modules of its subschemas, e.g.:
|
196
202
|
|
@@ -211,8 +217,7 @@ bill.phone.first.number_with_dashes
|
|
211
217
|
# => "5-5-5"
|
212
218
|
```
|
213
219
|
|
214
|
-
A recommended convention for naming subschemas is to define them in the namespace of the module of their
|
215
|
-
parent schema. The module can then be opened to add methods to the subschema's module.
|
220
|
+
A recommended convention for naming subschemas is to define them in the namespace of the module of their parent schema. The module can then be opened to add methods to the subschema's module.
|
216
221
|
|
217
222
|
```ruby
|
218
223
|
module Contact
|
@@ -240,11 +245,15 @@ end
|
|
240
245
|
|
241
246
|
The classes used to instantiate JSIs are dynamically generated subclasses of JSI::Base which include the JSI Schema Module of each schema describing the given instance. These are mostly intended to be ignored: applications aren't expected to instantiate these directly (rather, `#new_jsi` on a Schema or Schema Module is intended), and they are not intended for subclassing or method definition (applications should instead define methods on a schema's {JSI::Schema#jsi_schema_module}).
|
242
247
|
|
248
|
+
## Mutability
|
249
|
+
|
250
|
+
JSI instances are immutable by default. Mutable JSIs may be instantiated using the `mutable` param of `new_jsi`. Immutable JSIs are much more performant, because mutation may change what schemas apply to nodes in a document, and checking for that is costly. It is not recommended to instantiate large documents as mutable; their JSI instances become unusably slow.
|
251
|
+
|
243
252
|
## Registration
|
244
253
|
|
245
|
-
In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry which associates URIs with schemas (or resources containing schemas).
|
254
|
+
In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry (a {JSI::SchemaRegistry}) which associates URIs with schemas (or resources containing schemas). The default registry is accessible on {JSI.schema_registry}.
|
246
255
|
|
247
|
-
Schemas instantiated with `.new_schema`, and their subschemas, are
|
256
|
+
Schemas instantiated with `.new_schema`, and their subschemas, are by default registered with `JSI.schema_registry` if they are identified by an absolute URI. This can be controlled by params `register` and `schema_registry`.
|
248
257
|
|
249
258
|
Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::SchemaRegistry#autoload_uri} (see its documentation).
|
250
259
|
|
@@ -258,13 +267,13 @@ The following optional features are not completely supported:
|
|
258
267
|
- Regular expressions are interpreted by Ruby's Regexp class, whereas JSON Schema recommends interpreting these as ECMA 262 regular expressions. Certain expressions behave differently, particularly `^` and `$`.
|
259
268
|
- Keywords `contentMediaType` and `contentEncoding` do not perform validation.
|
260
269
|
|
261
|
-
##
|
270
|
+
## Meta-Schemas
|
262
271
|
|
263
|
-
A
|
272
|
+
A meta-schema is a schema that describes schemas. Likewise, a schema is an instance of a meta-schema.
|
264
273
|
|
265
|
-
In JSI, a schema is generally a JSI::Base instance whose schemas include a
|
274
|
+
In JSI, a schema is generally a JSI::Base instance whose schemas include a meta-schema.
|
266
275
|
|
267
|
-
A self-descriptive
|
276
|
+
A self-descriptive meta-schema - most commonly one of the JSON schema draft meta-schemas - is an object whose schemas include itself. This is instantiated in JSI as a JSI::MetaSchemaNode, a special subclass of JSI::Base.
|
268
277
|
|
269
278
|
## ActiveRecord serialization
|
270
279
|
|
@@ -282,7 +291,7 @@ class User < ActiveRecord::Base
|
|
282
291
|
end
|
283
292
|
```
|
284
293
|
|
285
|
-
Now `user.contact_info` will be instantiated as a `Contact` JSI instance, from the JSON type in the database, with Contact's accessors, validations, and
|
294
|
+
Now `user.contact_info` will be instantiated as a `Contact` JSI instance, from the JSON type in the database, with Contact's accessors, validations, and application-defined instance methods.
|
286
295
|
|
287
296
|
See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
|
288
297
|
|
data/jsi.gemspec
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require "jsi/version"
|
1
|
+
require_relative "lib/jsi/version"
|
4
2
|
|
5
3
|
Gem::Specification.new do |spec|
|
6
4
|
spec.name = "jsi"
|
@@ -27,4 +25,5 @@ Gem::Specification.new do |spec|
|
|
27
25
|
spec.require_paths = ["lib"]
|
28
26
|
|
29
27
|
spec.add_dependency "addressable", '~> 2.3'
|
28
|
+
spec.add_dependency "bigdecimal"
|
30
29
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
module Base::Mutable
|
5
|
+
include(Util::FingerprintHash)
|
6
|
+
|
7
|
+
def jsi_node_content
|
8
|
+
jsi_ptr.evaluate(jsi_document)
|
9
|
+
end
|
10
|
+
|
11
|
+
def jsi_mutable?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def jsi_mutability_initialize
|
18
|
+
end
|
19
|
+
|
20
|
+
def jsi_memomap_class
|
21
|
+
Util::MemoMap::Mutable
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module Base::Immutable
|
26
|
+
include(Util::FingerprintHash::Immutable)
|
27
|
+
|
28
|
+
attr_reader(:jsi_node_content)
|
29
|
+
|
30
|
+
def jsi_mutable?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def jsi_mutability_initialize
|
37
|
+
@jsi_node_content = @jsi_ptr.evaluate(@jsi_document)
|
38
|
+
end
|
39
|
+
|
40
|
+
def jsi_memomap_class
|
41
|
+
Util::MemoMap::Immutable
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/jsi/base/node.rb
CHANGED
@@ -1,47 +1,73 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
module Base
|
5
|
-
|
6
|
-
|
7
|
-
#
|
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
|
7
|
+
# instantiates and yields each property name (hash key) as a JSI described by any `propertyNames` schemas.
|
8
8
|
#
|
9
|
-
# @
|
10
|
-
# @return [
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
9
|
+
# @yield [JSI::Base]
|
10
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
11
|
+
def jsi_each_propertyName
|
12
|
+
return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block_given?
|
13
|
+
|
14
|
+
property_schemas = SchemaSet.build do |schemas|
|
15
|
+
jsi_schemas.each do |s|
|
16
|
+
if s.keyword?('propertyNames') && s['propertyNames'].is_a?(Schema)
|
17
|
+
schemas << s['propertyNames']
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
jsi_node_content_hash_pubsend(:each_key) do |key|
|
22
|
+
yield property_schemas.new_jsi(key)
|
18
23
|
end
|
19
|
-
|
24
|
+
|
25
|
+
nil
|
20
26
|
end
|
21
27
|
|
22
|
-
|
28
|
+
# See {Base#jsi_hash?}. Always true for HashNode.
|
29
|
+
def jsi_hash?
|
30
|
+
true
|
31
|
+
end
|
23
32
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# although Base#as_json does clobber activesupport's, I want as_json defined correctly on the module too.
|
30
|
-
Typelike.as_json(jsi_node_content, *opt)
|
33
|
+
# Yields each key - see {Base#jsi_each_child_token}
|
34
|
+
def jsi_each_child_token(&block)
|
35
|
+
return to_enum(__method__) { jsi_node_content_hash_pubsend(:size) } unless block
|
36
|
+
jsi_node_content_hash_pubsend(:each_key, &block)
|
37
|
+
nil
|
31
38
|
end
|
32
|
-
end
|
33
39
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
40
|
+
# See {Base#jsi_child_token_in_range?}
|
41
|
+
def jsi_child_token_in_range?(token)
|
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)
|
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)
|
52
|
+
end
|
53
|
+
|
54
|
+
# See {Base#[]}
|
55
|
+
def [](token, as_jsi: jsi_child_as_jsi_default, use_default: jsi_child_use_default_default)
|
56
|
+
if jsi_node_content_hash_pubsend(:key?, token)
|
57
|
+
jsi_child(token, as_jsi: as_jsi)
|
58
|
+
else
|
59
|
+
if use_default
|
60
|
+
jsi_default_child(token, as_jsi: as_jsi)
|
61
|
+
else
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
38
66
|
|
39
|
-
# yields each
|
67
|
+
# yields each Hash key (JSON object property name) and value of this node.
|
40
68
|
#
|
41
69
|
# each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
|
42
70
|
#
|
43
|
-
# returns an Enumerator if no block is given.
|
44
|
-
#
|
45
71
|
# @param kw keyword arguments are passed to {Base#[]}
|
46
72
|
# @yield [Object, Object] each key and value of this hash node
|
47
73
|
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
@@ -59,7 +85,22 @@ module JSI
|
|
59
85
|
# @param kw keyword arguments are passed to {Base#[]}
|
60
86
|
# @return [Hash]
|
61
87
|
def to_hash(**kw)
|
62
|
-
|
88
|
+
hash = {}
|
89
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| hash[k] = self[k, **kw] }
|
90
|
+
hash.freeze
|
91
|
+
end
|
92
|
+
|
93
|
+
# See {Base#as_json}
|
94
|
+
def as_json(options = {})
|
95
|
+
hash = {}
|
96
|
+
each_key do |k|
|
97
|
+
ks = k.is_a?(String) ? k :
|
98
|
+
k.is_a?(Symbol) ? k.to_s :
|
99
|
+
k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
|
100
|
+
raise(TypeError, "JSON object (Hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
|
101
|
+
hash[ks] = jsi_child_node(k).as_json(**options)
|
102
|
+
end
|
103
|
+
hash
|
63
104
|
end
|
64
105
|
|
65
106
|
include Util::Hashlike
|
@@ -110,14 +151,99 @@ module JSI
|
|
110
151
|
# module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
|
111
152
|
# is an Array (or responds to `#to_ary`)
|
112
153
|
module Base::ArrayNode
|
113
|
-
|
154
|
+
# See {Base#jsi_array?}. Always true for ArrayNode.
|
155
|
+
def jsi_array?
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
# Yields each index - see {Base#jsi_each_child_token}
|
160
|
+
def jsi_each_child_token(&block)
|
161
|
+
return to_enum(__method__) { jsi_node_content_ary_pubsend(:size) } unless block
|
162
|
+
jsi_node_content_ary_pubsend(:each_index, &block)
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
# See {Base#jsi_child_token_in_range?}
|
167
|
+
def jsi_child_token_in_range?(token)
|
168
|
+
token.is_a?(Integer) && token >= 0 && token < jsi_node_content_ary_pubsend(:size)
|
169
|
+
end
|
170
|
+
|
171
|
+
# See {Base#jsi_node_content_child}
|
172
|
+
def jsi_node_content_child(token)
|
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)
|
176
|
+
jsi_node_content_ary_pubsend(:[], token)
|
177
|
+
else
|
178
|
+
nil
|
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)
|
184
|
+
size = jsi_node_content_ary_pubsend(:size)
|
185
|
+
if token.is_a?(Integer)
|
186
|
+
if token < 0
|
187
|
+
if token < -size
|
188
|
+
nil
|
189
|
+
else
|
190
|
+
jsi_child(token + size, as_jsi: as_jsi)
|
191
|
+
end
|
192
|
+
else
|
193
|
+
if token < size
|
194
|
+
jsi_child(token, as_jsi: as_jsi)
|
195
|
+
else
|
196
|
+
if use_default
|
197
|
+
jsi_default_child(token, as_jsi: as_jsi)
|
198
|
+
else
|
199
|
+
nil
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
elsif token.is_a?(Range)
|
204
|
+
type_err = proc do
|
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
|
212
|
+
if start_idx.is_a?(Integer)
|
213
|
+
start_idx += size if start_idx < 0
|
214
|
+
return Util::EMPTY_ARY if start_idx == size
|
215
|
+
return nil if start_idx < 0 || start_idx > size
|
216
|
+
elsif start_idx.nil?
|
217
|
+
start_idx = 0
|
218
|
+
else
|
219
|
+
type_err.call
|
220
|
+
end
|
221
|
+
|
222
|
+
end_idx = token.end
|
223
|
+
if end_idx.is_a?(Integer)
|
224
|
+
end_idx += size if end_idx < 0
|
225
|
+
end_idx += 1 unless token.exclude_end?
|
226
|
+
end_idx = size if end_idx > size
|
227
|
+
return Util::EMPTY_ARY if start_idx >= end_idx
|
228
|
+
elsif end_idx.nil?
|
229
|
+
end_idx = size
|
230
|
+
else
|
231
|
+
type_err.call
|
232
|
+
end
|
233
|
+
|
234
|
+
(start_idx...end_idx).map { |i| jsi_child(i, as_jsi: as_jsi) }.freeze
|
235
|
+
else
|
236
|
+
raise(TypeError, [
|
237
|
+
"expected `token` param to be an Integer or Range",
|
238
|
+
"token: #{token.inspect}",
|
239
|
+
].join("\n"))
|
240
|
+
end
|
241
|
+
end
|
114
242
|
|
115
243
|
# yields each array element of this node.
|
116
244
|
#
|
117
245
|
# each yielded element is the result of {Base#[]} for each index of the instance array.
|
118
246
|
#
|
119
|
-
# returns an Enumerator if no block is given.
|
120
|
-
#
|
121
247
|
# @param kw keyword arguments are passed to {Base#[]}
|
122
248
|
# @yield [Object] each element of this array node
|
123
249
|
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
@@ -135,6 +261,11 @@ module JSI
|
|
135
261
|
to_a(**kw)
|
136
262
|
end
|
137
263
|
|
264
|
+
# See {Base#as_json}
|
265
|
+
def as_json(options = {})
|
266
|
+
each_index.map { |i| jsi_child_node(i).as_json(**options) }
|
267
|
+
end
|
268
|
+
|
138
269
|
include Util::Arraylike
|
139
270
|
|
140
271
|
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
@@ -180,4 +311,38 @@ module JSI
|
|
180
311
|
end
|
181
312
|
end
|
182
313
|
end
|
314
|
+
|
315
|
+
module Base::StringNode
|
316
|
+
delegate_methods = %w(% * + << =~ [] []=
|
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|
|
329
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
330
|
+
define_method(method_name) do |*a, &b|
|
331
|
+
if jsi_node_content.respond_to?(method_name)
|
332
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
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|
|
339
|
+
if jsi_node_content.respond_to?(method_name)
|
340
|
+
jsi_node_content.public_send(method_name, *a, **kw, &b)
|
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
|
183
348
|
end
|