structure 4.3.0 → 4.4.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/lib/structure/builder.rb +1 -1
- data/lib/structure/rbs.rb +29 -2
- data/lib/structure/version.rb +1 -1
- data/lib/tapioca/dsl/compilers/structure.rb +200 -0
- data/sig/structure/rbs.rbs +3 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 70c0f08018c0b2adccf83a0fd310719e21e79e2f0945d753fef9df5a08226537
|
|
4
|
+
data.tar.gz: e62bbdc33e63db5845d816ba84bbde60433e4137d95a7466dbbcd1376891eccf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4acad52488ad58e454cf052b14727aa70f75bfb921ee5ffb92310287194b2be4019caa98c5afd995df64a9a6356eafde4f6ffcb33680dc504355a79c575ba801
|
|
7
|
+
data.tar.gz: 9ed2e262bdf3488c5b074b9b1e8148049908883a92deaec90cd969715ff34879231017bbe7061b0a6d3ac9c812dc999497884f383754987e7bee9a64661e0027
|
data/lib/structure/builder.rb
CHANGED
|
@@ -68,7 +68,7 @@ module Structure
|
|
|
68
68
|
# @example Optional with default
|
|
69
69
|
# attribute? :status, String, default: "pending"
|
|
70
70
|
#
|
|
71
|
-
# @example Optional but non-nullable when present
|
|
71
|
+
# @example Optional but non-nullable when present (e.g. GraphQL String!)
|
|
72
72
|
# attribute? :name, String, null: false
|
|
73
73
|
def attribute?(name, type = nil, from: nil, default: nil, null: true, &block)
|
|
74
74
|
attribute(name, type, from: from, default: default, null: null, &block)
|
data/lib/structure/rbs.rb
CHANGED
|
@@ -28,6 +28,7 @@ module Structure
|
|
|
28
28
|
types = meta.fetch(:types, default_types)
|
|
29
29
|
# @type var required: Array[Symbol]
|
|
30
30
|
required = meta.fetch(:required, attributes)
|
|
31
|
+
non_nullable = meta.fetch(:non_nullable, [])
|
|
31
32
|
custom_methods = meta.fetch(:custom_methods, EMPTY_CUSTOM_METHODS)
|
|
32
33
|
|
|
33
34
|
emit_rbs_content(
|
|
@@ -35,6 +36,7 @@ module Structure
|
|
|
35
36
|
attributes:,
|
|
36
37
|
types:,
|
|
37
38
|
required:,
|
|
39
|
+
non_nullable:,
|
|
38
40
|
has_structure_modules: meta.any?,
|
|
39
41
|
custom_methods:,
|
|
40
42
|
)
|
|
@@ -59,9 +61,33 @@ module Structure
|
|
|
59
61
|
file_path
|
|
60
62
|
end
|
|
61
63
|
|
|
64
|
+
# Write RBS files for multiple classes or all Structure classes in a module
|
|
65
|
+
#
|
|
66
|
+
# @param classes [Array<Class>, Module] Classes to generate RBS for, or a module to scan
|
|
67
|
+
# @param dir [String] Output directory (default: "sig")
|
|
68
|
+
# @return [Array<String>] Paths of written RBS files
|
|
69
|
+
#
|
|
70
|
+
# @example With array of classes
|
|
71
|
+
# Structure::RBS.write_all([Person, Address, Order])
|
|
72
|
+
#
|
|
73
|
+
# @example With module namespace
|
|
74
|
+
# Structure::RBS.write_all(Peddler::Models)
|
|
75
|
+
def write_all(classes, dir: "sig")
|
|
76
|
+
classes = structure_classes_in(classes) if classes.is_a?(Module)
|
|
77
|
+
|
|
78
|
+
classes.filter_map { |klass| write(klass, dir: dir) }
|
|
79
|
+
end
|
|
80
|
+
|
|
62
81
|
private
|
|
63
82
|
|
|
64
|
-
def
|
|
83
|
+
def structure_classes_in(mod)
|
|
84
|
+
mod.constants.filter_map do |const_name|
|
|
85
|
+
const = mod.const_get(const_name)
|
|
86
|
+
const if const.is_a?(Class) && const < Data && const.respond_to?(:__structure_meta__)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def emit_rbs_content(class_name:, attributes:, types:, required:, non_nullable:, has_structure_modules:, custom_methods:)
|
|
65
91
|
# @type var lines: Array[String]
|
|
66
92
|
lines = []
|
|
67
93
|
lines << "class #{class_name} < Data"
|
|
@@ -71,8 +97,9 @@ module Structure
|
|
|
71
97
|
rbs_types = attributes.map do |attr|
|
|
72
98
|
type = types.fetch(attr, nil)
|
|
73
99
|
rbs_type = map_type_to_rbs(type, class_name)
|
|
100
|
+
nullable = !(required.include?(attr) && non_nullable.include?(attr))
|
|
74
101
|
|
|
75
|
-
[attr, rbs_type != "untyped" ? "#{rbs_type}?" : rbs_type]
|
|
102
|
+
[attr, rbs_type != "untyped" && nullable ? "#{rbs_type}?" : rbs_type]
|
|
76
103
|
end.to_h
|
|
77
104
|
|
|
78
105
|
# Sort keyword params: required first, then optional (with ? prefix)
|
data/lib/structure/version.rb
CHANGED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
return unless defined?(Tapioca::Dsl::Compiler)
|
|
5
|
+
|
|
6
|
+
module Tapioca
|
|
7
|
+
module Dsl
|
|
8
|
+
module Compilers
|
|
9
|
+
# Generates RBI files for Structure-based Data classes.
|
|
10
|
+
#
|
|
11
|
+
# For example, given:
|
|
12
|
+
#
|
|
13
|
+
# Person = Structure.new do
|
|
14
|
+
# attribute :name, String
|
|
15
|
+
# attribute? :age, Integer
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# This compiler will generate:
|
|
19
|
+
#
|
|
20
|
+
# class Person < Data
|
|
21
|
+
# sig { params(name: String, age: T.nilable(Integer)).returns(Person) }
|
|
22
|
+
# sig { params(name: String, age: T.nilable(Integer)).void }
|
|
23
|
+
# def initialize(name:, age: nil); end
|
|
24
|
+
#
|
|
25
|
+
# sig { returns(String) }
|
|
26
|
+
# def name; end
|
|
27
|
+
#
|
|
28
|
+
# sig { returns(T.nilable(Integer)) }
|
|
29
|
+
# def age; end
|
|
30
|
+
#
|
|
31
|
+
# sig { params(data: T::Hash[T.any(String, Symbol), T.untyped]).returns(Person) }
|
|
32
|
+
# def self.parse(data = {}); end
|
|
33
|
+
# end
|
|
34
|
+
#: [ConstantType = singleton(::Data)]
|
|
35
|
+
class Structure < Compiler
|
|
36
|
+
extend T::Sig
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
extend T::Sig
|
|
40
|
+
|
|
41
|
+
sig { override.returns(T::Enumerable[Module]) }
|
|
42
|
+
def gather_constants
|
|
43
|
+
all_classes.select do |klass|
|
|
44
|
+
klass < ::Data && klass.respond_to?(:__structure_meta__)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig { override.void }
|
|
50
|
+
def decorate
|
|
51
|
+
meta = constant.__structure_meta__
|
|
52
|
+
return unless meta
|
|
53
|
+
|
|
54
|
+
attributes = meta[:mappings]&.keys || constant.members
|
|
55
|
+
types = meta.fetch(:types, {})
|
|
56
|
+
required = meta.fetch(:required, attributes)
|
|
57
|
+
non_nullable = meta.fetch(:non_nullable, [])
|
|
58
|
+
|
|
59
|
+
root.create_path(constant) do |klass|
|
|
60
|
+
generate_new_and_brackets(klass, attributes, types, required, non_nullable)
|
|
61
|
+
generate_parse(klass)
|
|
62
|
+
generate_load_dump(klass)
|
|
63
|
+
generate_members(klass, attributes)
|
|
64
|
+
generate_attr_readers(klass, attributes, types, non_nullable, required)
|
|
65
|
+
generate_to_h(klass, attributes, types, non_nullable, required)
|
|
66
|
+
generate_boolean_predicates(klass, types)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
sig { params(klass: RBI::Scope, attributes: T::Array[Symbol], types: T::Hash[Symbol, T.untyped], required: T::Array[Symbol], non_nullable: T::Array[Symbol]).void }
|
|
73
|
+
def generate_new_and_brackets(klass, attributes, types, required, non_nullable)
|
|
74
|
+
params = attributes.map do |attr|
|
|
75
|
+
type = map_type_to_sorbet(types[attr], nullable: !(required.include?(attr) && non_nullable.include?(attr)))
|
|
76
|
+
is_required = required.include?(attr)
|
|
77
|
+
if is_required
|
|
78
|
+
create_kw_param(attr.to_s, type: type)
|
|
79
|
+
else
|
|
80
|
+
create_kw_opt_param(attr.to_s, type: type, default: "nil")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
klass.create_method("initialize", parameters: params, return_type: "void")
|
|
85
|
+
klass.create_method("new", parameters: params, return_type: constant.name.to_s, class_method: true)
|
|
86
|
+
klass.create_method("[]", parameters: params, return_type: constant.name.to_s, class_method: true)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
sig { params(klass: RBI::Scope).void }
|
|
90
|
+
def generate_parse(klass)
|
|
91
|
+
klass.create_method(
|
|
92
|
+
"parse",
|
|
93
|
+
parameters: [
|
|
94
|
+
create_opt_param("data", type: "T::Hash[T.any(String, Symbol), T.untyped]", default: "{}"),
|
|
95
|
+
create_opt_param("overrides", type: "T.nilable(T::Hash[Symbol, T.untyped])", default: "nil"),
|
|
96
|
+
],
|
|
97
|
+
return_type: constant.name.to_s,
|
|
98
|
+
class_method: true,
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
sig { params(klass: RBI::Scope).void }
|
|
103
|
+
def generate_load_dump(klass)
|
|
104
|
+
klass.create_method(
|
|
105
|
+
"load",
|
|
106
|
+
parameters: [create_param("data", type: "T.nilable(T::Hash[T.any(String, Symbol), T.untyped])")],
|
|
107
|
+
return_type: "T.nilable(#{constant.name})",
|
|
108
|
+
class_method: true,
|
|
109
|
+
)
|
|
110
|
+
klass.create_method(
|
|
111
|
+
"dump",
|
|
112
|
+
parameters: [create_param("value", type: "T.nilable(#{constant.name})")],
|
|
113
|
+
return_type: "T.nilable(T::Hash[Symbol, T.untyped])",
|
|
114
|
+
class_method: true,
|
|
115
|
+
)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
sig { params(klass: RBI::Scope, attributes: T::Array[Symbol]).void }
|
|
119
|
+
def generate_members(klass, attributes)
|
|
120
|
+
members_type = "[#{attributes.map { |a| ":#{a}" }.join(", ")}]"
|
|
121
|
+
klass.create_method("members", return_type: members_type, class_method: true)
|
|
122
|
+
klass.create_method("members", return_type: members_type)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
sig { params(klass: RBI::Scope, attributes: T::Array[Symbol], types: T::Hash[Symbol, T.untyped], non_nullable: T::Array[Symbol], required: T::Array[Symbol]).void }
|
|
126
|
+
def generate_attr_readers(klass, attributes, types, non_nullable, required)
|
|
127
|
+
attributes.each do |attr|
|
|
128
|
+
type = map_type_to_sorbet(types[attr], nullable: !(required.include?(attr) && non_nullable.include?(attr)))
|
|
129
|
+
klass.create_method(attr.to_s, return_type: type)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
sig { params(klass: RBI::Scope, attributes: T::Array[Symbol], types: T::Hash[Symbol, T.untyped], non_nullable: T::Array[Symbol], required: T::Array[Symbol]).void }
|
|
134
|
+
def generate_to_h(klass, attributes, types, non_nullable, required)
|
|
135
|
+
hash_pairs = attributes.map do |attr|
|
|
136
|
+
type = map_type_to_sorbet(types[attr], nullable: !(required.include?(attr) && non_nullable.include?(attr)))
|
|
137
|
+
"#{attr}: #{type}"
|
|
138
|
+
end.join(", ")
|
|
139
|
+
|
|
140
|
+
klass.create_method("to_h", return_type: "{ #{hash_pairs} }")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
sig { params(klass: RBI::Scope, types: T::Hash[Symbol, T.untyped]).void }
|
|
144
|
+
def generate_boolean_predicates(klass, types)
|
|
145
|
+
types.each do |attr, type|
|
|
146
|
+
next unless type == :boolean
|
|
147
|
+
next if attr.to_s.end_with?("?")
|
|
148
|
+
|
|
149
|
+
klass.create_method("#{attr}?", return_type: "T::Boolean")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
sig { params(type: T.untyped, nullable: T::Boolean).returns(String) }
|
|
154
|
+
def map_type_to_sorbet(type, nullable: true)
|
|
155
|
+
raw = case type
|
|
156
|
+
when Class
|
|
157
|
+
if type == Array
|
|
158
|
+
"T::Array[T.untyped]"
|
|
159
|
+
elsif type == Hash
|
|
160
|
+
"T::Hash[T.untyped, T.untyped]"
|
|
161
|
+
else
|
|
162
|
+
type.name || "T.untyped"
|
|
163
|
+
end
|
|
164
|
+
when :boolean
|
|
165
|
+
"T::Boolean"
|
|
166
|
+
when :self
|
|
167
|
+
constant.name.to_s
|
|
168
|
+
when Array
|
|
169
|
+
if type.size == 1
|
|
170
|
+
element_type = map_type_to_sorbet_element(type.first)
|
|
171
|
+
"T::Array[#{element_type}]"
|
|
172
|
+
else
|
|
173
|
+
"T.untyped"
|
|
174
|
+
end
|
|
175
|
+
when Proc
|
|
176
|
+
"T.untyped"
|
|
177
|
+
else
|
|
178
|
+
"T.untyped"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
nullable ? "T.nilable(#{raw})" : raw
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
sig { params(type: T.untyped).returns(String) }
|
|
185
|
+
def map_type_to_sorbet_element(type)
|
|
186
|
+
case type
|
|
187
|
+
when Class
|
|
188
|
+
type.name || "T.untyped"
|
|
189
|
+
when :boolean
|
|
190
|
+
"T::Boolean"
|
|
191
|
+
when :self
|
|
192
|
+
constant.name.to_s
|
|
193
|
+
else
|
|
194
|
+
"T.untyped"
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
data/sig/structure/rbs.rbs
CHANGED
|
@@ -4,8 +4,10 @@ module Structure
|
|
|
4
4
|
|
|
5
5
|
def self.emit: (untyped klass) -> String?
|
|
6
6
|
def self.write: (untyped klass, ?dir: String) -> String?
|
|
7
|
+
def self.write_all: (Array[untyped] | Module classes, ?dir: String) -> Array[String]
|
|
7
8
|
|
|
8
|
-
private def self.
|
|
9
|
+
private def self.structure_classes_in: (Module mod) -> Array[untyped]
|
|
10
|
+
private def self.emit_rbs_content: (class_name: String, attributes: Array[Symbol], types: Hash[Symbol, untyped], required: Array[Symbol], non_nullable: Array[Symbol], has_structure_modules: bool, custom_methods: untyped) -> String
|
|
9
11
|
private def self.parse_data_type: (untyped type, String class_name) -> String
|
|
10
12
|
private def self.map_type_to_rbs: (untyped type, String class_name) -> String
|
|
11
13
|
private def self.format_method_signature: (Hash[Symbol, untyped] method_meta, singleton: bool) -> String
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: structure
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hakan Ensari
|
|
@@ -20,6 +20,7 @@ files:
|
|
|
20
20
|
- lib/structure/rbs.rb
|
|
21
21
|
- lib/structure/types.rb
|
|
22
22
|
- lib/structure/version.rb
|
|
23
|
+
- lib/tapioca/dsl/compilers/structure.rb
|
|
23
24
|
- sig/structure.rbs
|
|
24
25
|
- sig/structure/builder.rbs
|
|
25
26
|
- sig/structure/rbs.rbs
|
|
@@ -43,7 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
43
44
|
- !ruby/object:Gem::Version
|
|
44
45
|
version: '0'
|
|
45
46
|
requirements: []
|
|
46
|
-
rubygems_version:
|
|
47
|
+
rubygems_version: 4.0.3
|
|
47
48
|
specification_version: 4
|
|
48
49
|
summary: Structure your data
|
|
49
50
|
test_files: []
|