uinit-structure 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +3 -0
- data/lib/uinit/struct.rb +13 -0
- data/lib/uinit/structure/attribute.rb +144 -0
- data/lib/uinit/structure/attribute_builder.rb +121 -0
- data/lib/uinit/structure/attribute_context.rb +52 -0
- data/lib/uinit/structure/attribute_error.rb +19 -0
- data/lib/uinit/structure/attribute_type_error.rb +22 -0
- data/lib/uinit/structure/compilers/as_json.rb +96 -0
- data/lib/uinit/structure/compilers/attribute.rb +190 -0
- data/lib/uinit/structure/compilers/base.rb +23 -0
- data/lib/uinit/structure/compilers/constructor.rb +84 -0
- data/lib/uinit/structure/compilers.rb +8 -0
- data/lib/uinit/structure/version.rb +7 -0
- data/lib/uinit/structure.rb +61 -0
- data/uinit-structure.gemspec +38 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3c8c42aab899fa0b64aa674b8096c9b1ed6be23e39643fd63d06f168876a18cd
|
4
|
+
data.tar.gz: 3499d5ba022a96ab76bf9aca62313787388271343caa2dbf5760997a972ebcc9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ca55bfc366a4b15b90c6306f7b5b724988e341ade9f2feaa38aa58c86335e4a89b214ce014e19af4e73d3d768c06a202f8a7e98345a011e8ac503c56f95a367b
|
7
|
+
data.tar.gz: 5c15f70a14a1b8ba09ab08302322e5bdb37c634110dfae0b27d77039ce13cd630d6a61ab1c5efb82e8e8ad6a8b9d86f8efbee83f64ea54e7103f53df9be31064
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Kimoja
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/lib/uinit/struct.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
# rubocop:disable Metrics/ClassLength
|
6
|
+
class Attribute
|
7
|
+
NAME_REGEX = /\A[A-Za-z]\w*\z/
|
8
|
+
UNDEFINED = Class.new.new.freeze
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@private_get = false
|
12
|
+
@private_set = false
|
13
|
+
@name = nil
|
14
|
+
@type = nil
|
15
|
+
@default = UNDEFINED
|
16
|
+
@struct = nil
|
17
|
+
@array_struct = false
|
18
|
+
@init = true
|
19
|
+
@get = nil
|
20
|
+
@set = nil
|
21
|
+
@as_json = true
|
22
|
+
@aliases = []
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_accessor :default
|
26
|
+
attr_reader :private_get,
|
27
|
+
:private_set,
|
28
|
+
:name,
|
29
|
+
:type,
|
30
|
+
:struct,
|
31
|
+
:array_struct,
|
32
|
+
:init,
|
33
|
+
:get,
|
34
|
+
:set,
|
35
|
+
:as_json,
|
36
|
+
:aliases
|
37
|
+
|
38
|
+
def private_get=(val)
|
39
|
+
raise ArgumentError, 'private_get must be a boolean' unless [true, false].include?(val)
|
40
|
+
|
41
|
+
@private_get = val
|
42
|
+
end
|
43
|
+
|
44
|
+
def private_set=(val)
|
45
|
+
raise ArgumentError, 'private_set must be a boolean' unless [true, false].include?(val)
|
46
|
+
|
47
|
+
@private_set = val
|
48
|
+
end
|
49
|
+
|
50
|
+
def name=(val)
|
51
|
+
optional = val.to_s.end_with?('?')
|
52
|
+
|
53
|
+
if optional
|
54
|
+
self.default = nil if default == UNDEFINED
|
55
|
+
val = val.to_s.sub('?', '').to_sym
|
56
|
+
end
|
57
|
+
|
58
|
+
raise NameError, "Invalid attribute name '#{val}'" unless NAME_REGEX.match?(val)
|
59
|
+
|
60
|
+
@name = val
|
61
|
+
end
|
62
|
+
|
63
|
+
def type=(val)
|
64
|
+
val = Type.from(val)
|
65
|
+
raise ArgumentError, 'type must be a Type::Base' unless val.is_a?(Type::Base)
|
66
|
+
raise ArgumentError, 'Attribute cannot have a type and a struct at the same time' if struct
|
67
|
+
|
68
|
+
@type = val
|
69
|
+
end
|
70
|
+
|
71
|
+
def struct=(val)
|
72
|
+
raise ArgumentError, 'Attribute cannot have a type and a struct at the same time' if type
|
73
|
+
|
74
|
+
if val.is_a?(Class) && val < Struct
|
75
|
+
@struct = val
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
raise ArgumentError, 'struct must be a Struct Class or a Proc' unless val.is_a?(Proc)
|
80
|
+
|
81
|
+
@struct =
|
82
|
+
Class.new(Struct) do
|
83
|
+
struct(&val)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def array_struct=(val)
|
88
|
+
self.struct = val
|
89
|
+
|
90
|
+
@array_struct = true
|
91
|
+
end
|
92
|
+
|
93
|
+
def optional?
|
94
|
+
@default != UNDEFINED
|
95
|
+
end
|
96
|
+
|
97
|
+
def init=(val)
|
98
|
+
raise ArgumentError, 'init must be a boolean' unless [true, false].include?(val)
|
99
|
+
|
100
|
+
@init = val
|
101
|
+
end
|
102
|
+
|
103
|
+
def get=(val)
|
104
|
+
unless val.is_a?(Proc) || val.is_a?(Symbol)
|
105
|
+
raise ArgumentError,
|
106
|
+
'`get` must be a Proc or a Symbol'
|
107
|
+
end
|
108
|
+
|
109
|
+
@get = val
|
110
|
+
end
|
111
|
+
|
112
|
+
def set=(val)
|
113
|
+
unless val.is_a?(Proc) || val.is_a?(Symbol)
|
114
|
+
raise ArgumentError,
|
115
|
+
'`set` must be a Proc or a Symbol'
|
116
|
+
end
|
117
|
+
|
118
|
+
@set = val
|
119
|
+
end
|
120
|
+
|
121
|
+
def as_json=(val)
|
122
|
+
unless val.is_a?(Proc) || val.is_a?(Symbol) || val == false || val == true
|
123
|
+
raise ArgumentError,
|
124
|
+
'`as_json` must be a Proc or a Symbol or a Boolean'
|
125
|
+
end
|
126
|
+
|
127
|
+
@as_json = val
|
128
|
+
end
|
129
|
+
|
130
|
+
def aliases=(val)
|
131
|
+
val.each do |v|
|
132
|
+
raise NameError, "Invalid alias name '#{v}'" unless NAME_REGEX.match?(v)
|
133
|
+
end
|
134
|
+
|
135
|
+
@aliases = val
|
136
|
+
end
|
137
|
+
|
138
|
+
def as_json?
|
139
|
+
@as_json != false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
# rubocop:enable Metrics/ClassLength
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
class AttributeBuilder
|
6
|
+
def initialize(attribute = nil)
|
7
|
+
@attribute = attribute || Attribute.new
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :attribute
|
11
|
+
|
12
|
+
def attr(name, type = nil, default = Attribute::UNDEFINED, &)
|
13
|
+
return build_mutliple(name, type, default, &) if name.is_a?(Array)
|
14
|
+
|
15
|
+
self.name(name)
|
16
|
+
|
17
|
+
build(type, default, &)
|
18
|
+
end
|
19
|
+
|
20
|
+
def abstract(type = nil, default = Attribute::UNDEFINED, &)
|
21
|
+
build(type, default, &)
|
22
|
+
end
|
23
|
+
|
24
|
+
def private(*get_set)
|
25
|
+
if get_set.empty?
|
26
|
+
attribute.private_get = true
|
27
|
+
attribute.private_set = true
|
28
|
+
else
|
29
|
+
attribute.private_get = true if get_set.include?(:get)
|
30
|
+
attribute.private_set = true if get_set.include?(:set)
|
31
|
+
end
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def name(val)
|
37
|
+
attribute.name = val
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
def type(val)
|
43
|
+
attribute.type = val
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def struct(val = nil, &val_proc)
|
49
|
+
attribute.struct = val.nil? ? val_proc : val
|
50
|
+
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def array_struct(val = nil, &val_proc)
|
55
|
+
attribute.array_struct = val.nil? ? val_proc : val
|
56
|
+
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def default(val = nil, &val_proc)
|
61
|
+
attribute.default = val.nil? ? val_proc : val
|
62
|
+
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def optional(val)
|
67
|
+
attribute.optional = val
|
68
|
+
|
69
|
+
self
|
70
|
+
end
|
71
|
+
|
72
|
+
def init(val)
|
73
|
+
attribute.init = val
|
74
|
+
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
def get(val = nil, &val_proc)
|
79
|
+
attribute.get = val.nil? ? val_proc : val
|
80
|
+
|
81
|
+
self
|
82
|
+
end
|
83
|
+
|
84
|
+
def set(val = nil, &val_proc)
|
85
|
+
attribute.set = val.nil? ? val_proc : val
|
86
|
+
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def as_json(val = nil, &val_proc)
|
91
|
+
attribute.as_json = val.nil? ? val_proc : val
|
92
|
+
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
def alias(*aliases)
|
97
|
+
attribute.aliases = aliases
|
98
|
+
|
99
|
+
self
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def build_mutliple(name, type, default, &)
|
105
|
+
name.map do |nm|
|
106
|
+
builder = AttributeBuilder.new(attribute.clone)
|
107
|
+
builder.attr(nm, type, default, &)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build(type, default, &attr_builder)
|
112
|
+
self.type(type) unless type.nil?
|
113
|
+
self.default(default) unless default == Attribute::UNDEFINED
|
114
|
+
|
115
|
+
instance_eval(&attr_builder) if attr_builder
|
116
|
+
|
117
|
+
self
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
module AttributeContext
|
6
|
+
include Memoizable
|
7
|
+
include Type::Context
|
8
|
+
|
9
|
+
def self.scope(&)
|
10
|
+
context = Class.new { include AttributeContext }.new
|
11
|
+
context.instance_eval(&)
|
12
|
+
|
13
|
+
context.attributes
|
14
|
+
end
|
15
|
+
|
16
|
+
memo def attributes = []
|
17
|
+
|
18
|
+
def private(att_or_get_set = nil, *)
|
19
|
+
return att_or_getset.private(:get, :set) if att_or_get_set.is_a?(AttributeBuilder)
|
20
|
+
|
21
|
+
builder = AttributeBuilder.new.private(*[att_or_get_set, *].compact)
|
22
|
+
attributes << builder.attribute
|
23
|
+
|
24
|
+
builder
|
25
|
+
end
|
26
|
+
|
27
|
+
def attr(...)
|
28
|
+
push_attribute(AttributeBuilder.new.attr(...))
|
29
|
+
end
|
30
|
+
|
31
|
+
def abstract(...)
|
32
|
+
push_attribute(AttributeBuilder.new.abstract(...))
|
33
|
+
end
|
34
|
+
|
35
|
+
def using(attribute_builder)
|
36
|
+
push_attribute(AttributeBuilder.new(attribute_builder.attribute))
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def push_attribute(builder)
|
42
|
+
if builder.is_a?(Array)
|
43
|
+
attributes.push(*builder.map(&:attribute))
|
44
|
+
else
|
45
|
+
attributes << builder.attribute
|
46
|
+
end
|
47
|
+
|
48
|
+
builder
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
class AttributeError < StandardError
|
6
|
+
def initialize(attribute, msg)
|
7
|
+
super(msg)
|
8
|
+
|
9
|
+
@attribute = attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :attribute
|
13
|
+
|
14
|
+
def message
|
15
|
+
"Error on attribute '#{attribute.name}', detail:\n#{super}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
class AttributeTypeError < StandardError
|
6
|
+
def initialize(attribute, type_error)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@attribute = attribute
|
10
|
+
@type_error = type_error
|
11
|
+
|
12
|
+
set_backtrace(type_error.backtrace)
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :attribute, :type_error
|
16
|
+
|
17
|
+
def message
|
18
|
+
"Type error on attribute '#{attribute.name}', detail:\n#{type_error.message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
module Compilers
|
6
|
+
class AsJson < Base
|
7
|
+
def initialize(mod, schema)
|
8
|
+
super(mod)
|
9
|
+
|
10
|
+
@schema = schema
|
11
|
+
@attributes = schema.class.instance_methods(false).map { schema.send(_1) }
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :schema, :attributes
|
15
|
+
|
16
|
+
def compile
|
17
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
18
|
+
def as_json
|
19
|
+
#{compile_super}
|
20
|
+
#{compile_json_attributes * "\n"}
|
21
|
+
json
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def compile_super
|
29
|
+
unless schema.class.ancestors[1..].any? { _1 < Schema }
|
30
|
+
return <<~RUBY
|
31
|
+
json = {}
|
32
|
+
RUBY
|
33
|
+
end
|
34
|
+
|
35
|
+
<<~RUBY
|
36
|
+
json = super
|
37
|
+
RUBY
|
38
|
+
end
|
39
|
+
|
40
|
+
def compile_json_attributes
|
41
|
+
attributes.filter_map do |attribute|
|
42
|
+
compile_json_attribute(attribute)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def compile_json_attribute(attribute)
|
47
|
+
return unless attribute.as_json?
|
48
|
+
|
49
|
+
name = attribute.name
|
50
|
+
|
51
|
+
if attribute.struct
|
52
|
+
return compile_json_struct(name) unless attribute.array_struct
|
53
|
+
|
54
|
+
return compile_json_array_struct(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
return compile_json_true(name) if attribute.as_json == true
|
58
|
+
|
59
|
+
return compile_json_sym(name, attribute.as_json) if attribute.as_json.is_a?(Symbol)
|
60
|
+
|
61
|
+
compile_json_proc(name)
|
62
|
+
end
|
63
|
+
|
64
|
+
def compile_json_struct(name)
|
65
|
+
<<~RUBY
|
66
|
+
json[:#{name}] = self.#{name}.nil? ? nil : self.#{name}.as_json
|
67
|
+
RUBY
|
68
|
+
end
|
69
|
+
|
70
|
+
def compile_json_array_struct(name)
|
71
|
+
<<~RUBY
|
72
|
+
json[:#{name}] = self.#{name}.nil? ? nil : self.#{name}.map(&:as_json)
|
73
|
+
RUBY
|
74
|
+
end
|
75
|
+
|
76
|
+
def compile_json_true(name)
|
77
|
+
<<~RUBY
|
78
|
+
json[:#{name}] = self.#{name}
|
79
|
+
RUBY
|
80
|
+
end
|
81
|
+
|
82
|
+
def compile_json_sym(name, sym)
|
83
|
+
<<~RUBY
|
84
|
+
json[:#{name}] = self.#{name}.#{sym}
|
85
|
+
RUBY
|
86
|
+
end
|
87
|
+
|
88
|
+
def compile_json_proc(name)
|
89
|
+
<<~RUBY
|
90
|
+
_structure_schema.#{name}.as_json.call(json, self.#{name}, :#{name})
|
91
|
+
RUBY
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
module Compilers
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
7
|
+
class Attribute < Base
|
8
|
+
def initialize(mod, attribute)
|
9
|
+
super(mod)
|
10
|
+
|
11
|
+
@attribute = attribute
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :attribute
|
15
|
+
|
16
|
+
def compile
|
17
|
+
compile_getter
|
18
|
+
compile_predicate
|
19
|
+
compile_setter
|
20
|
+
compile_aliases
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def compile_getter
|
26
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
27
|
+
#{vis(:get)}def #{attribute.name}
|
28
|
+
return #{compile_get}
|
29
|
+
end
|
30
|
+
RUBY
|
31
|
+
end
|
32
|
+
|
33
|
+
def compile_get
|
34
|
+
case attribute.get
|
35
|
+
when Symbol
|
36
|
+
<<~RUBY.strip
|
37
|
+
@#{attribute.name}.#{attribute.get}
|
38
|
+
RUBY
|
39
|
+
when Proc
|
40
|
+
<<~RUBY.strip
|
41
|
+
#{lookup(:get)}.call(@#{attribute.name})
|
42
|
+
RUBY
|
43
|
+
else
|
44
|
+
<<~RUBY.strip
|
45
|
+
@#{attribute.name}
|
46
|
+
RUBY
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def compile_predicate
|
51
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
52
|
+
#{vis(:get)}def #{attribute.name}?; @#{attribute.name}.present? end
|
53
|
+
RUBY
|
54
|
+
end
|
55
|
+
|
56
|
+
def compile_setter
|
57
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
58
|
+
#{vis(:set)}def #{attribute.name}=(value)
|
59
|
+
#{compile_set}
|
60
|
+
#{compile_struct}
|
61
|
+
#{compile_array_struct}
|
62
|
+
#{compile_type}
|
63
|
+
@#{attribute.name} = value
|
64
|
+
end
|
65
|
+
RUBY
|
66
|
+
end
|
67
|
+
|
68
|
+
def compile_set
|
69
|
+
case attribute.set
|
70
|
+
when Symbol
|
71
|
+
<<~RUBY.strip
|
72
|
+
value = value.#{lookup(:set)}
|
73
|
+
RUBY
|
74
|
+
when Proc
|
75
|
+
<<~RUBY.strip
|
76
|
+
value = #{lookup(:set)}.call(value)
|
77
|
+
RUBY
|
78
|
+
else
|
79
|
+
''
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def compile_struct
|
84
|
+
return unless attribute.struct && !attribute.array_struct
|
85
|
+
|
86
|
+
compile_optional_check(<<~RUBY)
|
87
|
+
unless value.is_a?(#{lookup(:struct)})
|
88
|
+
unless value.is_a?(Hash)
|
89
|
+
binding.pry
|
90
|
+
raise AttributeError.new(#{lookup}, "no implicit conversion of \#{ value.class } into Hash")
|
91
|
+
end
|
92
|
+
|
93
|
+
value = #{lookup(:struct)}.new(**value)
|
94
|
+
end
|
95
|
+
RUBY
|
96
|
+
end
|
97
|
+
|
98
|
+
def compile_array_struct
|
99
|
+
return unless attribute.struct && attribute.array_struct
|
100
|
+
|
101
|
+
compile_optional_check(<<~RUBY)
|
102
|
+
unless value.is_a?(Array)
|
103
|
+
raise AttributeError.new(#{lookup}, "no implicit conversion of \#{ value.class } into Array")
|
104
|
+
end
|
105
|
+
|
106
|
+
struct = #{lookup(:struct)}
|
107
|
+
|
108
|
+
value = value.map do |val|
|
109
|
+
next val if val.is_a?(struct)
|
110
|
+
|
111
|
+
unless val.is_a?(Hash)
|
112
|
+
raise AttributeError.new(#{lookup}, "no implicit conversion of \#{ val.class } into Hash")
|
113
|
+
end
|
114
|
+
|
115
|
+
struct.new(**val)
|
116
|
+
end
|
117
|
+
RUBY
|
118
|
+
end
|
119
|
+
|
120
|
+
def compile_optional_check(code)
|
121
|
+
return code unless attribute.optional? && attribute.default.nil?
|
122
|
+
|
123
|
+
<<~RUBY
|
124
|
+
unless value.nil?
|
125
|
+
#{code}
|
126
|
+
end
|
127
|
+
RUBY
|
128
|
+
end
|
129
|
+
|
130
|
+
def compile_type
|
131
|
+
return unless attribute.type
|
132
|
+
|
133
|
+
<<~RUBY
|
134
|
+
begin
|
135
|
+
#{lookup(:type)}.is!(value)
|
136
|
+
rescue Type::Error => error
|
137
|
+
raise AttributeTypeError.new(#{lookup}, error)
|
138
|
+
end
|
139
|
+
RUBY
|
140
|
+
end
|
141
|
+
|
142
|
+
def compile_aliases
|
143
|
+
attribute.aliases.each do |alia|
|
144
|
+
compile_alias_get(alia)
|
145
|
+
compile_alias_predicate(alia)
|
146
|
+
compile_alias_set(alia)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def compile_alias_get(alia)
|
151
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
152
|
+
#{vis(:get)}def #{alia}
|
153
|
+
self.#{attribute.name}
|
154
|
+
end
|
155
|
+
RUBY
|
156
|
+
end
|
157
|
+
|
158
|
+
def compile_alias_predicate(alia)
|
159
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
160
|
+
#{vis(:get)}def #{alia}?
|
161
|
+
self.#{attribute.name}?
|
162
|
+
end
|
163
|
+
RUBY
|
164
|
+
end
|
165
|
+
|
166
|
+
def compile_alias_set(alia)
|
167
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
168
|
+
#{vis(:get)}def #{alia}=(value)
|
169
|
+
self.#{attribute.name} = value
|
170
|
+
end
|
171
|
+
RUBY
|
172
|
+
end
|
173
|
+
|
174
|
+
def vis(met)
|
175
|
+
if (met == :get && attribute.private_get) ||
|
176
|
+
(met == :set && attribute.private_set)
|
177
|
+
'private '
|
178
|
+
else
|
179
|
+
''
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def lookup(att = nil)
|
184
|
+
"_structure_schema.#{attribute.name}#{att ? ".#{att}" : ''}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
# rubocop:enable Metrics/ClassLength
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
module Compilers
|
6
|
+
class Base
|
7
|
+
def self.compile(...)
|
8
|
+
new(...).compile
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(mod)
|
12
|
+
@mod = mod
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :mod
|
16
|
+
|
17
|
+
def compile_method(str, file, line)
|
18
|
+
mod.class_eval(str.gsub(/^$\s*\n/, '').gsub(/\s+$/, ''), file, line)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Uinit
|
4
|
+
module Structure
|
5
|
+
module Compilers
|
6
|
+
class Constructor < Base
|
7
|
+
def initialize(mod, schema)
|
8
|
+
super(mod)
|
9
|
+
|
10
|
+
@schema = schema
|
11
|
+
@attributes = schema.class.instance_methods(false).map { schema.send(_1) }
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :schema, :attributes
|
15
|
+
|
16
|
+
def compile
|
17
|
+
compile_method(<<~RUBY, __FILE__, __LINE__ + 1)
|
18
|
+
def initialize_structure!(hsh)
|
19
|
+
#{compile_super}
|
20
|
+
#{compile_attribute_initializers * "\n"}
|
21
|
+
end
|
22
|
+
RUBY
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def compile_super
|
28
|
+
return unless schema.class.ancestors[1..].any? do |ancestor|
|
29
|
+
ancestor < Schema
|
30
|
+
end
|
31
|
+
|
32
|
+
<<~RUBY
|
33
|
+
begin
|
34
|
+
super(hsh)
|
35
|
+
end
|
36
|
+
RUBY
|
37
|
+
end
|
38
|
+
|
39
|
+
def compile_attribute_initializers
|
40
|
+
attributes.filter_map do |attribute|
|
41
|
+
compile_attribute_initializer(attribute)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def compile_attribute_initializer(attribute)
|
46
|
+
name = attribute.name
|
47
|
+
|
48
|
+
unless attribute.init
|
49
|
+
return unless attribute.optional?
|
50
|
+
|
51
|
+
return compile_default_attribute(name, attribute)
|
52
|
+
end
|
53
|
+
|
54
|
+
return compile_non_optional_attribute(name) unless attribute.optional?
|
55
|
+
|
56
|
+
compile_optional_attribute(name, attribute)
|
57
|
+
end
|
58
|
+
|
59
|
+
def compile_non_optional_attribute(name)
|
60
|
+
<<~RUBY
|
61
|
+
raise ArgumentError, "'#{name}' must be defined" unless hsh.key?(:#{name})
|
62
|
+
self.#{name} = hsh[:#{name}]
|
63
|
+
RUBY
|
64
|
+
end
|
65
|
+
|
66
|
+
def compile_optional_attribute(name, attribute)
|
67
|
+
<<~RUBY
|
68
|
+
if hsh.key?(:#{name})
|
69
|
+
self.#{name} = hsh[:#{name}]
|
70
|
+
else
|
71
|
+
#{compile_default_attribute(name, attribute)}
|
72
|
+
end
|
73
|
+
RUBY
|
74
|
+
end
|
75
|
+
|
76
|
+
def compile_default_attribute(name, attribute)
|
77
|
+
<<~RUBY
|
78
|
+
self.#{name} = _structure_schema.#{name}.default#{attribute.default.is_a?(Proc) ? '.call' : ''}
|
79
|
+
RUBY
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'zeitwerk'
|
4
|
+
|
5
|
+
require 'uinit/type'
|
6
|
+
require 'uinit/memoizable'
|
7
|
+
|
8
|
+
module Uinit; end
|
9
|
+
|
10
|
+
Zeitwerk::Loader.for_gem.tap do |loader|
|
11
|
+
loader.push_dir(__dir__, namespace: Uinit)
|
12
|
+
loader.setup
|
13
|
+
end
|
14
|
+
|
15
|
+
module Uinit
|
16
|
+
module Structure
|
17
|
+
include Memoizable
|
18
|
+
|
19
|
+
def self.included(base)
|
20
|
+
base.extend(ClassMethods)
|
21
|
+
end
|
22
|
+
|
23
|
+
class Schema; end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
include Memoizable
|
27
|
+
|
28
|
+
memo def structure_schema
|
29
|
+
if respond_to?(:superclass) && superclass.respond_to?(:structure_schema)
|
30
|
+
return Class.new(superclass.structure_schema.class).new
|
31
|
+
end
|
32
|
+
|
33
|
+
Class.new(Schema).new
|
34
|
+
end
|
35
|
+
|
36
|
+
memo def structure_module
|
37
|
+
structure_module = Module.new
|
38
|
+
|
39
|
+
include structure_module
|
40
|
+
|
41
|
+
structure_module
|
42
|
+
end
|
43
|
+
|
44
|
+
def struct(&)
|
45
|
+
AttributeContext.scope(&).each do |attribute|
|
46
|
+
raise NameError, 'Attribute must have a name' unless attribute.name
|
47
|
+
|
48
|
+
structure_schema.class.define_method(attribute.name) { attribute }
|
49
|
+
Compilers::Attribute.compile(structure_module, attribute)
|
50
|
+
end
|
51
|
+
|
52
|
+
Compilers::Constructor.compile(structure_module, structure_schema)
|
53
|
+
Compilers::AsJson.compile(structure_module, structure_schema)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
memo def _structure_schema = self.class.structure_schema
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'uinit/structure/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = 'uinit-structure'
|
10
|
+
spec.version = Uinit::Structure::VERSION
|
11
|
+
spec.authors = ['Kimoja']
|
12
|
+
spec.email = ['joakim.carrilho@cheerz.com']
|
13
|
+
|
14
|
+
spec.summary = 'Typed structure'
|
15
|
+
spec.description = 'Typed structure'
|
16
|
+
spec.homepage = 'https://github.com/Kimoja/uinit-structure'
|
17
|
+
spec.license = 'MIT'
|
18
|
+
spec.required_ruby_version = '>= 3.2.1'
|
19
|
+
spec.files = Dir['CHANGELOG.md', 'LICENSE.txt', 'README.md', 'uinit-structure.gemspec', 'lib/**/*']
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
spec.executables = []
|
22
|
+
|
23
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
24
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
25
|
+
spec.metadata['source_code_uri'] = 'https://github.com/Kimoja/uinit-structure'
|
26
|
+
spec.metadata['changelog_uri'] = 'https://github.com/Kimoja/uinit-structure/blob/main/CHANGELOG.md'
|
27
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/Kimoja/uinit-structure/issues'
|
28
|
+
|
29
|
+
spec.add_runtime_dependency 'zeitwerk', '~> 2.6'
|
30
|
+
|
31
|
+
spec.add_dependency 'uinit-memoizable', '~> 0.1.0'
|
32
|
+
spec.add_dependency 'uinit-type', '~> 0.1.0'
|
33
|
+
|
34
|
+
spec.add_development_dependency 'bundler'
|
35
|
+
spec.add_development_dependency 'rspec'
|
36
|
+
spec.add_development_dependency 'rubocop'
|
37
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uinit-structure
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kimoja
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: zeitwerk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: uinit-memoizable
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.1.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.1.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: uinit-type
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.1.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.1.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: Typed structure
|
98
|
+
email:
|
99
|
+
- joakim.carrilho@cheerz.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- lib/uinit/struct.rb
|
107
|
+
- lib/uinit/structure.rb
|
108
|
+
- lib/uinit/structure/attribute.rb
|
109
|
+
- lib/uinit/structure/attribute_builder.rb
|
110
|
+
- lib/uinit/structure/attribute_context.rb
|
111
|
+
- lib/uinit/structure/attribute_error.rb
|
112
|
+
- lib/uinit/structure/attribute_type_error.rb
|
113
|
+
- lib/uinit/structure/compilers.rb
|
114
|
+
- lib/uinit/structure/compilers/as_json.rb
|
115
|
+
- lib/uinit/structure/compilers/attribute.rb
|
116
|
+
- lib/uinit/structure/compilers/base.rb
|
117
|
+
- lib/uinit/structure/compilers/constructor.rb
|
118
|
+
- lib/uinit/structure/version.rb
|
119
|
+
- uinit-structure.gemspec
|
120
|
+
homepage: https://github.com/Kimoja/uinit-structure
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata:
|
124
|
+
homepage_uri: https://github.com/Kimoja/uinit-structure
|
125
|
+
allowed_push_host: https://rubygems.org
|
126
|
+
source_code_uri: https://github.com/Kimoja/uinit-structure
|
127
|
+
changelog_uri: https://github.com/Kimoja/uinit-structure/blob/main/CHANGELOG.md
|
128
|
+
bug_tracker_uri: https://github.com/Kimoja/uinit-structure/issues
|
129
|
+
rubygems_mfa_required: 'true'
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: 3.2.1
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubygems_version: 3.4.6
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: Typed structure
|
149
|
+
test_files: []
|