ruby-ulid 0.6.1 → 0.7.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: 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