ruby-ulid 0.0.17 → 0.0.18

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: 29a9e3cd7ec91b93c24691976fef74ea5ab0cbc8f4fa237f449fa9322e42c6d2
4
- data.tar.gz: 9a3c0e12e7d2fe8b6057662ebf6a392c749f7254ee41c4e77221a2e9f45ff6ac
3
+ metadata.gz: 8b1641b0894c0140c3a9c6f59fa8abcfca386362ad1aaa92fe196f3bf5f75cf1
4
+ data.tar.gz: 932fa04dceb504330289d4236f9670fecd702989504bdd402873d60c0bb1c0e5
5
5
  SHA512:
6
- metadata.gz: 53a12c330c32f76f13e651cef96ce29a6a442cf3dc7742faf2409a0ee17c7dfdcee1031c17696cd51d7bd3cf5b22b2df34b8be5a203cd1d4fd5df079eb357b82
7
- data.tar.gz: f6c8f8850272353bbd32cc15d437d0b021c13d9bc93c5c988255d6c50fe21ff41aef5b719261ca95b00c129024fe325e2d3c8683c70f9b55e89a09a261fff31f
6
+ metadata.gz: f073c7b240ef96b511c84c6bc5649c483fc97c62a3892f58531b6f36e8235e49dcd865433fe522eec48a3532c98c4deec8713f453a4b2a117157788eabddaf6b
7
+ data.tar.gz: a2486bbefd62d0d019c15044c9d67b6aabc33e370f4ee88239c1e2b33a40738c736fcc9221dd3ce457eabf83a3580be5dc501fef0c359345a453331bdd62d139
data/README.md CHANGED
@@ -49,7 +49,7 @@ Should be installed!
49
49
  Add this line to your application/library's `Gemfile` is needed in basic use-case
50
50
 
51
51
  ```ruby
52
- gem 'ruby-ulid', '0.0.16'
52
+ gem 'ruby-ulid', '0.0.18'
53
53
  ```
54
54
 
55
55
  ### Generator and Parser
@@ -62,7 +62,11 @@ require 'ulid'
62
62
 
63
63
  ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
64
64
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
65
+ ulid.milliseconds #=> 1619544442826
65
66
  ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
67
+ ulid.timestamp #=> "01F4A5Y1YA"
68
+ ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
69
+ ulid.to_i #=> 1957909092946624190749577070267409738
66
70
  ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
67
71
  ```
68
72
 
@@ -86,15 +90,16 @@ ulids.uniq(&:to_time).size #=> 1000
86
90
  ulids.sort == ulids #=> true
87
91
  ```
88
92
 
89
- `ULID.generate` can take fixed `Time` instance
93
+ `ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`
90
94
 
91
95
  ```ruby
92
96
  time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
93
97
  ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
94
98
  ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
99
+ ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
95
100
 
96
101
  ulids = 1000.times.map do |n|
97
- ULID.generate(moment: time + n)
102
+ ULID.at(time + n)
98
103
  end
99
104
  ulids.sort == ulids #=> true
100
105
  ```
@@ -173,6 +178,13 @@ ulids.grep_v(one_of_the_above)
173
178
  #=> I hope the results should be actually you want!
174
179
  ```
175
180
 
181
+ If you want to manually handle the Time objects, `ULID.floor` returns new `Time` with truncating excess precisions in ULID spec.
182
+
183
+ ```ruby
184
+ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.123456789 UTC
185
+ ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
186
+ ```
187
+
176
188
  ### Scanner for string (e.g. `JSON`)
177
189
 
178
190
  For rough operations, `ULID.scan` might be useful.
@@ -215,6 +227,18 @@ ULID.scan(json).to_a
215
227
  # ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
216
228
  ```
217
229
 
230
+ `ULID#patterns` is a util for text based operations.
231
+ The results and spec are not fixed. Should not be used except snippets/console operation
232
+
233
+ ```ruby
234
+ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
235
+ #=> returns like a fallowing Hash
236
+ {
237
+ named_captures: /(?<timestamp>01F4GNBXW1)(?<randomness>AM2KWW52PVT3ZY9X)/i,
238
+ strict_named_captures: /\A(?<timestamp>01F4GNBXW1)(?<randomness>AM2KWW52PVT3ZY9X)\z/i
239
+ }
240
+ ```
241
+
218
242
  ### Some methods to help manipulations
219
243
 
220
244
  `ULID.min` and `ULID.max` return termination values for ULID spec.
@@ -266,9 +290,12 @@ ULID.sample(5)
266
290
  ### UUIDv4 converter for migration use-cases
267
291
 
268
292
  `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
269
- The imported timestamp is meaningless. So ULID's benefit will lost
293
+ The imported timestamp is meaningless. So ULID's benefit will lost.
270
294
 
271
295
  ```ruby
296
+ # Currently experimental feature, so needed to load the extension.
297
+ require 'ulid/uuid'
298
+
272
299
  # Basically reversible
273
300
  ulid = ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39') #=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
274
301
  ulid.to_uuidv4 #=> "0983d0a2-ff15-4d83-8f37-7dd945b5aa39"
@@ -322,7 +349,7 @@ Major methods can be replaced as below.
322
349
  -ULID.generate
323
350
  +ULID.generate.to_s
324
351
  -ULID.at(time)
325
- +ULID.generate(moment: time).to_s
352
+ +ULID.at(time).to_s
326
353
  -ULID.time(string)
327
354
  +ULID.parse(string).to_time
328
355
  -ULID.min_ulid_at(time)
data/lib/ulid.rb CHANGED
@@ -17,33 +17,44 @@ class ULID
17
17
  class ParserError < Error; end
18
18
  class SetupError < ScriptError; end
19
19
 
20
+ # `Subset` of Crockford's Base32. Just excluded I, L, O, U, -.
21
+ # refs:
22
+ # * https://www.crockford.com/base32.html
23
+ # * https://github.com/ulid/spec/pull/57
24
+ # * https://github.com/kachick/ruby-ulid/issues/57
20
25
  encoding_string = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
21
- # Crockford's Base32. Excluded I, L, O, U.
22
- # @see https://www.crockford.com/base32.html
23
- ENCODING_CHARS = encoding_string.chars.map(&:freeze).freeze
26
+ encoding_chars = encoding_string.chars.map(&:freeze).freeze
24
27
 
25
- TIMESTAMP_PART_LENGTH = 10
26
- RANDOMNESS_PART_LENGTH = 16
27
- ENCODED_ID_LENGTH = TIMESTAMP_PART_LENGTH + RANDOMNESS_PART_LENGTH
28
+ TIMESTAMP_ENCODED_LENGTH = 10
29
+ RANDOMNESS_ENCODED_LENGTH = 16
30
+ ENCODED_LENGTH = TIMESTAMP_ENCODED_LENGTH + RANDOMNESS_ENCODED_LENGTH
28
31
  TIMESTAMP_OCTETS_LENGTH = 6
29
32
  RANDOMNESS_OCTETS_LENGTH = 10
30
33
  OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
31
34
  MAX_MILLISECONDS = 281474976710655
32
35
  MAX_ENTROPY = 1208925819614629174706175
33
36
  MAX_INTEGER = 340282366920938463463374607431768211455
34
- PATTERN = /(?<timestamp>[0-7][#{encoding_string}]{#{TIMESTAMP_PART_LENGTH - 1}})(?<randomness>[#{encoding_string}]{#{RANDOMNESS_PART_LENGTH}})/i.freeze
37
+ PATTERN = /(?<timestamp>[0-7][#{encoding_string}]{#{TIMESTAMP_ENCODED_LENGTH - 1}})(?<randomness>[#{encoding_string}]{#{RANDOMNESS_ENCODED_LENGTH}})/i.freeze
35
38
  STRICT_PATTERN = /\A#{PATTERN.source}\z/i.freeze
36
39
 
37
- # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
38
- 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
39
-
40
40
  # Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
41
41
  # @see https://bugs.ruby-lang.org/issues/15958
42
42
  TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
43
43
 
44
44
  UNDEFINED = BasicObject.new
45
+ # @return [String]
46
+ def UNDEFINED.to_s
47
+ 'ULID::UNDEFINED'
48
+ end
49
+
50
+ # @return [String]
51
+ def UNDEFINED.inspect
52
+ to_s
53
+ end
45
54
  Kernel.instance_method(:freeze).bind(UNDEFINED).call
46
55
 
56
+ private_class_method :new
57
+
47
58
  # @param [Integer, Time] moment
48
59
  # @param [Integer] entropy
49
60
  # @return [ULID]
@@ -51,6 +62,14 @@ class ULID
51
62
  new milliseconds: milliseconds_from_moment(moment), entropy: entropy
52
63
  end
53
64
 
65
+ # Short hand of `ULID.generate(moment: time)`
66
+ # @param [Time] time
67
+ # @return [ULID]
68
+ def self.at(time)
69
+ raise ArgumentError, 'ULID.at takes only `Time` instance' unless Time === time
70
+ new milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy
71
+ end
72
+
54
73
  # @param [Integer, Time] moment
55
74
  # @return [ULID]
56
75
  def self.min(moment: 0)
@@ -101,38 +120,23 @@ class ULID
101
120
  self
102
121
  end
103
122
 
104
- # @param [String, #to_str] uuid
105
- # @return [ULID]
106
- # @raise [ParserError] if the given format is not correct for UUIDv4 specs
107
- def self.from_uuidv4(uuid)
108
- begin
109
- uuid = uuid.to_str
110
- prefix_trimmed = uuid.sub(/\Aurn:uuid:/, '')
111
- raise "given string is not matched to pattern #{UUIDV4_PATTERN.inspect}" unless UUIDV4_PATTERN.match?(prefix_trimmed)
112
- normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
113
- from_integer(normalized.to_i(16))
114
- rescue => err
115
- raise ParserError, "parsing failure as #{err.inspect} for given #{uuid}"
116
- end
117
- end
118
-
119
123
  # @param [Integer, #to_int] integer
120
124
  # @return [ULID]
121
125
  # @raise [OverflowError] if the given integer is larger than the ULID limit
122
126
  # @raise [ArgumentError] if the given integer is negative number
123
- # @todo Need optimized for performance
124
127
  def self.from_integer(integer)
125
128
  integer = integer.to_int
126
129
  raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
127
130
  raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
128
131
 
129
- octets = octets_from_integer(integer, length: OCTETS_LENGTH).freeze
130
- time_octets = octets.slice(0, TIMESTAMP_OCTETS_LENGTH).freeze
131
- randomness_octets = octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH).freeze
132
- milliseconds = inverse_of_digits(time_octets)
133
- entropy = inverse_of_digits(randomness_octets)
132
+ n32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
133
+ n32encoded_timestamp = n32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
134
+ n32encoded_randomness = n32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
135
+
136
+ milliseconds = n32encoded_timestamp.to_i(32)
137
+ entropy = n32encoded_randomness.to_i(32)
134
138
 
135
- new milliseconds: milliseconds, entropy: entropy
139
+ new milliseconds: milliseconds, entropy: entropy, integer: integer
136
140
  end
137
141
 
138
142
  # @param [Range<Time>, Range<nil>] time_range
@@ -239,7 +243,7 @@ class ULID
239
243
  'Z' => 31
240
244
  }.freeze
241
245
 
242
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR = ENCODING_CHARS.each_with_object({}) do |encoding_char, map|
246
+ N32_CHAR_BY_CROCKFORD_BASE32_CHAR = encoding_chars.each_with_object({}) do |encoding_char, map|
243
247
  if n = crockford_base32_mappings[encoding_char]
244
248
  char_32 = n32_char_by_number.fetch(n)
245
249
  map[encoding_char] = char_32
@@ -258,21 +262,12 @@ class ULID
258
262
  begin
259
263
  string = string.to_str
260
264
  raise "given argument does not match to `#{STRICT_PATTERN.inspect}`" unless STRICT_PATTERN.match?(string)
261
- n32encoded = convert_crockford_base32_to_n32(string.upcase)
262
- timestamp = n32encoded.slice(0, TIMESTAMP_PART_LENGTH)
263
- randomness = n32encoded.slice(TIMESTAMP_PART_LENGTH, RANDOMNESS_PART_LENGTH)
264
- milliseconds = timestamp.to_i(32)
265
- entropy = randomness.to_i(32)
266
265
  rescue => err
267
266
  raise ParserError, "parsing failure as #{err.inspect} for given #{string.inspect}"
268
267
  end
269
268
 
270
- new milliseconds: milliseconds, entropy: entropy
271
- end
272
-
273
- # @api private
274
- private_class_method def self.convert_crockford_base32_to_n32(string)
275
- string.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
269
+ n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
270
+ from_integer(n32encoded.to_i(32))
276
271
  end
277
272
 
278
273
  # @return [Boolean]
@@ -284,18 +279,6 @@ class ULID
284
279
  true
285
280
  end
286
281
 
287
- # @api private
288
- # @param [Integer] integer
289
- # @param [Integer] length
290
- # @return [Array<Integer>]
291
- def self.octets_from_integer(integer, length:)
292
- digits = integer.digits(256)
293
- (length - digits.size).times do
294
- digits.push 0
295
- end
296
- digits.reverse!
297
- end
298
-
299
282
  # @api private
300
283
  # @see The logics taken from https://bugs.ruby-lang.org/issues/14401, thanks!
301
284
  # @param [Array<Integer>] reversed_digits
@@ -309,6 +292,15 @@ class ULID
309
292
  num
310
293
  end
311
294
 
295
+ # @api private
296
+ # @param [MonotonicGenerator] generator
297
+ # @return [ULID]
298
+ def self.from_monotonic_generator(generator)
299
+ raise ArgumentError, 'this method provided only for MonotonicGenerator' unless MonotonicGenerator === generator
300
+ new milliseconds: generator.latest_milliseconds, entropy: generator.latest_entropy
301
+ end
302
+
303
+ # @api private
312
304
  # @return [ArgumentError]
313
305
  private_class_method def self.argument_error_for_range_building(argument)
314
306
  ArgumentError.new "ULID.range takes only `Range[Time]` or `Range[nil]`, given: #{argument.inspect}"
@@ -319,15 +311,21 @@ class ULID
319
311
  # @api private
320
312
  # @param [Integer] milliseconds
321
313
  # @param [Integer] entropy
314
+ # @param [Integer] integer
322
315
  # @return [void]
323
316
  # @raise [OverflowError] if the given value is larger than the ULID limit
324
317
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
325
- def initialize(milliseconds:, entropy:)
326
- milliseconds = milliseconds.to_int
327
- entropy = entropy.to_int
328
- raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
329
- raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
330
- raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
318
+ def initialize(milliseconds:, entropy:, integer: UNDEFINED)
319
+ if UNDEFINED.equal?(integer)
320
+ milliseconds = milliseconds.to_int
321
+ entropy = entropy.to_int
322
+
323
+ raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
324
+ raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
325
+ raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
326
+ else
327
+ @integer = integer
328
+ end
331
329
 
332
330
  @milliseconds = milliseconds
333
331
  @entropy = entropy
@@ -335,18 +333,22 @@ class ULID
335
333
 
336
334
  # @return [String]
337
335
  def to_s
338
- @string ||= convert_n32_to_crockford_base32(to_i.to_s(32).rjust(ENCODED_ID_LENGTH, '0').upcase).freeze
336
+ @string ||= to_i.to_s(32).upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0').freeze
339
337
  end
340
338
 
341
339
  # @return [Integer]
342
340
  def to_i
343
- @integer ||= self.class.inverse_of_digits(octets)
341
+ @integer ||= begin
342
+ n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
343
+ n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
344
+ (n32encoded_timestamp + n32encoded_randomness).to_i(32)
345
+ end
344
346
  end
345
347
  alias_method :hash, :to_i
346
348
 
347
349
  # @return [Integer, nil]
348
350
  def <=>(other)
349
- other.kind_of?(ULID) ? (to_i <=> other.to_i) : nil
351
+ (ULID === other) ? (to_i <=> other.to_i) : nil
350
352
  end
351
353
 
352
354
  # @return [String]
@@ -356,7 +358,7 @@ class ULID
356
358
 
357
359
  # @return [Boolean]
358
360
  def eql?(other)
359
- other.equal?(self) || (other.kind_of?(ULID) && other.to_i == to_i)
361
+ equal?(other) || (ULID === other && to_i == other.to_i)
360
362
  end
361
363
  alias_method :==, :eql?
362
364
 
@@ -389,39 +391,49 @@ class ULID
389
391
 
390
392
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
391
393
  def octets
392
- @octets ||= (timestamp_octets + randomness_octets).freeze
394
+ @octets ||= octets_from_integer(to_i).freeze
393
395
  end
394
396
 
395
397
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
396
398
  def timestamp_octets
397
- @timestamp_octets ||= self.class.octets_from_integer(@milliseconds, length: TIMESTAMP_OCTETS_LENGTH).freeze
399
+ @timestamp_octets ||= octets.slice(0, TIMESTAMP_OCTETS_LENGTH).freeze
398
400
  end
399
401
 
400
402
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
401
403
  def randomness_octets
402
- @randomness_octets ||= self.class.octets_from_integer(@entropy, length: RANDOMNESS_OCTETS_LENGTH).freeze
404
+ @randomness_octets ||= octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH).freeze
403
405
  end
404
406
 
405
407
  # @return [String]
406
408
  def timestamp
407
- @timestamp ||= matchdata[:timestamp].freeze
409
+ @timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
408
410
  end
409
411
 
410
412
  # @return [String]
411
413
  def randomness
412
- @randomness ||= matchdata[:randomness].freeze
414
+ @randomness ||= to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze
415
+ end
416
+
417
+ # @note Providing for rough operations. The keys and values is not fixed.
418
+ # @return [Hash{Symbol => Regexp, String}]
419
+ def patterns
420
+ named_captures = /(?<timestamp>#{timestamp})(?<randomness>#{randomness})/i.freeze
421
+ {
422
+ named_captures: named_captures,
423
+ strict_named_captures: /\A#{named_captures.source}\z/i.freeze
424
+ }
413
425
  end
414
426
 
415
- # @deprecated This method might be changed in https://github.com/kachick/ruby-ulid/issues/84
427
+ # @deprecated Use {#patterns} instead. ref: https://github.com/kachick/ruby-ulid/issues/84
416
428
  # @return [Regexp]
417
429
  def pattern
418
- @pattern ||= /(?<timestamp>#{timestamp})(?<randomness>#{randomness})/i.freeze
430
+ patterns.fetch(:named_captures)
419
431
  end
420
432
 
421
- # @deprecated This method might be changed in https://github.com/kachick/ruby-ulid/issues/84
433
+ # @deprecated Use {#patterns} instead. ref: https://github.com/kachick/ruby-ulid/issues/84
422
434
  # @return [Regexp]
423
435
  def strict_pattern
424
- @strict_pattern ||= /\A#{pattern.source}\z/i.freeze
436
+ patterns.fetch(:strict_named_captures)
425
437
  end
426
438
 
427
439
  # @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
@@ -439,17 +451,6 @@ class ULID
439
451
  @pred ||= self.class.from_integer(pre_int)
440
452
  end
441
453
 
442
- # @return [String]
443
- def to_uuidv4
444
- @uuidv4 ||= begin
445
- # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
446
- array = octets.pack('C*').unpack('NnnnnN')
447
- array[2] = (array[2] & 0x0fff) | 0x4000
448
- array[3] = (array[3] & 0x3fff) | 0x8000
449
- ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
450
- end
451
- end
452
-
453
454
  # @return [self]
454
455
  def freeze
455
456
  # Need to cache before freezing, because frozen objects can't assign instance variables
@@ -459,14 +460,14 @@ class ULID
459
460
 
460
461
  private
461
462
 
462
- # @api private
463
- def convert_n32_to_crockford_base32(string)
464
- string.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR)
465
- end
466
-
467
- # @return [MatchData]
468
- def matchdata
469
- @matchdata ||= STRICT_PATTERN.match(to_s).freeze
463
+ # @param [Integer] integer
464
+ # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
465
+ def octets_from_integer(integer)
466
+ digits = integer.digits(256)
467
+ (OCTETS_LENGTH - digits.size).times do
468
+ digits.push 0
469
+ end
470
+ digits.reverse!
470
471
  end
471
472
 
472
473
  # @return [void]
@@ -476,8 +477,8 @@ class ULID
476
477
  to_i
477
478
  succ
478
479
  pred
479
- strict_pattern
480
- to_uuidv4
480
+ timestamp
481
+ randomness
481
482
  end
482
483
  end
483
484
 
@@ -488,5 +489,5 @@ class ULID
488
489
  MIN = parse('00000000000000000000000000').freeze
489
490
  MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
490
491
 
491
- private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN, :MIN, :MAX, :CROCKFORD_BASE32_CHAR_PATTERN, :N32_CHAR_BY_CROCKFORD_BASE32_CHAR, :CROCKFORD_BASE32_CHAR_BY_N32_CHAR, :N32_CHAR_PATTERN, :UNDEFINED
492
+ private_constant :TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :CROCKFORD_BASE32_CHAR_PATTERN, :N32_CHAR_BY_CROCKFORD_BASE32_CHAR, :CROCKFORD_BASE32_CHAR_BY_N32_CHAR, :N32_CHAR_PATTERN, :UNDEFINED
492
493
  end
@@ -26,7 +26,7 @@ class ULID
26
26
  @latest_entropy += 1
27
27
  end
28
28
 
29
- ULID.new milliseconds: @latest_milliseconds, entropy: @latest_entropy
29
+ ULID.from_monotonic_generator(self)
30
30
  end
31
31
 
32
32
  # @api private
data/lib/ulid/uuid.rb ADDED
@@ -0,0 +1,37 @@
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
+ begin
19
+ uuid = uuid.to_str
20
+ prefix_trimmed = uuid.sub(/\Aurn:uuid:/, '')
21
+ raise "given string is not matched to pattern #{UUIDV4_PATTERN.inspect}" unless UUIDV4_PATTERN.match?(prefix_trimmed)
22
+ normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
23
+ from_integer(normalized.to_i(16))
24
+ rescue => err
25
+ raise ParserError, "parsing failure as #{err.inspect} for given #{uuid}"
26
+ end
27
+ end
28
+
29
+ # @return [String]
30
+ def to_uuidv4
31
+ # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
32
+ array = octets.pack('C*').unpack('NnnnnN')
33
+ array[2] = (array[2] & 0x0fff) | 0x4000
34
+ array[3] = (array[3] & 0x3fff) | 0x8000
35
+ ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
36
+ end
37
+ 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.0.18'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -1,10 +1,9 @@
1
1
  # Classes
2
2
  class ULID
3
3
  VERSION: String
4
- ENCODING_CHARS: Array[String]
5
- TIMESTAMP_PART_LENGTH: 10
6
- RANDOMNESS_PART_LENGTH: 16
7
- ENCODED_ID_LENGTH: 26
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
@@ -64,12 +63,9 @@ class ULID
64
63
  @inspect: String?
65
64
  @time: Time?
66
65
  @next: ULID?
67
- @pattern: Regexp?
68
- @strict_pattern: Regexp?
69
- @uuidv4: String?
70
- @matchdata: MatchData?
71
66
 
72
67
  def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
68
+ def self.at: (Time time) -> ULID
73
69
  def self.current_milliseconds: -> Integer
74
70
  def self.milliseconds_from_time: (Time time) -> Integer
75
71
  def self.milliseconds_from_moment: (moment moment) -> Integer
@@ -86,16 +82,17 @@ class ULID
86
82
  def self.valid?: (untyped string) -> bool
87
83
  def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
88
84
  | (String string) { (ULID ulid) -> void } -> singleton(ULID)
89
- def self.octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
85
+ def self.octets_from_integer: (Integer integer) -> octets
90
86
  def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
87
+ def self.from_monotonic_generator: (MonotonicGenerator generator) -> ULID
91
88
  attr_reader milliseconds: Integer
92
89
  attr_reader entropy: Integer
93
- def initialize: (milliseconds: Integer, entropy: Integer) -> void
90
+ def initialize: (milliseconds: Integer, entropy: Integer, ?integer: Integer) -> void
94
91
  def to_s: -> String
95
92
  def to_i: -> Integer
96
93
  alias hash to_i
97
94
  def <=>: (ULID other) -> Integer
98
- | (untyped other) -> Integer?
95
+ | (untyped other) -> nil
99
96
  def inspect: -> String
100
97
  def eql?: (untyped other) -> bool
101
98
  alias == eql?
@@ -103,6 +100,7 @@ class ULID
103
100
  def to_time: -> Time
104
101
  def timestamp: -> String
105
102
  def randomness: -> String
103
+ def patterns: -> Hash[Symbol, Regexp | String]
106
104
  def pattern: -> Regexp
107
105
  def strict_pattern: -> Regexp
108
106
  def octets: -> octets
@@ -115,9 +113,6 @@ class ULID
115
113
  def freeze: -> self
116
114
 
117
115
  private
118
- def self.convert_crockford_base32_to_n32: (String) -> String
119
116
  def self.argument_error_for_range_building: (untyped argument) -> ArgumentError
120
- def convert_n32_to_crockford_base32: (String) -> String
121
- def matchdata: -> MatchData
122
117
  def cache_all_instance_variables: -> void
123
118
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.17
4
+ version: 0.0.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-06 00:00:00.000000000 Z
11
+ date: 2021-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rbs
@@ -78,6 +78,7 @@ files:
78
78
  - README.md
79
79
  - lib/ulid.rb
80
80
  - lib/ulid/monotonic_generator.rb
81
+ - lib/ulid/uuid.rb
81
82
  - lib/ulid/version.rb
82
83
  - sig/ulid.rbs
83
84
  homepage: https://github.com/kachick/ruby-ulid