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.
- checksums.yaml +4 -4
- data/lib/ulid.rb +110 -31
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +33 -13
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc7674472c14c213d6fe01c6994520b33bc82ab164b538948a4f1b532d0d88f2
|
4
|
+
data.tar.gz: 85987c59ca6f119d40caaf69cc830a8e1b5cc2f9ea3250c315be2f9d8b7faa09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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 =
|
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
|
-
|
29
|
+
TIMESTAMP_OCTETS_LENGTH = 6
|
29
30
|
RANDOMNESS_OCTETS_LENGTH = 10
|
30
|
-
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 ||= (
|
294
|
+
@octets ||= (timestamp_octets + randomness_octets).freeze
|
219
295
|
end
|
220
296
|
|
221
297
|
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
|
222
|
-
def
|
223
|
-
@
|
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
|
-
# @
|
251
|
-
|
252
|
-
|
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
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
|
-
|
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
|
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
|
-
@
|
40
|
-
@
|
41
|
-
@
|
42
|
-
@
|
43
|
-
@
|
44
|
-
@
|
45
|
-
@
|
46
|
-
@
|
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
|
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
|
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.
|
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-
|
11
|
+
date: 2021-04-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: integer-base
|