uinit-structure 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,3 @@
1
+ # Uinit::Structure
2
+
3
+ Typed structure
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uinit
4
+ class Struct
5
+ include Structure
6
+
7
+ struct {} # rubocop:disable Lint/EmptyBlock: Empty block detected
8
+
9
+ def initialize(**hsh)
10
+ initialize_structure!(hsh)
11
+ end
12
+ end
13
+ end
@@ -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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uinit
4
+ module Structure
5
+ module Compilers
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Uinit
4
+ module Structure
5
+ VERSION = '0.1.0'
6
+ end
7
+ 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: []