ulid 1.3.0 → 1.4.0
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/.github/workflows/ruby.yml +13 -7
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +26 -28
- data/README.md +24 -0
- data/lib/ulid/generator.rb +30 -26
- data/lib/ulid/version.rb +2 -1
- data/spec/lib/ulid_spec.rb +38 -3
- data/spec/spec_helper.rb +1 -1
- data/ulid.gemspec +2 -8
- metadata +6 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cbdba2a6645c66ab28595928ff1121cb32b15de3a687b865678c00903e9379e
|
4
|
+
data.tar.gz: e5b1d117960ba3babdf3c73f067529f9f501bba90a7a8401625f0555c6b97b38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07d641b18cabe49adb75117b744b03b442f5d9be7a97d433927265ec9cae1031e6ba51626860bcd2289be68a228971282e0ae45db37940697437c5a79c158d35
|
7
|
+
data.tar.gz: 823d8b48de6d9eda3eaad2955384c8d9fc4251f8c1b447ec7c743c731d6dd5c942880d9e93aacd97ea7c9f66cc755e1f3ab2031307377be24a5abd2fb262606f
|
data/.github/workflows/ruby.yml
CHANGED
@@ -1,20 +1,26 @@
|
|
1
1
|
name: Ruby
|
2
2
|
|
3
|
-
on:
|
3
|
+
on:
|
4
|
+
pull_request: ~
|
5
|
+
push:
|
6
|
+
branches:
|
7
|
+
- master
|
4
8
|
|
5
9
|
jobs:
|
6
10
|
build:
|
7
11
|
|
8
12
|
runs-on: ubuntu-latest
|
13
|
+
strategy:
|
14
|
+
matrix:
|
15
|
+
ruby-version: ['2.6', '2.7', '3.0', '3.1', '3.2']
|
9
16
|
|
10
17
|
steps:
|
11
18
|
- uses: actions/checkout@v2
|
12
|
-
- name: Set up Ruby
|
13
|
-
uses:
|
19
|
+
- name: Set up Ruby
|
20
|
+
uses: ruby/setup-ruby@v1
|
14
21
|
with:
|
15
|
-
ruby-version:
|
16
|
-
|
22
|
+
ruby-version: ${{ matrix.ruby-version }}
|
23
|
+
bundler-cache: true # Run "bundle install", and cache the result automatically.
|
24
|
+
- name: Run tests
|
17
25
|
run: |
|
18
|
-
gem install bundler
|
19
|
-
bundle install --jobs 4 --retry 3
|
20
26
|
bundle exec rake test
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
# 1.4.0
|
2
|
+
|
3
|
+
- PR #32 - Allow fully deterministic ULID generation by providing both the timestamp
|
4
|
+
component and a string suffix to replace the randomness component. Thanks @andjosh
|
5
|
+
|
1
6
|
# 1.2.0
|
2
7
|
|
3
8
|
- PR #20 - Use an array to improve speed / reduce memory allocations. Thanks, @jamescook
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,50 +1,48 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
ulid (1.
|
4
|
+
ulid (1.3.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
-
ast (2.
|
9
|
+
ast (2.4.2)
|
10
10
|
base32-crockford (0.1.0)
|
11
|
-
byebug (
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
byebug (~> 9.1)
|
24
|
-
pry (~> 0.10)
|
25
|
-
rainbow (2.2.2)
|
26
|
-
rake
|
27
|
-
rake (13.0.1)
|
28
|
-
rubocop (0.50.0)
|
11
|
+
byebug (11.1.3)
|
12
|
+
json (2.6.3)
|
13
|
+
minitest (5.18.0)
|
14
|
+
parallel (1.22.1)
|
15
|
+
parser (3.2.1.0)
|
16
|
+
ast (~> 2.4.1)
|
17
|
+
rainbow (3.1.1)
|
18
|
+
rake (13.0.6)
|
19
|
+
regexp_parser (2.7.0)
|
20
|
+
rexml (3.2.5)
|
21
|
+
rubocop (1.47.0)
|
22
|
+
json (~> 2.3)
|
29
23
|
parallel (~> 1.10)
|
30
|
-
parser (>=
|
31
|
-
|
32
|
-
|
24
|
+
parser (>= 3.2.0.0)
|
25
|
+
rainbow (>= 2.2.2, < 4.0)
|
26
|
+
regexp_parser (>= 1.8, < 3.0)
|
27
|
+
rexml (>= 3.2.5, < 4.0)
|
28
|
+
rubocop-ast (>= 1.26.0, < 2.0)
|
33
29
|
ruby-progressbar (~> 1.7)
|
34
|
-
unicode-display_width (
|
35
|
-
|
36
|
-
|
30
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
31
|
+
rubocop-ast (1.27.0)
|
32
|
+
parser (>= 3.2.1.0)
|
33
|
+
ruby-progressbar (1.13.0)
|
34
|
+
unicode-display_width (2.4.2)
|
37
35
|
|
38
36
|
PLATFORMS
|
39
37
|
ruby
|
40
38
|
|
41
39
|
DEPENDENCIES
|
42
40
|
base32-crockford
|
41
|
+
byebug
|
43
42
|
minitest
|
44
|
-
pry-byebug
|
45
43
|
rake
|
46
44
|
rubocop
|
47
45
|
ulid!
|
48
46
|
|
49
47
|
BUNDLED WITH
|
50
|
-
2.
|
48
|
+
2.4.7
|
data/README.md
CHANGED
@@ -48,6 +48,30 @@ require 'ulid'
|
|
48
48
|
ULID.generate # 01ARZ3NDEKTSV4RRFFQ69G5FAV
|
49
49
|
```
|
50
50
|
|
51
|
+
**I want to generate a ULID using an arbitrary timestamp**
|
52
|
+
|
53
|
+
You can optionally pass a `Time` instance to `ULID.generate` to set an arbitrary timestamp component, i.e. the prefix of the ULID.
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
time_t1 = Time.now
|
57
|
+
ulid = ULID.generate(time_t1)
|
58
|
+
```
|
59
|
+
|
60
|
+
**I want to generate a ULID using an arbitrary suffix, i.e. without the randomness component**
|
61
|
+
|
62
|
+
You can optionally pass a 80-bit hex-encodable `String` on the argument `suffix` to `ULID.generate`. This will replace the randomness component
|
63
|
+
by the suffix provided. This allows for fully deterministic ULIDs.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
require 'securerandom'
|
67
|
+
|
68
|
+
time = Time.now
|
69
|
+
an_event_identifier = SecureRandom.uuid
|
70
|
+
ulid1 = ULID.generate(time, suffix: an_event_identifier)
|
71
|
+
ulid2 = ULID.generate(time, suffix: an_event_identifier)
|
72
|
+
ulid1 == ulid2 # true
|
73
|
+
```
|
74
|
+
|
51
75
|
## Specification
|
52
76
|
|
53
77
|
Below is the current specification of ULID as implemented in this repository. *Note: the binary format has not been implemented.*
|
data/lib/ulid/generator.rb
CHANGED
@@ -1,52 +1,60 @@
|
|
1
1
|
# frozen-string-literal: true
|
2
2
|
|
3
|
-
|
4
|
-
require 'securerandom'
|
5
|
-
else
|
6
|
-
require 'sysrandom/securerandom'
|
7
|
-
end
|
3
|
+
require 'securerandom'
|
8
4
|
|
9
5
|
module ULID
|
10
6
|
module Generator
|
11
7
|
ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'.bytes.freeze # Crockford's Base32
|
12
|
-
|
8
|
+
RANDOM_BITS = 80
|
13
9
|
ENCODED_LENGTH = 26
|
14
|
-
BIT_LENGTH = 128
|
15
10
|
BITS_PER_B32_CHAR = 5
|
16
11
|
ZERO = '0'.ord
|
17
|
-
|
18
12
|
MASK = 0x1f
|
19
13
|
|
20
|
-
|
21
|
-
|
14
|
+
# Generates a 128-bit ULID string.
|
15
|
+
# @param [Time] time (Time.now) Timestamp - first 48 bits
|
16
|
+
# @param [String] suffix (80 random bits) - the remaining 80 bits as hex encodable string
|
17
|
+
def generate(time = Time.now, suffix: nil)
|
18
|
+
(hi, lo) = generate_bytes(time, suffix: suffix).unpack('Q>Q>')
|
19
|
+
if hi.nil? || lo.nil?
|
20
|
+
raise ArgumentError, 'suffix string without hex encoding passed to ULID generator'
|
21
|
+
end
|
22
22
|
|
23
|
-
|
23
|
+
integer = (hi << 64) | lo
|
24
|
+
encode(integer, ENCODED_LENGTH)
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
# Generates a 128-bit ULID.
|
28
|
+
# @param [Time] time (Time.now) Timestamp - first 48 bits
|
29
|
+
# @param [String] suffix (80 random bits) - the remaining 80 bits as hex encodable string
|
30
|
+
def generate_bytes(time = Time.now, suffix: nil)
|
31
|
+
suffix_bytes =
|
32
|
+
if suffix
|
33
|
+
suffix.split('').map { |char| char.to_i(32) }.pack('C*')
|
34
|
+
else
|
35
|
+
SecureRandom.random_bytes(RANDOM_BITS / 8)
|
36
|
+
end
|
37
|
+
|
38
|
+
time_48bit(time) + suffix_bytes
|
28
39
|
end
|
29
40
|
|
30
41
|
private
|
31
42
|
|
43
|
+
# Encodes a 128-bit integer input as a 26-character ULID string using Crockford's Base32.
|
32
44
|
def encode(input, length)
|
33
|
-
|
45
|
+
encoded = Array.new(length, ZERO)
|
34
46
|
i = length - 1
|
35
47
|
|
36
48
|
while input > 0
|
37
|
-
|
38
|
-
input >>=
|
49
|
+
encoded[i] = ENCODING[input & MASK]
|
50
|
+
input >>= BITS_PER_B32_CHAR
|
39
51
|
i -= 1
|
40
52
|
end
|
41
53
|
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
def octo_word(time = Time.now)
|
46
|
-
(hi, lo) = generate_bytes(time).unpack('Q>Q>')
|
47
|
-
(hi << 64) | lo
|
54
|
+
encoded.pack('c*')
|
48
55
|
end
|
49
56
|
|
57
|
+
# Returns the first 6 bytes of a timestamp (in milliseconds since the Unix epoch) as a 48-bit byte string
|
50
58
|
def time_48bit(time = Time.now)
|
51
59
|
# Avoid `time.to_f` since we want to accurately represent a whole number of milliseconds:
|
52
60
|
#
|
@@ -62,9 +70,5 @@ module ULID
|
|
62
70
|
time_ms = (time.to_r * 1000).to_i
|
63
71
|
[time_ms].pack('Q>')[2..-1]
|
64
72
|
end
|
65
|
-
|
66
|
-
def random_bytes
|
67
|
-
SecureRandom.random_bytes(RANDOM_BYTES)
|
68
|
-
end
|
69
73
|
end
|
70
74
|
end
|
data/lib/ulid/version.rb
CHANGED
data/spec/lib/ulid_spec.rb
CHANGED
@@ -6,7 +6,7 @@ describe ULID do
|
|
6
6
|
it 'ensures it has 26 chars' do
|
7
7
|
ulid = ULID.generate
|
8
8
|
|
9
|
-
ulid.length
|
9
|
+
assert_equal ulid.length, 26
|
10
10
|
end
|
11
11
|
|
12
12
|
it 'is sortable' do
|
@@ -49,6 +49,41 @@ describe ULID do
|
|
49
49
|
|
50
50
|
assert_equal(ulids, ulids.sort)
|
51
51
|
end
|
52
|
+
|
53
|
+
it 'is deterministic based on time' do
|
54
|
+
input_time = Time.now
|
55
|
+
ulid1 = ULID.generate(input_time)
|
56
|
+
ulid2 = ULID.generate(input_time)
|
57
|
+
assert_equal ulid2.slice(0, 10), ulid1.slice(0, 10)
|
58
|
+
assert ulid2 != ulid1
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'is deterministic based on suffix' do
|
62
|
+
input_time = Time.now
|
63
|
+
suffix = SecureRandom.uuid
|
64
|
+
ulid1 = ULID.generate(input_time, suffix: suffix)
|
65
|
+
ulid2 = ULID.generate(input_time + 1, suffix: suffix)
|
66
|
+
assert_equal ulid2.slice(10, 26), ulid1.slice(10, 26)
|
67
|
+
assert ulid2 != ulid1
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'is fully deterministic based on time and suffix' do
|
71
|
+
input_time = Time.now
|
72
|
+
suffix = SecureRandom.uuid
|
73
|
+
ulid1 = ULID.generate(input_time, suffix: suffix)
|
74
|
+
ulid2 = ULID.generate(input_time, suffix: suffix)
|
75
|
+
assert_equal ulid2, ulid1
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'raises exception when non-encodable 80-bit suffix string is used' do
|
79
|
+
input_time = Time.now
|
80
|
+
suffix = SecureRandom.uuid
|
81
|
+
assert_raises(ArgumentError) do
|
82
|
+
ULID.generate(input_time, suffix: suffix[0...9])
|
83
|
+
end
|
84
|
+
|
85
|
+
ULID.generate(input_time, suffix: suffix[0...10])
|
86
|
+
end
|
52
87
|
end
|
53
88
|
|
54
89
|
describe 'underlying binary' do
|
@@ -61,8 +96,8 @@ describe ULID do
|
|
61
96
|
end
|
62
97
|
|
63
98
|
it 'encodes the remaining 80 bits as random' do
|
64
|
-
random_bytes = SecureRandom.random_bytes(ULID::Generator::
|
65
|
-
|
99
|
+
random_bytes = SecureRandom.random_bytes(ULID::Generator::RANDOM_BITS / 8)
|
100
|
+
SecureRandom.stub(:random_bytes, random_bytes) do
|
66
101
|
bytes = ULID.generate_bytes
|
67
102
|
assert bytes[6..-1] == random_bytes
|
68
103
|
end
|
data/spec/spec_helper.rb
CHANGED
data/ulid.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
lib = File.expand_path('lib', __dir__)
|
2
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
|
3
|
+
require_relative 'lib/ulid/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = 'ulid'
|
@@ -12,12 +12,6 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.license = 'MIT'
|
13
13
|
|
14
14
|
spec.files = `git ls-files -z`.split("\x0")
|
15
|
-
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
16
|
-
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
15
|
spec.require_paths = ['lib']
|
18
|
-
|
19
|
-
spec.post_install_message = '
|
20
|
-
ulid gem needs to install sysrandom gem if you use Ruby 2.4 or older.
|
21
|
-
Execute `gem install sysrandom` or add `gem "sysrandom"` to Gemfile.
|
22
|
-
'
|
16
|
+
spec.required_ruby_version = '>= 2.6.8'
|
23
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rafael Sales
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -38,10 +38,7 @@ homepage: https://github.com/rafaelsales/ulid
|
|
38
38
|
licenses:
|
39
39
|
- MIT
|
40
40
|
metadata: {}
|
41
|
-
post_install_message:
|
42
|
-
|
43
|
-
ulid gem needs to install sysrandom gem if you use Ruby 2.4 or older.
|
44
|
-
Execute `gem install sysrandom` or add `gem "sysrandom"` to Gemfile.
|
41
|
+
post_install_message:
|
45
42
|
rdoc_options: []
|
46
43
|
require_paths:
|
47
44
|
- lib
|
@@ -49,18 +46,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
46
|
requirements:
|
50
47
|
- - ">="
|
51
48
|
- !ruby/object:Gem::Version
|
52
|
-
version:
|
49
|
+
version: 2.6.8
|
53
50
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
51
|
requirements:
|
55
52
|
- - ">="
|
56
53
|
- !ruby/object:Gem::Version
|
57
54
|
version: '0'
|
58
55
|
requirements: []
|
59
|
-
rubygems_version: 3.
|
56
|
+
rubygems_version: 3.3.7
|
60
57
|
signing_key:
|
61
58
|
specification_version: 4
|
62
59
|
summary: Universally Unique Lexicographically Sortable Identifier implementation for
|
63
60
|
Ruby
|
64
|
-
test_files:
|
65
|
-
- spec/lib/ulid_spec.rb
|
66
|
-
- spec/spec_helper.rb
|
61
|
+
test_files: []
|