ruby-ulid 0.6.1 → 0.7.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: 5c7cf89034d0f74026278e2a4c113d57f8fc28b861b8bcf1d2210dbb76688e52
4
- data.tar.gz: 340bfbea8fb1ff3ec0e49339e2a5a388a81552f8cd73a65ad9ff049448fc0c3f
3
+ metadata.gz: 637c2b95fc9d18a59e1bc6d23a3791bc80362b146c52ff962c407894c29c782e
4
+ data.tar.gz: 74a9e3ea4be5d9a541d4cb6dd012997406ef3cb68a08f7a1a42959f40f7ab4e5
5
5
  SHA512:
6
- metadata.gz: 74d26cb5dc49a8d79a5c933817c1c8913954d675d996a91057f29859fd1fdf0a32c99d8121ef954e1d7d400110fa1737be17a9084bfc03655082de96c9614392
7
- data.tar.gz: 96f19838c4c90ee313722857f88693e88f247e5380293291b362eadc834f6adb1613c01034a0cfe430f24576b4fea55f252b79f689b00c3e05b739118db8906e
6
+ metadata.gz: c39c42f8da0aac22356cb26fefb7d23855d118f68148be90c1e253577847b91135f8a04ef1fdca1af59075bd673e61cf2f492f046f30616f7c6b493e28332174
7
+ data.tar.gz: 623f7f9b4d46e46c2a47a143330b3d1c2cf83a033e83c8e68ab3af13e56f1ff5a082b3a15f57efcf4a3180fcc5786e18e94a0221fd03c44343322d641b69e31a
data/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # ruby-ulid
2
2
 
3
- [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/test_behaviors.yml/?branch=main)
3
+ [![Build Status](https://github.com/kachick/ruby-ulid/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kachick/ruby-ulid/actions/workflows/ci.yml?query=branch%3Amain)
4
4
  [![Gem Version](https://badge.fury.io/rb/ruby-ulid.svg)](http://badge.fury.io/rb/ruby-ulid)
5
5
 
6
6
  ## Overview
7
7
 
8
- [ulid/spec](https://github.com/ulid/spec) is useful.
9
- Especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
10
- This gem aims to provide the generator, optional monotonicity, parser and other manipulation features around ULID with included [RBS](https://github.com/ruby/rbs).
8
+ [ulid/spec](https://github.com/ulid/spec) has some useful features.\
9
+ Especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortability`.\
10
+ This gem aims to provide the generator, optional monotonicity, parser and other manipulation ways around ULID with included [RBS](https://github.com/ruby/rbs).
11
11
 
12
12
  ---
13
13
 
@@ -49,7 +49,7 @@ Should be installed!
49
49
  Add this line in your Gemfile.
50
50
 
51
51
  ```ruby
52
- gem('ruby-ulid', '~> 0.6.1')
52
+ gem('ruby-ulid', '~> 0.7.0')
53
53
  ```
54
54
 
55
55
  ### How to use
@@ -58,10 +58,10 @@ gem('ruby-ulid', '~> 0.6.1')
58
58
  require 'ulid'
59
59
 
60
60
  ULID::VERSION
61
- # => "0.6.1"
61
+ # => "0.7.0"
62
62
  ```
63
63
 
64
- NOTE: This README includes info about development version. If you would see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.6.1).
64
+ NOTE: This README includes info about development version. If you would see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.7.0).
65
65
 
66
66
  ### Generator and Parser
67
67
 
@@ -99,9 +99,9 @@ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT
99
99
  ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
100
100
  ```
101
101
 
102
- Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
102
+ Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
103
103
 
104
- `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.
104
+ `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.\
105
105
  It can take same arguments as `ULID.generate`.
106
106
 
107
107
  ```ruby
@@ -116,13 +116,13 @@ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1') #=> 2000-01-01 00:00:00 UTC
116
116
  ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:00:00 +0900
117
117
  ```
118
118
 
119
- This project does not prioritize the speed. However it actually works faster than others! :zap:
119
+ This project does not prioritize on the speed. However it actually works faster than others! :zap:
120
120
 
121
- Snapshot on 0.6.1 is below
121
+ Snapshot on 0.7.0 is below
122
122
 
123
- * Generator is 1.5x faster than - [ulid gem](https://github.com/rafaelsales/ulid)
124
- * Generator is 1.9x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
125
- * Parser is 2.7x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
123
+ - Generator is 1.6x faster than - [ulid gem](https://github.com/rafaelsales/ulid)
124
+ - Generator is 1.9x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
125
+ - Parser is 2.6x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
126
126
 
127
127
  You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
128
128
 
@@ -151,7 +151,7 @@ ulids.sort == ulids #=> false
151
151
 
152
152
  ### How to keep `Sortable` even if in same timestamp
153
153
 
154
- If you want to prefer `sortable`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.
154
+ If you want to prefer `sortable`, Use `MonotonicGenerator` instead. It is called as [Monotonicity](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#monotonicity) on the spec.\
155
155
  (Though it starts with new random value when changed the timestamp)
156
156
 
157
157
  ```ruby
@@ -195,7 +195,7 @@ ulids.grep(one_of_the_above)
195
195
  ulids.grep_v(one_of_the_above)
196
196
  ```
197
197
 
198
- When want to filter ULIDs with `Time`, we should consider to handle the precision.
198
+ When want to filter ULIDs with `Time`, we should consider to handle the precision.\
199
199
  So this gem provides `ULID.range` to generate reasonable `Range[ULID]` from `Range[Time]`
200
200
 
201
201
  ```ruby
@@ -294,7 +294,7 @@ ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
294
294
 
295
295
  #### As element in Enumerable
296
296
 
297
- `ULID#next` and `ULID#succ` returns next(successor) ULID.
297
+ `ULID#next` and `ULID#succ` returns next(successor) ULID.\
298
298
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
299
299
 
300
300
  NOTE: However basically `Range[ULID]#each` should not be used. Incrementing 128 bits IDs are not reasonable operation in most cases.
@@ -360,15 +360,15 @@ ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
360
360
 
361
361
  I'm afraid so, we should consider [Current ULID spec](https://github.com/ulid/spec/tree/d0c7170df4517939e70129b4d6462cc162f2d5bf#universally-unique-lexicographically-sortable-identifier) has `orthographical variants of the format` possibilities.
362
362
 
363
- >Case insensitive
363
+ > Case insensitive
364
364
 
365
- I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.
365
+ I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.\
366
366
  However it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
367
367
 
368
- >Uses Crockford's base32 for better efficiency and readability (5 bits per character)
368
+ > Uses Crockford's base32 for better efficiency and readability (5 bits per character)
369
369
 
370
- The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
371
- And accepts freestyle inserting `Hyphens (-)`.
370
+ The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.\
371
+ And accepts freestyle inserting `Hyphens (-)`.\
372
372
  To consider this patterns or not is different in each implementations.
373
373
 
374
374
  I have suggested to clarify `subset of Crockford's base32` in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
@@ -387,7 +387,7 @@ ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:
387
387
 
388
388
  #### UUIDv4 converter (experimental)
389
389
 
390
- `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
390
+ `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.\
391
391
  The imported timestamp is meaningless. So ULID's benefit will lost.
392
392
 
393
393
  ```ruby
@@ -426,11 +426,11 @@ See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-
426
426
 
427
427
  Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
428
428
 
429
- I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) + [soutaro/steep@1.0.1](https://github.com/soutaro/steep) + [soutaro/steep-vscode](https://github.com/soutaro/steep-vscode).
429
+ I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) + [soutaro/steep@1.0.1](https://github.com/soutaro/steep) + [soutaro/steep-vscode](https://github.com/soutaro/steep-vscode).
430
430
 
431
- * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
432
- * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
433
- * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
431
+ - ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
432
+ - ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
433
+ - ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
434
434
 
435
435
  ## References
436
436
 
@@ -440,5 +440,5 @@ I have checked the behavior with [ruby/rbs@2.6.0](https://github.com/ruby/rbs) +
440
440
 
441
441
  ## Note
442
442
 
443
- - [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html) is another choice for sortable and randomness ID.
443
+ - [UUIDv6, UUIDv7, UUIDv8](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html) is another choice for sortable and randomness ID.\
444
444
  However they are stayed in draft state. ref: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
@@ -1,9 +1,10 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
- # shareable_constant_value: literal
4
3
 
5
4
  # Copyright (C) 2021 Kenichi Kamiya
6
5
 
6
+ require_relative('utils')
7
+
7
8
  class ULID
8
9
  # @see https://www.crockford.com/base32.html
9
10
  #
@@ -68,36 +69,36 @@ class ULID
68
69
  VARIANT_TR_PATTERN = variant_to_normarized.keys.join.freeze
69
70
  NORMALIZED_TR_PATTERN = variant_to_normarized.values.join.freeze
70
71
 
72
+ Utils.make_sharable_constantans(self)
73
+
71
74
  # @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
72
75
 
73
- # @api private
74
76
  # @param [String] string
75
77
  # @return [Integer]
76
78
  def self.decode(string)
77
- n32encoded = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32_TR_PATTERN)
78
- n32encoded.to_i(32)
79
+ base32encoded = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32_TR_PATTERN)
80
+ Integer(base32encoded, 32, exception: true)
79
81
  end
80
82
 
81
- # @api private
82
83
  # @param [Integer] integer
83
84
  # @return [String]
84
85
  def self.encode(integer)
85
- n32encoded = integer.to_s(32)
86
- from_n32(n32encoded).rjust(ENCODED_LENGTH, '0')
86
+ base32encoded = integer.to_s(32)
87
+ from_base32(base32encoded).rjust(ENCODED_LENGTH, '0')
87
88
  end
88
89
 
89
- # @api private
90
90
  # @param [String] string
91
91
  # @return [String]
92
92
  def self.normalize(string)
93
93
  string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
94
94
  end
95
95
 
96
- # @api private
97
- # @param [String] n32encoded
96
+ # @param [String] base32encoded
98
97
  # @return [String]
99
- def self.from_n32(n32encoded)
100
- n32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_TR_PATTERN)
98
+ def self.from_base32(base32encoded)
99
+ base32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_TR_PATTERN)
101
100
  end
102
101
  end
102
+
103
+ private_constant(:CrockfordBase32)
103
104
  end
@@ -4,6 +4,9 @@
4
4
 
5
5
  # Copyright (C) 2021 Kenichi Kamiya
6
6
 
7
+ require_relative('errors')
8
+ require_relative('utils')
9
+
7
10
  class ULID
8
11
  class MonotonicGenerator
9
12
  # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
@@ -11,7 +14,8 @@ class ULID
11
14
  include(MonitorMixin)
12
15
 
13
16
  # @return [ULID, nil]
14
- attr_reader(:prev)
17
+ attr_accessor(:prev)
18
+ private(:prev=)
15
19
 
16
20
  undef_method(:instance_variable_set)
17
21
 
@@ -31,7 +35,7 @@ class ULID
31
35
  # @raise [OverflowError] if the entropy part is larger than the ULID limit in same milliseconds
32
36
  # @raise [UnexpectedError] if the generated ULID is an invalid value in monotonicity spec.
33
37
  # Basically will not happen. Just means this feature prefers error rather than invalid value.
34
- def generate(moment: ULID.current_milliseconds)
38
+ def generate(moment: Utils.current_milliseconds)
35
39
  synchronize do
36
40
  prev_ulid = @prev
37
41
  unless prev_ulid
@@ -40,13 +44,13 @@ class ULID
40
44
  return ret
41
45
  end
42
46
 
43
- milliseconds = ULID.milliseconds_from_moment(moment)
47
+ milliseconds = Utils.milliseconds_from_moment(moment)
44
48
 
45
49
  ulid = (
46
50
  if prev_ulid.milliseconds < milliseconds
47
51
  ULID.generate(moment: milliseconds)
48
52
  else
49
- ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
53
+ ULID.generate(moment: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
50
54
  end
51
55
  )
52
56
 
@@ -68,9 +72,12 @@ class ULID
68
72
  end
69
73
  end
70
74
 
75
+ # Just providing similar api as `ULID.generate` and `ULID.encode` relation. No performance benefit exists in monotonic generator's one.
76
+ #
77
+ # @see https://github.com/kachick/ruby-ulid/pull/220
71
78
  # @param [Time, Integer] moment
72
79
  # @return [String]
73
- def encode(moment: ULID.current_milliseconds)
80
+ def encode(moment: Utils.current_milliseconds)
74
81
  generate(moment: moment).encode
75
82
  end
76
83
 
data/lib/ulid/utils.rb ADDED
@@ -0,0 +1,117 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # shareable_constant_value: literal
4
+
5
+ # Copyright (C) 2021 Kenichi Kamiya
6
+
7
+ require('securerandom')
8
+
9
+ class ULID
10
+ # @note I don't have confidence for the naming of `Utils`. However some standard libraries have same name.
11
+ # https://github.com/ruby/webrick/blob/14612a7540fdd7373344461851c4bfff64985b3e/lib/webrick/utils.rb#L17
12
+ # https://docs.ruby-lang.org/ja/latest/class/ERB=3a=3aUtil.html
13
+ # https://github.com/ruby/rss/blob/af1c3c9c9630ec0a48abec48ed1ef348ba82aa13/lib/rss/utils.rb#L9
14
+ module Utils
15
+ # @return [Integer]
16
+ def self.current_milliseconds
17
+ milliseconds_from_time(Time.now)
18
+ end
19
+
20
+ # @param [Time] time
21
+ # @return [Integer]
22
+ def self.milliseconds_from_time(time)
23
+ (time.to_r * 1000).to_i
24
+ end
25
+
26
+ # @param [Time, Integer] moment
27
+ # @return [Integer]
28
+ def self.milliseconds_from_moment(moment)
29
+ case moment
30
+ when Integer
31
+ moment
32
+ when Time
33
+ milliseconds_from_time(moment)
34
+ else
35
+ raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
36
+ end
37
+ end
38
+
39
+ # @return [Integer]
40
+ def self.reasonable_entropy
41
+ SecureRandom.random_number(MAX_ENTROPY)
42
+ end
43
+
44
+ # @param [Integer] milliseconds
45
+ # @param [Integer] entropy
46
+ # @return [String]
47
+ # @raise [OverflowError] if the given value is larger than the ULID limit
48
+ # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
49
+ def self.encode_base32(milliseconds:, entropy:)
50
+ raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
51
+ raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
52
+ raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
53
+ raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
54
+
55
+ base32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
56
+ base32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
57
+ "#{base32encoded_timestamp}#{base32encoded_randomness}"
58
+ end
59
+
60
+ # @param [BasicObject] object
61
+ # @return [String]
62
+ def self.safe_get_class_name(object)
63
+ fallback = 'UnknownObject'
64
+
65
+ # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
66
+ # ref: https://twitter.com/_kachick/status/1400064896759304196
67
+ klass = (
68
+ begin
69
+ object.class
70
+ rescue NoMethodError
71
+ # steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
72
+ # So this annotation is hack for the type infer.
73
+ # @type var object: BasicObject
74
+ # @type var singleton_class: untyped
75
+ singleton_class = class << object; self; end
76
+ (Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
77
+ end
78
+ )
79
+
80
+ begin
81
+ name = String.try_convert(klass.name)
82
+ rescue Exception
83
+ fallback
84
+ else
85
+ name || fallback
86
+ end
87
+ end
88
+
89
+ def self.make_sharable_value(value)
90
+ value.freeze
91
+ if defined?(Ractor)
92
+ case value
93
+ when ULID, Time
94
+ if ractor_can_make_shareable_time?
95
+ Ractor.make_shareable(value)
96
+ end
97
+ else
98
+ Ractor.make_shareable(value)
99
+ end
100
+ end
101
+ end
102
+
103
+ # @note Call before Module#private_constant
104
+ def self.make_sharable_constantans(mod)
105
+ mod.constants.each do |const_name|
106
+ value = mod.const_get(const_name)
107
+ make_sharable_value(value)
108
+ end
109
+ end
110
+
111
+ def self.ractor_can_make_shareable_time?
112
+ RUBY_VERSION >= '3.1'
113
+ end
114
+ end
115
+
116
+ private_constant(:Utils)
117
+ end
data/lib/ulid/uuid.rb CHANGED
@@ -1,9 +1,10 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
- # shareable_constant_value: literal
4
3
 
5
4
  # Copyright (C) 2021 Kenichi Kamiya
6
5
 
6
+ require_relative('errors')
7
+
7
8
  # Extracted features around UUID from some reasons
8
9
  # ref:
9
10
  # * https://github.com/kachick/ruby-ulid/issues/105
@@ -11,6 +12,7 @@
11
12
  class ULID
12
13
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
13
14
  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.freeze
15
+ Utils.make_sharable_value(UUIDV4_PATTERN)
14
16
  private_constant(:UUIDV4_PATTERN)
15
17
 
16
18
  # @param [String, #to_str] uuid
data/lib/ulid/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # shareable_constant_value: literal
4
4
 
5
5
  class ULID
6
- VERSION = '0.6.1'
6
+ VERSION = '0.7.0'
7
7
  end
data/lib/ulid.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
- # shareable_constant_value: experimental_everything
4
3
 
5
4
  # Copyright (C) 2021 Kenichi Kamiya
6
5
 
@@ -9,6 +8,7 @@ require('securerandom')
9
8
  require_relative('ulid/version')
10
9
  require_relative('ulid/errors')
11
10
  require_relative('ulid/crockford_base32')
11
+ require_relative('ulid/utils')
12
12
  require_relative('ulid/monotonic_generator')
13
13
 
14
14
  # @see https://github.com/ulid/spec
@@ -46,13 +46,36 @@ class ULID
46
46
  # @see https://github.com/ruby/ruby/blob/744d17ff6c33b09334508e8110007ea2a82252f5/time.c#L4026-L4078
47
47
  TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
48
48
 
49
+ RANDOM_INTEGER_GENERATOR = -> {
50
+ SecureRandom.random_number(MAX_INTEGER)
51
+ }.freeze
52
+
53
+ Utils.make_sharable_constantans(self)
54
+
55
+ private_constant(
56
+ :PATTERN_WITH_CROCKFORD_BASE32_SUBSET,
57
+ :STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET,
58
+ :SCANNING_PATTERN,
59
+ :TIME_FORMAT_IN_INSPECT,
60
+ :RANDOM_INTEGER_GENERATOR
61
+ )
62
+
49
63
  private_class_method(:new)
50
64
 
51
65
  # @param [Integer, Time] moment
52
66
  # @param [Integer] entropy
53
67
  # @return [ULID]
54
- def self.generate(moment: current_milliseconds, entropy: reasonable_entropy)
55
- from_milliseconds_and_entropy(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
68
+ # @raise [OverflowError] if the given value is larger than the ULID limit
69
+ # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
70
+ def self.generate(moment: Utils.current_milliseconds, entropy: Utils.reasonable_entropy)
71
+ milliseconds = Utils.milliseconds_from_moment(moment)
72
+ base32_encoded = Utils.encode_base32(milliseconds: milliseconds, entropy: entropy)
73
+ new(
74
+ milliseconds: milliseconds,
75
+ entropy: entropy,
76
+ integer: base32_encoded.to_i(32),
77
+ encoded: CrockfordBase32.from_base32(base32_encoded).freeze
78
+ )
56
79
  end
57
80
 
58
81
  # Almost same as [.generate] except directly returning String without needless object creation
@@ -60,9 +83,9 @@ class ULID
60
83
  # @param [Integer, Time] moment
61
84
  # @param [Integer] entropy
62
85
  # @return [String]
63
- def self.encode(moment: current_milliseconds, entropy: reasonable_entropy)
64
- n32_encoded = encode_n32(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
65
- CrockfordBase32.from_n32(n32_encoded)
86
+ def self.encode(moment: Utils.current_milliseconds, entropy: Utils.reasonable_entropy)
87
+ base32_encoded = Utils.encode_base32(milliseconds: Utils.milliseconds_from_moment(moment), entropy: entropy)
88
+ CrockfordBase32.from_base32(base32_encoded)
66
89
  end
67
90
 
68
91
  # Short hand of `ULID.generate(moment: time)`
@@ -71,7 +94,7 @@ class ULID
71
94
  def self.at(time)
72
95
  raise(ArgumentError, 'ULID.at takes only `Time` instance') unless Time === time
73
96
 
74
- from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
97
+ generate(moment: time)
75
98
  end
76
99
 
77
100
  # @param [Time, Integer] moment
@@ -86,10 +109,6 @@ class ULID
86
109
  MAX_MILLISECONDS.equal?(moment) ? MAX : generate(moment: moment, entropy: MAX_ENTROPY)
87
110
  end
88
111
 
89
- RANDOM_INTEGER_GENERATOR = -> {
90
- SecureRandom.random_number(MAX_INTEGER)
91
- }.freeze
92
-
93
112
  # @param [Range<Time>, Range<nil>, Range[ULID], nil] period
94
113
  # @overload sample(number, period: nil)
95
114
  # @param [Integer] number
@@ -162,20 +181,20 @@ class ULID
162
181
  raise(OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}") unless integer <= MAX_INTEGER
163
182
  raise(ArgumentError, "integer should not be negative: given: #{integer}") if integer.negative?
164
183
 
165
- n32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
166
- n32encoded_timestamp = n32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
167
- n32encoded_randomness = n32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
184
+ base32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
185
+ base32encoded_timestamp = base32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
186
+ base32encoded_randomness = base32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
168
187
 
169
- raise(UnexpectedError) unless n32encoded_timestamp && n32encoded_randomness
188
+ raise(UnexpectedError) unless base32encoded_timestamp && base32encoded_randomness
170
189
 
171
- milliseconds = n32encoded_timestamp.to_i(32)
172
- entropy = n32encoded_randomness.to_i(32)
190
+ milliseconds = base32encoded_timestamp.to_i(32)
191
+ entropy = base32encoded_randomness.to_i(32)
173
192
 
174
193
  new(
175
194
  milliseconds: milliseconds,
176
195
  entropy: entropy,
177
196
  integer: integer,
178
- encoded: CrockfordBase32.from_n32(n32encoded).freeze
197
+ encoded: CrockfordBase32.from_base32(base32encoded).freeze
179
198
  )
180
199
  end
181
200
 
@@ -232,49 +251,6 @@ class ULID
232
251
  time.floor(3)
233
252
  end
234
253
 
235
- # @api private
236
- # @return [Integer]
237
- def self.current_milliseconds
238
- milliseconds_from_time(Time.now)
239
- end
240
-
241
- # @api private
242
- # @param [Time] time
243
- # @return [Integer]
244
- private_class_method def self.milliseconds_from_time(time)
245
- (time.to_r * 1000).to_i
246
- end
247
-
248
- # @api private
249
- # @param [Time, Integer] moment
250
- # @return [Integer]
251
- def self.milliseconds_from_moment(moment)
252
- case moment
253
- when Integer
254
- moment
255
- when Time
256
- milliseconds_from_time(moment)
257
- else
258
- raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
259
- end
260
- end
261
-
262
- # @return [Integer]
263
- private_class_method def self.reasonable_entropy
264
- SecureRandom.random_number(MAX_ENTROPY)
265
- end
266
-
267
- private_class_method def self.encode_n32(milliseconds:, entropy:)
268
- raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
269
- raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
270
- raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
271
- raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
272
-
273
- n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
274
- n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
275
- "#{n32encoded_timestamp}#{n32encoded_randomness}"
276
- end
277
-
278
254
  # @param [String, #to_str] string
279
255
  # @return [ULID]
280
256
  # @raise [ParserError] if the given format is not correct for ULID specs
@@ -303,6 +279,7 @@ class ULID
303
279
  # Almost same as `ULID.parse(string).to_time` except directly returning Time instance without needless object creation
304
280
  #
305
281
  # @param [String, #to_str] string
282
+ # @param [String, Integer, nil] in
306
283
  # @return [Time]
307
284
  # @raise [ParserError] if the given format is not correct for ULID specs
308
285
  def self.decode_time(string, in: 'UTC')
@@ -375,74 +352,15 @@ class ULID
375
352
  if ULID === converted
376
353
  converted
377
354
  else
378
- object_class_name = safe_get_class_name(object)
379
- converted_class_name = safe_get_class_name(converted)
355
+ object_class_name = Utils.safe_get_class_name(object)
356
+ converted_class_name = Utils.safe_get_class_name(converted)
380
357
  raise(TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})")
381
358
  end
382
359
  end
383
360
  end
384
361
 
385
- # @param [BasicObject] object
386
- # @return [String]
387
- private_class_method def self.safe_get_class_name(object)
388
- fallback = 'UnknownObject'
389
-
390
- # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
391
- # ref: https://twitter.com/_kachick/status/1400064896759304196
392
- klass = (
393
- begin
394
- object.class
395
- rescue NoMethodError
396
- # steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
397
- # So this annotation is hack for the type infer.
398
- # @type var object: BasicObject
399
- # @type var singleton_class: untyped
400
- singleton_class = class << object; self; end
401
- (Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
402
- end
403
- )
404
-
405
- begin
406
- name = String.try_convert(klass.name)
407
- rescue Exception
408
- fallback
409
- else
410
- name || fallback
411
- end
412
- end
413
-
414
- # @api private
415
- # @param [Integer] milliseconds
416
- # @param [Integer] entropy
417
- # @return [ULID]
418
- # @raise [OverflowError] if the given value is larger than the ULID limit
419
- # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
420
- def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
421
- n32_encoded = encode_n32(milliseconds: milliseconds, entropy: entropy)
422
- new(
423
- milliseconds: milliseconds,
424
- entropy: entropy,
425
- integer: n32_encoded.to_i(32),
426
- encoded: CrockfordBase32.from_n32(n32_encoded).upcase.freeze
427
- )
428
- end
429
-
430
362
  attr_reader(:milliseconds, :entropy)
431
363
 
432
- # @api private
433
- # @param [Integer] milliseconds
434
- # @param [Integer] entropy
435
- # @param [Integer] integer
436
- # @param [String] encoded
437
- # @return [void]
438
- def initialize(milliseconds:, entropy:, integer:, encoded:)
439
- # All arguments check should be done with each constructors, not here
440
- @integer = integer
441
- @encoded = encoded
442
- @milliseconds = milliseconds
443
- @entropy = entropy
444
- end
445
-
446
364
  # @return [String]
447
365
  def encode
448
366
  @encoded
@@ -578,18 +496,21 @@ class ULID
578
496
  super
579
497
  end
580
498
 
581
- # @api private
582
499
  # @return [Integer]
583
500
  def marshal_dump
584
501
  @integer
585
502
  end
586
503
 
587
- # @api private
588
504
  # @param [Integer] integer
589
505
  # @return [void]
590
506
  def marshal_load(integer)
591
507
  unmarshaled = ULID.from_integer(integer)
592
- initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy, encoded: unmarshaled.to_s)
508
+ initialize(
509
+ integer: unmarshaled.to_i,
510
+ milliseconds: unmarshaled.milliseconds,
511
+ entropy: unmarshaled.entropy,
512
+ encoded: unmarshaled.to_s
513
+ )
593
514
  end
594
515
 
595
516
  # @return [self]
@@ -611,17 +532,31 @@ class ULID
611
532
 
612
533
  private
613
534
 
535
+ # @param [Integer] milliseconds
536
+ # @param [Integer] entropy
537
+ # @param [Integer] integer
538
+ # @param [String] encoded
539
+ # @return [void]
540
+ def initialize(milliseconds:, entropy:, integer:, encoded:)
541
+ # All arguments check should be done with each constructors, not here
542
+ @integer = integer
543
+ @encoded = encoded
544
+ @milliseconds = milliseconds
545
+ @entropy = entropy
546
+ end
547
+
614
548
  # @return [void]
615
549
  def cache_all_instance_variables
616
550
  inspect
617
551
  timestamp
618
552
  randomness
619
553
  end
620
- end
621
554
 
622
- require_relative('ulid/ractor_unshareable_constants')
555
+ MIN = parse('00000000000000000000000000').freeze
556
+ MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
623
557
 
624
- class ULID
625
- # Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
626
- private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR)
558
+ Utils.make_sharable_value(MIN)
559
+ Utils.make_sharable_value(MAX)
560
+
561
+ private_constant(:MIN, :MAX)
627
562
  end
data/sig/ulid.rbs CHANGED
@@ -19,8 +19,8 @@ class ULID < Object
19
19
  MAX: ULID
20
20
  include Comparable
21
21
 
22
- # The `moment` is a `Time` or `Intger of the milliseconds`
23
- type moment = Time | Integer
22
+ type milliseconds = Integer
23
+ type moment = Time | milliseconds
24
24
 
25
25
  class Error < StandardError
26
26
  end
@@ -34,27 +34,42 @@ class ULID < Object
34
34
  class UnexpectedError < Error
35
35
  end
36
36
 
37
- module CrockfordBase32
38
- class SetupError < UnexpectedError
39
- end
37
+ # Private module
38
+ module Utils
39
+ def self.encode_base32: (milliseconds: milliseconds, entropy: Integer) -> String
40
+
41
+ def self.current_milliseconds: -> milliseconds
42
+
43
+ def self.milliseconds_from_moment: (moment moment) -> milliseconds
44
+
45
+ def self.reasonable_entropy: -> Integer
46
+
47
+ def self.milliseconds_from_time: (Time time) -> milliseconds
48
+
49
+ def self.safe_get_class_name: (untyped object) -> String
40
50
 
51
+ def self.make_sharable_value: (untyped object) -> void
52
+
53
+ def self.make_sharable_constantans: (Module) -> void
54
+
55
+ def self.ractor_can_make_shareable_time?: -> bool
56
+ end
57
+
58
+ # Private module
59
+ module CrockfordBase32
41
60
  ENCODING_STRING: String
42
61
  CROCKFORD_TR_PATTERN: String
43
62
  BASE32_TR_PATTERN: String
44
63
  VARIANT_TR_PATTERN: String
45
64
  NORMALIZED_TR_PATTERN: String
46
65
 
47
- # A private API. Should not be used in your code.
48
66
  def self.encode: (Integer integer) -> String
49
67
 
50
- # A private API. Should not be used in your code.
51
68
  def self.decode: (String string) -> Integer
52
69
 
53
- # A private API. Should not be used in your code.
54
70
  def self.normalize: (String string) -> String
55
71
 
56
- # A private API. Should not be used in your code.
57
- def self.from_n32: (String n32encoded) -> String
72
+ def self.from_base32: (String base32encoded) -> String
58
73
  end
59
74
 
60
75
  class MonotonicGenerator
@@ -72,9 +87,6 @@ class ULID < Object
72
87
  # ```
73
88
  attr_reader prev: ULID | nil
74
89
 
75
- # A private API. Should not be used in your code.
76
- def initialize: -> void
77
-
78
90
  # See [How to keep `Sortable` even if in same timestamp](https://github.com/kachick/ruby-ulid#how-to-keep-sortable-even-if-in-same-timestamp)
79
91
  # The `Thread-safety` is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
80
92
  def generate: (?moment: moment) -> ULID
@@ -95,6 +107,12 @@ class ULID < Object
95
107
  def inspect: -> String
96
108
  alias to_s inspect
97
109
  def freeze: -> void
110
+
111
+ private
112
+
113
+ def initialize: -> void
114
+
115
+ def prev=: (ULID?) -> void
98
116
  end
99
117
 
100
118
  interface _ToULID
@@ -156,9 +174,6 @@ class ULID < Object
156
174
  #
157
175
  def self.encode: (?moment: moment, ?entropy: Integer) -> String
158
176
 
159
- # A private API. Should not be used in your code.
160
- def self.encode_n32: (milliseconds: Integer, entropy: Integer) -> String
161
-
162
177
  # Shorthand of `ULID.generate(moment: Time)`
163
178
  # See also [ULID.generate](https://kachick.github.io/ruby-ulid/ULID.html#generate-class_method)
164
179
  #
@@ -173,12 +188,6 @@ class ULID < Object
173
188
  # ```
174
189
  def self.at: (Time time) -> ULID
175
190
 
176
- # A private API. Should not be used in your code.
177
- def self.current_milliseconds: -> Integer
178
-
179
- # A private API. Should not be used in your code.
180
- def self.milliseconds_from_moment: (moment moment) -> Integer
181
-
182
191
  # `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
183
192
  #
184
193
  # ```ruby
@@ -440,7 +449,7 @@ class ULID < Object
440
449
  # ```
441
450
  def self.scan: (_ToStr string) -> Enumerator[self, singleton(ULID)]
442
451
  | (_ToStr string) { (ULID ulid) -> void } -> singleton(ULID)
443
- def self.from_milliseconds_and_entropy: (milliseconds: Integer, entropy: Integer) -> ULID
452
+
444
453
  def self.try_convert: (_ToULID) -> ULID
445
454
  | (untyped) -> nil
446
455
 
@@ -591,10 +600,8 @@ class ULID < Object
591
600
  def pred: -> ULID?
592
601
  def freeze: -> self
593
602
 
594
- # A private API. Should not be used in your code.
595
603
  def marshal_dump: -> Integer
596
604
 
597
- # A private API. Should not be used in your code.
598
605
  def marshal_load: (Integer integer) -> void
599
606
 
600
607
  # Returns `self`
@@ -609,18 +616,7 @@ class ULID < Object
609
616
 
610
617
  private
611
618
 
612
- # A private API. Should not be used in your code.
613
- def self.reasonable_entropy: -> Integer
614
-
615
- # A private API. Should not be used in your code.
616
- def self.milliseconds_from_time: (Time time) -> Integer
617
-
618
- # A private API. Should not be used in your code.
619
- def self.safe_get_class_name: (untyped object) -> String
620
-
621
- # A private API. Should not be used in your code.
622
- def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer, encoded: String) -> void
619
+ def initialize: (milliseconds: milliseconds, entropy: Integer, integer: Integer, encoded: String) -> void
623
620
 
624
- # A private API. Should not be used in your code.
625
621
  def cache_all_instance_variables: -> void
626
622
  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.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kenichi Kamiya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-18 00:00:00.000000000 Z
11
+ date: 2022-08-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: " generator, optional monotonicity, parser and tools for ULID (RBS
14
14
  included)\n"
@@ -25,7 +25,7 @@ files:
25
25
  - lib/ulid/crockford_base32.rb
26
26
  - lib/ulid/errors.rb
27
27
  - lib/ulid/monotonic_generator.rb
28
- - lib/ulid/ractor_unshareable_constants.rb
28
+ - lib/ulid/utils.rb
29
29
  - lib/ulid/uuid.rb
30
30
  - lib/ulid/version.rb
31
31
  - sig/ulid.rbs
@@ -1,12 +0,0 @@
1
- # coding: us-ascii
2
- # frozen_string_literal: true
3
-
4
- class ULID
5
- min = parse('00000000000000000000000000').freeze
6
- max = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
7
-
8
- ractor_can_make_shareable_time = RUBY_VERSION >= '3.1'
9
-
10
- MIN = ractor_can_make_shareable_time ? Ractor.make_shareable(min) : min
11
- MAX = ractor_can_make_shareable_time ? Ractor.make_shareable(max) : max
12
- end