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 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