literal 1.3.0 → 1.5.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/README.md +1 -1
- data/lib/literal/array.rb +191 -3
- data/lib/literal/data.rb +1 -7
- data/lib/literal/enum.rb +80 -30
- data/lib/literal/failure.rb +7 -0
- data/lib/literal/null.rb +4 -0
- data/lib/literal/property.rb +17 -31
- data/lib/literal/rails/enum_type.rb +5 -11
- data/lib/literal/rails/patches/active_record.rb +4 -3
- data/lib/literal/railtie.rb +8 -14
- data/lib/literal/result.rb +4 -0
- data/lib/literal/success.rb +7 -0
- data/lib/literal/transforms.rb +1 -0
- data/lib/literal/tuple.rb +72 -0
- data/lib/literal/types/constraint_type.rb +7 -2
- data/lib/literal/types.rb +26 -0
- data/lib/literal/version.rb +1 -1
- data/lib/literal.rb +6 -3
- metadata +9 -9
- data/lib/literal/data_property.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e3b0596d3836316a8c9f2dc13b42707da7e4b158cdf17af864c22be88856e76c
|
4
|
+
data.tar.gz: be38909c66845eb4d09187803903f46cbe8d4392d0245d089aa7aec03c0ac7af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfe44962b770aa87464249cd41540e2c26cce61a1b59292fca71b3d79331595483214e22eaa907e3b8453d49a75365219291d196854d843ec09ac1c4ed3b19dd
|
7
|
+
data.tar.gz: 3b6cd6cfeb4fd440c43385462ac3147606389f2afe046eab45cdd3cd9b626dc1fd6e7cdce39c4c5f9b060ea659b99b6ffb7a9ab930a5edb393a11e64f7234557
|
data/README.md
CHANGED
data/lib/literal/array.rb
CHANGED
@@ -23,7 +23,7 @@ class Literal::Array
|
|
23
23
|
def >=(other)
|
24
24
|
case other
|
25
25
|
when Literal::Array::Generic
|
26
|
-
|
26
|
+
Literal.subtype?(other.type, of: @type)
|
27
27
|
else
|
28
28
|
false
|
29
29
|
end
|
@@ -32,6 +32,19 @@ class Literal::Array
|
|
32
32
|
def inspect
|
33
33
|
"Literal::Array(#{@type.inspect})"
|
34
34
|
end
|
35
|
+
|
36
|
+
def coerce(value)
|
37
|
+
case value
|
38
|
+
when self
|
39
|
+
value
|
40
|
+
when Array
|
41
|
+
Literal::Array.new(value, type: @type)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_proc
|
46
|
+
method(:coerce).to_proc
|
47
|
+
end
|
35
48
|
end
|
36
49
|
|
37
50
|
include Enumerable
|
@@ -224,6 +237,10 @@ class Literal::Array
|
|
224
237
|
@__value__.each(...)
|
225
238
|
end
|
226
239
|
|
240
|
+
def each_index(...)
|
241
|
+
@__value__.each_index(...)
|
242
|
+
end
|
243
|
+
|
227
244
|
def empty?
|
228
245
|
@__value__.empty?
|
229
246
|
end
|
@@ -256,6 +273,14 @@ class Literal::Array
|
|
256
273
|
super
|
257
274
|
end
|
258
275
|
|
276
|
+
def hash
|
277
|
+
[self.class, @__value__].hash
|
278
|
+
end
|
279
|
+
|
280
|
+
def include?(...)
|
281
|
+
@__value__.include?(...)
|
282
|
+
end
|
283
|
+
|
259
284
|
alias_method :index, :find_index
|
260
285
|
|
261
286
|
def insert(index, *value)
|
@@ -268,7 +293,7 @@ class Literal::Array
|
|
268
293
|
end
|
269
294
|
|
270
295
|
def inspect
|
271
|
-
@__value__.inspect
|
296
|
+
"Literal::Array(#{@__type__.inspect})#{@__value__.inspect}"
|
272
297
|
end
|
273
298
|
|
274
299
|
def intersect?(other)
|
@@ -389,10 +414,44 @@ class Literal::Array
|
|
389
414
|
@__value__.one?(...)
|
390
415
|
end
|
391
416
|
|
417
|
+
def pack(...)
|
418
|
+
@__value__.pack(...)
|
419
|
+
end
|
420
|
+
|
392
421
|
def pop(...)
|
393
422
|
@__value__.pop(...)
|
394
423
|
end
|
395
424
|
|
425
|
+
def product(*others, &)
|
426
|
+
if block_given?
|
427
|
+
@__value__.product(
|
428
|
+
*others.map do |other|
|
429
|
+
case other
|
430
|
+
when Array
|
431
|
+
other
|
432
|
+
when Literal::Array
|
433
|
+
other.__value__
|
434
|
+
end
|
435
|
+
end, &
|
436
|
+
)
|
437
|
+
|
438
|
+
self
|
439
|
+
elsif others.all?(Literal::Array)
|
440
|
+
tuple_type = Literal::Tuple(
|
441
|
+
@__type__,
|
442
|
+
*others.map(&:__type__)
|
443
|
+
)
|
444
|
+
|
445
|
+
values = @__value__.product(*others.map(&:__value__)).map do |tuple_values|
|
446
|
+
tuple_type.new(*tuple_values)
|
447
|
+
end
|
448
|
+
|
449
|
+
Literal::Array(tuple_type).new(*values)
|
450
|
+
else
|
451
|
+
@__value__.product(*others)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
396
455
|
def push(*value)
|
397
456
|
Literal.check(actual: value, expected: _Array(@__type__)) do |c|
|
398
457
|
c.fill_receiver(receiver: self, method: "#push")
|
@@ -434,6 +493,17 @@ class Literal::Array
|
|
434
493
|
self
|
435
494
|
end
|
436
495
|
|
496
|
+
alias_method :initialize_copy, :replace
|
497
|
+
|
498
|
+
def rotate(...)
|
499
|
+
__with__(@__value__.rotate(...))
|
500
|
+
end
|
501
|
+
|
502
|
+
def rotate!(...)
|
503
|
+
@__value__.rotate!(...)
|
504
|
+
self
|
505
|
+
end
|
506
|
+
|
437
507
|
def sample(...)
|
438
508
|
@__value__.sample(...)
|
439
509
|
end
|
@@ -444,12 +514,22 @@ class Literal::Array
|
|
444
514
|
|
445
515
|
def select!(...)
|
446
516
|
@__value__.select!(...)
|
517
|
+
self
|
447
518
|
end
|
448
519
|
|
449
520
|
def shift(...)
|
450
521
|
@__value__.shift(...)
|
451
522
|
end
|
452
523
|
|
524
|
+
def shuffle(...)
|
525
|
+
__with__(@__value__.shuffle(...))
|
526
|
+
end
|
527
|
+
|
528
|
+
def shuffle!(...)
|
529
|
+
@__value__.shuffle!(...)
|
530
|
+
self
|
531
|
+
end
|
532
|
+
|
453
533
|
def size(...)
|
454
534
|
@__value__.size(...)
|
455
535
|
end
|
@@ -463,13 +543,50 @@ class Literal::Array
|
|
463
543
|
self
|
464
544
|
end
|
465
545
|
|
546
|
+
def sort_by!(...)
|
547
|
+
@__value__.sort_by!(...)
|
548
|
+
self
|
549
|
+
end
|
550
|
+
|
551
|
+
def take(...)
|
552
|
+
__with__(@__value__.take(...))
|
553
|
+
end
|
554
|
+
|
555
|
+
def take_while(...)
|
556
|
+
__with__(@__value__.take_while(...))
|
557
|
+
end
|
558
|
+
|
559
|
+
def transpose
|
560
|
+
case @__type__
|
561
|
+
when Literal::Tuple::Generic
|
562
|
+
tuple_types = @__type__.types
|
563
|
+
new_array_types = tuple_types.map { |t| Literal::Array(t) }
|
564
|
+
|
565
|
+
Literal::Tuple(*new_array_types).new(
|
566
|
+
*new_array_types.each_with_index.map do |t, i|
|
567
|
+
t.new(
|
568
|
+
*@__value__.map { |it| it[i] }
|
569
|
+
)
|
570
|
+
end
|
571
|
+
)
|
572
|
+
when Literal::Array::Generic
|
573
|
+
__with__(
|
574
|
+
@__value__.map(&:to_a).transpose.map! { |it| @__type__.new(*it) }
|
575
|
+
)
|
576
|
+
else
|
577
|
+
@__value__.transpose
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
466
581
|
def to_a
|
467
582
|
@__value__.dup
|
468
583
|
end
|
469
584
|
|
470
585
|
alias_method :to_ary, :to_a
|
471
586
|
|
472
|
-
|
587
|
+
def to_s
|
588
|
+
@__value__.to_s
|
589
|
+
end
|
473
590
|
|
474
591
|
def uniq
|
475
592
|
__with__(@__value__.uniq)
|
@@ -541,4 +658,75 @@ class Literal::Array
|
|
541
658
|
def fetch(...)
|
542
659
|
@__value__.fetch(...)
|
543
660
|
end
|
661
|
+
|
662
|
+
def zip(*others)
|
663
|
+
other_types = others.map do |other|
|
664
|
+
case other
|
665
|
+
when Literal::Array
|
666
|
+
other.__type__
|
667
|
+
when Array
|
668
|
+
_Any?
|
669
|
+
else
|
670
|
+
raise ArgumentError
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
tuple = Literal::Tuple(
|
675
|
+
@__type__,
|
676
|
+
*other_types
|
677
|
+
)
|
678
|
+
|
679
|
+
my_length = length
|
680
|
+
max_length = [my_length, *others.map(&:length)].max
|
681
|
+
|
682
|
+
# Check we match the max length or our type is nilable
|
683
|
+
unless my_length == max_length || @__type__ === nil
|
684
|
+
raise ArgumentError.new(<<~MESSAGE)
|
685
|
+
The literal array could not be zipped becuase its type is not nilable and it has fewer items than the maximum number of items in the other arrays.
|
686
|
+
|
687
|
+
You can either make the type of this array nilable, or add more items so its length matches the others.
|
688
|
+
|
689
|
+
#{inspect}
|
690
|
+
MESSAGE
|
691
|
+
end
|
692
|
+
|
693
|
+
# Check others match the max length or their types is nilable
|
694
|
+
others.each_with_index do |other, index|
|
695
|
+
unless other.length == max_length || other_types[index] === nil
|
696
|
+
raise ArgumentError.new(<<~MESSAGE)
|
697
|
+
The literal array could not be zipped becuase its type is not nilable and it has fewer items than the maximum number of items in the other arrays.
|
698
|
+
|
699
|
+
You can either make the type of this array nilable, or add more items so its length matches the others.
|
700
|
+
|
701
|
+
#{inspect}
|
702
|
+
MESSAGE
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
i = 0
|
707
|
+
|
708
|
+
if block_given?
|
709
|
+
while i < max_length
|
710
|
+
yield tuple.new(
|
711
|
+
@__value__[i],
|
712
|
+
*others.map { |it| it[i] }
|
713
|
+
)
|
714
|
+
i += 1
|
715
|
+
end
|
716
|
+
|
717
|
+
nil
|
718
|
+
else
|
719
|
+
result_value = []
|
720
|
+
|
721
|
+
while i < max_length
|
722
|
+
result_value << tuple.new(
|
723
|
+
@__value__[i],
|
724
|
+
*others.map { |it| it[i] }
|
725
|
+
)
|
726
|
+
i += 1
|
727
|
+
end
|
728
|
+
|
729
|
+
__with__(result_value)
|
730
|
+
end
|
731
|
+
end
|
544
732
|
end
|
data/lib/literal/data.rb
CHANGED
@@ -9,17 +9,11 @@ class Literal::Data < Literal::DataStructure
|
|
9
9
|
def literal_properties
|
10
10
|
return @literal_properties if defined?(@literal_properties)
|
11
11
|
|
12
|
-
if superclass
|
12
|
+
if superclass < Literal::Data
|
13
13
|
@literal_properties = superclass.literal_properties.dup
|
14
14
|
else
|
15
15
|
@literal_properties = Literal::Properties::DataSchema.new
|
16
16
|
end
|
17
17
|
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def __literal_property_class__
|
22
|
-
Literal::DataProperty
|
23
|
-
end
|
24
18
|
end
|
25
19
|
end
|
data/lib/literal/enum.rb
CHANGED
@@ -9,17 +9,19 @@ class Literal::Enum
|
|
9
9
|
attr_reader :members
|
10
10
|
|
11
11
|
def values = @values.keys
|
12
|
+
def names = @names
|
12
13
|
|
13
|
-
def prop(name, type, kind = :keyword, reader: :public, default: nil)
|
14
|
-
super(name, type, kind, reader:, writer: false, default:)
|
14
|
+
def prop(name, type, kind = :keyword, reader: :public, predicate: false, default: nil)
|
15
|
+
super(name, type, kind, reader:, writer: false, predicate:, default:)
|
15
16
|
end
|
16
17
|
|
17
18
|
def inherited(subclass)
|
18
19
|
subclass.instance_exec do
|
19
20
|
@values = {}
|
20
|
-
@members =
|
21
|
+
@members = []
|
22
|
+
@indexes_definitions = {}
|
21
23
|
@indexes = {}
|
22
|
-
@
|
24
|
+
@names = {}
|
23
25
|
end
|
24
26
|
|
25
27
|
if RUBY_ENGINE != "truffleruby"
|
@@ -32,8 +34,16 @@ class Literal::Enum
|
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
37
|
+
def position_of(member)
|
38
|
+
coerce(member).__position__
|
39
|
+
end
|
40
|
+
|
41
|
+
def at_position(n)
|
42
|
+
@members[n]
|
43
|
+
end
|
44
|
+
|
35
45
|
def index(name, type, unique: true, &block)
|
36
|
-
@
|
46
|
+
@indexes_definitions[name] = [type, unique, block || name.to_proc]
|
37
47
|
end
|
38
48
|
|
39
49
|
def where(**kwargs)
|
@@ -43,11 +53,11 @@ class Literal::Enum
|
|
43
53
|
|
44
54
|
key, value = kwargs.first
|
45
55
|
|
46
|
-
types = @
|
56
|
+
types = @indexes_definitions.fetch(key)
|
47
57
|
type = types.first
|
48
58
|
Literal.check(actual: value, expected: type) { |c| raise NotImplementedError }
|
49
59
|
|
50
|
-
@
|
60
|
+
@indexes.fetch(key)[value]
|
51
61
|
end
|
52
62
|
|
53
63
|
def find_by(**kwargs)
|
@@ -57,15 +67,15 @@ class Literal::Enum
|
|
57
67
|
|
58
68
|
key, value = kwargs.first
|
59
69
|
|
60
|
-
unless @
|
70
|
+
unless @indexes_definitions.fetch(key)[1]
|
61
71
|
raise ArgumentError.new("You can only use `find_by` on unique indexes.")
|
62
72
|
end
|
63
73
|
|
64
|
-
unless (type = @
|
74
|
+
unless (type = @indexes_definitions.fetch(key)[0]) === value
|
65
75
|
raise Literal::TypeError.expected(value, to_be_a: type)
|
66
76
|
end
|
67
77
|
|
68
|
-
@
|
78
|
+
@indexes.fetch(key)[value]&.first
|
69
79
|
end
|
70
80
|
|
71
81
|
def _load(data)
|
@@ -77,9 +87,8 @@ class Literal::Enum
|
|
77
87
|
object = const_get(name)
|
78
88
|
|
79
89
|
if self === object
|
80
|
-
object.instance_variable_set(:@name, name)
|
81
|
-
@
|
82
|
-
@members << object
|
90
|
+
# object.instance_variable_set(:@name, name)
|
91
|
+
@names[object] = name
|
83
92
|
define_method("#{name.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase}?") { self == object }
|
84
93
|
object.freeze
|
85
94
|
end
|
@@ -87,12 +96,21 @@ class Literal::Enum
|
|
87
96
|
|
88
97
|
def new(*args, **kwargs, &block)
|
89
98
|
raise ArgumentError if frozen?
|
99
|
+
|
90
100
|
new_object = super(*args, **kwargs, &nil)
|
91
101
|
|
92
|
-
if
|
93
|
-
new_object.
|
102
|
+
if @values.key?(new_object.value)
|
103
|
+
raise ArgumentError.new("The value #{new_object.value} is already used by #{@values[new_object.value].name}.")
|
94
104
|
end
|
95
105
|
|
106
|
+
@values[new_object.value] = new_object
|
107
|
+
|
108
|
+
new_object.instance_variable_set(:@__position__, @members.length)
|
109
|
+
|
110
|
+
@members << new_object
|
111
|
+
|
112
|
+
new_object.instance_exec(&block) if block
|
113
|
+
|
96
114
|
new_object
|
97
115
|
end
|
98
116
|
|
@@ -103,7 +121,7 @@ class Literal::Enum
|
|
103
121
|
constants(false).each { |name| const_added(name) }
|
104
122
|
end
|
105
123
|
|
106
|
-
@
|
124
|
+
@indexes_definitions.each do |name, (type, unique, block)|
|
107
125
|
index = @members.group_by(&block).freeze
|
108
126
|
|
109
127
|
index.each do |key, values|
|
@@ -116,7 +134,7 @@ class Literal::Enum
|
|
116
134
|
end
|
117
135
|
end
|
118
136
|
|
119
|
-
@
|
137
|
+
@indexes[name] = index
|
120
138
|
end
|
121
139
|
|
122
140
|
@values.freeze
|
@@ -146,8 +164,20 @@ class Literal::Enum
|
|
146
164
|
case value
|
147
165
|
when self
|
148
166
|
value
|
167
|
+
when Symbol
|
168
|
+
self[value] || begin
|
169
|
+
const_get(value)
|
170
|
+
rescue NameError
|
171
|
+
raise ArgumentError.new(
|
172
|
+
"Can't coerce #{value.inspect} into a #{inspect}."
|
173
|
+
)
|
174
|
+
end
|
149
175
|
else
|
150
|
-
self[value]
|
176
|
+
self[value] || raise(
|
177
|
+
ArgumentError.new(
|
178
|
+
"Can't coerce #{value.inspect} into a #{inspect}."
|
179
|
+
)
|
180
|
+
)
|
151
181
|
end
|
152
182
|
end
|
153
183
|
|
@@ -155,25 +185,22 @@ class Literal::Enum
|
|
155
185
|
method(:coerce).to_proc
|
156
186
|
end
|
157
187
|
|
158
|
-
def to_h(
|
159
|
-
|
188
|
+
def to_h(&)
|
189
|
+
if block_given?
|
190
|
+
@members.to_h(&)
|
191
|
+
else
|
192
|
+
@members.to_h { |it| [it, it.value] }
|
193
|
+
end
|
160
194
|
end
|
161
195
|
end
|
162
196
|
|
163
|
-
def initialize(name, value, &block)
|
164
|
-
@name = name
|
165
|
-
@value = value
|
166
|
-
instance_exec(&block) if block
|
167
|
-
freeze
|
168
|
-
end
|
169
|
-
|
170
|
-
attr_reader :value
|
171
|
-
|
172
197
|
def name
|
173
|
-
|
198
|
+
klass = self.class
|
199
|
+
"#{klass.name}::#{klass.names[self]}"
|
174
200
|
end
|
175
201
|
|
176
202
|
alias_method :inspect, :name
|
203
|
+
alias_method :to_s, :name
|
177
204
|
|
178
205
|
def deconstruct
|
179
206
|
[@value]
|
@@ -187,4 +214,27 @@ class Literal::Enum
|
|
187
214
|
def _dump(level)
|
188
215
|
Marshal.dump(@value)
|
189
216
|
end
|
217
|
+
|
218
|
+
def <=>(other)
|
219
|
+
case other
|
220
|
+
when self.class
|
221
|
+
@__position__ <=> other.__position__
|
222
|
+
else
|
223
|
+
raise ArgumentError.new("Can't compare instances of #{other.class} to instances of #{self.class}")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def succ
|
228
|
+
self.class.members[@__position__ + 1]
|
229
|
+
end
|
230
|
+
|
231
|
+
def pred
|
232
|
+
if @__position__ <= 0
|
233
|
+
nil
|
234
|
+
else
|
235
|
+
self.class.members[@__position__ - 1]
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
attr_reader :__position__
|
190
240
|
end
|
data/lib/literal/null.rb
CHANGED
data/lib/literal/property.rb
CHANGED
@@ -118,29 +118,18 @@ class Literal::Property
|
|
118
118
|
"\n value\nend\n"
|
119
119
|
end
|
120
120
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
(@writer ? @writer.name : "public") <<
|
134
|
-
" def " <<
|
135
|
-
@name.name <<
|
136
|
-
"=(value)\n" <<
|
137
|
-
" self.class.literal_properties[:" <<
|
138
|
-
@name.name <<
|
139
|
-
"].check_writer(self, value)\n" <<
|
140
|
-
" @" << @name.name << " = value\n" <<
|
141
|
-
"rescue Literal::TypeError => error\n error.set_backtrace(caller(1))\n raise\n" <<
|
142
|
-
"end\n"
|
143
|
-
end
|
121
|
+
def generate_writer_method(buffer = +"")
|
122
|
+
buffer <<
|
123
|
+
(@writer ? @writer.name : "public") <<
|
124
|
+
" def " <<
|
125
|
+
@name.name <<
|
126
|
+
"=(value)\n" <<
|
127
|
+
" self.class.literal_properties[:" <<
|
128
|
+
@name.name <<
|
129
|
+
"].check_writer(self, value)\n" <<
|
130
|
+
" @" << @name.name << " = value\n" <<
|
131
|
+
"rescue Literal::TypeError => error\n error.set_backtrace(caller(1))\n raise\n" <<
|
132
|
+
"end\n"
|
144
133
|
end
|
145
134
|
|
146
135
|
def generate_predicate_method(buffer = +"")
|
@@ -157,7 +146,7 @@ class Literal::Property
|
|
157
146
|
|
158
147
|
def generate_initializer_handle_property(buffer = +"")
|
159
148
|
buffer << " # " << @name.name << "\n" <<
|
160
|
-
"
|
149
|
+
" __property__ = __properties__[:" << @name.name << "]\n"
|
161
150
|
|
162
151
|
if @kind == :keyword && ruby_keyword?
|
163
152
|
generate_initializer_escape_keyword(buffer)
|
@@ -171,10 +160,7 @@ class Literal::Property
|
|
171
160
|
generate_initializer_coerce_property(buffer)
|
172
161
|
end
|
173
162
|
|
174
|
-
|
175
|
-
generate_initializer_check_type(buffer)
|
176
|
-
end
|
177
|
-
|
163
|
+
generate_initializer_check_type(buffer)
|
178
164
|
generate_initializer_assign_value(buffer)
|
179
165
|
end
|
180
166
|
|
@@ -191,7 +177,7 @@ class Literal::Property
|
|
191
177
|
def generate_initializer_coerce_property(buffer = +"")
|
192
178
|
buffer <<
|
193
179
|
escaped_name <<
|
194
|
-
"=
|
180
|
+
"= __property__.coerce(" <<
|
195
181
|
escaped_name <<
|
196
182
|
", context: self)\n"
|
197
183
|
end
|
@@ -204,12 +190,12 @@ class Literal::Property
|
|
204
190
|
escaped_name <<
|
205
191
|
"\n " <<
|
206
192
|
escaped_name <<
|
207
|
-
" =
|
193
|
+
" = __property__.default_value\n end\n"
|
208
194
|
end
|
209
195
|
|
210
196
|
def generate_initializer_check_type(buffer = +"")
|
211
197
|
buffer <<
|
212
|
-
"
|
198
|
+
" __property__.check_initializer(self, " << escaped_name << ")\n"
|
213
199
|
end
|
214
200
|
|
215
201
|
def generate_initializer_assign_value(buffer = +"")
|
@@ -8,10 +8,10 @@ class Literal::Rails::EnumType < ActiveModel::Type::Value
|
|
8
8
|
|
9
9
|
def cast(value)
|
10
10
|
case value
|
11
|
-
when
|
12
|
-
|
11
|
+
when nil
|
12
|
+
nil
|
13
13
|
else
|
14
|
-
|
14
|
+
@enum.coerce(value)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -19,12 +19,8 @@ class Literal::Rails::EnumType < ActiveModel::Type::Value
|
|
19
19
|
case value
|
20
20
|
when nil
|
21
21
|
nil
|
22
|
-
when @enum
|
23
|
-
value.value
|
24
22
|
else
|
25
|
-
|
26
|
-
"Invalid value: #{value.inspect}. Expected an #{@enum.inspect}.",
|
27
|
-
)
|
23
|
+
@enum.coerce(value).value
|
28
24
|
end
|
29
25
|
end
|
30
26
|
|
@@ -33,9 +29,7 @@ class Literal::Rails::EnumType < ActiveModel::Type::Value
|
|
33
29
|
when nil
|
34
30
|
nil
|
35
31
|
else
|
36
|
-
@enum
|
37
|
-
ArgumentError.new("Invalid value: #{value.inspect} for #{@enum}"),
|
38
|
-
)
|
32
|
+
@enum.coerce(value)
|
39
33
|
end
|
40
34
|
end
|
41
35
|
end
|
@@ -4,9 +4,10 @@ module ActiveRecord
|
|
4
4
|
class RelationType
|
5
5
|
def initialize(model_class)
|
6
6
|
unless Class === model_class && model_class < ActiveRecord::Base
|
7
|
-
raise Literal::TypeError.
|
8
|
-
|
9
|
-
|
7
|
+
raise Literal::TypeError.new(
|
8
|
+
context: Literal::TypeError::Context.new(
|
9
|
+
expected: ActiveRecord::Base, actual: model_class
|
10
|
+
)
|
10
11
|
)
|
11
12
|
end
|
12
13
|
|
data/lib/literal/railtie.rb
CHANGED
@@ -2,20 +2,14 @@
|
|
2
2
|
|
3
3
|
class Literal::Railtie < Rails::Railtie
|
4
4
|
initializer "literal.register_literal_enum_type" do
|
5
|
-
ActiveRecord::Type.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
ActiveModel::Type.register(:literal_enum) do |name, type:|
|
14
|
-
Literal::Rails::EnumType.new(type)
|
15
|
-
end
|
16
|
-
|
17
|
-
ActiveModel::Type.register(:literal_flags) do |name, type:|
|
18
|
-
Literal::Rails::FlagsType.new(type)
|
5
|
+
[ActiveRecord::Type, ActiveModel::Type].each do |registry|
|
6
|
+
registry.register(:literal_enum) do |name, type:|
|
7
|
+
Literal::Rails::EnumType.new(type)
|
8
|
+
end
|
9
|
+
|
10
|
+
registry.register(:literal_flags) do |name, type:|
|
11
|
+
Literal::Rails::FlagsType.new(type)
|
12
|
+
end
|
19
13
|
end
|
20
14
|
end
|
21
15
|
end
|
data/lib/literal/transforms.rb
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Literal::Tuple
|
4
|
+
class Generic
|
5
|
+
include Literal::Type
|
6
|
+
|
7
|
+
def initialize(*types)
|
8
|
+
@types = types
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :types
|
12
|
+
|
13
|
+
def ===(other)
|
14
|
+
return false unless Literal::Tuple === other
|
15
|
+
types = @types
|
16
|
+
other_types = other.__types__
|
17
|
+
|
18
|
+
return false unless types.size == other_types.size
|
19
|
+
|
20
|
+
i, len = 0, types.size
|
21
|
+
while i < len
|
22
|
+
return false unless Literal.subtype?(other_types[i], of: types[i])
|
23
|
+
i += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def >=(other)
|
30
|
+
case other
|
31
|
+
when Literal::Tuple::Generic
|
32
|
+
types = @types
|
33
|
+
other_types = other.types
|
34
|
+
|
35
|
+
return false unless types.size == other_types.size
|
36
|
+
|
37
|
+
i, len = 0, types.size
|
38
|
+
while i < len
|
39
|
+
return false unless Literal.subtype?(other_types[i], of: types[i])
|
40
|
+
i += 1
|
41
|
+
end
|
42
|
+
|
43
|
+
true
|
44
|
+
else
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def new(*values)
|
50
|
+
Literal::Tuple.new(values, @types)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(values, types)
|
55
|
+
@__values__ = values
|
56
|
+
@__types__ = types
|
57
|
+
end
|
58
|
+
|
59
|
+
def inspect
|
60
|
+
"Literal::Tuple(#{@__types__.map(&:inspect).join(', ')})#{@__values__.inspect}"
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :__values__, :__types__
|
64
|
+
|
65
|
+
def ==(other)
|
66
|
+
(Literal::Tuple === other) && (@__values__ == other.__values__)
|
67
|
+
end
|
68
|
+
|
69
|
+
def [](index)
|
70
|
+
@__values__[index]
|
71
|
+
end
|
72
|
+
end
|
@@ -25,14 +25,19 @@ class Literal::Types::ConstraintType
|
|
25
25
|
i += 1
|
26
26
|
end
|
27
27
|
|
28
|
+
result = true
|
29
|
+
|
28
30
|
@property_constraints.each do |a, t|
|
29
|
-
|
31
|
+
# We intentionally don’t return early here becuase it triggers an allocation.
|
32
|
+
if result && !(t === value.public_send(a))
|
33
|
+
result = false
|
34
|
+
end
|
30
35
|
rescue NoMethodError => e
|
31
36
|
raise unless e.name == a && e.receiver == value
|
32
37
|
return false
|
33
38
|
end
|
34
39
|
|
35
|
-
|
40
|
+
result
|
36
41
|
end
|
37
42
|
|
38
43
|
def >=(other)
|
data/lib/literal/types.rb
CHANGED
@@ -103,6 +103,19 @@ module Literal::Types
|
|
103
103
|
)
|
104
104
|
end
|
105
105
|
|
106
|
+
# Matches if the value is a `Date` and matches the given constraints.
|
107
|
+
# If you don't need any constraints, use `Date` instead of `_Date`.
|
108
|
+
def _Date(...)
|
109
|
+
_Constraint(Date, ...)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Nilable version of `_Date`
|
113
|
+
def _Date?(...)
|
114
|
+
_Nilable(
|
115
|
+
_Date(...)
|
116
|
+
)
|
117
|
+
end
|
118
|
+
|
106
119
|
def _Deferred(...)
|
107
120
|
DeferredType.new(...)
|
108
121
|
end
|
@@ -324,6 +337,19 @@ module Literal::Types
|
|
324
337
|
)
|
325
338
|
end
|
326
339
|
|
340
|
+
# Matches if the value is a `Time` and matches the given constraints.
|
341
|
+
# If you don't need any constraints, use `Time` instead of `_Time`.
|
342
|
+
def _Time(...)
|
343
|
+
_Constraint(Time, ...)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Nilable version of `_Time`
|
347
|
+
def _Time?(...)
|
348
|
+
_Nilable(
|
349
|
+
_Time(...)
|
350
|
+
)
|
351
|
+
end
|
352
|
+
|
327
353
|
# Matches *"truthy"* values (anything except `nil` and `false`).
|
328
354
|
def _Truthy
|
329
355
|
TruthyType::Instance
|
data/lib/literal/version.rb
CHANGED
data/lib/literal.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Literal
|
4
|
-
TYPE_CHECKS_DISABLED = ENV["LITERAL_TYPE_CHECKS"] == "false"
|
5
|
-
|
6
4
|
autoload :Array, "literal/array"
|
7
5
|
autoload :Data, "literal/data"
|
8
6
|
autoload :DataProperty, "literal/data_property"
|
@@ -22,6 +20,7 @@ module Literal
|
|
22
20
|
autoload :Struct, "literal/struct"
|
23
21
|
autoload :Type, "literal/type"
|
24
22
|
autoload :Types, "literal/types"
|
23
|
+
autoload :Tuple, "literal/tuple"
|
25
24
|
|
26
25
|
# Errors
|
27
26
|
autoload :Error, "literal/errors/error"
|
@@ -32,7 +31,7 @@ module Literal
|
|
32
31
|
|
33
32
|
def self.Enum(type)
|
34
33
|
Class.new(Literal::Enum) do
|
35
|
-
prop :value, type, :positional
|
34
|
+
prop :value, type, :positional, reader: :public
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
@@ -48,6 +47,10 @@ module Literal
|
|
48
47
|
Literal::Hash::Generic.new(key_type, value_type)
|
49
48
|
end
|
50
49
|
|
50
|
+
def self.Tuple(*types)
|
51
|
+
Literal::Tuple::Generic.new(*types)
|
52
|
+
end
|
53
|
+
|
51
54
|
def self.check(actual:, expected:)
|
52
55
|
if expected === actual
|
53
56
|
true
|
metadata
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: literal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joel Drapper
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-01-17 00:00:00.000000000 Z
|
12
11
|
dependencies: []
|
13
|
-
description:
|
12
|
+
description: Enums, properties, generics, structured objects and runtime type checking.
|
14
13
|
email:
|
15
14
|
- joel@drapper.me
|
16
15
|
executables: []
|
@@ -22,13 +21,13 @@ files:
|
|
22
21
|
- lib/literal.rb
|
23
22
|
- lib/literal/array.rb
|
24
23
|
- lib/literal/data.rb
|
25
|
-
- lib/literal/data_property.rb
|
26
24
|
- lib/literal/data_structure.rb
|
27
25
|
- lib/literal/deferred_type.rb
|
28
26
|
- lib/literal/enum.rb
|
29
27
|
- lib/literal/errors/argument_error.rb
|
30
28
|
- lib/literal/errors/error.rb
|
31
29
|
- lib/literal/errors/type_error.rb
|
30
|
+
- lib/literal/failure.rb
|
32
31
|
- lib/literal/flags.rb
|
33
32
|
- lib/literal/hash.rb
|
34
33
|
- lib/literal/null.rb
|
@@ -43,9 +42,12 @@ files:
|
|
43
42
|
- lib/literal/rails/flags_type.rb
|
44
43
|
- lib/literal/rails/patches/active_record.rb
|
45
44
|
- lib/literal/railtie.rb
|
45
|
+
- lib/literal/result.rb
|
46
46
|
- lib/literal/set.rb
|
47
47
|
- lib/literal/struct.rb
|
48
|
+
- lib/literal/success.rb
|
48
49
|
- lib/literal/transforms.rb
|
50
|
+
- lib/literal/tuple.rb
|
49
51
|
- lib/literal/type.rb
|
50
52
|
- lib/literal/types.rb
|
51
53
|
- lib/literal/types/any_type.rb
|
@@ -78,10 +80,9 @@ licenses:
|
|
78
80
|
metadata:
|
79
81
|
homepage_uri: https://literal.fun
|
80
82
|
source_code_uri: https://github.com/joeldrapper/literal
|
81
|
-
changelog_uri: https://github.com/joeldrapper/literal/
|
83
|
+
changelog_uri: https://github.com/joeldrapper/literal/releases
|
82
84
|
funding_uri: https://github.com/sponsors/joeldrapper
|
83
85
|
rubygems_mfa_required: 'true'
|
84
|
-
post_install_message:
|
85
86
|
rdoc_options: []
|
86
87
|
require_paths:
|
87
88
|
- lib
|
@@ -96,8 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
97
|
- !ruby/object:Gem::Version
|
97
98
|
version: '0'
|
98
99
|
requirements: []
|
99
|
-
rubygems_version: 3.
|
100
|
-
signing_key:
|
100
|
+
rubygems_version: 3.6.2
|
101
101
|
specification_version: 4
|
102
102
|
summary: Enums, properties, generics, structured objects and runtime type checking.
|
103
103
|
test_files: []
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Literal::DataProperty < Literal::Property
|
4
|
-
def generate_initializer_assign_value(buffer = +"")
|
5
|
-
buffer <<
|
6
|
-
"@" <<
|
7
|
-
@name.name <<
|
8
|
-
" = " <<
|
9
|
-
escaped_name <<
|
10
|
-
".frozen? ? " <<
|
11
|
-
escaped_name <<
|
12
|
-
" : " <<
|
13
|
-
escaped_name <<
|
14
|
-
".dup.freeze\n"
|
15
|
-
end
|
16
|
-
end
|