ruby-ulid 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ulid.rb +110 -31
  3. data/lib/ulid/version.rb +1 -1
  4. data/sig/ulid.rbs +33 -13
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a1179379859a437f04ff904c941372fffc4aa3027d550ae50c67f744e4899d9a
4
- data.tar.gz: 71781ee8481373bb7229f10f88a1703b2653c11a4f18cbc7b50eb4adbec58908
3
+ metadata.gz: cc7674472c14c213d6fe01c6994520b33bc82ab164b538948a4f1b532d0d88f2
4
+ data.tar.gz: 85987c59ca6f119d40caaf69cc830a8e1b5cc2f9ea3250c315be2f9d8b7faa09
5
5
  SHA512:
6
- metadata.gz: 6d48e489221851290a486ffe3a39847196e900bb54751fa7d0c808f7580c8838d1a6382b203da1b90ad4f74dd9e998239e0a6c996b06d99881c39489ff607697
7
- data.tar.gz: fbef689db966901800b24e18820ee6744aeeaeb2476f14349b74d634ed1e5d359a4bff4abb3eb0eee94e4d0183f5db1420719d7e94afd3e803d21d62510087d7
6
+ metadata.gz: f56db0a74a7a6559189039a9f7f13a1f514b4fc956f7993a52ca16a7a978b4b93aa352b9f74b72da73c7b0485ebe1f80052d2e3c7b04a1baba231d06ba594f42
7
+ data.tar.gz: 597260a0eb43c0fdc187aeb961521fbce22c68fc75b35d445e73d60564b72208aaf1e83a8428daa99423e001572f187872f4077445d6bced8b35d197d7e700ff
data/lib/ulid.rb CHANGED
@@ -18,18 +18,25 @@ class ULID
18
18
  class OverflowError < Error; end
19
19
  class ParserError < Error; end
20
20
 
21
+ encoding_string = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
21
22
  # Crockford's Base32. Excluded I, L, O, U.
22
23
  # @see https://www.crockford.com/base32.html
23
- ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'.chars.map(&:freeze).freeze
24
+ ENCODING_CHARS = encoding_string.chars.map(&:freeze).freeze
24
25
 
25
26
  TIME_PART_LENGTH = 10
26
27
  RANDOMNESS_PART_LENGTH = 16
27
28
  ENCODED_ID_LENGTH = TIME_PART_LENGTH + RANDOMNESS_PART_LENGTH
28
- TIME_OCTETS_LENGTH = 6
29
+ TIMESTAMP_OCTETS_LENGTH = 6
29
30
  RANDOMNESS_OCTETS_LENGTH = 10
30
- OCTETS_LENGTH = TIME_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
31
+ OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
31
32
  MAX_MILLISECONDS = 281474976710655
32
33
  MAX_ENTROPY = 1208925819614629174706175
34
+ MAX_INTEGER = 340282366920938463463374607431768211455
35
+ PATTERN = /(?<timestamp>[0-7][#{encoding_string}]{#{TIME_PART_LENGTH - 1}})(?<randomness>[#{encoding_string}]{#{RANDOMNESS_PART_LENGTH}})/i.freeze
36
+ STRICT_PATTERN = /\A#{PATTERN.source}\z/i.freeze
37
+
38
+ # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
39
+ 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
33
40
 
34
41
  # Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
35
42
  # @see https://bugs.ruby-lang.org/issues/15958
@@ -76,7 +83,7 @@ class ULID
76
83
 
77
84
  MONOTONIC_GENERATOR = MonotonicGenerator.new
78
85
 
79
- private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT
86
+ private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
80
87
 
81
88
  # @param [Integer, Time] moment
82
89
  # @param [Integer] entropy
@@ -100,6 +107,52 @@ class ULID
100
107
  MONOTONIC_GENERATOR.generate
101
108
  end
102
109
 
110
+ # @param [String, #to_str] string
111
+ # @return [Enumerator]
112
+ # @yieldparam [ULID] ulid
113
+ # @yieldreturn [self]
114
+ def self.scan(string)
115
+ string = string.to_str
116
+ return to_enum(__callee__, string) unless block_given?
117
+ string.scan(PATTERN) do |pair|
118
+ yield parse(pair.join)
119
+ end
120
+ self
121
+ end
122
+
123
+ # @param [String, #to_str] uuid
124
+ # @return [ULID]
125
+ # @raise [ParserError] if the given format is not correct for UUIDv4 specs
126
+ def self.from_uuidv4(uuid)
127
+ begin
128
+ uuid = uuid.to_str
129
+ prefix_trimmed = uuid.sub(/\Aurn:uuid:/, '')
130
+ raise "given string is not matched to pattern #{UUIDV4_PATTERN.inspect}" unless UUIDV4_PATTERN.match?(prefix_trimmed)
131
+ normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
132
+ from_integer(normalized.to_i(16))
133
+ rescue => err
134
+ raise ParserError, "parsing failure as #{err.inspect} for given #{uuid}"
135
+ end
136
+ end
137
+
138
+ # @param [Integer, #to_int] integer
139
+ # @return [ULID]
140
+ # @raise [OverflowError] if the given integer is larger than the ULID limit
141
+ # @raise [ArgumentError] if the given integer is negative number
142
+ def self.from_integer(integer)
143
+ integer = integer.to_int
144
+ raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
145
+ raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
146
+
147
+ octets = octets_from_integer(integer, length: OCTETS_LENGTH).freeze
148
+ time_octets = octets.slice(0, TIMESTAMP_OCTETS_LENGTH).freeze
149
+ randomness_octets = octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH).freeze
150
+ milliseconds = inverse_of_digits(time_octets)
151
+ entropy = inverse_of_digits(randomness_octets)
152
+
153
+ new milliseconds: milliseconds, entropy: entropy
154
+ end
155
+
103
156
  # @return [Integer]
104
157
  def self.current_milliseconds
105
158
  time_to_milliseconds(Time.now)
@@ -146,6 +199,29 @@ class ULID
146
199
  true
147
200
  end
148
201
 
202
+ # @param [Integer] integer
203
+ # @param [Integer] length
204
+ # @return [Array<Integer>]
205
+ def self.octets_from_integer(integer, length:)
206
+ digits = integer.digits(256)
207
+ (length - digits.size).times do
208
+ digits.push 0
209
+ end
210
+ digits.reverse!
211
+ end
212
+
213
+ # @see The logics taken from https://bugs.ruby-lang.org/issues/14401, thanks!
214
+ # @param [Array<Integer>] reversed_digits
215
+ # @return [Integer]
216
+ def self.inverse_of_digits(reversed_digits)
217
+ base = 256
218
+ num = 0
219
+ reversed_digits.each do |digit|
220
+ num = (num * base) + digit
221
+ end
222
+ num
223
+ end
224
+
149
225
  attr_reader :milliseconds, :entropy
150
226
 
151
227
  # @param [Integer] milliseconds
@@ -172,7 +248,7 @@ class ULID
172
248
 
173
249
  # @return [Integer]
174
250
  def to_i
175
- @integer ||= inverse_of_digits(octets)
251
+ @integer ||= self.class.inverse_of_digits(octets)
176
252
  end
177
253
  alias_method :hash, :to_i
178
254
 
@@ -210,22 +286,42 @@ class ULID
210
286
 
211
287
  # @return [Time]
212
288
  def to_time
213
- @time ||= Time.at(0, @milliseconds, :millisecond).utc
289
+ @time ||= Time.at(0, @milliseconds, :millisecond).utc.freeze
214
290
  end
215
291
 
216
292
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
217
293
  def octets
218
- @octets ||= (time_octets + randomness_octets).freeze
294
+ @octets ||= (timestamp_octets + randomness_octets).freeze
219
295
  end
220
296
 
221
297
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
222
- def time_octets
223
- @time_octets ||= octets_from_integer(@milliseconds, length: TIME_OCTETS_LENGTH).freeze
298
+ def timestamp_octets
299
+ @timestamp_octets ||= self.class.octets_from_integer(@milliseconds, length: TIMESTAMP_OCTETS_LENGTH).freeze
224
300
  end
225
301
 
226
302
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
227
303
  def randomness_octets
228
- @randomness_octets ||= octets_from_integer(@entropy, length: RANDOMNESS_OCTETS_LENGTH).freeze
304
+ @randomness_octets ||= self.class.octets_from_integer(@entropy, length: RANDOMNESS_OCTETS_LENGTH).freeze
305
+ end
306
+
307
+ # @return [String]
308
+ def timestamp
309
+ @timestamp ||= matchdata[:timestamp].freeze
310
+ end
311
+
312
+ # @return [String]
313
+ def randomness
314
+ @randomness ||= matchdata[:randomness].freeze
315
+ end
316
+
317
+ # @return [Regexp]
318
+ def pattern
319
+ @pattern ||= /(?<timestamp>#{timestamp})(?<randomness>#{randomness})/i.freeze
320
+ end
321
+
322
+ # @return [Regexp]
323
+ def strict_pattern
324
+ @strict_pattern ||= /\A#{pattern.source}\z/i.freeze
229
325
  end
230
326
 
231
327
  # @raise [OverflowError] if the next entropy part is larger than the ULID limit
@@ -242,31 +338,14 @@ class ULID
242
338
  octets
243
339
  succ
244
340
  to_i
341
+ strict_pattern
245
342
  super
246
343
  end
247
344
 
248
345
  private
249
346
 
250
- # @param [Integer] integer
251
- # @param [Integer] length
252
- # @return [Array<Integer>]
253
- def octets_from_integer(integer, length:)
254
- digits = integer.digits(256)
255
- (length - digits.size).times do
256
- digits.push 0
257
- end
258
- digits.reverse!
259
- end
260
-
261
- # @see The logics taken from https://bugs.ruby-lang.org/issues/14401, thanks!
262
- # @param [Array<Integer>] reversed_digits
263
- # @return [Integer]
264
- def inverse_of_digits(reversed_digits)
265
- base = 256
266
- num = 0
267
- reversed_digits.each do |digit|
268
- num = (num * base) + digit
269
- end
270
- num
347
+ # @return [MatchData]
348
+ def matchdata
349
+ @matchdata ||= STRICT_PATTERN.match(to_str).freeze
271
350
  end
272
351
  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.8'
5
+ VERSION = '0.0.9'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -5,12 +5,16 @@ class ULID
5
5
  TIME_PART_LENGTH: 10
6
6
  RANDOMNESS_PART_LENGTH: 16
7
7
  ENCODED_ID_LENGTH: 26
8
- TIME_OCTETS_LENGTH: 6
8
+ TIMESTAMP_OCTETS_LENGTH: 6
9
9
  RANDOMNESS_OCTETS_LENGTH: 10
10
10
  OCTETS_LENGTH: 16
11
11
  MAX_MILLISECONDS: 281474976710655
12
12
  MAX_ENTROPY: 1208925819614629174706175
13
+ MAX_INTEGER: 340282366920938463463374607431768211455
13
14
  TIME_FORMAT_IN_INSPECT: '%Y-%m-%d %H:%M:%S.%3N %Z'
15
+ PATTERN: Regexp
16
+ STRICT_PATTERN: Regexp
17
+ UUIDV4_PATTERN: Regexp
14
18
  MONOTONIC_GENERATOR: MonotonicGenerator
15
19
  include Comparable
16
20
 
@@ -33,17 +37,24 @@ class ULID
33
37
  end
34
38
 
35
39
  type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
36
- type time_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
40
+ type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
37
41
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
38
42
 
39
- @string: String
40
- @integer: Integer
41
- @octets: octets
42
- @time_octets: time_octets
43
- @randomness_octets: randomness_octets
44
- @inspect: String
45
- @time: Time
46
- @next: ULID
43
+ @milliseconds: Integer
44
+ @entropy: Integer
45
+ @string: String?
46
+ @integer: Integer?
47
+ @octets: octets?
48
+ @timestamp_octets: timestamp_octets?
49
+ @randomness_octets: randomness_octets?
50
+ @timestamp: String?
51
+ @randomness: String?
52
+ @inspect: String?
53
+ @time: Time?
54
+ @next: ULID?
55
+ @pattern: Regexp?
56
+ @strict_pattern: Regexp?
57
+ @matchdata: MatchData?
47
58
 
48
59
  def self.generate: (?moment: Time | Integer, ?entropy: Integer) -> ULID
49
60
  def self.monotonic_generate: -> ULID
@@ -51,7 +62,13 @@ class ULID
51
62
  def self.time_to_milliseconds: (Time time) -> Integer
52
63
  def self.reasonable_entropy: -> Integer
53
64
  def self.parse: (String string) -> ULID
65
+ def self.from_uuidv4: (String uuid) -> ULID
66
+ def self.from_integer: (Integer integer) -> ULID
54
67
  def self.valid?: (untyped string) -> bool
68
+ def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
69
+ | (String string) { (ULID ulid) -> void } -> singleton(ULID)
70
+ def self.octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
71
+ def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
55
72
  attr_reader milliseconds: Integer
56
73
  attr_reader entropy: Integer
57
74
  def initialize: (milliseconds: Integer, entropy: Integer) -> void
@@ -66,14 +83,17 @@ class ULID
66
83
  alias == eql?
67
84
  def ===: (untyped other) -> bool
68
85
  def to_time: -> Time
86
+ def timestamp: -> String
87
+ def randomness: -> String
88
+ def pattern: -> Regexp
89
+ def strict_pattern: -> Regexp
69
90
  def octets: -> octets
70
- def time_octets: -> time_octets
91
+ def timestamp_octets: -> timestamp_octets
71
92
  def randomness_octets: -> randomness_octets
72
93
  def next: -> ULID
73
94
  alias succ next
74
95
  def freeze: -> self
75
96
 
76
97
  private
77
- def octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
78
- def inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
98
+ def matchdata: -> MatchData
79
99
  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.8
4
+ version: 0.0.9
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-04-29 00:00:00.000000000 Z
11
+ date: 2021-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: integer-base