structure 3.3.0 → 3.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 +9 -6
- data/lib/structure/rbs.rb +111 -0
- data/lib/structure/types.rb +0 -4
- data/lib/structure/version.rb +1 -1
- data/lib/structure.rb +56 -54
- data/sig/structure/builder.rbs +17 -0
- data/sig/structure/rbs.rbs +11 -0
- data/sig/structure/types.rbs +16 -0
- data/sig/structure.rbs +21 -0
- metadata +6 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a7bd562939e000315d1904c5f766c0293c570413aa69930e522a01e70849715
|
4
|
+
data.tar.gz: '0649cee7d47286ad9ba6e36721cc350f20a9ba40c971214e0edb91bde8bec7d9'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d09a3983e553ccbf3f031c2bb2f920b430032ee5d778b1572f9423e2593808e458662f77615fc84a002136b0896a7bfb5ceff0119d880df1fc833f4123dd493
|
7
|
+
data.tar.gz: ec6da8a717b8ccab314c808bb9ab3218d733790378f3b61dbdf52dffbf4b28fd7fd7d6ae7b484d390809a8396fccd894b0d087a3d1a90926149bc55db5a98848
|
data/lib/structure/builder.rb
CHANGED
@@ -42,7 +42,7 @@ module Structure
|
|
42
42
|
elsif block
|
43
43
|
@types[name] = block
|
44
44
|
elsif type
|
45
|
-
@types[name] =
|
45
|
+
@types[name] = type
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -63,13 +63,16 @@ module Structure
|
|
63
63
|
@mappings.keys
|
64
64
|
end
|
65
65
|
|
66
|
+
def coercions
|
67
|
+
@types.transform_values { |type| Types.coerce(type) }
|
68
|
+
end
|
69
|
+
|
66
70
|
def predicate_methods
|
67
|
-
@types.filter_map do |name,
|
68
|
-
if
|
69
|
-
|
70
|
-
[predicate_name.to_sym, name]
|
71
|
+
@types.filter_map do |name, type|
|
72
|
+
if type == :boolean
|
73
|
+
["#{name}?".to_sym, name] unless name.to_s.end_with?("?")
|
71
74
|
end
|
72
|
-
end.to_h
|
75
|
+
end.compact.to_h
|
73
76
|
end
|
74
77
|
end
|
75
78
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "fileutils"
|
4
|
+
require "pathname"
|
5
|
+
|
6
|
+
module Structure
|
7
|
+
module RBS
|
8
|
+
class << self
|
9
|
+
def emit(klass)
|
10
|
+
return unless klass < Data
|
11
|
+
|
12
|
+
class_name = klass.name
|
13
|
+
return unless class_name
|
14
|
+
|
15
|
+
meta = klass.respond_to?(:__structure_meta__) ? klass.__structure_meta__ : {}
|
16
|
+
|
17
|
+
emit_rbs_content(
|
18
|
+
class_name: class_name,
|
19
|
+
attributes: meta.fetch(:attributes, klass.members),
|
20
|
+
types: meta.fetch(:types, {}),
|
21
|
+
has_structure_modules: meta.any?,
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(klass, dir: "sig")
|
26
|
+
rbs_content = emit(klass)
|
27
|
+
return unless rbs_content
|
28
|
+
|
29
|
+
# User::Address -> user/address.rbs
|
30
|
+
path_segments = klass.name.split("::").map(&:downcase)
|
31
|
+
filename = "#{path_segments.pop}.rbs"
|
32
|
+
|
33
|
+
# full path
|
34
|
+
dir_path = Pathname.new(dir)
|
35
|
+
dir_path = dir_path.join(*path_segments) unless path_segments.empty?
|
36
|
+
FileUtils.mkdir_p(dir_path)
|
37
|
+
|
38
|
+
file_path = dir_path.join(filename)
|
39
|
+
File.write(file_path, rbs_content)
|
40
|
+
|
41
|
+
file_path.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def emit_rbs_content(class_name:, attributes:, types:, has_structure_modules:)
|
47
|
+
lines = []
|
48
|
+
lines << "class #{class_name} < Data"
|
49
|
+
lines << " extend Structure::ClassMethods" if has_structure_modules
|
50
|
+
lines << " include Structure::InstanceMethods"
|
51
|
+
lines << ""
|
52
|
+
|
53
|
+
unless attributes.empty?
|
54
|
+
# map types to rbs
|
55
|
+
rbs_types = attributes.map do |attr|
|
56
|
+
type = types.fetch(attr, nil)
|
57
|
+
rbs_type = map_type_to_rbs(type, class_name)
|
58
|
+
|
59
|
+
[attr, rbs_type != "untyped" ? "#{rbs_type}?" : rbs_type]
|
60
|
+
end.to_h
|
61
|
+
|
62
|
+
keyword_params = attributes.map { |attr| "#{attr}: #{rbs_types[attr]}" }.join(", ")
|
63
|
+
positional_params = attributes.map { |attr| rbs_types[attr] }.join(", ")
|
64
|
+
|
65
|
+
lines << " def self.new: (#{keyword_params}) -> instance"
|
66
|
+
lines << " | (#{positional_params}) -> instance"
|
67
|
+
lines << ""
|
68
|
+
|
69
|
+
attributes.each do |attr|
|
70
|
+
lines << " attr_reader #{attr}: #{rbs_types[attr]}"
|
71
|
+
end
|
72
|
+
lines << ""
|
73
|
+
|
74
|
+
types.each do |attr, type|
|
75
|
+
if type == :boolean && !attr.to_s.end_with?("?")
|
76
|
+
lines << " def #{attr}?: () -> bool"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
hash_type = attributes.map { |attr| "#{attr}: #{rbs_types[attr]}" }.join(", ")
|
81
|
+
lines << " def to_h: () -> { #{hash_type} }"
|
82
|
+
end
|
83
|
+
|
84
|
+
lines << "end"
|
85
|
+
lines.join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
def map_type_to_rbs(type, class_name)
|
89
|
+
case type
|
90
|
+
when Class
|
91
|
+
type.name || "untyped"
|
92
|
+
when :boolean
|
93
|
+
"bool"
|
94
|
+
when :self
|
95
|
+
class_name || "untyped"
|
96
|
+
when Array
|
97
|
+
if type.size == 2 && type.first == :array
|
98
|
+
element_type = map_type_to_rbs(type.last, class_name)
|
99
|
+
"Array[#{element_type}]"
|
100
|
+
elsif type == [:self]
|
101
|
+
"Array[#{class_name || "untyped"}]"
|
102
|
+
else
|
103
|
+
"untyped"
|
104
|
+
end
|
105
|
+
else
|
106
|
+
"untyped"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/lib/structure/types.rb
CHANGED
@@ -10,10 +10,6 @@ module Structure
|
|
10
10
|
BOOLEAN_TRUTHY = [true, 1, "1", "t", "T", "true", "TRUE", "on", "ON"].freeze
|
11
11
|
private_constant :BOOLEAN_TRUTHY
|
12
12
|
|
13
|
-
def boolean?(type)
|
14
|
-
type == boolean
|
15
|
-
end
|
16
|
-
|
17
13
|
# Main factory method for creating type coercers
|
18
14
|
#
|
19
15
|
# @param type [Class, Symbol, Array] Type specification
|
data/lib/structure/version.rb
CHANGED
data/lib/structure.rb
CHANGED
@@ -23,78 +23,80 @@ module Structure
|
|
23
23
|
builder = Builder.new
|
24
24
|
builder.instance_eval(&block) if block
|
25
25
|
|
26
|
-
|
26
|
+
klass = Data.define(*builder.attributes)
|
27
27
|
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
# capture metadata and attach to class
|
29
|
+
meta = {
|
30
|
+
attributes: builder.attributes.freeze,
|
31
|
+
types: builder.types.freeze,
|
32
|
+
defaults: builder.defaults.freeze,
|
33
|
+
}.freeze
|
34
|
+
klass.instance_variable_set(:@__structure_meta__, meta)
|
35
|
+
klass.singleton_class.attr_reader(:__structure_meta__)
|
34
36
|
|
35
|
-
#
|
37
|
+
# capture locals for method generation
|
36
38
|
mappings = builder.mappings
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
after_parse_callback = builder.after_parse_callback
|
39
|
+
coercions = builder.coercions
|
40
|
+
predicates = builder.predicate_methods
|
41
|
+
after = builder.after_parse_callback
|
41
42
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
43
|
+
# Define predicate methods
|
44
|
+
predicates.each do |pred, attr|
|
45
|
+
klass.define_method(pred) { public_send(attr) }
|
46
|
+
end
|
47
|
+
|
48
|
+
# recursive to_h
|
49
|
+
klass.define_method(:to_h) do
|
50
|
+
#: Hash[Symbol, untyped]
|
51
|
+
self.class.members.each_with_object({}) do |m, h|
|
52
|
+
v = public_send(m)
|
53
|
+
h[m] =
|
54
|
+
case v
|
55
|
+
when Array then v.map { |x| x.respond_to?(:to_h) && x ? x.to_h : x }
|
56
|
+
when ->(x) { x.respond_to?(:to_h) && x } then v.to_h
|
57
|
+
else v
|
58
|
+
end
|
57
59
|
end
|
58
|
-
result
|
59
60
|
end
|
60
61
|
|
61
|
-
|
62
|
-
|
63
|
-
# Convert kwargs symbol keys to strings to match source_key lookups
|
62
|
+
# parse accepts JSON-ish hashes + kwargs override
|
63
|
+
klass.define_singleton_method(:parse) do |data = {}, **kwargs|
|
64
64
|
string_kwargs = kwargs.transform_keys(&:to_s)
|
65
65
|
data = data.merge(string_kwargs)
|
66
66
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
value = if data.key?(source_key)
|
71
|
-
data[source_key]
|
72
|
-
elsif data.key?(source_key.to_sym)
|
73
|
-
data[source_key.to_sym]
|
74
|
-
elsif defaults.key?(attr)
|
75
|
-
defaults[attr]
|
76
|
-
end
|
67
|
+
#: Hash[Symbol, untyped]
|
68
|
+
final = {}
|
69
|
+
meta = __structure_meta__
|
77
70
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
else
|
85
|
-
type_or_proc.call(value)
|
71
|
+
meta[:attributes].each do |attr|
|
72
|
+
source = mappings[attr] || attr.to_s
|
73
|
+
value =
|
74
|
+
if data.key?(source) then data[source]
|
75
|
+
elsif data.key?(source.to_sym) then data[source.to_sym]
|
76
|
+
elsif meta[:defaults].key?(attr) then meta[:defaults][attr]
|
86
77
|
end
|
78
|
+
|
79
|
+
coercion = coercions[attr]
|
80
|
+
if coercion && !value.nil?
|
81
|
+
# self-referential types need class context to call parse
|
82
|
+
value =
|
83
|
+
if coercion.is_a?(Proc) && !coercion.lambda?
|
84
|
+
instance_exec(value, &coercion)
|
85
|
+
else
|
86
|
+
coercion.call(value)
|
87
|
+
end
|
87
88
|
end
|
88
89
|
|
89
|
-
|
90
|
+
final[attr] = value
|
90
91
|
end
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
#: untyped
|
94
|
+
obj = new(**final)
|
95
|
+
after&.call(obj)
|
96
|
+
obj
|
95
97
|
end
|
96
98
|
|
97
|
-
|
99
|
+
klass
|
98
100
|
end
|
99
101
|
end
|
100
102
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Structure
|
2
|
+
class Builder
|
3
|
+
def attribute: (Symbol name, untyped type, ?from: String, ?default: untyped) ?{ (untyped) -> untyped } -> void
|
4
|
+
| (Symbol name, ?from: String, ?default: untyped) ?{ (untyped) -> untyped } -> void
|
5
|
+
|
6
|
+
def after_parse: () { (Data) -> void } -> void
|
7
|
+
|
8
|
+
# Methods used by the factory after the block
|
9
|
+
def attributes: () -> Array[Symbol]
|
10
|
+
def mappings: () -> Hash[Symbol, String]
|
11
|
+
def types: () -> Hash[Symbol, untyped]
|
12
|
+
def defaults: () -> Hash[Symbol, untyped]
|
13
|
+
def coercions: () -> Hash[Symbol, Proc]
|
14
|
+
def predicate_methods: () -> Hash[Symbol, Symbol]
|
15
|
+
def after_parse_callback: () -> (Proc | nil)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Structure
|
2
|
+
module RBS
|
3
|
+
def self.emit: (Class klass) -> String?
|
4
|
+
def self.write: (Class klass, ?dir: String) -> String?
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def self.emit_rbs_content: (class_name: String, attributes: Array[Symbol], types: Hash[Symbol, untyped], has_structure_modules: bool) -> String
|
9
|
+
def self.map_type_to_rbs: (untyped type, String class_name) -> String
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Structure
|
2
|
+
module Types
|
3
|
+
BOOLEAN_TRUTHY: Array[untyped]
|
4
|
+
|
5
|
+
def self.coerce: (untyped type) -> Proc
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def self.boolean: () -> Proc
|
10
|
+
def self.self_referential: () -> Proc
|
11
|
+
def self.array: (untyped element_type) -> Proc
|
12
|
+
def self.parseable: (Class type) -> Proc
|
13
|
+
def self.kernel: (Class type) -> Proc
|
14
|
+
def self.parse: (untyped val) -> untyped
|
15
|
+
end
|
16
|
+
end
|
data/sig/structure.rbs
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Structure
|
2
|
+
def self.new: () ?{ () [self: Structure::Builder] -> void } -> singleton(Data)
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def parse: (?(Hash[String | Symbol, untyped] | nil) data, **untyped kwargs) -> instance
|
6
|
+
def members: () -> Array[Symbol]
|
7
|
+
def __structure_meta__: () -> {
|
8
|
+
attributes: Array[Symbol],
|
9
|
+
types: Hash[Symbol, untyped],
|
10
|
+
defaults: Hash[Symbol, untyped],
|
11
|
+
from: Hash[Symbol, String]
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
def deconstruct: () -> Array[untyped]
|
17
|
+
def deconstruct_keys: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
18
|
+
def with: (**untyped) -> instance
|
19
|
+
def to_h: () -> Hash[Symbol, untyped]
|
20
|
+
end
|
21
|
+
end
|
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: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hakan Ensari
|
@@ -17,8 +17,13 @@ extra_rdoc_files: []
|
|
17
17
|
files:
|
18
18
|
- lib/structure.rb
|
19
19
|
- lib/structure/builder.rb
|
20
|
+
- lib/structure/rbs.rb
|
20
21
|
- lib/structure/types.rb
|
21
22
|
- lib/structure/version.rb
|
23
|
+
- sig/structure.rbs
|
24
|
+
- sig/structure/builder.rbs
|
25
|
+
- sig/structure/rbs.rbs
|
26
|
+
- sig/structure/types.rbs
|
22
27
|
licenses:
|
23
28
|
- MIT
|
24
29
|
metadata:
|