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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c072f00beeac3732fbbee2fe32af5f95e14a4eb516e659f70e9e08c0bc525e01
4
- data.tar.gz: f8d7ad8d5e714c142c0309a6e814be406c61519cc6e36adcaccae4982d94b96f
3
+ metadata.gz: 8cbdba2a6645c66ab28595928ff1121cb32b15de3a687b865678c00903e9379e
4
+ data.tar.gz: e5b1d117960ba3babdf3c73f067529f9f501bba90a7a8401625f0555c6b97b38
5
5
  SHA512:
6
- metadata.gz: 0e7aaedb437ae82765ec74cf36ddbeb5b529948c9c65c077a0e648156c21ebb23dc26f2e85b3430434dd1babfaaa1b4b1d8bfd00c8dbf9f09f332bad33b93f97
7
- data.tar.gz: 01f353360ca4b82534de75f8e715d872b9c976a85655a9f419dfc1a93b6e867d0bea342ad970a3b4653048f3484bba1bc8842f75980142869d13005d3fabc1e7
6
+ metadata.gz: 07d641b18cabe49adb75117b744b03b442f5d9be7a97d433927265ec9cae1031e6ba51626860bcd2289be68a228971282e0ae45db37940697437c5a79c158d35
7
+ data.tar.gz: 823d8b48de6d9eda3eaad2955384c8d9fc4251f8c1b447ec7c743c731d6dd5c942880d9e93aacd97ea7c9f66cc755e1f3ab2031307377be24a5abd2fb262606f
@@ -1,20 +1,26 @@
1
1
  name: Ruby
2
2
 
3
- on: [push]
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 2.6
13
- uses: actions/setup-ruby@v1
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
14
21
  with:
15
- ruby-version: 2.6.x
16
- - name: Build and test with Rake
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
@@ -3,7 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec
4
4
 
5
5
  group :development, :test do
6
- gem 'pry-byebug'
6
+ gem 'byebug'
7
7
  gem 'rake'
8
8
  gem 'rubocop'
9
9
  end
data/Gemfile.lock CHANGED
@@ -1,50 +1,48 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ulid (1.2.0)
4
+ ulid (1.3.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- ast (2.3.0)
9
+ ast (2.4.2)
10
10
  base32-crockford (0.1.0)
11
- byebug (9.1.0)
12
- coderay (1.1.2)
13
- method_source (0.9.0)
14
- minitest (5.10.3)
15
- parallel (1.12.0)
16
- parser (2.4.0.0)
17
- ast (~> 2.2)
18
- powerpack (0.1.1)
19
- pry (0.11.3)
20
- coderay (~> 1.1.0)
21
- method_source (~> 0.9.0)
22
- pry-byebug (3.5.1)
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 (>= 2.3.3.1, < 3.0)
31
- powerpack (~> 0.1)
32
- rainbow (>= 2.2.2, < 3.0)
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 (~> 1.0, >= 1.0.1)
35
- ruby-progressbar (1.8.3)
36
- unicode-display_width (1.3.0)
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.0.1
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.*
@@ -1,52 +1,60 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- if RUBY_VERSION >= '2.5'
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
- RANDOM_BYTES = 10
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
- def generate(time = Time.now)
21
- input = octo_word(time)
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
- encode(input, ENCODED_LENGTH)
23
+ integer = (hi << 64) | lo
24
+ encode(integer, ENCODED_LENGTH)
24
25
  end
25
26
 
26
- def generate_bytes(time = Time.now)
27
- time_48bit(time) + random_bytes
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
- e = Array.new(length, ZERO)
45
+ encoded = Array.new(length, ZERO)
34
46
  i = length - 1
35
47
 
36
48
  while input > 0
37
- e[i] = ENCODING[input & MASK]
38
- input >>= 5
49
+ encoded[i] = ENCODING[input & MASK]
50
+ input >>= BITS_PER_B32_CHAR
39
51
  i -= 1
40
52
  end
41
53
 
42
- e.pack('c*')
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
@@ -1,3 +1,4 @@
1
+ # frozen-string-literal: true
1
2
  module ULID
2
- VERSION = '1.3.0'.freeze
3
+ VERSION = '1.4.0'
3
4
  end
@@ -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.must_equal 26
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::RANDOM_BYTES)
65
- ULID.stub(:random_bytes, random_bytes) do
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
@@ -1,4 +1,4 @@
1
- require 'pry'
1
+ require 'byebug'
2
2
  require 'minitest/autorun'
3
3
  require 'minitest/pride'
4
4
  require 'ulid'
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
- require 'ulid/version'
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.3.0
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: 2021-03-01 00:00:00.000000000 Z
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: |2
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: '0'
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.1.2
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: []