ruby-ulid 0.0.6 → 0.0.11
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/LICENSE +21 -0
- data/README.md +210 -0
- data/Steepfile +7 -0
- data/lib/ulid.rb +186 -85
- data/lib/ulid/monotonic_generator.rb +44 -0
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +78 -49
- metadata +14 -25
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 91f6d4c9dad8e12663686099c487dbbf3ec5b7995e0e4b2b210ac8a16fbcd91c
|
|
4
|
+
data.tar.gz: 1ca6e29d83771636b2a20aa71d90f15699d07d6a7a75f22c306094507bb51d89
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22dcc2541f7e4fa1a1d4014f996aef3d3bfcb69b002c1cc6ca4864e17bce50a327e1ec98453b4dab0773400b678055dc024a7287342a08916ce30772aadb418d
|
|
7
|
+
data.tar.gz: 27c69c7319858a4c732f04f6944ff180f3dfc9a5df12bd67cd1e4cac91421a2bc00d1a79c8ff91190b425731f831fdaef1a2f5bc0c3ef4fdaff73a6ce6749e35
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Kenichi Kamiya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# ruby-ulid
|
|
2
|
+
|
|
3
|
+
A handy `ULID` library
|
|
4
|
+
|
|
5
|
+
The `ULID` spec is defined on [ulid/spec](https://github.com/ulid/spec).
|
|
6
|
+
This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
|
|
7
|
+
Also providing rbs signature files.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
[](http://badge.fury.io/rb/ruby-ulid)
|
|
15
|
+
|
|
16
|
+
## Universally Unique Lexicographically Sortable Identifier
|
|
17
|
+
|
|
18
|
+
UUID can be suboptimal for many uses-cases because:
|
|
19
|
+
|
|
20
|
+
- It isn't the most character efficient way of encoding 128 bits of randomness
|
|
21
|
+
- UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
|
|
22
|
+
- UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
|
|
23
|
+
- UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
|
|
24
|
+
|
|
25
|
+
Instead, herein is proposed ULID:
|
|
26
|
+
|
|
27
|
+
- 128-bit compatibility with UUID
|
|
28
|
+
- 1.21e+24 unique ULIDs per millisecond
|
|
29
|
+
- Lexicographically sortable!
|
|
30
|
+
- Canonically encoded as a 26 character string, as opposed to the 36 character UUID
|
|
31
|
+
- Uses Crockford's base32 for better efficiency and readability (5 bits per character)
|
|
32
|
+
- Case insensitive
|
|
33
|
+
- No special characters (URL safe)
|
|
34
|
+
- Monotonic sort order (correctly detects and handles the same millisecond)
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
```console
|
|
39
|
+
$ gem install ruby-ulid
|
|
40
|
+
#=> Installed
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
The generated `ULID` is an object not just a string.
|
|
46
|
+
It means easily get the timestamps and binary formats.
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require 'ulid'
|
|
50
|
+
|
|
51
|
+
ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
|
|
52
|
+
ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
|
|
53
|
+
ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
|
|
54
|
+
ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
|
|
55
|
+
ulid.pattern #=> /(?<timestamp>01F4A5Y1YA)(?<randomness>QCYAYCTC7GRMJ9AA)/i
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Generator can take `Time` instance
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
time = Time.at(946684800, in: 'UTC') #=> 2000-01-01 00:00:00 UTC
|
|
62
|
+
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
|
|
63
|
+
ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
|
|
64
|
+
|
|
65
|
+
ulids = 1000.times.map do
|
|
66
|
+
ULID.generate(moment: time)
|
|
67
|
+
end
|
|
68
|
+
ulids.sort == ulids #=> false
|
|
69
|
+
|
|
70
|
+
ulids = 1000.times.map do |n|
|
|
71
|
+
ULID.generate(moment: time + n)
|
|
72
|
+
end
|
|
73
|
+
ulids.sort == ulids #=> true
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
You can parse from exists IDs
|
|
77
|
+
|
|
78
|
+
FYI: Current parser/validator/matcher implementation aims `strict`, It might be changed in [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ruby-ulid#57](https://github.com/kachick/ruby-ulid/issues/57).
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
|
|
82
|
+
ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
ULIDs are sortable when they are generated in different timestamp with milliseconds precision
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
ulids = 1000.times.map do
|
|
89
|
+
sleep(0.001)
|
|
90
|
+
ULID.generate
|
|
91
|
+
end
|
|
92
|
+
ulids.sort == ulids #=> true
|
|
93
|
+
ulids.uniq(&:to_time).size #=> 1000
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
ulids = 10000.times.map do
|
|
100
|
+
ULID.generate
|
|
101
|
+
end
|
|
102
|
+
ulids.uniq(&:to_time).size #=> 35 (the size is not fixed, might be changed in environment)
|
|
103
|
+
ulids.sort == ulids #=> false
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
If you want to prefer `sortable` rather than the `strict randomness`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
|
|
107
|
+
(Though it starts with new random value when changed the timestamp)
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
monotonic_generator = ULID::MonotonicGenerator.new
|
|
111
|
+
monotonic_ulids = 10000.times.map do
|
|
112
|
+
monotonic_generator.generate
|
|
113
|
+
end
|
|
114
|
+
sample_ulids_by_the_time = monotonic_ulids.uniq(&:to_time)
|
|
115
|
+
sample_ulids_by_the_time.size #=> 34 (the size is not fixed, might be changed in environment)
|
|
116
|
+
sample_ulids_by_the_time.take(10).map(&:randomness)
|
|
117
|
+
#=>
|
|
118
|
+
["JZW56CTA8704D5AQ",
|
|
119
|
+
"JGEBH2A2B2EA97MW",
|
|
120
|
+
"0XPE4NS3MZH0NAJ4",
|
|
121
|
+
"E0S3ZAVADFBPW57Y",
|
|
122
|
+
"E5CX1T6281443THQ",
|
|
123
|
+
"3SK8WHSH03CVF7J2",
|
|
124
|
+
"DDS35BT0R20P3V49",
|
|
125
|
+
"60KG2W9FVEN1ZX8C",
|
|
126
|
+
"X59YJVXXVH7AXJJE",
|
|
127
|
+
"1ZBQ7SNGFKXGH1Y4"]
|
|
128
|
+
|
|
129
|
+
monotonic_ulids.sort == monotonic_ulids #=> true
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For rough operations, `ULID.scan` might be useful.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
json =<<'EOD'
|
|
136
|
+
{
|
|
137
|
+
"id": "01F4GNAV5ZR6FJQ5SFQC7WDSY3",
|
|
138
|
+
"author": {
|
|
139
|
+
"id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
|
|
140
|
+
"name": "kachick"
|
|
141
|
+
},
|
|
142
|
+
"title": "My awesome blog post",
|
|
143
|
+
"comments": [
|
|
144
|
+
{
|
|
145
|
+
"id": "01F4GNCNC3CH0BCRZBPPDEKBKS",
|
|
146
|
+
"commenter": {
|
|
147
|
+
"id": "01F4GNBXW1AM2KWW52PVT3ZY9X",
|
|
148
|
+
"name": "kachick"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"id": "01F4GNCXAMXQ1SGBH5XCR6ZH0M",
|
|
153
|
+
"commenter": {
|
|
154
|
+
"id": "01F4GND4RYYSKNAADHQ9BNXAWJ",
|
|
155
|
+
"name": "pankona"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
]
|
|
159
|
+
}
|
|
160
|
+
EOD
|
|
161
|
+
|
|
162
|
+
ULID.scan(json).to_a
|
|
163
|
+
#=>
|
|
164
|
+
[ULID(2021-04-30 05:51:57.119 UTC: 01F4GNAV5ZR6FJQ5SFQC7WDSY3),
|
|
165
|
+
ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
|
|
166
|
+
ULID(2021-04-30 05:52:56.707 UTC: 01F4GNCNC3CH0BCRZBPPDEKBKS),
|
|
167
|
+
ULID(2021-04-30 05:52:32.641 UTC: 01F4GNBXW1AM2KWW52PVT3ZY9X),
|
|
168
|
+
ULID(2021-04-30 05:53:04.852 UTC: 01F4GNCXAMXQ1SGBH5XCR6ZH0M),
|
|
169
|
+
ULID(2021-04-30 05:53:12.478 UTC: 01F4GND4RYYSKNAADHQ9BNXAWJ)]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`ULID.min` and `ULID.max` return termination values for ULID spec.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
ULID.min #=> ULID(1970-01-01 00:00:00.000 UTC: 00000000000000000000000000)
|
|
176
|
+
ULID.max #=> ULID(10889-08-02 05:31:50.655 UTC: 7ZZZZZZZZZZZZZZZZZZZZZZZZZ)
|
|
177
|
+
|
|
178
|
+
time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.123456789 UTC
|
|
179
|
+
ULID.min(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
|
|
180
|
+
ULID.max(moment: time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
`ULID#next` and `ULID#succ` returns next(successor) ULID
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
|
|
187
|
+
ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM0000000000000000"
|
|
188
|
+
ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`ULID#pred` returns predecessor ULID
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
|
|
195
|
+
ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZZZ"
|
|
196
|
+
ULID.parse('00000000000000000000000000').pred #=> nil
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
UUIDv4 converter for migration use-cases. (Of course the timestamp will be useless one. Sortable benefit is lost.)
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
ULID.from_uuidv4('0983d0a2-ff15-4d83-8f37-7dd945b5aa39')
|
|
203
|
+
#=> ULID(2301-07-10 00:28:28.821 UTC: 09GF8A5ZRN9P1RYDVXV52VBAHS)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## References
|
|
207
|
+
|
|
208
|
+
- [API documents](https://kachick.github.io/ruby-ulid/)
|
|
209
|
+
- [ulid/spec](https://github.com/ulid/spec)
|
|
210
|
+
- [Another choices are UUIDv6, UUIDv7, UUIDv8. But they are still in draft state](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html)
|
data/Steepfile
ADDED
data/lib/ulid.rb
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
# Copyright (C) 2021 Kenichi Kamiya
|
|
4
4
|
|
|
5
5
|
require 'securerandom'
|
|
6
|
-
require 'singleton'
|
|
7
6
|
require 'integer/base'
|
|
8
|
-
require_relative 'ulid/version'
|
|
9
7
|
|
|
10
8
|
# @see https://github.com/ulid/spec
|
|
9
|
+
# @!attribute [r] milliseconds
|
|
10
|
+
# @return [Integer]
|
|
11
|
+
# @!attribute [r] entropy
|
|
12
|
+
# @return [Integer]
|
|
11
13
|
class ULID
|
|
12
14
|
include Comparable
|
|
13
15
|
|
|
@@ -15,77 +17,109 @@ class ULID
|
|
|
15
17
|
class OverflowError < Error; end
|
|
16
18
|
class ParserError < Error; end
|
|
17
19
|
|
|
20
|
+
encoding_string = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
|
|
18
21
|
# Crockford's Base32. Excluded I, L, O, U.
|
|
19
22
|
# @see https://www.crockford.com/base32.html
|
|
20
|
-
ENCODING_CHARS =
|
|
23
|
+
ENCODING_CHARS = encoding_string.chars.map(&:freeze).freeze
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
TIMESTAMP_PART_LENGTH = 10
|
|
23
26
|
RANDOMNESS_PART_LENGTH = 16
|
|
24
|
-
ENCODED_ID_LENGTH =
|
|
25
|
-
|
|
27
|
+
ENCODED_ID_LENGTH = TIMESTAMP_PART_LENGTH + RANDOMNESS_PART_LENGTH
|
|
28
|
+
TIMESTAMP_OCTETS_LENGTH = 6
|
|
26
29
|
RANDOMNESS_OCTETS_LENGTH = 10
|
|
27
|
-
OCTETS_LENGTH =
|
|
30
|
+
OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
|
|
28
31
|
MAX_MILLISECONDS = 281474976710655
|
|
29
32
|
MAX_ENTROPY = 1208925819614629174706175
|
|
33
|
+
MAX_INTEGER = 340282366920938463463374607431768211455
|
|
34
|
+
PATTERN = /(?<timestamp>[0-7][#{encoding_string}]{#{TIMESTAMP_PART_LENGTH - 1}})(?<randomness>[#{encoding_string}]{#{RANDOMNESS_PART_LENGTH}})/i.freeze
|
|
35
|
+
STRICT_PATTERN = /\A#{PATTERN.source}\z/i.freeze
|
|
36
|
+
|
|
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
|
|
30
39
|
|
|
31
40
|
# Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
|
|
32
41
|
# @see https://bugs.ruby-lang.org/issues/15958
|
|
33
42
|
TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
44
|
+
# @param [Integer, Time] moment
|
|
45
|
+
# @param [Integer] entropy
|
|
46
|
+
# @return [ULID]
|
|
47
|
+
def self.generate(moment: current_milliseconds, entropy: reasonable_entropy)
|
|
48
|
+
milliseconds = moment.kind_of?(Time) ? time_to_milliseconds(moment) : moment
|
|
49
|
+
new milliseconds: milliseconds, entropy: entropy
|
|
50
|
+
end
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
# @param [Integer, Time] moment
|
|
53
|
+
# @return [ULID]
|
|
54
|
+
def self.min(moment: 0)
|
|
55
|
+
generate(moment: moment, entropy: 0)
|
|
56
|
+
end
|
|
43
57
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
@latest_milliseconds ||= milliseconds
|
|
50
|
-
@latest_entropy ||= reasonable_entropy
|
|
51
|
-
if @latest_milliseconds != milliseconds
|
|
52
|
-
@latest_milliseconds = milliseconds
|
|
53
|
-
@latest_entropy = reasonable_entropy
|
|
54
|
-
else
|
|
55
|
-
@latest_entropy += 1
|
|
56
|
-
end
|
|
58
|
+
# @param [Integer, Time] moment
|
|
59
|
+
# @return [ULID]
|
|
60
|
+
def self.max(moment: MAX_MILLISECONDS)
|
|
61
|
+
generate(moment: moment, entropy: MAX_ENTROPY)
|
|
62
|
+
end
|
|
57
63
|
|
|
58
|
-
|
|
64
|
+
# @deprecated This method actually changes class state. Use {ULID::MonotonicGenerator} instead.
|
|
65
|
+
# @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
|
|
66
|
+
# @return [ULID]
|
|
67
|
+
def self.monotonic_generate
|
|
68
|
+
warning = "`ULID.monotonic_generate` actually changes class state. Use `ULID::MonotonicGenerator` instead."
|
|
69
|
+
if RUBY_VERSION >= '3.0'
|
|
70
|
+
Warning.warn(warning, category: :deprecated)
|
|
71
|
+
else
|
|
72
|
+
Warning.warn(warning)
|
|
59
73
|
end
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@latest_milliseconds = nil
|
|
64
|
-
@latest_entropy = nil
|
|
65
|
-
self
|
|
66
|
-
end
|
|
75
|
+
MONOTONIC_GENERATOR.generate
|
|
76
|
+
end
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
78
|
+
# @param [String, #to_str] string
|
|
79
|
+
# @return [Enumerator]
|
|
80
|
+
# @yieldparam [ULID] ulid
|
|
81
|
+
# @yieldreturn [self]
|
|
82
|
+
def self.scan(string)
|
|
83
|
+
string = string.to_str
|
|
84
|
+
return to_enum(__callee__, string) unless block_given?
|
|
85
|
+
string.scan(PATTERN) do |pair|
|
|
86
|
+
yield parse(pair.join)
|
|
71
87
|
end
|
|
88
|
+
self
|
|
72
89
|
end
|
|
73
90
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :MonotonicGenerator
|
|
77
|
-
|
|
78
|
-
# @param [Integer, Time] moment
|
|
79
|
-
# @param [Integer] entropy
|
|
91
|
+
# @param [String, #to_str] uuid
|
|
80
92
|
# @return [ULID]
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
93
|
+
# @raise [ParserError] if the given format is not correct for UUIDv4 specs
|
|
94
|
+
def self.from_uuidv4(uuid)
|
|
95
|
+
begin
|
|
96
|
+
uuid = uuid.to_str
|
|
97
|
+
prefix_trimmed = uuid.sub(/\Aurn:uuid:/, '')
|
|
98
|
+
raise "given string is not matched to pattern #{UUIDV4_PATTERN.inspect}" unless UUIDV4_PATTERN.match?(prefix_trimmed)
|
|
99
|
+
normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
|
|
100
|
+
from_integer(normalized.to_i(16))
|
|
101
|
+
rescue => err
|
|
102
|
+
raise ParserError, "parsing failure as #{err.inspect} for given #{uuid}"
|
|
103
|
+
end
|
|
84
104
|
end
|
|
85
105
|
|
|
106
|
+
# @param [Integer, #to_int] integer
|
|
86
107
|
# @return [ULID]
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
# @raise [OverflowError] if the given integer is larger than the ULID limit
|
|
109
|
+
# @raise [ArgumentError] if the given integer is negative number
|
|
110
|
+
# @todo Need optimized for performance
|
|
111
|
+
def self.from_integer(integer)
|
|
112
|
+
integer = integer.to_int
|
|
113
|
+
raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
|
|
114
|
+
raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
|
|
115
|
+
|
|
116
|
+
octets = octets_from_integer(integer, length: OCTETS_LENGTH).freeze
|
|
117
|
+
time_octets = octets.slice(0, TIMESTAMP_OCTETS_LENGTH).freeze
|
|
118
|
+
randomness_octets = octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH).freeze
|
|
119
|
+
milliseconds = inverse_of_digits(time_octets)
|
|
120
|
+
entropy = inverse_of_digits(randomness_octets)
|
|
121
|
+
|
|
122
|
+
new milliseconds: milliseconds, entropy: entropy
|
|
89
123
|
end
|
|
90
124
|
|
|
91
125
|
# @return [Integer]
|
|
@@ -106,14 +140,16 @@ class ULID
|
|
|
106
140
|
|
|
107
141
|
# @param [String, #to_str] string
|
|
108
142
|
# @return [ULID]
|
|
143
|
+
# @raise [ParserError] if the given format is not correct for ULID specs
|
|
144
|
+
# @raise [OverflowError] if the given value is larger than the ULID limit
|
|
109
145
|
def self.parse(string)
|
|
110
146
|
begin
|
|
111
147
|
string = string.to_str
|
|
112
148
|
unless string.size == ENCODED_ID_LENGTH
|
|
113
149
|
raise "parsable string must be #{ENCODED_ID_LENGTH} characters, but actually given #{string.size} characters"
|
|
114
150
|
end
|
|
115
|
-
timestamp = string.slice(0,
|
|
116
|
-
randomness = string.slice(
|
|
151
|
+
timestamp = string.slice(0, TIMESTAMP_PART_LENGTH)
|
|
152
|
+
randomness = string.slice(TIMESTAMP_PART_LENGTH, RANDOMNESS_PART_LENGTH)
|
|
117
153
|
milliseconds = Integer::Base.parse(timestamp, ENCODING_CHARS)
|
|
118
154
|
entropy = Integer::Base.parse(randomness, ENCODING_CHARS)
|
|
119
155
|
rescue => err
|
|
@@ -123,7 +159,6 @@ class ULID
|
|
|
123
159
|
new milliseconds: milliseconds, entropy: entropy
|
|
124
160
|
end
|
|
125
161
|
|
|
126
|
-
# @param [String] string
|
|
127
162
|
# @return [Boolean]
|
|
128
163
|
def self.valid?(string)
|
|
129
164
|
parse(string)
|
|
@@ -133,8 +168,36 @@ class ULID
|
|
|
133
168
|
true
|
|
134
169
|
end
|
|
135
170
|
|
|
171
|
+
# @param [Integer] integer
|
|
172
|
+
# @param [Integer] length
|
|
173
|
+
# @return [Array<Integer>]
|
|
174
|
+
def self.octets_from_integer(integer, length:)
|
|
175
|
+
digits = integer.digits(256)
|
|
176
|
+
(length - digits.size).times do
|
|
177
|
+
digits.push 0
|
|
178
|
+
end
|
|
179
|
+
digits.reverse!
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @see The logics taken from https://bugs.ruby-lang.org/issues/14401, thanks!
|
|
183
|
+
# @param [Array<Integer>] reversed_digits
|
|
184
|
+
# @return [Integer]
|
|
185
|
+
def self.inverse_of_digits(reversed_digits)
|
|
186
|
+
base = 256
|
|
187
|
+
num = 0
|
|
188
|
+
reversed_digits.each do |digit|
|
|
189
|
+
num = (num * base) + digit
|
|
190
|
+
end
|
|
191
|
+
num
|
|
192
|
+
end
|
|
193
|
+
|
|
136
194
|
attr_reader :milliseconds, :entropy
|
|
137
195
|
|
|
196
|
+
# @param [Integer] milliseconds
|
|
197
|
+
# @param [Integer] entropy
|
|
198
|
+
# @return [void]
|
|
199
|
+
# @raise [OverflowError] if the given value is larger than the ULID limit
|
|
200
|
+
# @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
|
|
138
201
|
def initialize(milliseconds:, entropy:)
|
|
139
202
|
milliseconds = milliseconds.to_int
|
|
140
203
|
entropy = entropy.to_int
|
|
@@ -154,13 +217,13 @@ class ULID
|
|
|
154
217
|
|
|
155
218
|
# @return [Integer]
|
|
156
219
|
def to_i
|
|
157
|
-
@integer ||= inverse_of_digits(octets)
|
|
220
|
+
@integer ||= self.class.inverse_of_digits(octets)
|
|
158
221
|
end
|
|
159
222
|
alias_method :hash, :to_i
|
|
160
223
|
|
|
161
224
|
# @return [Integer, nil]
|
|
162
225
|
def <=>(other)
|
|
163
|
-
other.kind_of?(
|
|
226
|
+
other.kind_of?(ULID) ? (to_i <=> other.to_i) : nil
|
|
164
227
|
end
|
|
165
228
|
|
|
166
229
|
# @return [String]
|
|
@@ -170,68 +233,106 @@ class ULID
|
|
|
170
233
|
|
|
171
234
|
# @return [Boolean]
|
|
172
235
|
def eql?(other)
|
|
173
|
-
other.equal?(self) || (other.kind_of?(
|
|
236
|
+
other.equal?(self) || (other.kind_of?(ULID) && other.to_i == to_i)
|
|
174
237
|
end
|
|
175
238
|
alias_method :==, :eql?
|
|
176
239
|
|
|
240
|
+
# @return [Boolean]
|
|
241
|
+
def ===(other)
|
|
242
|
+
case other
|
|
243
|
+
when ULID
|
|
244
|
+
self == other
|
|
245
|
+
when String
|
|
246
|
+
begin
|
|
247
|
+
self == self.class.parse(other)
|
|
248
|
+
rescue Exception
|
|
249
|
+
false
|
|
250
|
+
end
|
|
251
|
+
else
|
|
252
|
+
false
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
177
256
|
# @return [Time]
|
|
178
257
|
def to_time
|
|
179
|
-
@time ||= Time.at(0, @milliseconds, :millisecond).utc
|
|
258
|
+
@time ||= Time.at(0, @milliseconds, :millisecond).utc.freeze
|
|
180
259
|
end
|
|
181
260
|
|
|
182
|
-
# @return [Array
|
|
261
|
+
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
|
|
183
262
|
def octets
|
|
184
|
-
@octets ||= (
|
|
263
|
+
@octets ||= (timestamp_octets + randomness_octets).freeze
|
|
185
264
|
end
|
|
186
265
|
|
|
187
|
-
# @return [Array
|
|
188
|
-
def
|
|
189
|
-
@
|
|
266
|
+
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
|
|
267
|
+
def timestamp_octets
|
|
268
|
+
@timestamp_octets ||= self.class.octets_from_integer(@milliseconds, length: TIMESTAMP_OCTETS_LENGTH).freeze
|
|
190
269
|
end
|
|
191
270
|
|
|
192
|
-
# @return [Array
|
|
271
|
+
# @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
|
|
193
272
|
def randomness_octets
|
|
194
|
-
@randomness_octets ||= octets_from_integer(@entropy, length: RANDOMNESS_OCTETS_LENGTH).freeze
|
|
273
|
+
@randomness_octets ||= self.class.octets_from_integer(@entropy, length: RANDOMNESS_OCTETS_LENGTH).freeze
|
|
195
274
|
end
|
|
196
275
|
|
|
197
|
-
# @return [
|
|
276
|
+
# @return [String]
|
|
277
|
+
def timestamp
|
|
278
|
+
@timestamp ||= matchdata[:timestamp].freeze
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# @return [String]
|
|
282
|
+
def randomness
|
|
283
|
+
@randomness ||= matchdata[:randomness].freeze
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# @return [Regexp]
|
|
287
|
+
def pattern
|
|
288
|
+
@pattern ||= /(?<timestamp>#{timestamp})(?<randomness>#{randomness})/i.freeze
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# @return [Regexp]
|
|
292
|
+
def strict_pattern
|
|
293
|
+
@strict_pattern ||= /\A#{pattern.source}\z/i.freeze
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# @return [ULID, nil] when called on ULID as `7ZZZZZZZZZZZZZZZZZZZZZZZZZ`, returns `nil` instead of ULID
|
|
198
297
|
def next
|
|
199
|
-
|
|
298
|
+
next_int = to_i.next
|
|
299
|
+
return nil if next_int > MAX_INTEGER
|
|
300
|
+
@next ||= self.class.from_integer(next_int)
|
|
200
301
|
end
|
|
201
302
|
alias_method :succ, :next
|
|
202
303
|
|
|
304
|
+
# @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
|
|
305
|
+
def pred
|
|
306
|
+
pre_int = to_i.pred
|
|
307
|
+
return nil if pre_int.negative?
|
|
308
|
+
@pred ||= self.class.from_integer(pre_int)
|
|
309
|
+
end
|
|
310
|
+
|
|
203
311
|
# @return [self]
|
|
204
312
|
def freeze
|
|
205
313
|
# Evaluate all caching
|
|
206
314
|
inspect
|
|
207
315
|
octets
|
|
208
|
-
succ
|
|
209
316
|
to_i
|
|
317
|
+
succ
|
|
318
|
+
pred
|
|
319
|
+
strict_pattern
|
|
210
320
|
super
|
|
211
321
|
end
|
|
212
322
|
|
|
213
323
|
private
|
|
214
324
|
|
|
215
|
-
# @
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
def octets_from_integer(integer, length:)
|
|
219
|
-
digits = integer.digits(256)
|
|
220
|
-
(length - digits.size).times do
|
|
221
|
-
digits.push 0
|
|
222
|
-
end
|
|
223
|
-
digits.reverse!
|
|
325
|
+
# @return [MatchData]
|
|
326
|
+
def matchdata
|
|
327
|
+
@matchdata ||= STRICT_PATTERN.match(to_str).freeze
|
|
224
328
|
end
|
|
329
|
+
end
|
|
225
330
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
num = (num * base) + digit
|
|
234
|
-
end
|
|
235
|
-
num
|
|
236
|
-
end
|
|
331
|
+
require_relative 'ulid/version'
|
|
332
|
+
require_relative 'ulid/monotonic_generator'
|
|
333
|
+
|
|
334
|
+
class ULID
|
|
335
|
+
MONOTONIC_GENERATOR = MonotonicGenerator.new
|
|
336
|
+
|
|
337
|
+
private_constant :ENCODING_CHARS, :TIME_FORMAT_IN_INSPECT, :UUIDV4_PATTERN
|
|
237
338
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# coding: us-ascii
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
# Copyright (C) 2021 Kenichi Kamiya
|
|
4
|
+
|
|
5
|
+
class ULID
|
|
6
|
+
class MonotonicGenerator
|
|
7
|
+
attr_accessor :latest_milliseconds, :latest_entropy
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
reset
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
|
|
14
|
+
# @return [ULID]
|
|
15
|
+
def generate
|
|
16
|
+
milliseconds = ULID.current_milliseconds
|
|
17
|
+
reasonable_entropy = ULID.reasonable_entropy
|
|
18
|
+
|
|
19
|
+
@latest_milliseconds ||= milliseconds
|
|
20
|
+
@latest_entropy ||= reasonable_entropy
|
|
21
|
+
if @latest_milliseconds != milliseconds
|
|
22
|
+
@latest_milliseconds = milliseconds
|
|
23
|
+
@latest_entropy = reasonable_entropy
|
|
24
|
+
else
|
|
25
|
+
@latest_entropy += 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
ULID.new milliseconds: milliseconds, entropy: @latest_entropy
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @return [self]
|
|
32
|
+
def reset
|
|
33
|
+
@latest_milliseconds = nil
|
|
34
|
+
@latest_entropy = nil
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @raise [TypeError] always raises exception and does not freeze self
|
|
39
|
+
# @return [void]
|
|
40
|
+
def freeze
|
|
41
|
+
raise TypeError, "cannot freeze #{self.class}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/ulid/version.rb
CHANGED
data/sig/ulid.rbs
CHANGED
|
@@ -2,55 +2,21 @@
|
|
|
2
2
|
class ULID
|
|
3
3
|
VERSION: String
|
|
4
4
|
ENCODING_CHARS: Array[String]
|
|
5
|
-
|
|
6
|
-
RANDOMNESS_PART_LENGTH:
|
|
7
|
-
ENCODED_ID_LENGTH:
|
|
8
|
-
|
|
9
|
-
RANDOMNESS_OCTETS_LENGTH:
|
|
10
|
-
OCTETS_LENGTH:
|
|
11
|
-
MAX_MILLISECONDS:
|
|
12
|
-
MAX_ENTROPY:
|
|
13
|
-
|
|
5
|
+
TIMESTAMP_PART_LENGTH: 10
|
|
6
|
+
RANDOMNESS_PART_LENGTH: 16
|
|
7
|
+
ENCODED_ID_LENGTH: 26
|
|
8
|
+
TIMESTAMP_OCTETS_LENGTH: 6
|
|
9
|
+
RANDOMNESS_OCTETS_LENGTH: 10
|
|
10
|
+
OCTETS_LENGTH: 16
|
|
11
|
+
MAX_MILLISECONDS: 281474976710655
|
|
12
|
+
MAX_ENTROPY: 1208925819614629174706175
|
|
13
|
+
MAX_INTEGER: 340282366920938463463374607431768211455
|
|
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
|
-
@string: String
|
|
17
|
-
@integer: Integer
|
|
18
|
-
@octets: Array[Integer]
|
|
19
|
-
@time_octets: Array[Integer]
|
|
20
|
-
@randomness_octets: Array[Integer]
|
|
21
|
-
@inspect: String
|
|
22
|
-
@time: Time
|
|
23
|
-
@next: ULID
|
|
24
|
-
|
|
25
|
-
def self.generate: (?moment: Time | Integer, ?entropy: Integer) -> ULID
|
|
26
|
-
def self.monotonic_generate: -> ULID
|
|
27
|
-
def self.current_milliseconds: -> Integer
|
|
28
|
-
def self.time_to_milliseconds: (Time time) -> Integer
|
|
29
|
-
def self.reasonable_entropy: -> Integer
|
|
30
|
-
def self.parse: (String string) -> ULID
|
|
31
|
-
def self.valid?: (untyped string) -> bool
|
|
32
|
-
attr_reader milliseconds: Integer
|
|
33
|
-
attr_reader entropy: Integer
|
|
34
|
-
def initialize: (milliseconds: Integer, entropy: Integer) -> void
|
|
35
|
-
def to_str: -> String
|
|
36
|
-
def to_s: -> String
|
|
37
|
-
def to_i: -> Integer
|
|
38
|
-
def hash: -> Integer
|
|
39
|
-
def <=>: (untyped other) -> Integer?
|
|
40
|
-
def inspect: -> String
|
|
41
|
-
def eql?: (untyped other) -> bool
|
|
42
|
-
def ==: (untyped other) -> bool
|
|
43
|
-
def to_time: -> Time
|
|
44
|
-
def octets: -> Array[Integer]
|
|
45
|
-
def time_octets: -> Array[Integer]
|
|
46
|
-
def randomness_octets: -> Array[Integer]
|
|
47
|
-
def next: -> ULID
|
|
48
|
-
def succ: -> ULID
|
|
49
|
-
def freeze: -> self
|
|
50
|
-
|
|
51
|
-
private
|
|
52
|
-
def octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
|
|
53
|
-
def inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
|
|
54
20
|
|
|
55
21
|
class Error < StandardError
|
|
56
22
|
end
|
|
@@ -62,8 +28,6 @@ class ULID
|
|
|
62
28
|
end
|
|
63
29
|
|
|
64
30
|
class MonotonicGenerator
|
|
65
|
-
include Singleton
|
|
66
|
-
|
|
67
31
|
attr_accessor latest_milliseconds: Integer?
|
|
68
32
|
attr_accessor latest_entropy: Integer?
|
|
69
33
|
def initialize: -> void
|
|
@@ -71,4 +35,69 @@ class ULID
|
|
|
71
35
|
def reset: -> void
|
|
72
36
|
def freeze: -> void
|
|
73
37
|
end
|
|
38
|
+
|
|
39
|
+
type moment = Time | Integer
|
|
40
|
+
type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
|
41
|
+
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
|
|
42
|
+
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
|
43
|
+
|
|
44
|
+
@milliseconds: Integer
|
|
45
|
+
@entropy: Integer
|
|
46
|
+
@string: String?
|
|
47
|
+
@integer: Integer?
|
|
48
|
+
@octets: octets?
|
|
49
|
+
@timestamp_octets: timestamp_octets?
|
|
50
|
+
@randomness_octets: randomness_octets?
|
|
51
|
+
@timestamp: String?
|
|
52
|
+
@randomness: String?
|
|
53
|
+
@inspect: String?
|
|
54
|
+
@time: Time?
|
|
55
|
+
@next: ULID?
|
|
56
|
+
@pattern: Regexp?
|
|
57
|
+
@strict_pattern: Regexp?
|
|
58
|
+
@matchdata: MatchData?
|
|
59
|
+
|
|
60
|
+
def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
|
|
61
|
+
def self.monotonic_generate: -> ULID
|
|
62
|
+
def self.current_milliseconds: -> Integer
|
|
63
|
+
def self.time_to_milliseconds: (Time time) -> Integer
|
|
64
|
+
def self.reasonable_entropy: -> Integer
|
|
65
|
+
def self.parse: (String string) -> ULID
|
|
66
|
+
def self.from_uuidv4: (String uuid) -> ULID
|
|
67
|
+
def self.from_integer: (Integer integer) -> ULID
|
|
68
|
+
def self.min: (?moment: moment) -> ULID
|
|
69
|
+
def self.max: (?moment: moment) -> ULID
|
|
70
|
+
def self.valid?: (untyped string) -> bool
|
|
71
|
+
def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
|
|
72
|
+
| (String string) { (ULID ulid) -> void } -> singleton(ULID)
|
|
73
|
+
def self.octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
|
|
74
|
+
def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
|
|
75
|
+
attr_reader milliseconds: Integer
|
|
76
|
+
attr_reader entropy: Integer
|
|
77
|
+
def initialize: (milliseconds: Integer, entropy: Integer) -> void
|
|
78
|
+
def to_str: -> String
|
|
79
|
+
alias to_s to_str
|
|
80
|
+
def to_i: -> Integer
|
|
81
|
+
alias hash to_i
|
|
82
|
+
def <=>: (ULID other) -> Integer
|
|
83
|
+
| (untyped other) -> Integer?
|
|
84
|
+
def inspect: -> String
|
|
85
|
+
def eql?: (untyped other) -> bool
|
|
86
|
+
alias == eql?
|
|
87
|
+
def ===: (untyped other) -> bool
|
|
88
|
+
def to_time: -> Time
|
|
89
|
+
def timestamp: -> String
|
|
90
|
+
def randomness: -> String
|
|
91
|
+
def pattern: -> Regexp
|
|
92
|
+
def strict_pattern: -> Regexp
|
|
93
|
+
def octets: -> octets
|
|
94
|
+
def timestamp_octets: -> timestamp_octets
|
|
95
|
+
def randomness_octets: -> randomness_octets
|
|
96
|
+
def next: -> ULID?
|
|
97
|
+
alias succ next
|
|
98
|
+
def pred: -> ULID?
|
|
99
|
+
def freeze: -> self
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
def matchdata: -> MatchData
|
|
74
103
|
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.11
|
|
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-
|
|
11
|
+
date: 2021-05-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: integer-base
|
|
@@ -17,33 +17,19 @@ dependencies:
|
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: 0.1.2
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.1.2
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: test-unit
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 3.4.1
|
|
34
20
|
- - "<"
|
|
35
21
|
- !ruby/object:Gem::Version
|
|
36
|
-
version:
|
|
37
|
-
type: :
|
|
22
|
+
version: 0.2.0
|
|
23
|
+
type: :runtime
|
|
38
24
|
prerelease: false
|
|
39
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
40
26
|
requirements:
|
|
41
27
|
- - ">="
|
|
42
28
|
- !ruby/object:Gem::Version
|
|
43
|
-
version:
|
|
29
|
+
version: 0.1.2
|
|
44
30
|
- - "<"
|
|
45
31
|
- !ruby/object:Gem::Version
|
|
46
|
-
version:
|
|
32
|
+
version: 0.2.0
|
|
47
33
|
- !ruby/object:Gem::Dependency
|
|
48
34
|
name: benchmark-ips
|
|
49
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -84,18 +70,21 @@ dependencies:
|
|
|
84
70
|
- - "<"
|
|
85
71
|
- !ruby/object:Gem::Version
|
|
86
72
|
version: '2'
|
|
87
|
-
description:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Also having rbs signature files.
|
|
73
|
+
description: " ULID(Universally Unique Lexicographically Sortable Identifier) has
|
|
74
|
+
useful specs for applications (e.g. `Database key`). \n This gem aims to provide
|
|
75
|
+
the generator, monotonic generator, parser and handy manipulation features around
|
|
76
|
+
the ULID.\n Also providing `rbs` signature files.\n"
|
|
92
77
|
email:
|
|
93
78
|
- kachick1+ruby@gmail.com
|
|
94
79
|
executables: []
|
|
95
80
|
extensions: []
|
|
96
81
|
extra_rdoc_files: []
|
|
97
82
|
files:
|
|
83
|
+
- LICENSE
|
|
84
|
+
- README.md
|
|
85
|
+
- Steepfile
|
|
98
86
|
- lib/ulid.rb
|
|
87
|
+
- lib/ulid/monotonic_generator.rb
|
|
99
88
|
- lib/ulid/version.rb
|
|
100
89
|
- sig/ulid.rbs
|
|
101
90
|
homepage: https://github.com/kachick/ruby-ulid
|