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 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: []