domainic-type 0.1.0.alpha.3.2.0 → 0.1.0.alpha.3.4.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +11 -0
  3. data/README.md +66 -10
  4. data/docs/USAGE.md +787 -0
  5. data/lib/domainic/type/accessors.rb +3 -2
  6. data/lib/domainic/type/behavior/date_time_behavior.rb +121 -37
  7. data/lib/domainic/type/behavior.rb +16 -0
  8. data/lib/domainic/type/config/registry.yml +24 -0
  9. data/lib/domainic/type/constraint/constraints/nor_constraint.rb +1 -1
  10. data/lib/domainic/type/constraint/constraints/predicate_constraint.rb +76 -0
  11. data/lib/domainic/type/definitions.rb +212 -0
  12. data/lib/domainic/type/types/core/complex_type.rb +122 -0
  13. data/lib/domainic/type/types/core/range_type.rb +47 -0
  14. data/lib/domainic/type/types/core/rational_type.rb +38 -0
  15. data/lib/domainic/type/types/core_extended/big_decimal_type.rb +34 -0
  16. data/lib/domainic/type/types/core_extended/set_type.rb +34 -0
  17. data/lib/domainic/type/types/datetime/date_time_string_type.rb +156 -0
  18. data/lib/domainic/type/types/datetime/timestamp_type.rb +50 -0
  19. data/sig/domainic/type/accessors.rbs +2 -2
  20. data/sig/domainic/type/behavior/date_time_behavior.rbs +35 -23
  21. data/sig/domainic/type/behavior.rbs +9 -0
  22. data/sig/domainic/type/constraint/constraints/predicate_constraint.rbs +56 -0
  23. data/sig/domainic/type/definitions.rbs +165 -0
  24. data/sig/domainic/type/types/core/complex_type.rbs +96 -0
  25. data/sig/domainic/type/types/core/range_type.rbs +41 -0
  26. data/sig/domainic/type/types/core/rational_type.rbs +32 -0
  27. data/sig/domainic/type/types/core_extended/big_decimal_type.rbs +27 -0
  28. data/sig/domainic/type/types/core_extended/set_type.rbs +27 -0
  29. data/sig/domainic/type/types/datetime/date_time_string_type.rbs +124 -0
  30. data/sig/domainic/type/types/datetime/timestamp_type.rbs +44 -0
  31. metadata +25 -6
@@ -77,6 +77,36 @@ module Domainic
77
77
  end
78
78
  alias _List? _Array?
79
79
 
80
+ # Creates a BigDecimalType instance.
81
+ #
82
+ # @example
83
+ # type = _BigDecimal
84
+ # type.validate(BigDecimal('123.45')) # => true
85
+ #
86
+ # @param options [Hash] additional configuration options
87
+ #
88
+ # @return [Domainic::Type::BigDecimalType] the created type
89
+ # @rbs (**__todo__ options) -> BigDecimalType
90
+ def _BigDecimal(**options)
91
+ require 'domainic/type/types/core_extended/big_decimal_type'
92
+ Domainic::Type::BigDecimalType.new(**options)
93
+ end
94
+
95
+ # Creates a nilable BigDecimalType instance.
96
+ #
97
+ # @example
98
+ # type = _BigDecimal?
99
+ # type.validate(BigDecimal('123.45')) # => true
100
+ # type.validate(nil) # => true
101
+ #
102
+ # @param options [Hash] additional configuration options
103
+ #
104
+ # @return [Domainic::Type::UnionType] the created type (BigDecimal or NilClass)
105
+ # @rbs (**__todo__ options) -> UnionType
106
+ def _BigDecimal?(**options)
107
+ _Nilable(_BigDecimal(**options))
108
+ end
109
+
80
110
  # Creates a Boolean type.
81
111
  #
82
112
  # Represents a union of TrueClass and FalseClass.
@@ -133,6 +163,35 @@ module Domainic
133
163
  end
134
164
  alias _Cuid? _CUID?
135
165
 
166
+ # Creates a ComplexType instance.
167
+ #
168
+ # @example
169
+ # type = _Complex
170
+ # type.validate(Complex(1, 2)) # => true
171
+ #
172
+ # @param options [Hash] additional configuration options
173
+ #
174
+ # @return [Domainic::Type::ComplexType] the created type
175
+ # @rbs (**__todo__ options) -> ComplexType
176
+ def _Complex(**options)
177
+ require 'domainic/type/types/core/complex_type'
178
+ Domainic::Type::ComplexType.new(**options)
179
+ end
180
+
181
+ # Creates a nilable ComplexType instance.
182
+ #
183
+ # @example
184
+ # type = _Complex?
185
+ # type.validate(Complex(1, 2)) # => true
186
+ #
187
+ # @param options [Hash] additional configuration options
188
+ #
189
+ # @return [Domainic::Type::UnionType] the created type (Complex or NilClass)
190
+ # @rbs (**__todo__ options) -> UnionType
191
+ def _Complex?(**options)
192
+ _Nilable(_Complex(**options))
193
+ end
194
+
136
195
  # Creates a DateType instance.
137
196
  #
138
197
  # DateType restricts values to valid `Date` objects.
@@ -195,6 +254,39 @@ module Domainic
195
254
  _Nilable(_DateTime(**options))
196
255
  end
197
256
 
257
+ # Creates a DateTimeStringType instance.
258
+ #
259
+ # DateTimeStringType restricts values to valid date-time strings.
260
+ #
261
+ # @example
262
+ # type = _DateTimeString
263
+ # type.validate('2024-01-01T12:00:00Z') # => true
264
+ #
265
+ # @param options [Hash] additional configuration options
266
+ #
267
+ # @return [Domainic::Type::DateTimeStringType] the created type
268
+ # @rbs (**__todo__ options) -> DateTimeStringType
269
+ def _DateTimeString(**options)
270
+ require 'domainic/type/types/datetime/date_time_string_type'
271
+ Domainic::Type::DateTimeStringType.new(**options)
272
+ end
273
+ alias _DateString _DateTimeString
274
+
275
+ # Creates a nilable DateTimeStringType instance.
276
+ #
277
+ # @example
278
+ # _DateTimeString? === '2024-01-01T12:00:00Z' # => true
279
+ # _DateTimeString? === nil # => true
280
+ #
281
+ # @param options [Hash] additional configuration options
282
+ #
283
+ # @return [Domainic::Type::UnionType] the created type (DateTimeStringType or NilClass)
284
+ # @rbs (**__todo__ options) -> UnionType
285
+ def _DateTimeString?(**options)
286
+ _Nilable(_DateTimeString(**options))
287
+ end
288
+ alias _DateString? _DateTimeString?
289
+
198
290
  # Creates a DuckType instance.
199
291
  #
200
292
  # DuckType allows specifying behavior based on method availability.
@@ -477,6 +569,95 @@ module Domainic
477
569
  end
478
570
  alias _Nullable _Nilable
479
571
 
572
+ # Creates a RangeType instance.
573
+ #
574
+ # @example
575
+ # type = _Range
576
+ # type.validate(1..10) # => true
577
+ #
578
+ # @param options [Hash] additional configuration options
579
+ #
580
+ # @return [Domainic::Type::RangeType] the created type
581
+ # @rbs (**__todo__ options) -> RangeType
582
+ def _Range(**options)
583
+ require 'domainic/type/types/core/range_type'
584
+ Domainic::Type::RangeType.new(**options)
585
+ end
586
+
587
+ # Creates a nilable RangeType instance.
588
+ #
589
+ # @example
590
+ # type = _Range?
591
+ # type.validate(1..10) # => true
592
+ #
593
+ # @param options [Hash] additional configuration options
594
+ #
595
+ # @return [Domainic::Type::UnionType] the created type (Range or NilClass)
596
+ # @rbs (**__todo__ options) -> UnionType
597
+ def _Range?(**options)
598
+ _Nilable(_Range(**options))
599
+ end
600
+
601
+ # Creates a RationalType instance.
602
+ #
603
+ # @example
604
+ # type = _Rational
605
+ # type.validate(Rational(1, 2)) # => true
606
+ #
607
+ # @param options [Hash] additional configuration options
608
+ #
609
+ # @return [Domainic::Type::RationalType] the created type
610
+ # @rbs (**__todo__ options) -> RationalType
611
+ def _Rational(**options)
612
+ require 'domainic/type/types/core/rational_type'
613
+ Domainic::Type::RationalType.new(**options)
614
+ end
615
+
616
+ # Creates a nilable RationalType instance.
617
+ #
618
+ # @example
619
+ # type = _Rational?
620
+ # type.validate(Rational(1, 2)) # => true
621
+ # type.validate(nil) # => true
622
+ #
623
+ # @param options [Hash] additional configuration options
624
+ #
625
+ # @return [Domainic::Type::UnionType] the created type (Rational or NilClass)
626
+ # @rbs (**__todo__ options) -> UnionType
627
+ def _Rational?(**options)
628
+ _Nilable(_Rational(**options))
629
+ end
630
+
631
+ # Creates a SetType instance.
632
+ #
633
+ # @example
634
+ # type = _Set
635
+ # type.validate(Set[1, 2, 3]) # => true
636
+ #
637
+ # @param options [Hash] additional configuration options
638
+ #
639
+ # @return [Domainic::Type::SetType] the created type
640
+ # @rbs (**__todo__ options) -> SetType
641
+ def _Set(**options)
642
+ require 'domainic/type/types/core_extended/set_type'
643
+ Domainic::Type::SetType.new(**options)
644
+ end
645
+
646
+ # Creates a nilable SetType instance.
647
+ #
648
+ # @example
649
+ # type = _Set?
650
+ # type.validate(Set[1, 2, 3]) # => true
651
+ # type.validate(nil) # => true
652
+ #
653
+ # @param options [Hash] additional configuration options
654
+ #
655
+ # @return [Domainic::Type::UnionType] the created type (Set or NilClass)
656
+ # @rbs (**__todo__ options) -> UnionType
657
+ def _Set?(**options)
658
+ _Nilable(_Set(**options))
659
+ end
660
+
480
661
  # Creates a StringType instance.
481
662
  #
482
663
  # @example
@@ -568,6 +749,37 @@ module Domainic
568
749
  _Nilable(_Time(**options))
569
750
  end
570
751
 
752
+ # Creates a TimestampType instance.
753
+ #
754
+ # TimestampType restricts values to valid timestamps.
755
+ #
756
+ # @example
757
+ # type = _Timestamp
758
+ # type.validate(1640995200) # => true
759
+ #
760
+ # @param options [Hash] additional configuration options
761
+ #
762
+ # @return [Domainic::Type::TimestampType] the created type
763
+ # @rbs (**__todo__ options) -> TimestampType
764
+ def _Timestamp(**options)
765
+ require 'domainic/type/types/datetime/timestamp_type'
766
+ Domainic::Type::TimestampType.new(**options)
767
+ end
768
+
769
+ # Creates a nilable TimestampType instance.
770
+ #
771
+ # @example
772
+ # _Timestamp? === 1640995200 # => true
773
+ # _Timestamp? === nil # => true
774
+ #
775
+ # @param options [Hash] additional configuration options
776
+ #
777
+ # @return [Domainic::Type::UnionType] the created type (TimestampType or NilClass)
778
+ # @rbs (**__todo__ options) -> UnionType
779
+ def _Timestamp?(**options)
780
+ _Nilable(_Timestamp(**options))
781
+ end
782
+
571
783
  # Creates a URIType instance.
572
784
  #
573
785
  # URIType restricts values to valid URIs.
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/numeric_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating and constraining `Complex` numbers.
9
+ #
10
+ # This type extends `NumericBehavior` to provide a fluent interface for numeric-specific validations such as
11
+ # polarity and divisibility checks.
12
+ #
13
+ # @example Validating a `Complex` value
14
+ # type = Domainic::Type::ComplexType.new
15
+ # type.validate!(Complex(3, 4)) # => true
16
+ #
17
+ # @example Enforcing constraints
18
+ # type = Domainic::Type::ComplexType.new
19
+ # type.being_positive.validate!(Complex(42, 0)) # => raises TypeError
20
+ #
21
+ # @author {https://aaronmallen.me Aaron Allen}
22
+ # @since 0.1.0
23
+ class ComplexType
24
+ # @rbs! extend Behavior::ClassMethods
25
+
26
+ include Behavior
27
+ include Behavior::NumericBehavior
28
+
29
+ intrinsically_constrain :self, :type, Complex, abort_on_failure: true, description: :not_described
30
+
31
+ # Constrain the Complex real to be divisible by a given divisor.
32
+ #
33
+ # @param arguments [Array<Numeric>] a list of arguments, typically one divisor
34
+ # @param options [Hash] additional options such as tolerance for floating-point checks
35
+ # @option options [Numeric] :divisor the divisor to check for divisibility
36
+ # @option options [Numeric] :tolerance the tolerance for floating-point checks
37
+ #
38
+ # @return [self] the current instance for chaining
39
+ # @rbs (*Numeric arguments, ?divisor: Numeric, ?tolerance: Numeric) -> Behavior
40
+ def being_divisible_by(*arguments, **options)
41
+ if arguments.size > 1 || (arguments.empty? && !options.key?(:divisor))
42
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 1)"
43
+ end
44
+
45
+ divisor = arguments.first || options[:divisor]
46
+ constrain :real, :divisibility, divisor, description: 'being', **options.except(:divisor)
47
+ end
48
+ alias being_multiple_of being_divisible_by
49
+ alias divisible_by being_divisible_by
50
+ alias multiple_of being_divisible_by
51
+
52
+ # Constrain the Complex real to be even.
53
+ #
54
+ # @return [self] the current instance for chaining
55
+ # @rbs () -> Behavior
56
+ def being_even
57
+ # @type self: Object & Behavior
58
+ constrain :real, :parity, :even, description: 'being'
59
+ end
60
+ alias even being_even
61
+ alias not_being_odd being_even
62
+
63
+ # Constrain the Complex real to be negative.
64
+ #
65
+ # @return [self] the current instance for chaining
66
+ # @rbs () -> Behavior
67
+ def being_negative
68
+ # @type self: Object & Behavior
69
+ constrain :real, :polarity, :negative, description: 'being'
70
+ end
71
+ alias negative being_negative
72
+ alias not_being_positive being_negative
73
+
74
+ # Constrain the Complex real value to be odd.
75
+ #
76
+ # @return [self] the current instance for chaining
77
+ # @rbs () -> Behavior
78
+ def being_odd
79
+ # @type self: Object & Behavior
80
+ constrain :real, :parity, :odd, description: 'being'
81
+ end
82
+ alias odd being_odd
83
+ alias not_being_even being_odd
84
+
85
+ # Constrain the Complex real to be positive.
86
+ #
87
+ # @return [self] the current instance for chaining
88
+ # @rbs () -> Behavior
89
+ def being_positive
90
+ # @type self: Object & Behavior
91
+ constrain :real, :polarity, :positive, description: 'being'
92
+ end
93
+ alias positive being_positive
94
+ alias not_being_negative being_positive
95
+
96
+ # Constrain the Complex real to not be divisible by a specific divisor.
97
+ #
98
+ # @note the divisor MUST be provided as an argument or in the options Hash.
99
+ #
100
+ # @param arguments [Array<Numeric>] a list of arguments, typically one divisor
101
+ # @param options [Hash] additional options such as tolerance for floating-point checks
102
+ # @option options [Numeric] :divisor the divisor to check for divisibility
103
+ # @option options [Numeric] :tolerance the tolerance for floating-point checks
104
+ #
105
+ # @return [self] the current instance for chaining
106
+ # @rbs (*Numeric arguments, ?divisor: Numeric, ?tolerance: Numeric) -> Behavior
107
+ def not_being_divisible_by(*arguments, **options)
108
+ if arguments.size > 1 || (arguments.empty? && !options.key?(:divisor))
109
+ raise ArgumentError, "wrong number of arguments (given #{arguments.size}, expected 1)"
110
+ end
111
+
112
+ # @type self: Object & Behavior
113
+ divisor = arguments.first || options[:divisor]
114
+ divisible_by = @constraints.prepare :self, :divisibility, divisor, **options.except(:divisor)
115
+ constrain :real, :not, divisible_by, concerning: :divisibility, description: 'being'
116
+ end
117
+ alias not_being_multiple_of not_being_divisible_by
118
+ alias not_divisible_by not_being_divisible_by
119
+ alias not_multiple_of not_being_divisible_by
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/enumerable_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating `Range` objects with comprehensive constraints.
9
+ #
10
+ # This class allows flexible validation of ranges, supporting type checks,
11
+ # inclusion/exclusion of values, and range-specific behaviors such as bounds validation.
12
+ # It integrates with `EnumerableBehavior` for enumerable-style validations
13
+ # (e.g., size constraints, element presence).
14
+ #
15
+ # Key features:
16
+ # - Ensures the value is a `Range`.
17
+ # - Supports constraints for range boundaries (`begin` and `end`).
18
+ # - Provides validations for inclusion and exclusion of values.
19
+ # - Leverages `EnumerableBehavior` for size and collection-style constraints.
20
+ #
21
+ # @example Basic usage
22
+ # type = RangeType.new
23
+ # type.having_bounds(1, 10) # Validates range bounds (inclusive or exclusive).
24
+ # type.containing(5) # Ensures value is within the range.
25
+ # type.not_containing(15) # Ensures value is not within the range.
26
+ #
27
+ # @example Integration with EnumerableBehavior
28
+ # type = RangeType.new
29
+ # type.having_size(10) # Validates size (total elements in the range).
30
+ # type.containing_exactly(3, 7) # Validates specific values within the range.
31
+ #
32
+ # @example Flexible boundaries
33
+ # type = RangeType.new
34
+ # type.having_bounds(1, 10, exclusive: true) # Upper and lower bounds are exclusive.
35
+ #
36
+ # @author {https://aaronmallen.me Aaron Allen}
37
+ # @since 0.1.0
38
+ class RangeType
39
+ # @rbs! extend Behavior::ClassMethods
40
+
41
+ include Behavior
42
+ include Behavior::EnumerableBehavior
43
+
44
+ intrinsically_constrain :self, :type, Range, abort_on_failure: true, description: :not_described
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/numeric_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating Rational numbers.
9
+ #
10
+ # This type ensures values are of type `Rational` and supports a range of
11
+ # constraints for numerical validation, such as positivity, negativity,
12
+ # and divisibility. It integrates with `NumericBehavior` to provide a
13
+ # consistent and fluent interface for numeric constraints.
14
+ #
15
+ # @example Validating a positive Rational number
16
+ # type = Domainic::Type::RationalType.new
17
+ # type.being_positive.validate!(Rational(3, 4)) # => true
18
+ #
19
+ # @example Validating divisibility
20
+ # type = Domainic::Type::RationalType.new
21
+ # type.being_divisible_by(1).validate!(Rational(3, 1)) # => true
22
+ #
23
+ # @example Using constraints with chaining
24
+ # type = Domainic::Type::RationalType.new
25
+ # type.being_greater_than(0).being_less_than(1).validate!(Rational(1, 2)) # => true
26
+ #
27
+ # @author {https://aaronmallen.me Aaron Allen}
28
+ # @since 0.1.0
29
+ class RationalType
30
+ # @rbs! extend Behavior::ClassMethods
31
+
32
+ include Behavior
33
+ include Behavior::NumericBehavior
34
+
35
+ intrinsically_constrain :self, :type, Rational, abort_on_failure: true, description: :not_described
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bigdecimal'
4
+ require 'domainic/type/behavior'
5
+ require 'domainic/type/behavior/numeric_behavior'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating and constraining `BigDecimal` objects.
10
+ #
11
+ # This type extends `NumericBehavior` to provide a fluent interface for numeric-specific validations such as being
12
+ # positive, being within a range, or satisfying divisibility rules.
13
+ #
14
+ # @example Validating a `BigDecimal` value
15
+ # type = Domainic::Type::BigDecimalType.new
16
+ # type.validate!(BigDecimal('3.14')) # => true
17
+ #
18
+ # @example Enforcing constraints
19
+ # type = Domainic::Type::BigDecimalType.new
20
+ # type.being_positive.validate!(BigDecimal('42')) # => true
21
+ # type.being_positive.validate!(BigDecimal('-42')) # raises TypeError
22
+ #
23
+ # @author {https://aaronmallen.me Aaron Allen}
24
+ # @since 0.1.0
25
+ class BigDecimalType
26
+ # @rbs! extend Behavior::ClassMethods
27
+
28
+ include Behavior
29
+ include Behavior::NumericBehavior
30
+
31
+ intrinsically_constrain :self, :type, BigDecimal, abort_on_failure: true, description: :not_described
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/enumerable_behavior'
5
+ require 'set'
6
+
7
+ module Domainic
8
+ module Type
9
+ # A type for validating and constraining `Set` objects.
10
+ #
11
+ # This type extends `EnumerableBehavior` to support validations and constraints
12
+ # such as being empty, containing specific elements, or having a minimum or maximum count.
13
+ #
14
+ # @example Validating a `Set` object
15
+ # type = Domainic::Type::SetType.new
16
+ # type.validate!(Set.new([1, 2, 3])) # => true
17
+ #
18
+ # @example Enforcing constraints
19
+ # type = Domainic::Type::SetType.new
20
+ # type.being_empty.validate!(Set.new) # => true
21
+ # type.being_empty.validate!(Set.new([1])) # raises TypeError
22
+ #
23
+ # @author {https://aaronmallen.me Aaron Allen}
24
+ # @since 0.1.0
25
+ class SetType
26
+ # @rbs! extend Behavior::ClassMethods
27
+
28
+ include Behavior
29
+ include Behavior::EnumerableBehavior
30
+
31
+ intrinsically_constrain :self, :type, Set, abort_on_failure: true, description: :not_described
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'domainic/type/behavior'
4
+ require 'domainic/type/behavior/date_time_behavior'
5
+
6
+ module Domainic
7
+ module Type
8
+ # A type for validating and constraining strings to match various datetime formats.
9
+ #
10
+ # This class includes behaviors for handling datetime constraints and ensures that
11
+ # a string matches one of the predefined datetime formats (e.g., ISO 8601, RFC2822).
12
+ #
13
+ # @example Validating American format
14
+ # type = Domainic::Type::DateTimeStringType.new
15
+ # type.having_american_format.validate!("01/31/2024")
16
+ #
17
+ # @example Validating ISO 8601 format
18
+ # type = Domainic::Type::DateTimeStringType.new
19
+ # type.having_iso8601_format.validate!("2024-01-01T12:00:00Z")
20
+ #
21
+ # @example Validating RFC2822 format
22
+ # type = Domainic::Type::DateTimeStringType.new
23
+ # type.having_rfc2822_format.validate!("Thu, 31 Jan 2024 13:30:00 +0000")
24
+ #
25
+ # @author {https://aaronmallen.me Aaron Allen}
26
+ # @since 0.1.0
27
+ class DateTimeStringType
28
+ # @rbs! extend Behavior::ClassMethods
29
+
30
+ include Behavior
31
+ include Behavior::DateTimeBehavior
32
+
33
+ # The `Format` module provides a set of regular expressions for validating
34
+ # various datetime formats, including ISO 8601, RFC2822, American, European,
35
+ # full month names, abbreviated month names, and 12-hour clock formats.
36
+ #
37
+ # @author {https://aaronmallen.me Aaron Allen}
38
+ # @since 0.1.0
39
+ module Format
40
+ # Matches ISO 8601 datetime formats, including:
41
+ # - `2024-01-01T12:00:00.000+00:00`
42
+ # - `2024-01-01T12:00:00+00:00`
43
+ # - `2024-01-01T12:00:00.000`
44
+ # - `2024-01-01T12:00:00`
45
+ # - `20240101T120000+0000` (basic format)
46
+ # @return [Regexp]
47
+ ISO8601 = /\A\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[-+]\d{2}:?\d{2})?)?\z/ #: Regexp
48
+
49
+ # Matches RFC2822 datetime formats, including:
50
+ # - `Thu, 31 Jan 2024 13:30:00 +0000`
51
+ # - `31 Jan 2024 13:30:00 +0000`
52
+ # @return [Regexp]
53
+ RFC2822 = /\A
54
+ (?: # Optional day of the week
55
+ (?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s+
56
+ )?
57
+ \d{1,2}\s+ # Day of the month
58
+ (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+ # Month
59
+ \d{2,4}\s+ # Year
60
+ \d{2}:\d{2} # Time in HH:MM
61
+ (?::\d{2})? # Optional seconds
62
+ \s+ # Space
63
+ (?: # Time zone
64
+ [+-]\d{4} | # Offset (e.g., +0000)
65
+ GMT | UTC # Literal GMT or UTC
66
+ )
67
+ \z/x #: Regexp
68
+
69
+ # Matches American-style dates with optional time in 12-hour or 24-hour formats, including:
70
+ # - `01/31/2024`
71
+ # - `01/31/2024 12:30:00`
72
+ # - `01/31/2024 12:30 PM`
73
+ # @return [Regexp]
74
+ AMERICAN = %r{\A\d{1,2}/\d{1,2}/\d{2,4}(?: \d{1,2}:\d{1,2}(?::\d{1,2})?(?:\s*[AaPp][Mm])?)?\z} #: Regexp
75
+
76
+ # Matches European-style dates with optional time in 24-hour format, including:
77
+ # - `31.01.2024`
78
+ # - `31.01.2024 12:30:00`
79
+ # @return [Regexp]
80
+ EUROPEAN = /\A\d{1,2}\.\d{1,2}\.\d{2,4}(?: \d{1,2}:\d{2}(?::\d{2})?)?\z/ #: Regexp
81
+
82
+ # Matches datetime formats with full month names, including:
83
+ # - `January 31, 2024 12:00:00`
84
+ # - `January 31, 2024 12:00`
85
+ # @return [Regexp]
86
+ FULL_MONTH_NAME = /\A
87
+ (?: # Full month name
88
+ (?:January|February|March|April|May|June|July|August|September|October|November|December)
89
+ )\s+\d{1,2},\s+\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?
90
+ \z/x #: Regexp
91
+
92
+ # Matches datetime formats with abbreviated month names, including:
93
+ # - `Jan 31, 2024 12:00:00`
94
+ # - `Jan 31, 2024 12:00`
95
+ # @return [Regexp]
96
+ ABBREVIATED_MONTH_NAME = /\A
97
+ \d{1,2}\s+(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec),\s+\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?
98
+ \z/x #: Regexp
99
+
100
+ # Matches datetime formats using a 12-hour clock with AM/PM, including:
101
+ # - `2024-01-01 01:30:00 PM`
102
+ # - `2024-01-01 01:30 PM`
103
+ # @return [Regexp]
104
+ TWELVE_HOUR_FORMAT = /\A\d{4}-\d{2}-\d{2}\s+\d{1,2}:\d{2}(?::\d{2})?\s+[APap][Mm]\z/x #: Regexp
105
+ end
106
+ private_constant :Format
107
+
108
+ intrinsically_constrain :self, :type, String, abort_on_failure: true, description: :not_described
109
+ intrinsically_constrain :self, :match_pattern,
110
+ Regexp.union(Format.constants.map { |name| Format.const_get(name) }),
111
+ abort_on_failure: true, concerning: :format, description: :not_described
112
+
113
+ # Constrain the type to match American-style datetime formats.
114
+ #
115
+ # @return [self] self for method chaining
116
+ # @rbs () -> self
117
+ def having_american_format
118
+ constrain :self, :match_pattern, Format::AMERICAN, concerning: :format
119
+ end
120
+ alias american having_american_format
121
+ alias having_us_format having_american_format
122
+ alias us_format having_american_format
123
+
124
+ # Constrain the type to match European-style datetime formats.
125
+ #
126
+ # @return [self] self for method chaining
127
+ # @rbs () -> self
128
+ def having_european_format
129
+ constrain :self, :match_pattern, Format::EUROPEAN, concerning: :format
130
+ end
131
+ alias european having_european_format
132
+ alias having_eu_format having_european_format
133
+ alias eu_format having_european_format
134
+
135
+ # Constrain the type to match ISO 8601 datetime formats.
136
+ #
137
+ # @return [self] self for method chaining
138
+ # @rbs () -> self
139
+ def having_iso8601_format
140
+ constrain :self, :match_pattern, Format::ISO8601, concerning: :format
141
+ end
142
+ alias iso8601 having_iso8601_format
143
+ alias iso8601_format having_iso8601_format
144
+
145
+ # Constrain the type to match RFC2822 datetime formats.
146
+ #
147
+ # @return [self] self for method chaining
148
+ # @rbs () -> self
149
+ def having_rfc2822_format
150
+ constrain :self, :match_pattern, Format::RFC2822, concerning: :format
151
+ end
152
+ alias rfc2822 having_rfc2822_format
153
+ alias rfc2822_format having_rfc2822_format
154
+ end
155
+ end
156
+ end