literal 1.4.0 → 1.6.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: 8ee13d939895155fc3ce4ae1717036b565b0a59d7a4e87b50ea0a1390cc40bf3
4
- data.tar.gz: 42df47f2efce39eb14976d226645adb7a9dacc681948a71a9254ca44e19d8d1f
3
+ metadata.gz: 98bc237de806f82733e8c812bccc840c5451183afd22f369ecffc6720c3b88ba
4
+ data.tar.gz: b340a2c25c5d23d033a83dbf3ab1483a9b071424f343f0edf5c104bee83402c2
5
5
  SHA512:
6
- metadata.gz: f3a9b286744de95185baea476e8d7387238912aeb7f956eeef4e7b203bd0a55313885baaf6c5e71e27eb6d252961d44f850ede7e09abbc05c323c88a099be6f6
7
- data.tar.gz: f2cc33927ce452c94b3146dc7714cd9078cd532da5523afbbae5372ec2a5aa0319460db07525d9d7a5d709deb9ff929b804d0f3a86425f3d4844bf641a5a669b
6
+ metadata.gz: 17b16bdea93b6dff73b9fdcbdd269053648e996f3e2b5d6773933710281b756a50eb93684d727b46711aef990858985111f9d59d80250d20a3f71963767032ca
7
+ data.tar.gz: 67647cf3e25cdbdbdb12638081a7cbd40b5bee6771d9be54ca4100646740a97989259f24242b60064c41b016c7687d91a3969632d2195a0715c6015bede8e839
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
@@ -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
@@ -276,7 +293,7 @@ class Literal::Array
276
293
  end
277
294
 
278
295
  def inspect
279
- @__value__.inspect
296
+ "Literal::Array(#{@__type__.inspect})#{@__value__.inspect}"
280
297
  end
281
298
 
282
299
  def intersect?(other)
@@ -497,6 +514,7 @@ class Literal::Array
497
514
 
498
515
  def select!(...)
499
516
  @__value__.select!(...)
517
+ self
500
518
  end
501
519
 
502
520
  def shift(...)
@@ -525,17 +543,50 @@ class Literal::Array
525
543
  self
526
544
  end
527
545
 
546
+ def sort_by!(...)
547
+ @__value__.sort_by!(...)
548
+ self
549
+ end
550
+
528
551
  def take(...)
529
552
  __with__(@__value__.take(...))
530
553
  end
531
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
+
532
581
  def to_a
533
582
  @__value__.dup
534
583
  end
535
584
 
536
585
  alias_method :to_ary, :to_a
537
586
 
538
- alias_method :to_s, :inspect
587
+ def to_s
588
+ @__value__.to_s
589
+ end
539
590
 
540
591
  def uniq
541
592
  __with__(@__value__.uniq)
@@ -607,4 +658,75 @@ class Literal::Array
607
658
  def fetch(...)
608
659
  @__value__.fetch(...)
609
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
610
732
  end
data/lib/literal/enum.rb CHANGED
@@ -9,20 +9,22 @@ 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
- if RUBY_ENGINE != "truffleruby"
27
+ if subclass.name && RUBY_ENGINE != "truffleruby"
26
28
  TracePoint.trace(:end) do |tp|
27
29
  if tp.self == subclass
28
30
  tp.self.__after_defined__
@@ -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,14 @@ 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
65
- raise Literal::TypeError.expected(value, to_be_a: type)
66
- end
74
+ type = @indexes_definitions.fetch(key)[0]
75
+ Literal.check(actual: value, expected: type)
67
76
 
68
- @index.fetch(key)[value]&.first
77
+ @indexes.fetch(key)[value]&.first
69
78
  end
70
79
 
71
80
  def _load(data)
@@ -77,12 +86,8 @@ class Literal::Enum
77
86
  object = const_get(name)
78
87
 
79
88
  if self === object
80
- if @values.key?(object.value)
81
- raise ArgumentError.new("The value #{object.value} is already used by #{@values[object.value].name}.")
82
- end
83
- object.instance_variable_set(:@name, name)
84
- @values[object.value] = object
85
- @members << object
89
+ # object.instance_variable_set(:@name, name)
90
+ @names[object] = name
86
91
  define_method("#{name.to_s.gsub(/([^A-Z])([A-Z]+)/, '\1_\2').downcase}?") { self == object }
87
92
  object.freeze
88
93
  end
@@ -90,12 +95,21 @@ class Literal::Enum
90
95
 
91
96
  def new(*args, **kwargs, &block)
92
97
  raise ArgumentError if frozen?
98
+
93
99
  new_object = super(*args, **kwargs, &nil)
94
100
 
95
- if block
96
- new_object.instance_exec(&block)
101
+ if @values.key?(new_object.value)
102
+ raise ArgumentError.new("The value #{new_object.value} is already used by #{@values[new_object.value].name}.")
97
103
  end
98
104
 
105
+ @values[new_object.value] = new_object
106
+
107
+ new_object.instance_variable_set(:@__position__, @members.length)
108
+
109
+ @members << new_object
110
+
111
+ new_object.instance_exec(&block) if block
112
+
99
113
  new_object
100
114
  end
101
115
 
@@ -106,7 +120,7 @@ class Literal::Enum
106
120
  constants(false).each { |name| const_added(name) }
107
121
  end
108
122
 
109
- @indexes.each do |name, (type, unique, block)|
123
+ @indexes_definitions.each do |name, (type, unique, block)|
110
124
  index = @members.group_by(&block).freeze
111
125
 
112
126
  index.each do |key, values|
@@ -119,7 +133,7 @@ class Literal::Enum
119
133
  end
120
134
  end
121
135
 
122
- @index[name] = index
136
+ @indexes[name] = index
123
137
  end
124
138
 
125
139
  @values.freeze
@@ -149,8 +163,20 @@ class Literal::Enum
149
163
  case value
150
164
  when self
151
165
  value
166
+ when Symbol
167
+ self[value] || begin
168
+ const_get(value)
169
+ rescue NameError
170
+ raise ArgumentError.new(
171
+ "Can't coerce #{value.inspect} into a #{inspect}."
172
+ )
173
+ end
152
174
  else
153
- self[value]
175
+ self[value] || raise(
176
+ ArgumentError.new(
177
+ "Can't coerce #{value.inspect} into a #{inspect}."
178
+ )
179
+ )
154
180
  end
155
181
  end
156
182
 
@@ -167,20 +193,13 @@ class Literal::Enum
167
193
  end
168
194
  end
169
195
 
170
- def initialize(name, value, &block)
171
- @name = name
172
- @value = value
173
- instance_exec(&block) if block
174
- freeze
175
- end
176
-
177
- attr_reader :value
178
-
179
196
  def name
180
- "#{self.class.name}::#{@name}"
197
+ klass = self.class
198
+ "#{klass.name}::#{klass.names[self]}"
181
199
  end
182
200
 
183
201
  alias_method :inspect, :name
202
+ alias_method :to_s, :name
184
203
 
185
204
  def deconstruct
186
205
  [@value]
@@ -194,4 +213,27 @@ class Literal::Enum
194
213
  def _dump(level)
195
214
  Marshal.dump(@value)
196
215
  end
216
+
217
+ def <=>(other)
218
+ case other
219
+ when self.class
220
+ @__position__ <=> other.__position__
221
+ else
222
+ raise ArgumentError.new("Can't compare instances of #{other.class} to instances of #{self.class}")
223
+ end
224
+ end
225
+
226
+ def succ
227
+ self.class.members[@__position__ + 1]
228
+ end
229
+
230
+ def pred
231
+ if @__position__ <= 0
232
+ nil
233
+ else
234
+ self.class.members[@__position__ - 1]
235
+ end
236
+ end
237
+
238
+ attr_reader :__position__
197
239
  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
@@ -102,7 +102,7 @@ class Literal::Properties::Schema
102
102
  i, n = 0, sorted_properties.size
103
103
  while i < n
104
104
  property = sorted_properties[i]
105
- buffer << " @" << property.name.name << " == other." << property.name.name
105
+ buffer << " @#{property.name.name} == other.instance_variable_get(:@#{property.name.name})"
106
106
  buffer << " &&\n " if i < n - 1
107
107
  i += 1
108
108
  end
@@ -30,7 +30,7 @@ module Literal::Properties
30
30
  end
31
31
 
32
32
  unless Literal::Property::VISIBILITY_OPTIONS.include?(predicate)
33
- raise Literal::ArgumentError.new("The predicate must be one of #{Literal::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.")
33
+ raise Literal::ArgumentError.new("The predicate must be one of #{Literal::Property::VISIBILITY_OPTIONS.map(&:inspect).join(', ')}.")
34
34
  end
35
35
 
36
36
  if reader && :class == name
@@ -90,7 +90,7 @@ module Literal::Properties
90
90
  end
91
91
 
92
92
  def to_h
93
- {}
93
+ {}
94
94
  end
95
95
 
96
96
  set_temporary_name "Literal::Properties(Extension)" if respond_to?(:set_temporary_name)
@@ -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 = +"")
@@ -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
 
@@ -6,12 +6,16 @@ class Literal::Rails::EnumType < ActiveModel::Type::Value
6
6
  super()
7
7
  end
8
8
 
9
+ def type
10
+ :literal_enum
11
+ end
12
+
9
13
  def cast(value)
10
14
  case value
11
- when @enum
12
- value
15
+ when nil
16
+ nil
13
17
  else
14
- deserialize(value)
18
+ @enum.coerce(value)
15
19
  end
16
20
  end
17
21
 
@@ -19,12 +23,8 @@ class Literal::Rails::EnumType < ActiveModel::Type::Value
19
23
  case value
20
24
  when nil
21
25
  nil
22
- when @enum
23
- value.value
24
26
  else
25
- raise Literal::ArgumentError.new(
26
- "Invalid value: #{value.inspect}. Expected an #{@enum.inspect}.",
27
- )
27
+ @enum.coerce(value).value
28
28
  end
29
29
  end
30
30
 
@@ -33,9 +33,7 @@ class Literal::Rails::EnumType < ActiveModel::Type::Value
33
33
  when nil
34
34
  nil
35
35
  else
36
- @enum[value] || raise(
37
- ArgumentError.new("Invalid value: #{value.inspect} for #{@enum}"),
38
- )
36
+ @enum.coerce(value)
39
37
  end
40
38
  end
41
39
  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,
data/lib/literal/tuple.rb CHANGED
@@ -56,5 +56,17 @@ class Literal::Tuple
56
56
  @__types__ = types
57
57
  end
58
58
 
59
+ def inspect
60
+ "Literal::Tuple(#{@__types__.map(&:inspect).join(', ')})#{@__values__.inspect}"
61
+ end
62
+
59
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
60
72
  end
@@ -6,6 +6,7 @@ class Literal::Types::ArrayType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -6,6 +6,7 @@ class Literal::Types::ClassType
6
6
 
7
7
  def initialize(type)
8
8
  @type = type
9
+ freeze
9
10
  end
10
11
 
11
12
  attr_reader :type
@@ -7,6 +7,7 @@ class Literal::Types::ConstraintType
7
7
  def initialize(*object_constraints, **property_constraints)
8
8
  @object_constraints = object_constraints
9
9
  @property_constraints = property_constraints
10
+ freeze
10
11
  end
11
12
 
12
13
  attr_reader :object_constraints
@@ -25,14 +26,19 @@ class Literal::Types::ConstraintType
25
26
  i += 1
26
27
  end
27
28
 
29
+ result = true
30
+
28
31
  @property_constraints.each do |a, t|
29
- return false unless t === value.public_send(a)
32
+ # We intentionally don’t return early here becuase it triggers an allocation.
33
+ if result && !(t === value.public_send(a))
34
+ result = false
35
+ end
30
36
  rescue NoMethodError => e
31
37
  raise unless e.name == a && e.receiver == value
32
38
  return false
33
39
  end
34
40
 
35
- true
41
+ result
36
42
  end
37
43
 
38
44
  def >=(other)
@@ -5,6 +5,7 @@ class Literal::Types::DescendantType
5
5
 
6
6
  def initialize(type)
7
7
  @type = type
8
+ freeze
8
9
  end
9
10
 
10
11
  attr_reader :type