ulid 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: []