ruby-ulid 0.0.8 → 0.0.9
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 +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
|