jimmy 0.5.1 → 2.0.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 +5 -5
- data/.circleci/config.yml +35 -0
- data/.gitignore +4 -3
- data/.rspec +3 -0
- data/.rubocop.yml +61 -0
- data/.ruby-version +1 -1
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +13 -0
- data/LICENSE +1 -1
- data/README.md +22 -134
- data/Rakefile +91 -1
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/jimmy.gemspec +25 -21
- data/lib/jimmy.rb +23 -15
- data/lib/jimmy/declaration.rb +150 -0
- data/lib/jimmy/declaration/assertion.rb +81 -0
- data/lib/jimmy/declaration/casting.rb +34 -0
- data/lib/jimmy/declaration/composites.rb +41 -0
- data/lib/jimmy/declaration/number.rb +27 -0
- data/lib/jimmy/declaration/object.rb +11 -0
- data/lib/jimmy/declaration/string.rb +100 -0
- data/lib/jimmy/declaration/types.rb +57 -0
- data/lib/jimmy/error.rb +9 -0
- data/lib/jimmy/file_map.rb +166 -0
- data/lib/jimmy/index.rb +78 -0
- data/lib/jimmy/json/array.rb +93 -0
- data/lib/jimmy/json/collection.rb +90 -0
- data/lib/jimmy/json/hash.rb +118 -0
- data/lib/jimmy/json/pointer.rb +119 -0
- data/lib/jimmy/json/uri.rb +144 -0
- data/lib/jimmy/loaders/base.rb +30 -0
- data/lib/jimmy/loaders/json.rb +15 -0
- data/lib/jimmy/loaders/ruby.rb +21 -0
- data/lib/jimmy/macros.rb +37 -0
- data/lib/jimmy/schema.rb +106 -86
- data/lib/jimmy/schema/array.rb +95 -0
- data/lib/jimmy/schema/casting.rb +17 -0
- data/lib/jimmy/schema/json.rb +40 -0
- data/lib/jimmy/schema/number.rb +47 -0
- data/lib/jimmy/schema/object.rb +108 -0
- data/lib/jimmy/schema/operators.rb +96 -0
- data/lib/jimmy/schema/string.rb +44 -0
- data/lib/jimmy/schema_with_uri.rb +53 -0
- data/lib/jimmy/schemer_factory.rb +65 -0
- data/lib/jimmy/version.rb +3 -1
- data/schema07.json +172 -0
- metadata +50 -101
- data/circle.yml +0 -11
- data/lib/jimmy/combination.rb +0 -34
- data/lib/jimmy/definitions.rb +0 -38
- data/lib/jimmy/domain.rb +0 -111
- data/lib/jimmy/link.rb +0 -93
- data/lib/jimmy/reference.rb +0 -39
- data/lib/jimmy/schema_creation.rb +0 -121
- data/lib/jimmy/schema_type.rb +0 -100
- data/lib/jimmy/schema_types.rb +0 -42
- data/lib/jimmy/schema_types/array.rb +0 -30
- data/lib/jimmy/schema_types/boolean.rb +0 -6
- data/lib/jimmy/schema_types/integer.rb +0 -8
- data/lib/jimmy/schema_types/null.rb +0 -6
- data/lib/jimmy/schema_types/number.rb +0 -34
- data/lib/jimmy/schema_types/object.rb +0 -45
- data/lib/jimmy/schema_types/string.rb +0 -40
- data/lib/jimmy/symbol_array.rb +0 -17
- data/lib/jimmy/transform_keys.rb +0 -39
- data/lib/jimmy/type_reference.rb +0 -10
- data/lib/jimmy/validation_error.rb +0 -20
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
module Declaration
|
5
|
+
FORMATS = Set.new(
|
6
|
+
%w[
|
7
|
+
date-time
|
8
|
+
date
|
9
|
+
time
|
10
|
+
email
|
11
|
+
idn-email
|
12
|
+
hostname
|
13
|
+
idn-hostname
|
14
|
+
ipv4
|
15
|
+
ipv6
|
16
|
+
uri
|
17
|
+
uri-reference
|
18
|
+
iri
|
19
|
+
iri-reference
|
20
|
+
uri-template
|
21
|
+
json-pointer
|
22
|
+
relative-json-pointer
|
23
|
+
regex
|
24
|
+
]
|
25
|
+
).freeze
|
26
|
+
|
27
|
+
# Set the pattern for a string value.
|
28
|
+
# @param [Regexp] expression The pattern for a string value. Cannot include
|
29
|
+
# any options such as +/i+.
|
30
|
+
# @return [self] self, for chaining
|
31
|
+
def pattern(expression)
|
32
|
+
assert_regexp expression
|
33
|
+
string
|
34
|
+
set pattern: expression.source
|
35
|
+
end
|
36
|
+
|
37
|
+
FORMATS.each do |format|
|
38
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
39
|
+
def #{format.gsub '-', '_'}
|
40
|
+
string
|
41
|
+
format '#{format}'
|
42
|
+
end
|
43
|
+
RUBY
|
44
|
+
end
|
45
|
+
|
46
|
+
# Generated by `rake yard`. Do not modify anything below here.
|
47
|
+
#
|
48
|
+
# @!method date_time()
|
49
|
+
# Validate a string with format "date-time".
|
50
|
+
# @return [Schema]
|
51
|
+
# @!method date()
|
52
|
+
# Validate a string with format "date".
|
53
|
+
# @return [Schema]
|
54
|
+
# @!method time()
|
55
|
+
# Validate a string with format "time".
|
56
|
+
# @return [Schema]
|
57
|
+
# @!method email()
|
58
|
+
# Validate a string with format "email".
|
59
|
+
# @return [Schema]
|
60
|
+
# @!method idn_email()
|
61
|
+
# Validate a string with format "idn-email".
|
62
|
+
# @return [Schema]
|
63
|
+
# @!method hostname()
|
64
|
+
# Validate a string with format "hostname".
|
65
|
+
# @return [Schema]
|
66
|
+
# @!method idn_hostname()
|
67
|
+
# Validate a string with format "idn-hostname".
|
68
|
+
# @return [Schema]
|
69
|
+
# @!method ipv4()
|
70
|
+
# Validate a string with format "ipv4".
|
71
|
+
# @return [Schema]
|
72
|
+
# @!method ipv6()
|
73
|
+
# Validate a string with format "ipv6".
|
74
|
+
# @return [Schema]
|
75
|
+
# @!method uri()
|
76
|
+
# Validate a string with format "uri".
|
77
|
+
# @return [Schema]
|
78
|
+
# @!method uri_reference()
|
79
|
+
# Validate a string with format "uri-reference".
|
80
|
+
# @return [Schema]
|
81
|
+
# @!method iri()
|
82
|
+
# Validate a string with format "iri".
|
83
|
+
# @return [Schema]
|
84
|
+
# @!method iri_reference()
|
85
|
+
# Validate a string with format "iri-reference".
|
86
|
+
# @return [Schema]
|
87
|
+
# @!method uri_template()
|
88
|
+
# Validate a string with format "uri-template".
|
89
|
+
# @return [Schema]
|
90
|
+
# @!method json_pointer()
|
91
|
+
# Validate a string with format "json-pointer".
|
92
|
+
# @return [Schema]
|
93
|
+
# @!method relative_json_pointer()
|
94
|
+
# Validate a string with format "relative-json-pointer".
|
95
|
+
# @return [Schema]
|
96
|
+
# @!method regex()
|
97
|
+
# Validate a string with format "regex".
|
98
|
+
# @return [Schema]
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Jimmy
|
4
|
+
module Declaration
|
5
|
+
# Acceptable values for +#type+.
|
6
|
+
SIMPLE_TYPES =
|
7
|
+
Set.new(%w[array boolean integer null number object string]).freeze
|
8
|
+
|
9
|
+
# Set the type(s) of the schema.
|
10
|
+
# @param [String, Array<String>] types The type(s) of the schema.
|
11
|
+
# @return [self] self, for chaining
|
12
|
+
def type(*types)
|
13
|
+
types = types.flatten
|
14
|
+
types.each &method(:assert_simple_type)
|
15
|
+
assert_array types, unique: true, minimum: 1
|
16
|
+
types = Array(get('type') { [] }) | types.flatten
|
17
|
+
types = types.first if types.one?
|
18
|
+
set type: types
|
19
|
+
end
|
20
|
+
|
21
|
+
alias types type
|
22
|
+
|
23
|
+
SIMPLE_TYPES.each do |type|
|
24
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
25
|
+
def #{type}
|
26
|
+
type '#{type}'
|
27
|
+
end
|
28
|
+
RUBY
|
29
|
+
end
|
30
|
+
|
31
|
+
alias nullable null
|
32
|
+
|
33
|
+
# Generated by `rake yard`. Do not modify anything below here.
|
34
|
+
#
|
35
|
+
# @!method array()
|
36
|
+
# Make the schema allow type "array".
|
37
|
+
# @return [Schema]
|
38
|
+
# @!method boolean()
|
39
|
+
# Make the schema allow type "boolean".
|
40
|
+
# @return [Schema]
|
41
|
+
# @!method integer()
|
42
|
+
# Make the schema allow type "integer".
|
43
|
+
# @return [Schema]
|
44
|
+
# @!method null()
|
45
|
+
# Make the schema allow type "null".
|
46
|
+
# @return [Schema]
|
47
|
+
# @!method number()
|
48
|
+
# Make the schema allow type "number".
|
49
|
+
# @return [Schema]
|
50
|
+
# @!method object()
|
51
|
+
# Make the schema allow type "object".
|
52
|
+
# @return [Schema]
|
53
|
+
# @!method string()
|
54
|
+
# Make the schema allow type "string".
|
55
|
+
# @return [Schema]
|
56
|
+
end
|
57
|
+
end
|
data/lib/jimmy/error.rb
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/index'
|
4
|
+
require 'jimmy/loaders/ruby'
|
5
|
+
require 'jimmy/loaders/json'
|
6
|
+
require 'jimmy/schema_with_uri'
|
7
|
+
require 'jimmy/schemer_factory'
|
8
|
+
|
9
|
+
module Jimmy
|
10
|
+
# Maps a directory of files to schemas with URIs. Can be used as a URI
|
11
|
+
# resolver with {SchemerFactory}.
|
12
|
+
#
|
13
|
+
# Given +~/schemas/user.rb+ as a schema file:
|
14
|
+
#
|
15
|
+
# file_map = FileMap.new('~/schemas', 'http://example.com/schemas/', suffix: '.json')
|
16
|
+
# file_map.resolve('user.json') # => SchemaWithURI
|
17
|
+
#
|
18
|
+
# Calling {SchemaWithURI#as_json} on the above will include the full ID
|
19
|
+
# +http://example.com/schemas/user.json#+ in the +$id+ key.
|
20
|
+
#
|
21
|
+
# Including the suffix in the call to {FileMap#resolve} is optional.
|
22
|
+
#
|
23
|
+
# If you initialize a {FileMap} with +live: true+, files will be loaded
|
24
|
+
# lazily and repeatedly, every time {FileMap#resolve} or {FileMap#index} is
|
25
|
+
# called. This is intended as convenience for development environments.
|
26
|
+
class FileMap
|
27
|
+
DEFAULT_LOADERS = {
|
28
|
+
'rb' => Loaders::Ruby,
|
29
|
+
'json' => Loaders::JSON
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
# @param [Pathname, String] base_dir The directory that should map to the
|
33
|
+
# given URI.
|
34
|
+
# @param [Json::URI, URI, String] base_uri The URI that should resolve to
|
35
|
+
# the given directory. If omitted, a +file://+ URI will be used for the
|
36
|
+
# absolute path of the given directory.
|
37
|
+
# @param [true, false] live When true, schemas are not stored in memory, and
|
38
|
+
# are instead live-reloaded on demand. Typically only useful for
|
39
|
+
# development environments.
|
40
|
+
# @param [Hash{String => #call}] loaders Loaders for one or more file types.
|
41
|
+
# By default, +.json+ files are parsed as-is, and +.rb+ files are
|
42
|
+
# evaluated in the context of a {Loaders::Ruby} instance, which
|
43
|
+
# exposes the methods in {Macros}.
|
44
|
+
# @param [String] suffix Optional suffix that will be appended to each
|
45
|
+
# schema ID. This can be set to +".json"+ if, for example, you want your
|
46
|
+
# schemas to have +.json+ suffixes when you serve them over HTTP.
|
47
|
+
def initialize(
|
48
|
+
base_dir,
|
49
|
+
base_uri = nil,
|
50
|
+
live: false,
|
51
|
+
loaders: DEFAULT_LOADERS,
|
52
|
+
suffix: ''
|
53
|
+
)
|
54
|
+
@dir = Pathname(base_dir).realpath
|
55
|
+
unless @dir.directory? && @dir.readable?
|
56
|
+
raise Error::BadArgument, 'Expected a readable directory'
|
57
|
+
end
|
58
|
+
|
59
|
+
base_uri ||= uri_for_dir
|
60
|
+
@uri = Json::URI.new(base_uri.to_s, container: true)
|
61
|
+
|
62
|
+
@live = live
|
63
|
+
@loaders = loaders
|
64
|
+
@suffix = suffix
|
65
|
+
|
66
|
+
index unless live
|
67
|
+
end
|
68
|
+
|
69
|
+
# Given a URI, either absolute or relative to the file map's base URI,
|
70
|
+
# returns a {SchemaWithURI} if a matching schema is found.
|
71
|
+
# @param [Json::URI, URI, String] uri
|
72
|
+
# @return [Jimmy::SchemaWithURI, nil]
|
73
|
+
def resolve(uri)
|
74
|
+
uri = make_child_uri(uri)
|
75
|
+
absolute_uri = @uri + uri
|
76
|
+
|
77
|
+
return index.resolve(absolute_uri) unless live?
|
78
|
+
|
79
|
+
schema = load_file(path_for_uri uri)&.get_fragment(uri.fragment)
|
80
|
+
schema && SchemaWithURI.new(absolute_uri, schema)
|
81
|
+
end
|
82
|
+
|
83
|
+
alias [] resolve
|
84
|
+
|
85
|
+
# Get an index of all schemas in the file map's directory.
|
86
|
+
# @return [Jimmy::Index]
|
87
|
+
def index
|
88
|
+
return @index if @index
|
89
|
+
|
90
|
+
index = build_index
|
91
|
+
@index = index unless live?
|
92
|
+
|
93
|
+
index
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns true if live-reloading is enabled.
|
97
|
+
# @return [true, false]
|
98
|
+
def live?
|
99
|
+
@live
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def load_file(file_base)
|
105
|
+
@loaders.each do |ext, loader|
|
106
|
+
file = Pathname("#{file_base}.#{ext}")
|
107
|
+
next unless file.file?
|
108
|
+
return loader.call(file) if file.readable?
|
109
|
+
|
110
|
+
warn "Jimmy cannot read #{file}"
|
111
|
+
end
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
def uri_for_dir
|
116
|
+
Json::URI.new 'file://' + fs_to_rfc3968(@dir)
|
117
|
+
end
|
118
|
+
|
119
|
+
def path_for_uri(uri)
|
120
|
+
path = uri.path[0..(-@suffix.length - 1)]
|
121
|
+
parts = path.split('/').map(&URI.method(:decode_www_form_component))
|
122
|
+
@dir.join(*parts)
|
123
|
+
end
|
124
|
+
|
125
|
+
def make_child_uri(uri)
|
126
|
+
uri = @uri.route_to(@uri + uri)
|
127
|
+
|
128
|
+
unless uri.host.nil? && !uri.path.match?(%r{\A(\.\.|/)})
|
129
|
+
raise Error::BadArgument, 'The given URI is outside this FileMap'
|
130
|
+
end
|
131
|
+
|
132
|
+
uri.path += @suffix unless uri.path.end_with? @suffix
|
133
|
+
uri
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_index
|
137
|
+
index = Index.new
|
138
|
+
|
139
|
+
Dir[@dir + "**/*.{#{@loaders.keys.join ','}}"].sort.each do |file|
|
140
|
+
relative_uri = relative_uri_for_file(file)
|
141
|
+
uri = @uri + relative_uri
|
142
|
+
|
143
|
+
index[uri] = @loaders.fetch(File.extname(file)[1..]).call(file)
|
144
|
+
end
|
145
|
+
|
146
|
+
index
|
147
|
+
end
|
148
|
+
|
149
|
+
def relative_uri_for_file(file)
|
150
|
+
path = Pathname(file)
|
151
|
+
.relative_path_from(@dir)
|
152
|
+
.to_s
|
153
|
+
.sub(/\.[^.]+\z/, '')
|
154
|
+
|
155
|
+
Json::URI.new fs_to_rfc3968(path) + @suffix
|
156
|
+
end
|
157
|
+
|
158
|
+
def fs_to_rfc3968(path)
|
159
|
+
path
|
160
|
+
.to_s
|
161
|
+
.split(File::SEPARATOR)
|
162
|
+
.map { |p| URI.encode_www_form_component p.sub(/:\z/, '') }
|
163
|
+
.join('/')
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
data/lib/jimmy/index.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/schema'
|
4
|
+
require 'jimmy/schema_with_uri'
|
5
|
+
|
6
|
+
module Jimmy
|
7
|
+
# Represents an in-memory collection of schemas
|
8
|
+
class Index
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
def initialize # rubocop:disable Style/DocumentationMethod
|
12
|
+
@by_uri = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Json::URI, URI, String] uri
|
16
|
+
# @return [Jimmy::SchemaWithURI, nil]
|
17
|
+
def resolve(uri)
|
18
|
+
uri = Json::URI.new(uri)
|
19
|
+
raise Error::BadArgument, 'Cannot resolve relative URIs' if uri.relative?
|
20
|
+
|
21
|
+
return @by_uri[uri] if @by_uri.key? uri
|
22
|
+
return if uri.pointer.empty?
|
23
|
+
|
24
|
+
resolve(uri / -1)&.resolve uri
|
25
|
+
end
|
26
|
+
|
27
|
+
alias [] resolve
|
28
|
+
|
29
|
+
# @param [Json::URI, URI, String] uri
|
30
|
+
# @param [Jimmy::Schema] schema
|
31
|
+
# @return [self] self, for chaining
|
32
|
+
def add(uri, schema)
|
33
|
+
uri = Json::URI.new(uri)
|
34
|
+
raise Error::BadArgument, 'Expected a schema' unless schema.is_a? Schema
|
35
|
+
raise Error::BadArgument, 'Cannot index relative URIs' if uri.relative?
|
36
|
+
|
37
|
+
push SchemaWithURI.new(uri, schema)
|
38
|
+
end
|
39
|
+
|
40
|
+
alias []= add
|
41
|
+
|
42
|
+
# @param [Array<Jimmy::SchemaWithURI>] schemas_with_uris
|
43
|
+
# @return [self]
|
44
|
+
def push(*schemas_with_uris)
|
45
|
+
schemas_with_uris.each do |schema_with_uri|
|
46
|
+
unless schema_with_uri.is_a? SchemaWithURI
|
47
|
+
raise Error::BadArgument, 'Expected a SchemaWithURI'
|
48
|
+
end
|
49
|
+
|
50
|
+
@by_uri[schema_with_uri.uri] = schema_with_uri
|
51
|
+
end
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
alias << push
|
56
|
+
|
57
|
+
# @return [Array<Json::URI>]
|
58
|
+
def uris
|
59
|
+
@by_uri.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
alias keys uris
|
63
|
+
|
64
|
+
# @param [Json::URI, URI, String] uri
|
65
|
+
# @return [true, false]
|
66
|
+
def uri?(uri)
|
67
|
+
!resolve(uri).nil?
|
68
|
+
end
|
69
|
+
|
70
|
+
alias key? uri?
|
71
|
+
alias has_key? key?
|
72
|
+
|
73
|
+
# @yieldparam [Jimmy::SchemaWithURI] schema_with_uri
|
74
|
+
def each(&block)
|
75
|
+
@by_uri.each_value &block
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'jimmy/json/collection'
|
4
|
+
|
5
|
+
module Jimmy
|
6
|
+
module Json
|
7
|
+
# Represents an array in a JSON schema.
|
8
|
+
class Array
|
9
|
+
include Collection
|
10
|
+
|
11
|
+
KEY_PATTERN = /\A(?:\d|[1-9]\d+)\z/.freeze
|
12
|
+
|
13
|
+
# @param [Array, ::Array, Set] array Items to be included in the array.
|
14
|
+
def initialize(array = [])
|
15
|
+
super()
|
16
|
+
@members = []
|
17
|
+
concat array
|
18
|
+
end
|
19
|
+
|
20
|
+
# Append items in +array+ to self.
|
21
|
+
# @param [Array, ::Array, Set] array
|
22
|
+
# @return [self]
|
23
|
+
def concat(array)
|
24
|
+
array = array.to_a if array.is_a? Set
|
25
|
+
push *array
|
26
|
+
end
|
27
|
+
|
28
|
+
# Add one or more items to self.
|
29
|
+
# @param [Array] members Things to add.
|
30
|
+
# @return [self]
|
31
|
+
def push(*members)
|
32
|
+
@members.concat members.map(&method(:cast_value))
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
alias << push
|
37
|
+
|
38
|
+
# Iterate over items in the array. If a block with a single argument is
|
39
|
+
# given, only values will be yielded. Otherwise, indexes and values will
|
40
|
+
# be yielded.
|
41
|
+
# @yieldparam [Integer] index The index of each item.
|
42
|
+
# @yieldparam [Object] member Each item.
|
43
|
+
# @return [Enumerable, self] If no block is given, an {::Enumerable} is
|
44
|
+
# returned. Otherwise, +self+ is returned.
|
45
|
+
def each(&block)
|
46
|
+
return enum_for :each unless block
|
47
|
+
|
48
|
+
if block.arity == 1
|
49
|
+
@members.each { |member| yield member }
|
50
|
+
else
|
51
|
+
@members.each.with_index { |member, i| yield i, member }
|
52
|
+
end
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
# @return [Integer] The length of the array.
|
57
|
+
def length
|
58
|
+
@members.length
|
59
|
+
end
|
60
|
+
|
61
|
+
# Dig into the array.
|
62
|
+
# @param [Integer] key Index of the item to be dug or returned.
|
63
|
+
# @param [Array<String, Integer>] rest Keys or indexes to be passed to
|
64
|
+
# resolved hashes/arrays.
|
65
|
+
def dig(key, *rest)
|
66
|
+
key = key.to_i if key.is_a?(String) && key.match?(KEY_PATTERN)
|
67
|
+
super key, *rest
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [Array] Get a regular array.
|
71
|
+
def to_a
|
72
|
+
@members.dup
|
73
|
+
end
|
74
|
+
|
75
|
+
alias count length
|
76
|
+
alias size length
|
77
|
+
|
78
|
+
protected
|
79
|
+
|
80
|
+
def export_pairs(pairs)
|
81
|
+
pairs.map &:last
|
82
|
+
end
|
83
|
+
|
84
|
+
def cast_key(key)
|
85
|
+
unless key.is_a? Integer
|
86
|
+
raise Error::WrongType, "Invalid array index of type #{key.class}"
|
87
|
+
end
|
88
|
+
|
89
|
+
key
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|