eapi 0.2.1 → 0.3.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/README.md +316 -237
- data/lib/eapi/definition_runners/property.rb +21 -6
- data/lib/eapi/definition_runners/runner.rb +4 -0
- data/lib/eapi/errors.rb +6 -0
- data/lib/eapi/methods/accessor.rb +25 -2
- data/lib/eapi/methods/names.rb +4 -0
- data/lib/eapi/methods/properties.rb +17 -15
- data/lib/eapi/methods/types.rb +14 -2
- data/lib/eapi/type_checker.rb +23 -7
- data/lib/eapi/value_converter.rb +10 -0
- data/lib/eapi/version.rb +1 -1
- data/spec/list_elements_spec.rb +149 -48
- data/spec/validations_spec.rb +33 -0
- metadata +2 -2
|
@@ -4,6 +4,7 @@ module Eapi
|
|
|
4
4
|
class Property < Struct.new(:klass, :field, :definition)
|
|
5
5
|
def run
|
|
6
6
|
run_multiple_accessor
|
|
7
|
+
run_multiple_clearer
|
|
7
8
|
run_init
|
|
8
9
|
run_validations
|
|
9
10
|
run_allow_raw
|
|
@@ -53,8 +54,10 @@ module Eapi
|
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
def run_init
|
|
56
|
-
if
|
|
57
|
-
Runner.init(klass: klass, field: field, type:
|
|
57
|
+
if init_class
|
|
58
|
+
Runner.init(klass: klass, field: field, type: init_class)
|
|
59
|
+
elsif multiple? && (type.blank? || type.to_s == 'Array')
|
|
60
|
+
Runner.init(klass: klass, field: field, type: Array)
|
|
58
61
|
end
|
|
59
62
|
end
|
|
60
63
|
|
|
@@ -64,11 +67,19 @@ module Eapi
|
|
|
64
67
|
end
|
|
65
68
|
end
|
|
66
69
|
|
|
70
|
+
def run_multiple_clearer
|
|
71
|
+
if multiple?
|
|
72
|
+
Runner.multiple_clearer(klass: klass, field: field)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
67
76
|
def type_multiple?(type)
|
|
68
|
-
|
|
69
|
-
return true if type == Array || type == Set
|
|
77
|
+
type_class = Eapi::TypeChecker.constant_for_type type
|
|
70
78
|
|
|
71
|
-
|
|
79
|
+
return false if type_class.nil?
|
|
80
|
+
return true if type_class == Array || type_class == Set
|
|
81
|
+
|
|
82
|
+
type_class.respond_to?(:is_multiple?) && type_class.is_multiple?
|
|
72
83
|
end
|
|
73
84
|
|
|
74
85
|
def validate_element_with
|
|
@@ -76,7 +87,7 @@ module Eapi
|
|
|
76
87
|
end
|
|
77
88
|
|
|
78
89
|
def multiple?
|
|
79
|
-
definition.fetch(:multiple, false) || type_multiple?(type)
|
|
90
|
+
definition.fetch(:multiple, false) || type_multiple?(type) || type_multiple?(init_class)
|
|
80
91
|
end
|
|
81
92
|
|
|
82
93
|
def required?
|
|
@@ -95,6 +106,10 @@ module Eapi
|
|
|
95
106
|
definition.fetch(:type, nil)
|
|
96
107
|
end
|
|
97
108
|
|
|
109
|
+
def init_class
|
|
110
|
+
definition.fetch(:init_class, nil)
|
|
111
|
+
end
|
|
112
|
+
|
|
98
113
|
def allow_raw?
|
|
99
114
|
definition.fetch(:allow_raw, false)
|
|
100
115
|
end
|
data/lib/eapi/errors.rb
CHANGED
|
@@ -15,12 +15,35 @@ module Eapi
|
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def
|
|
18
|
+
def define_multiple_clearer(field)
|
|
19
|
+
init = Eapi::Methods::Names.init field
|
|
20
|
+
fluent_clearer = Eapi::Methods::Names.clearer field
|
|
21
|
+
getter = Eapi::Methods::Names.getter field
|
|
22
|
+
|
|
23
|
+
define_method fluent_clearer do
|
|
24
|
+
current = send(getter)
|
|
25
|
+
if current.nil?
|
|
26
|
+
# NOOP
|
|
27
|
+
elsif current.respond_to?(:clear)
|
|
28
|
+
current.clear
|
|
29
|
+
elsif respond_to?(init)
|
|
30
|
+
send(init)
|
|
31
|
+
else
|
|
32
|
+
raise Eapi::Errors::CannotClearFieldError, "#{self} can't clear #{field}: it does not respond to `clear` nor we have defined a `init_#{field}` method"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def define_init(field, init_class)
|
|
19
40
|
init = Eapi::Methods::Names.init field
|
|
20
41
|
instance_var = Eapi::Methods::Names.instance_var field
|
|
21
42
|
|
|
22
43
|
define_method init do
|
|
23
|
-
|
|
44
|
+
klass = Eapi::TypeChecker.constant_for_type init_class
|
|
45
|
+
raise Eapi::Errors::InvalidInitClass, "init_class: #{init_class}" if klass.nil?
|
|
46
|
+
value = klass.new
|
|
24
47
|
instance_variable_set instance_var, value
|
|
25
48
|
end
|
|
26
49
|
end
|
data/lib/eapi/methods/names.rb
CHANGED
|
@@ -29,21 +29,15 @@ module Eapi
|
|
|
29
29
|
|
|
30
30
|
module ClassMethods
|
|
31
31
|
def property_allow_raw(field)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
@_property_allow_raw[field.to_sym] = true
|
|
32
|
+
_property_allow_raw[field.to_sym] = true
|
|
35
33
|
end
|
|
36
34
|
|
|
37
35
|
def property_disallow_raw(field)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@_property_allow_raw[field.to_sym] = false
|
|
36
|
+
_property_allow_raw[field.to_sym] = false
|
|
41
37
|
end
|
|
42
38
|
|
|
43
39
|
def property_allow_raw?(field)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@_property_allow_raw.fetch(field.to_sym, false)
|
|
40
|
+
_property_allow_raw.fetch(field.to_sym, false)
|
|
47
41
|
end
|
|
48
42
|
|
|
49
43
|
def property(field, definition = {})
|
|
@@ -54,23 +48,31 @@ module Eapi
|
|
|
54
48
|
end
|
|
55
49
|
|
|
56
50
|
def properties
|
|
57
|
-
|
|
51
|
+
_property_definitions.keys
|
|
58
52
|
end
|
|
59
53
|
|
|
60
54
|
def definition_for(field)
|
|
61
|
-
|
|
62
|
-
@_property_definitions.fetch(field.to_sym, {}).dup
|
|
55
|
+
_property_definitions.fetch(field.to_sym, {}).dup
|
|
63
56
|
end
|
|
64
57
|
|
|
65
58
|
def store_property_definition(field, definition)
|
|
66
|
-
|
|
67
|
-
@_property_definitions[field] = definition
|
|
59
|
+
_property_definitions[field] = definition.tap { |x| x.freeze }
|
|
68
60
|
end
|
|
69
61
|
|
|
70
62
|
def run_property_definition(property_field, definition)
|
|
71
63
|
Eapi::DefinitionRunners::Property.new(self, property_field, definition).run
|
|
72
64
|
end
|
|
73
65
|
|
|
66
|
+
def _property_allow_raw
|
|
67
|
+
@_property_allow_raw ||= {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def _property_definitions
|
|
71
|
+
@_property_definitions ||= {}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private :_property_allow_raw
|
|
75
|
+
private :_property_definitions
|
|
74
76
|
private :run_property_definition
|
|
75
77
|
private :store_property_definition
|
|
76
78
|
end
|
|
@@ -98,7 +100,7 @@ module Eapi
|
|
|
98
100
|
end
|
|
99
101
|
|
|
100
102
|
def store_list_definition(definition)
|
|
101
|
-
@_list_definition = definition
|
|
103
|
+
@_list_definition = definition.tap { |x| x.freeze }
|
|
102
104
|
end
|
|
103
105
|
|
|
104
106
|
def run_list_definition(definition)
|
data/lib/eapi/methods/types.rb
CHANGED
|
@@ -47,6 +47,7 @@ module Eapi
|
|
|
47
47
|
|
|
48
48
|
module InstanceMethods
|
|
49
49
|
def is?(type)
|
|
50
|
+
return true if type.kind_of?(Module) && kind_of?(type)
|
|
50
51
|
self.class.is?(type)
|
|
51
52
|
end
|
|
52
53
|
|
|
@@ -61,11 +62,12 @@ module Eapi
|
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def is?(type)
|
|
65
|
+
return true if Checker._is_type_module?(self, type)
|
|
66
|
+
|
|
64
67
|
type_sym = Types.to_type_sym type
|
|
68
|
+
return true if Checker._is_type_module_sym?(self, type_sym)
|
|
65
69
|
|
|
66
|
-
return true if self == type || Types.to_type_sym(self) == type_sym
|
|
67
70
|
return false unless @i_am_a.present?
|
|
68
|
-
|
|
69
71
|
!!@i_am_a.include?(type_sym) # force it to be a bool
|
|
70
72
|
end
|
|
71
73
|
|
|
@@ -77,6 +79,16 @@ module Eapi
|
|
|
77
79
|
end
|
|
78
80
|
end
|
|
79
81
|
|
|
82
|
+
module Checker
|
|
83
|
+
def self._is_type_module?(klass, mod_or_class)
|
|
84
|
+
return false unless mod_or_class.kind_of?(Module)
|
|
85
|
+
klass == mod_or_class || klass.ancestors.include?(mod_or_class)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def self._is_type_module_sym?(klass, type_sym)
|
|
89
|
+
Types.to_type_sym(self) == type_sym || klass.ancestors.any? { |a| Types.to_type_sym(a) == type_sym }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
80
92
|
end
|
|
81
93
|
end
|
|
82
94
|
end
|
data/lib/eapi/type_checker.rb
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
module Eapi
|
|
2
2
|
class TypeChecker
|
|
3
3
|
|
|
4
|
+
def self.constant_for_type(type)
|
|
5
|
+
if type.kind_of? Module
|
|
6
|
+
type
|
|
7
|
+
else
|
|
8
|
+
begin
|
|
9
|
+
type.to_s.constantize
|
|
10
|
+
rescue NameError
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
4
16
|
attr_reader :given_type, :allow_raw
|
|
5
17
|
|
|
6
18
|
def initialize(given_type, allow_raw = false)
|
|
@@ -9,7 +21,7 @@ module Eapi
|
|
|
9
21
|
end
|
|
10
22
|
|
|
11
23
|
def is_valid_type?(value)
|
|
12
|
-
value.nil? ||
|
|
24
|
+
value.nil? || valid_raw?(value) || is_same_type?(value) || poses_as_type?(value)
|
|
13
25
|
end
|
|
14
26
|
|
|
15
27
|
private
|
|
@@ -20,7 +32,7 @@ module Eapi
|
|
|
20
32
|
end
|
|
21
33
|
|
|
22
34
|
def is_same_type?(value)
|
|
23
|
-
value.kind_of?(
|
|
35
|
+
value.kind_of?(type_class) if type_class.present?
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
def poses_as_type?(value)
|
|
@@ -28,11 +40,15 @@ module Eapi
|
|
|
28
40
|
end
|
|
29
41
|
|
|
30
42
|
def type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
given_type
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def type_class
|
|
47
|
+
@type_class ||= load_type_class
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load_type_class
|
|
51
|
+
Eapi::TypeChecker.constant_for_type given_type
|
|
36
52
|
end
|
|
37
53
|
|
|
38
54
|
def allow_raw?
|
data/lib/eapi/value_converter.rb
CHANGED
|
@@ -3,6 +3,8 @@ module Eapi
|
|
|
3
3
|
def self.convert_value(value)
|
|
4
4
|
if value.nil?
|
|
5
5
|
nil
|
|
6
|
+
elsif can_render? value
|
|
7
|
+
value_from_render value
|
|
6
8
|
elsif is_list? value
|
|
7
9
|
value_from_list value
|
|
8
10
|
elsif is_hash?(value)
|
|
@@ -13,6 +15,10 @@ module Eapi
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
private
|
|
18
|
+
def self.can_render?(value)
|
|
19
|
+
value.respond_to? :render
|
|
20
|
+
end
|
|
21
|
+
|
|
16
22
|
def self.is_hash?(value)
|
|
17
23
|
value.respond_to? :to_h
|
|
18
24
|
end
|
|
@@ -23,6 +29,10 @@ module Eapi
|
|
|
23
29
|
value.respond_to? :to_a
|
|
24
30
|
end
|
|
25
31
|
|
|
32
|
+
def self.value_from_render(value)
|
|
33
|
+
value.render
|
|
34
|
+
end
|
|
35
|
+
|
|
26
36
|
def self.value_from_list(value)
|
|
27
37
|
value.to_a.map { |e| convert_value e }.compact
|
|
28
38
|
end
|
data/lib/eapi/version.rb
CHANGED
data/spec/list_elements_spec.rb
CHANGED
|
@@ -2,74 +2,103 @@ require 'spec_helper'
|
|
|
2
2
|
|
|
3
3
|
RSpec.describe Eapi do
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
property :something, multiple: true
|
|
5
|
+
class MyMultiple
|
|
6
|
+
def self.is_multiple?
|
|
7
|
+
true
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
expect(res).to be eapi
|
|
16
|
-
expect(eapi.something).to eq [1, 2, 3]
|
|
10
|
+
def <<(x)
|
|
11
|
+
@elements ||= []
|
|
12
|
+
@elements << x
|
|
17
13
|
end
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
res = eapi.add_something :a
|
|
22
|
-
expect(res).to be eapi
|
|
23
|
-
expect(eapi.something.to_a).to eq [:a]
|
|
15
|
+
def to_a
|
|
16
|
+
@elements.to_a
|
|
24
17
|
end
|
|
18
|
+
end
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
class MyMultipleValueTestKlass
|
|
21
|
+
include Eapi::MultipleValue
|
|
28
22
|
|
|
29
|
-
|
|
23
|
+
def <<(x)
|
|
24
|
+
@elements ||= []
|
|
25
|
+
@elements << x
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
28
|
+
def to_a
|
|
29
|
+
@elements.to_a
|
|
30
|
+
end
|
|
31
|
+
end
|
|
36
32
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@elements << x
|
|
40
|
-
end
|
|
33
|
+
class MyTestClassValMult
|
|
34
|
+
include Eapi::Item
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
end
|
|
45
|
-
end
|
|
36
|
+
property :something, multiple: true
|
|
37
|
+
end
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
class MyTestClassValMultImpl
|
|
40
|
+
include Eapi::Item
|
|
49
41
|
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
property :something, init_class: Set
|
|
43
|
+
end
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
class MyTestClassValMultImpl2
|
|
46
|
+
include Eapi::Item
|
|
55
47
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@elements << x
|
|
59
|
-
end
|
|
48
|
+
property :something, init_class: :MyMultiple
|
|
49
|
+
end
|
|
60
50
|
|
|
61
|
-
def to_a
|
|
62
|
-
@elements.to_a
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
51
|
|
|
66
|
-
|
|
67
|
-
|
|
52
|
+
class MyTestClassValMultImpl3
|
|
53
|
+
include Eapi::Item
|
|
54
|
+
|
|
55
|
+
property :something, init_class: "MyMultipleValueTestKlass"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class MyTestClassValMultTypeTest
|
|
60
|
+
include Eapi::Item
|
|
61
|
+
|
|
62
|
+
property :something, multiple: true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
class MyTestClassValMultImplTypeTest
|
|
66
|
+
include Eapi::Item
|
|
67
|
+
|
|
68
|
+
property :something, type: Set
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class MyTestClassValMultImpl2TypeTest
|
|
72
|
+
include Eapi::Item
|
|
73
|
+
|
|
74
|
+
property :something, type: :MyMultiple
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MyTestClassValMultImpl3TypeTest
|
|
79
|
+
include Eapi::Item
|
|
80
|
+
|
|
81
|
+
property :something, type: "MyMultipleValueTestKlass"
|
|
82
|
+
end
|
|
68
83
|
|
|
69
|
-
|
|
84
|
+
|
|
85
|
+
context 'list elements' do
|
|
86
|
+
it '#add_something' do
|
|
87
|
+
eapi = MyTestClassValMult.new something: [1, 2]
|
|
88
|
+
res = eapi.add_something 3
|
|
89
|
+
expect(res).to be eapi
|
|
90
|
+
expect(eapi.something).to eq [1, 2, 3]
|
|
70
91
|
end
|
|
71
92
|
|
|
72
|
-
it '
|
|
93
|
+
it '#init_something called on first add if element is nil' do
|
|
94
|
+
eapi = MyTestClassValMult.new
|
|
95
|
+
res = eapi.add_something :a
|
|
96
|
+
expect(res).to be eapi
|
|
97
|
+
expect(eapi.something.to_a).to eq [:a]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
it 'if init_class is Array or Set, or responds true to is_multiple?, it is multiple implicitly + uses that class to initialize the property when adding' do
|
|
73
102
|
[
|
|
74
103
|
[MyTestClassValMult, Array],
|
|
75
104
|
[MyTestClassValMultImpl, Set],
|
|
@@ -84,6 +113,78 @@ RSpec.describe Eapi do
|
|
|
84
113
|
end
|
|
85
114
|
end
|
|
86
115
|
|
|
116
|
+
it 'if type is Array or Set, or responds true to is_multiple?, it is multiple implicitly but it does not use it to initialize the property when adding (unless type == Array)' do
|
|
117
|
+
[
|
|
118
|
+
[MyTestClassValMultTypeTest, Array],
|
|
119
|
+
[MyTestClassValMultImplTypeTest, Set],
|
|
120
|
+
[MyTestClassValMultImpl2TypeTest, MyMultiple],
|
|
121
|
+
[MyTestClassValMultImpl3TypeTest, MyMultipleValueTestKlass],
|
|
122
|
+
].each do |(eapi_class, type_class)|
|
|
123
|
+
eapi = eapi_class.new
|
|
124
|
+
|
|
125
|
+
unless type_class == Array
|
|
126
|
+
expect { eapi.add_something :a }.to raise_exception
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
eapi.something type_class.new
|
|
130
|
+
|
|
131
|
+
res = eapi.add_something :a
|
|
132
|
+
expect(res).to be eapi
|
|
133
|
+
expect(eapi.something.to_a).to eq [:a]
|
|
134
|
+
expect(eapi.something).to be_a_kind_of type_class
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
context '#clear_something' do
|
|
139
|
+
it 'if value is nil, returns self' do
|
|
140
|
+
[
|
|
141
|
+
MyTestClassValMultTypeTest,
|
|
142
|
+
MyTestClassValMultImplTypeTest,
|
|
143
|
+
MyTestClassValMultImpl2TypeTest,
|
|
144
|
+
MyTestClassValMultImpl3TypeTest,
|
|
145
|
+
].each do |eapi_class|
|
|
146
|
+
eapi = eapi_class.new
|
|
147
|
+
expect(eapi.clear_something).to be eapi
|
|
148
|
+
expect(eapi.something).to be_nil
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'if value respond to clear, it will call it' do
|
|
153
|
+
[
|
|
154
|
+
MyTestClassValMultTypeTest,
|
|
155
|
+
MyTestClassValMultImplTypeTest,
|
|
156
|
+
MyTestClassValMultImpl2TypeTest,
|
|
157
|
+
MyTestClassValMultImpl3TypeTest,
|
|
158
|
+
].each do |eapi_class|
|
|
159
|
+
o = double()
|
|
160
|
+
expect(o).to receive(:clear)
|
|
161
|
+
|
|
162
|
+
eapi = eapi_class.new something: o
|
|
163
|
+
expect(eapi.clear_something).to be eapi
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'if value do not respond to clear, it will call the `init_something` method' do
|
|
168
|
+
[
|
|
169
|
+
MyTestClassValMultTypeTest,
|
|
170
|
+
MyTestClassValMultImplTypeTest,
|
|
171
|
+
MyTestClassValMultImpl2TypeTest,
|
|
172
|
+
MyTestClassValMultImpl3TypeTest,
|
|
173
|
+
].each do |eapi_class|
|
|
174
|
+
o = double()
|
|
175
|
+
eapi = eapi_class.new something: o
|
|
176
|
+
expect(eapi).to receive(:init_something)
|
|
177
|
+
expect(eapi.clear_something).to be eapi
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'if value do not respond to clear and we do not have `init_something` method, raise exception' do
|
|
182
|
+
eapi = MyTestClassValMultImpl3TypeTest.new something: MyMultipleValueTestKlass.new
|
|
183
|
+
expect { eapi.clear_something }.to raise_exception()
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
end
|
|
187
|
+
|
|
87
188
|
describe 'element validation' do
|
|
88
189
|
class MyTestClassValElements
|
|
89
190
|
include Eapi::Item
|