ruby-ulid 0.0.17 → 0.1.2

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.
@@ -0,0 +1,70 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # Copyright (C) 2021 Kenichi Kamiya
4
+
5
+ class ULID
6
+ # Currently supporting only for `subset` for actual use-case`
7
+ # Original decoding spec allows other characters.
8
+ # But I think ULID should allow `subset` of Crockford's Base32.
9
+ # See below
10
+ # * https://github.com/ulid/spec/pull/57
11
+ # * https://github.com/kachick/ruby-ulid/issues/57
12
+ # * https://github.com/kachick/ruby-ulid/issues/78
13
+ module CrockfordBase32
14
+ class SetupError < ScriptError; end
15
+
16
+ n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
17
+ raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
18
+
19
+ n32_char_by_number = {}
20
+ n32_chars.each_with_index do |char, index|
21
+ n32_char_by_number[index] = char
22
+ end
23
+ n32_char_by_number.freeze
24
+
25
+ crockford_base32_mappings = {
26
+ 'J' => 18,
27
+ 'K' => 19,
28
+ 'M' => 20,
29
+ 'N' => 21,
30
+ 'P' => 22,
31
+ 'Q' => 23,
32
+ 'R' => 24,
33
+ 'S' => 25,
34
+ 'T' => 26,
35
+ 'V' => 27,
36
+ 'W' => 28,
37
+ 'X' => 29,
38
+ 'Y' => 30,
39
+ 'Z' => 31
40
+ }.freeze
41
+
42
+ N32_CHAR_BY_CROCKFORD_BASE32_CHAR = CROCKFORD_BASE32_ENCODING_STRING.chars.map(&:freeze).each_with_object({}) do |encoding_char, map|
43
+ if n = crockford_base32_mappings[encoding_char]
44
+ char_32 = n32_char_by_number.fetch(n)
45
+ map[encoding_char] = char_32
46
+ end
47
+ end.freeze
48
+ raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
49
+ CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
50
+
51
+ CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
52
+ N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
53
+
54
+ # @api private
55
+ # @param [String] string
56
+ # @return [Integer]
57
+ def self.decode(string)
58
+ n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
59
+ n32encoded.to_i(32)
60
+ end
61
+
62
+ # @api private
63
+ # @param [Integer] integer
64
+ # @return [String]
65
+ def self.encode(integer)
66
+ n32encoded = integer.to_s(32)
67
+ n32encoded.upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0')
68
+ end
69
+ end
70
+ end
@@ -4,39 +4,60 @@
4
4
 
5
5
  class ULID
6
6
  class MonotonicGenerator
7
- # @api private
8
- attr_accessor :latest_milliseconds, :latest_entropy
7
+ # @return [ULID, nil]
8
+ attr_reader :prev
9
+
10
+ undef_method :instance_variable_set
9
11
 
10
12
  def initialize
11
- reset
13
+ @mutex = Thread::Mutex.new
14
+ @prev = nil
15
+ end
16
+
17
+ # @return [String]
18
+ def inspect
19
+ "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
12
20
  end
21
+ alias_method :to_s, :inspect
13
22
 
14
23
  # @param [Time, Integer] moment
15
24
  # @return [ULID]
16
25
  # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
17
- # @raise [ArgumentError] if the given moment(milliseconds) is negative number
26
+ # @raise [UnexpectedError] if the generated ULID is an invalid value in monotonicity spec.
27
+ # Basically will not happen. Just means this feature prefers error rather than invalid value.
18
28
  def generate(moment: ULID.current_milliseconds)
19
- milliseconds = ULID.milliseconds_from_moment(moment)
20
- raise ArgumentError, "milliseconds should not be negative: given: #{milliseconds}" if milliseconds.negative?
21
-
22
- if @latest_milliseconds < milliseconds
23
- @latest_milliseconds = milliseconds
24
- @latest_entropy = ULID.reasonable_entropy
25
- else
26
- @latest_entropy += 1
27
- end
29
+ @mutex.synchronize do
30
+ unless @prev
31
+ @prev = ULID.generate(moment: moment)
32
+ return @prev
33
+ end
28
34
 
29
- ULID.new milliseconds: @latest_milliseconds, entropy: @latest_entropy
30
- end
35
+ milliseconds = ULID.milliseconds_from_moment(moment)
31
36
 
32
- # @api private
33
- # @return [void]
34
- def reset
35
- @latest_milliseconds = 0
36
- @latest_entropy = ULID.reasonable_entropy
37
- nil
37
+ ulid = if @prev.milliseconds < milliseconds
38
+ ULID.generate(moment: milliseconds)
39
+ else
40
+ ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
41
+ end
42
+
43
+ unless ulid > @prev
44
+ base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{@prev.inspect}"
45
+ additional_information = if Thread.list == [Thread.main]
46
+ '# NOTE: looks single thread only exist'
47
+ else
48
+ '# NOTE: ran on multi threads, so this might from concurrency issue'
49
+ end
50
+
51
+ raise UnexpectedError, base_message + additional_information
52
+ end
53
+
54
+ @prev = ulid
55
+ ulid
56
+ end
38
57
  end
39
58
 
59
+ undef_method :freeze
60
+
40
61
  # @raise [TypeError] always raises exception and does not freeze self
41
62
  # @return [void]
42
63
  def freeze
data/lib/ulid/uuid.rb ADDED
@@ -0,0 +1,38 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # Copyright (C) 2021 Kenichi Kamiya
4
+
5
+ # Extracted features around UUID from some reasons
6
+ # ref:
7
+ # * https://github.com/kachick/ruby-ulid/issues/105
8
+ # * https://github.com/kachick/ruby-ulid/issues/76
9
+ class ULID
10
+ # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
11
+ UUIDV4_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i.freeze
12
+ private_constant :UUIDV4_PATTERN
13
+
14
+ # @param [String, #to_str] uuid
15
+ # @return [ULID]
16
+ # @raise [ParserError] if the given format is not correct for UUIDv4 specs
17
+ def self.from_uuidv4(uuid)
18
+ uuid = String.try_convert(uuid)
19
+ raise ArgumentError, 'ULID.from_uuidv4 takes only strings' unless uuid
20
+
21
+ prefix_trimmed = uuid.delete_prefix('urn:uuid:')
22
+ unless UUIDV4_PATTERN.match?(prefix_trimmed)
23
+ raise ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`"
24
+ end
25
+
26
+ normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
27
+ from_integer(normalized.to_i(16))
28
+ end
29
+
30
+ # @return [String]
31
+ def to_uuidv4
32
+ # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
33
+ array = octets.pack('C*').unpack('NnnnnN')
34
+ array[2] = (array[2] & 0x0fff) | 0x4000
35
+ array[3] = (array[3] & 0x3fff) | 0x8000
36
+ ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
37
+ end
38
+ end
data/lib/ulid/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.0.17'
5
+ VERSION = '0.1.2'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -1,10 +1,9 @@
1
- # Classes
2
- class ULID
1
+ class ULID < Object
3
2
  VERSION: String
4
- ENCODING_CHARS: Array[String]
5
- TIMESTAMP_PART_LENGTH: 10
6
- RANDOMNESS_PART_LENGTH: 16
7
- ENCODED_ID_LENGTH: 26
3
+ CROCKFORD_BASE32_ENCODING_STRING: String
4
+ TIMESTAMP_ENCODED_LENGTH: 10
5
+ RANDOMNESS_ENCODED_LENGTH: 16
6
+ ENCODED_LENGTH: 26
8
7
  TIMESTAMP_OCTETS_LENGTH: 6
9
8
  RANDOMNESS_OCTETS_LENGTH: 10
10
9
  OCTETS_LENGTH: 16
@@ -12,16 +11,13 @@ class ULID
12
11
  MAX_ENTROPY: 1208925819614629174706175
13
12
  MAX_INTEGER: 340282366920938463463374607431768211455
14
13
  TIME_FORMAT_IN_INSPECT: '%Y-%m-%d %H:%M:%S.%3N %Z'
15
- PATTERN: Regexp
16
- STRICT_PATTERN: Regexp
14
+ RANDOM_INTEGER_GENERATOR: ^() -> Integer
15
+ PATTERN_WITH_CROCKFORD_BASE32_SUBSET: Regexp
16
+ STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET: Regexp
17
+ SCANNING_PATTERN: Regexp
17
18
  UUIDV4_PATTERN: Regexp
18
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
19
- CROCKFORD_BASE32_CHAR_PATTERN: Regexp
20
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
21
- N32_CHAR_PATTERN: Regexp
22
19
  MIN: ULID
23
20
  MAX: ULID
24
- UNDEFINED: BasicObject
25
21
  include Comparable
26
22
 
27
23
  # The `moment` is a `Time` or `Intger of the milliseconds`
@@ -36,88 +32,458 @@ class ULID
36
32
  class ParserError < Error
37
33
  end
38
34
 
39
- class SetupError < ScriptError
35
+ class UnexpectedError < Error
36
+ end
37
+
38
+ module CrockfordBase32
39
+ class SetupError < ScriptError
40
+ end
41
+
42
+ N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
43
+ CROCKFORD_BASE32_CHAR_PATTERN: Regexp
44
+ CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
45
+ N32_CHAR_PATTERN: Regexp
46
+
47
+ # A pribate API. Should not be used in your code.
48
+ def self.encode: (Integer integer) -> String
49
+
50
+ # A pribate API. Should not be used in your code.
51
+ def self.decode: (String string) -> Integer
40
52
  end
41
53
 
42
54
  class MonotonicGenerator
43
- attr_accessor latest_milliseconds: Integer
44
- attr_accessor latest_entropy: Integer
55
+ @mutex: Thread::Mutex
56
+ attr_reader prev: ULID | nil
57
+
58
+ # A pribate API. Should not be used in your code.
45
59
  def initialize: -> void
60
+
61
+ # See [How to keep `Sortable` even if in same timestamp](https://github.com/kachick/ruby-ulid#how-to-keep-sortable-even-if-in-same-timestamp)
46
62
  def generate: (?moment: moment) -> ULID
47
- def reset: -> void
63
+ def inspect: -> String
64
+ alias to_s inspect
48
65
  def freeze: -> void
49
66
  end
50
67
 
68
+ interface _ToULID
69
+ def to_ulid: () -> ULID
70
+ end
71
+
51
72
  type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
52
73
  type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
53
74
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
75
+ type period = Range[Time] | Range[nil] | Range[ULID]
54
76
 
55
- @milliseconds: Integer
56
- @entropy: Integer
57
77
  @string: String?
58
- @integer: Integer?
59
- @octets: octets?
60
- @timestamp_octets: timestamp_octets?
61
- @randomness_octets: randomness_octets?
78
+ @integer: Integer
62
79
  @timestamp: String?
63
80
  @randomness: String?
64
81
  @inspect: String?
65
82
  @time: Time?
66
- @next: ULID?
67
- @pattern: Regexp?
68
- @strict_pattern: Regexp?
69
- @uuidv4: String?
70
- @matchdata: MatchData?
71
83
 
72
- def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
84
+ # Retuns a ULID
85
+ #
86
+ # They are sortable when generated in different timestamp with milliseconds precision
87
+ #
88
+ # ```ruby
89
+ # ulids = 1000.times.map do
90
+ # sleep(0.001)
91
+ # ULID.generate
92
+ # end
93
+ # ulids.uniq(&:to_time).size #=> 1000
94
+ # ulids.sort == ulids #=> true
95
+ # ```
96
+ #
97
+ # `ULID.generate` can take fixed `Time` instance.
98
+ # See also the short hand [ULID.at](https://kachick.github.io/ruby-ulid/ULID.html#at-class_method)
99
+ #
100
+ # ```ruby
101
+ # time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
102
+ # ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
103
+ # ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
104
+ # ```
105
+ #
106
+ # The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
107
+ #
108
+ # ```ruby
109
+ # ulids = 10000.times.map do
110
+ # ULID.generate
111
+ # end
112
+ # ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in environment)
113
+ # ulids.sort == ulids #=> false
114
+ # ```
115
+ #
116
+ # If you want to keep sortable even if in same timestamp, See also [ULID::MonotonicGenerator](https://github.com/kachick/ruby-ulid#how-to-keep-sortable-even-if-in-same-timestamp)
117
+ #
118
+ def self.generate: (?moment: moment, ?entropy: Integer) -> self
119
+
120
+ # Shorthand of `ULID.generate(moment: Time)`
121
+ # See also [ULID.generate](https://kachick.github.io/ruby-ulid/ULID.html#generate-class_method)
122
+ #
123
+ # ```ruby
124
+ # time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
125
+ # ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
126
+ #
127
+ # ulids = 1000.times.map do |n|
128
+ # ULID.at(time + n)
129
+ # end
130
+ # ulids.sort == ulids #=> true
131
+ # ```
132
+ def self.at: (Time time) -> self
133
+
134
+ # A pribate API. Should not be used in your code.
73
135
  def self.current_milliseconds: -> Integer
74
- def self.milliseconds_from_time: (Time time) -> Integer
136
+
137
+ # A pribate API. Should not be used in your code.
75
138
  def self.milliseconds_from_moment: (moment moment) -> Integer
76
- def self.range: (Range[Time] | Range[nil] time_range) -> Range[ULID]
139
+
140
+ # `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
141
+ #
142
+ # ```ruby
143
+ # include_end = ulid1..ulid2
144
+ # exclude_end = ulid1...ulid2
145
+ #
146
+ # ulids.grep(one_of_the_above)
147
+ # ulids.grep_v(one_of_the_above)
148
+ # ```
149
+ #
150
+ # When want to filter ULIDs with `Time`, we should consider to handle the precision.
151
+ # So this gem provides `ULID.range` to generate reasonable `Range[ULID]` from `Range[Time]`
152
+ #
153
+ # ```ruby
154
+ # # Both of below, The begin of `Range[ULID]` will be the minimum in the floored milliseconds of the time1
155
+ # include_end = ULID.range(time1..time2) #=> The end of `Range[ULID]` will be the maximum in the floored milliseconds of the time2
156
+ # exclude_end = ULID.range(time1...time2) #=> The end of `Range[ULID]` will be the minimum in the floored milliseconds of the time2
157
+ #
158
+ # # Below patterns are acceptable
159
+ # pinpointing = ULID.range(time1..time1) #=> This will match only for all IDs in `time1`
160
+ # until_the_end = ULID.range(..time1) #=> This will match only for all IDs upto `time1` (The `nil` starting `Range` can be used since Ruby 2.7)
161
+ # until_the_end = ULID.range(ULID.min.to_time..time1) #=> This is same as above for Ruby 2.6
162
+ # until_the_ulid_limit = ULID.range(time1..) # This will match only for all IDs from `time1` to max value of the ULID limit
163
+ #
164
+ # # So you can use the generated range objects as below
165
+ # ulids.grep(one_of_the_above)
166
+ # ulids.grep_v(one_of_the_above)
167
+ # #=> I hope the results should be actually you want!
168
+ # ```
169
+ #
170
+ def self.range: (period period) -> Range[ULID]
171
+
172
+ # Returns new `Time` with truncating excess precisions in ULID spec.
173
+ #
174
+ # ```ruby
175
+ # time = Time.at(946684800, Rational('123456.789')).utc
176
+ # #=> 2000-01-01 00:00:00.123456789 UTC
177
+ # ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
178
+ # ```
77
179
  def self.floor: (Time time) -> Time
78
- def self.reasonable_entropy: -> Integer
79
- def self.parse: (String string) -> ULID
180
+
181
+ # Get ULID instance from encoded String.
182
+ #
183
+ # ```ruby
184
+ # ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV')
185
+ # #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
186
+ # ```
187
+ def self.parse: (_ToStr string) -> self
188
+
189
+ # ```ruby
190
+ # # Currently experimental feature, so needed to load the extension.
191
+ # require 'ulid/uuid'
192
+ #
193
+ # # Basically reversible
194
+ # ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
195
+ # #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
196
+ # ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
197
+ # ```
198
+ #
199
+ # See also [Why this is experimental?](https://github.com/kachick/ruby-ulid/issues/76)
80
200
  def self.from_uuidv4: (String uuid) -> ULID
81
- def self.from_integer: (Integer integer) -> ULID
82
- def self.min: (?moment: moment) -> ULID
83
- def self.max: (?moment: moment) -> ULID
84
- def self.sample: -> ULID
85
- | (Integer number) -> Array[ULID]
86
- def self.valid?: (untyped string) -> bool
87
- def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
88
- | (String string) { (ULID ulid) -> void } -> singleton(ULID)
89
- def self.octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
90
- def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
201
+ def self.from_integer: (Integer integer) -> self
202
+
203
+ # Returns termination values for ULID spec.
204
+ #
205
+ # ```ruby
206
+ # ULID.min
207
+ # #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000)
208
+ # ```
209
+ #
210
+ # It can take `Time` instance as an optional argument.
211
+ # Then returns ULID that has minimum value of randomness part in the timestamp.
212
+ #
213
+ # ```ruby
214
+ # time = Time.at(946684800, Rational('123456.789')).utc
215
+ # #=> 2000-01-01 00:00:00.123456789 UTC
216
+ # ULID.min(time)
217
+ # #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
218
+ # ```
219
+ #
220
+ # See also [ULID.max](https://kachick.github.io/ruby-ulid/ULID.html#max-class_method)
221
+ def self.min: (?moment moment) -> ULID
222
+
223
+ # Returns termination values for ULID spec.
224
+ #
225
+ # ```ruby
226
+ # ULID.max
227
+ # #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZZZZZZZZZZZZZZZZZ)
228
+ # ```
229
+ #
230
+ # It can take `Time` instance as an optional argument.
231
+ # Then returns ULID that has maximum value of randomness part in the timestamp.
232
+ #
233
+ # ```ruby
234
+ # time = Time.at(946684800, Rational('123456.789')).utc
235
+ # #=> 2000-01-01 00:00:00.123456789 UTC
236
+ # ULID.max(time)
237
+ # #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
238
+ # ```
239
+ #
240
+ # See also [ULID.min](https://kachick.github.io/ruby-ulid/ULID.html#min-class_method)
241
+ def self.max: (?moment moment) -> ULID
242
+
243
+ # Returns random ULIDs.
244
+ #
245
+ # Basically ignores generating time.
246
+ #
247
+ # ```ruby
248
+ # ULID.sample #=> ULID(2545-07-26 06:51:20.085 UTC: 0GGKQ45GMNMZR6N8A8GFG0ZXST)
249
+ # ULID.sample #=> ULID(5098-07-26 21:31:06.946 UTC: 2SSBNGGYA272J7BMDCG4Z6EEM5)
250
+ # ULID.sample(0) #=> []
251
+ # ULID.sample(1) #=> [ULID(2241-04-16 03:31:18.440 UTC: 07S52YWZ98AZ8T565MD9VRYMQH)]
252
+ # ULID.sample(5)
253
+ # #=>
254
+ # #[ULID(5701-04-29 12:41:19.647 UTC: 3B2YH2DV0ZYDDATGTYSKMM1CMT),
255
+ # # ULID(2816-08-01 01:21:46.612 UTC: 0R9GT6RZKMK3RG02Q2HAFVKEY2),
256
+ # # ULID(10408-10-05 17:06:27.848 UTC: 7J6CPTEEC86Y24EQ4F1Y93YYN0),
257
+ # # ULID(2741-09-02 16:24:18.803 UTC: 0P4Q4V34KKAJW46QW47WQB5463),
258
+ # # ULID(2665-03-16 14:50:22.724 UTC: 0KYFW9DWM4CEGFNTAC6YFAVVJ6)]
259
+ # ```
260
+ #
261
+ # You can specify a range object for the timestamp restriction, See also [ULID.range](https://kachick.github.io/ruby-ulid/ULID.html#range-class_method).
262
+ #
263
+ # ```ruby
264
+ # ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
265
+ # ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
266
+ # ulids = ULID.sample(1000, period: ulid1..ulid2)
267
+ # ulids.uniq.size #=> 1000
268
+ # ulids.take(10)
269
+ # #=>
270
+ # #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
271
+ # # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
272
+ # # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
273
+ # # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
274
+ # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
275
+ # # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
276
+ # # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
277
+ # # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
278
+ # # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
279
+ # # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
280
+ # ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
281
+ # #=>
282
+ # # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
283
+ # # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
284
+ # # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
285
+ # # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
286
+ # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
287
+ # # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
288
+ # # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
289
+ # # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
290
+ # # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
291
+ # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
292
+ # ```
293
+ def self.sample: (?period: period) -> self
294
+ | (Integer number, ?period: period) -> Array[self]
295
+ def self.valid?: (untyped) -> bool
296
+
297
+ # Returns parsed ULIDs from given String for rough operations.
298
+ #
299
+ # ```ruby
300
+ # json =<<'EOD'
301
+ # {
302
+ # "id": "01F4GNAV5ZR6FJQ5SFQC7WDSY3",
303
+ # "author": {
304
+ # "id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
305
+ # "name": "kachick"
306
+ # },
307
+ # "title": "My awesome blog post",
308
+ # "comments": [
309
+ # {
310
+ # "id": "01F4GNCNC3CH0BCRZBPPDEKBKS",
311
+ # "commenter": {
312
+ # "id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
313
+ # "name": "kachick"
314
+ # }
315
+ # },
316
+ # {
317
+ # "id": "01F4GNCXAMXQ1SGBH5XCR6ZH0M",
318
+ # "commenter": {
319
+ # "id": "01F4GND4RYYSKNAADHQ9BNXAWJ",
320
+ # "name": "pankona"
321
+ # }
322
+ # }
323
+ # ]
324
+ # }
325
+ # EOD
326
+ #
327
+ # ULID.scan(json).to_a
328
+ # #=>
329
+ # # [ULID(2021-04-30 05:51:57.119 UTC: 01F4GNAV5ZR6FJQ5SFQC7WDSY3),
330
+ # # ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
331
+ # # ULID(2021-04-30 05:52:56.707 UTC: 01F4GNCNC3CH0BCRZBPPDEKBKS),
332
+ # # ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
333
+ # # ULID(2021-04-30 05:53:04.852 UTC: 01F4GNCXAMXQ1SGBH5XCR6ZH0M),
334
+ # # ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
335
+ # ```
336
+ def self.scan: (_ToStr string) -> Enumerator[self, singleton(ULID)]
337
+ | (_ToStr string) { (self ulid) -> void } -> singleton(ULID)
338
+ def self.from_milliseconds_and_entropy: (milliseconds: Integer, entropy: Integer) -> self
339
+ def self.try_convert: (_ToULID) -> ULID
340
+ | (untyped) -> nil
341
+
342
+ # ```ruby
343
+ # ulid = ULID.generate
344
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
345
+ # ulid.milliseconds #=> 1619544442826
346
+ # ```
91
347
  attr_reader milliseconds: Integer
92
348
  attr_reader entropy: Integer
93
- def initialize: (milliseconds: Integer, entropy: Integer) -> void
349
+
350
+ # ```ruby
351
+ # ulid = ULID.generate
352
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
353
+ # ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
354
+ # ```
94
355
  def to_s: -> String
356
+
357
+ # ```ruby
358
+ # ulid = ULID.generate
359
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
360
+ # ulid.to_i #=> 1957909092946624190749577070267409738
361
+ # ```
95
362
  def to_i: -> Integer
96
363
  alias hash to_i
364
+
365
+ # Basically same as String based sort.
366
+ #
367
+ # ```ruby
368
+ # ulids = ULID.sample(10000); nil
369
+ # ulids.map(&:to_s).sort == ulids.sort.map(&:to_s)
370
+ # #=> true
371
+ # ```
372
+ #
373
+ # To be precise, this sorting unaffected with `case sensitive or not` and might handle [ulid/spec#57](https://github.com/ulid/spec/pull/57) in future. So preferable than `lexicographically sortable` in actual case.
374
+ #
97
375
  def <=>: (ULID other) -> Integer
98
- | (untyped other) -> Integer?
376
+ | (untyped other) -> nil
377
+
378
+ # ```ruby
379
+ # ulid = ULID.generate
380
+ # ulid.inspect #=> "ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)"
381
+ # ```
99
382
  def inspect: -> String
383
+
384
+ # ```ruby
385
+ # ULID.parse('4NNB20D9C1ME2NGMTX51ERZJX0') == ULID.parse('4nnb20d9c1me2ngmtx51erzjx0')
386
+ # #=> true
387
+ # ```
100
388
  def eql?: (untyped other) -> bool
101
389
  alias == eql?
102
390
  def ===: (untyped other) -> bool
391
+
392
+ # ```ruby
393
+ # ulid = ULID.generate
394
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
395
+ # ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
396
+ # ```
103
397
  def to_time: -> Time
398
+
399
+ # ```ruby
400
+ # ulid = ULID.generate
401
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
402
+ # ulid.timestamp #=> "01F4A5Y1YA"
403
+ # ```
104
404
  def timestamp: -> String
405
+
406
+ # ```ruby
407
+ # ulid = ULID.generate
408
+ # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
409
+ # ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
410
+ # ```
105
411
  def randomness: -> String
106
- def pattern: -> Regexp
107
- def strict_pattern: -> Regexp
412
+
413
+ # Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
414
+ # Because the returning values are not fixed.
415
+ def patterns: -> Hash[Symbol, Regexp | String]
108
416
  def octets: -> octets
109
417
  def timestamp_octets: -> timestamp_octets
110
418
  def randomness_octets: -> randomness_octets
419
+
420
+ # ```ruby
421
+ # # Currently experimental feature, so needed to load the extension.
422
+ # require 'ulid/uuid'
423
+ #
424
+ # # Basically reversible
425
+ # ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
426
+ # #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
427
+ # ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
428
+ # ```
429
+ #
430
+ # See also [Why this is experimental?](https://github.com/kachick/ruby-ulid/issues/76)
111
431
  def to_uuidv4: -> String
112
- def next: -> ULID?
113
- alias succ next
432
+
433
+ # Returns next(successor) ULID.
434
+ # Especially `ULID#succ` makes it possible `Range[ULID]#each`.
435
+ #
436
+ # NOTE: But basically `Range[ULID]#each` should not be used, incrementing 128 bits IDs are not reasonable operation in most case
437
+ #
438
+ # ```ruby
439
+ # ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
440
+ # ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM0000000000000000"
441
+ # ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
442
+ # ```
443
+ #
444
+ # See also [ULID#pred](https://kachick.github.io/ruby-ulid/ULID.html#pred-instance_method)
445
+ def succ: -> ULID?
446
+ alias next succ
447
+
448
+ # Returns predecessor ULID.
449
+ #
450
+ # ```ruby
451
+ # ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
452
+ # ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZZZ"
453
+ # ULID.parse('00000000000000000000000000').pred #=> nil
454
+ # ```
455
+ #
456
+ # See also [ULID#succ](https://kachick.github.io/ruby-ulid/ULID.html#succ-instance_method)
114
457
  def pred: -> ULID?
115
458
  def freeze: -> self
116
459
 
460
+ # Returns `self`
461
+ def to_ulid: -> self
462
+
463
+ # Returns `self`. Not a new instance.
464
+ def dup: -> self
465
+
466
+ # Same API as [Object#clone](https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/core/object.rbs#L79)
467
+ # But actually returns `self`. Not a new instance.
468
+ def clone: (?freeze: bool) -> self
469
+
117
470
  private
118
- def self.convert_crockford_base32_to_n32: (String) -> String
119
- def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
120
- def convert_n32_to_crockford_base32: (String) -> String
121
- def matchdata: -> MatchData
471
+
472
+ # A pribate API. Should not be used in your code.
473
+ def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> self
474
+
475
+ # A pribate API. Should not be used in your code.
476
+ def self.reasonable_entropy: -> Integer
477
+
478
+ # A pribate API. Should not be used in your code.
479
+ def self.milliseconds_from_time: (Time time) -> Integer
480
+
481
+ # A pribate API. Should not be used in your code.
482
+ def self.safe_get_class_name: (untyped object) -> String
483
+
484
+ # A pribate API. Should not be used in your code.
485
+ def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
486
+
487
+ # A pribate API. Should not be used in your code.
122
488
  def cache_all_instance_variables: -> void
123
489
  end