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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9721432521677aa431dda4e94240521a54a8ac21ece07fef009393544d19214d
4
- data.tar.gz: c5b41c95f4c24ff4fad28869bf61ff93174118a2d3e5890928961b382bf9b285
3
+ metadata.gz: e3b0596d3836316a8c9f2dc13b42707da7e4b158cdf17af864c22be88856e76c
4
+ data.tar.gz: be38909c66845eb4d09187803903f46cbe8d4392d0245d089aa7aec03c0ac7af
5
5
  SHA512:
6
- metadata.gz: 715a03576aed152e2fd5737236eb227dd98da9f02b421a14722395a3b9992dc667302574490955f0f9d81192dc5f64f793d916b196bc4509244afad5daa8a37b
7
- data.tar.gz: 475df2645d537e5c8151ba6431e1469fdcfdd1a80d766489bfa00b135f3c3e57a86b0c09c7e6738c6033609a69864a596660a0b5498bd6762e314588a67bac35
6
+ metadata.gz: cfe44962b770aa87464249cd41540e2c26cce61a1b59292fca71b3d79331595483214e22eaa907e3b8453d49a75365219291d196854d843ec09ac1c4ed3b19dd
7
+ data.tar.gz: 3b6cd6cfeb4fd440c43385462ac3147606389f2afe046eab45cdd3cd9b626dc1fd6e7cdce39c4c5f9b060ea659b99b6ffb7a9ab930a5edb393a11e64f7234557
data/README.md CHANGED
@@ -1,3 +1,3 @@
1
1
  # A Literal Ruby Gem
2
2
 
3
- See the website for [documentation](https://literal.fun/docs).
3
+ See the website for [documentation](https://literal.fun/docs/).
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
- @type >= other.type
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
- alias_method :to_s, :inspect
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.is_a?(Literal::Data)
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 = Set[]
21
+ @members = []
22
+ @indexes_definitions = {}
21
23
  @indexes = {}
22
- @index = {}
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
- @indexes[name] = [type, unique, block || name.to_proc]
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 = @indexes.fetch(key)
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
- @index.fetch(key)[value]
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 @indexes.fetch(key)[1]
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 = @indexes.fetch(key)[0]) === value
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
- @index.fetch(key)[value]&.first
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
- @values[object.value] = object
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 block
93
- new_object.instance_exec(&block)
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
- @indexes.each do |name, (type, unique, block)|
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
- @index[name] = index
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(*args)
159
- @values.dup
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
- "#{self.class.name}::#{@name}"
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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Failure < Literal::Result
4
+ def initialize(error)
5
+ @error = error
6
+ end
7
+ end
data/lib/literal/null.rb CHANGED
@@ -5,5 +5,9 @@ module Literal::Null
5
5
  "Literal::Null"
6
6
  end
7
7
 
8
+ def self.===(value)
9
+ self == value
10
+ end
11
+
8
12
  freeze
9
13
  end
@@ -118,29 +118,18 @@ class Literal::Property
118
118
  "\n value\nend\n"
119
119
  end
120
120
 
121
- if Literal::TYPE_CHECKS_DISABLED
122
- def generate_writer_method(buffer = +"")
123
- buffer <<
124
- (@writer ? @writer.name : "public") <<
125
- " def " <<
126
- @name.name <<
127
- "=(value)\n" <<
128
- " @#{@name.name} = value\nend\n"
129
- end
130
- else # type checks are enabled
131
- def generate_writer_method(buffer = +"")
132
- buffer <<
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
- " property = __properties__[:" << @name.name << "]\n"
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
- unless Literal::TYPE_CHECKS_DISABLED
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
- "= property.coerce(" <<
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
- " = property.default_value\n end\n"
193
+ " = __property__.default_value\n end\n"
208
194
  end
209
195
 
210
196
  def generate_initializer_check_type(buffer = +"")
211
197
  buffer <<
212
- " property.check_initializer(self, " << escaped_name << ")\n"
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 @enum
12
- value
11
+ when nil
12
+ nil
13
13
  else
14
- deserialize(value)
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
- raise Literal::ArgumentError.new(
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[value] || raise(
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.expected(
8
- model_class,
9
- to_be_a: ActiveRecord::Base,
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
 
@@ -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.register(:literal_enum) do |name, type:|
6
- Literal::Rails::EnumType.new(type)
7
- end
8
-
9
- ActiveRecord::Type.register(:literal_flags) do |name, type:|
10
- Literal::Rails::FlagsType.new(type)
11
- end
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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Result
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Literal::Success < Literal::Result
4
+ def initialize(value)
5
+ @value = value
6
+ end
7
+ end
@@ -54,6 +54,7 @@ Literal::TRANSFORMS = {
54
54
  to_str: String,
55
55
  upcase: String,
56
56
  valid_encoding?: Literal::Types::BooleanType::Instance,
57
+ to_i: Integer,
57
58
  },
58
59
  Numeric => {
59
60
  to_i: Integer,
@@ -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
- return false unless t === value.public_send(a)
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
- true
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Literal
4
- VERSION = "1.3.0"
4
+ VERSION = "1.5.0"
5
5
  end
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.3.0
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: 2024-11-29 00:00:00.000000000 Z
10
+ date: 2025-01-17 00:00:00.000000000 Z
12
11
  dependencies: []
13
- description: A literal Ruby gem.
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/blob/main/CHANGELOG.md
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.5.18
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