literal 1.3.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|