ruby-ulid 0.2.2 → 0.3.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: cea320f797b834d5a0f246db9920a9453a6d229728b428590e2f99dd275c8fe9
4
- data.tar.gz: e703ca1896a7ee4155b1362cffdfaf9499e4ae32073cd440c50750d4e9cc9c1a
3
+ metadata.gz: 358b03a503f8a12c87f3f7daaf3b0acf6e55dc44f1aba7a38b9f9a38985c2c68
4
+ data.tar.gz: 15d25d443f4826b07fc9a74b092e35b9cd60dc34cc3615f03fee619d4fc39445
5
5
  SHA512:
6
- metadata.gz: b9d0436d98095dd0084675d87a6605848f6c7ee14d65724ce5bd9229945ffc17a3b7e1efaf9a295baeaeac11782c9fc63bc901149cfa58d96046e334d5d19067
7
- data.tar.gz: f7e016a4ca3d859f8469b5281d9f5581ffe6c0fdde737c554c627f7862461701844e46e4513ce09748e1cdf3fa4b8da2607a8d3267f74c64ae4a257ae54d6e78
6
+ metadata.gz: 5b18d13597bd2f3dcd85a15a26d9086fb9a80b91775292c9da82d93144b6abd37585996f1bf64dfc4ac12b6d6d49683012dfea8e04624005358459667864114c
7
+ data.tar.gz: d987aa9b60717577fae96eef68fca76b2395f11a580df4863a69dd0931d955e4cf68608c101959db18f5e3a26031fbe284ff90a0d107fa84bbebb63949bba66b
data/README.md CHANGED
@@ -1,18 +1,18 @@
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)
4
+ [![Gem Version](https://badge.fury.io/rb/ruby-ulid.svg)](http://badge.fury.io/rb/ruby-ulid)
5
+
3
6
  ## Overview
4
7
 
5
- The `ULID` spec is defined on [ulid/spec](https://github.com/ulid/spec). It has useful specs for applications (e.g. `Database key`), especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
6
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
7
- Also providing [ruby/rbs](https://github.com/ruby/rbs) signature files.
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, monotonic generator, parser and handy manipulation features around ULID.
11
+ Also providing [ruby/rbs](https://github.com/ruby/rbs) signatures.
8
12
 
9
13
  ---
10
14
 
11
- ![ULIDlogo](https://raw.githubusercontent.com/kachick/ruby-ulid/main/logo.png)
12
-
13
- [![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)
14
- [![Gem Version](https://badge.fury.io/rb/ruby-ulid.svg)](http://badge.fury.io/rb/ruby-ulid)
15
- [![Visual Studio Code](https://img.shields.io/badge/Visual%20Studio%20Code-0078d7.svg?style=for-the-badge&logo=visual-studio-code&logoColor=white)](https://github.dev/kachick/ruby-ulid)
15
+ ![ULIDlogo](./assets/logo.png)
16
16
 
17
17
  ## Universally Unique Lexicographically Sortable Identifier
18
18
 
@@ -47,10 +47,10 @@ $ gem install ruby-ulid
47
47
  Should be installed!
48
48
  ```
49
49
 
50
- Add this line to your application/library's `Gemfile` is needed in basic use-case
50
+ Add this line to your Gemfile`.
51
51
 
52
52
  ```ruby
53
- gem 'ruby-ulid', '~> 0.2.2'
53
+ gem('ruby-ulid', '~> 0.2.2')
54
54
  ```
55
55
 
56
56
  ### Generator and Parser
@@ -438,6 +438,18 @@ See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
438
438
 
439
439
  The results are not something to be proud of.
440
440
 
441
+ ## How to use rbs
442
+
443
+ See structure at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox)
444
+
445
+ 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).
446
+
447
+ * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
448
+ * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
449
+ * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
450
+ * ![rbs ok-at-time](./assets/ulid-rbs-ok-at-time.png?raw=true.png)
451
+ * ![rbs ng-at-int](./assets/ulid-rbs-ng-at-int.png?raw=true.png)
452
+
441
453
  ## References
442
454
 
443
455
  - [Repository](https://github.com/kachick/ruby-ulid)
@@ -446,4 +458,5 @@ The results are not something to be proud of.
446
458
 
447
459
  ## Note
448
460
 
449
- - Another choices for sortable and randomness IDs, [UUIDv6, UUIDv7, UUIDv8 might be the one. (But they are still in draft state)](https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-01.html), I will track them in [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
461
+ - [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.
462
+ However they are stayed in draft state. ref: [ruby-ulid#37](https://github.com/kachick/ruby-ulid/issues/37)
data/lib/ruby-ulid.rb CHANGED
@@ -3,4 +3,4 @@
3
3
 
4
4
  # Copyright (C) 2021 Kenichi Kamiya
5
5
 
6
- require_relative 'ulid'
6
+ require_relative('ulid')
@@ -14,10 +14,10 @@ class ULID
14
14
  # * https://github.com/kachick/ruby-ulid/issues/57
15
15
  # * https://github.com/kachick/ruby-ulid/issues/78
16
16
  module CrockfordBase32
17
- class SetupError < ScriptError; end
17
+ class SetupError < UnexpectedError; end
18
18
 
19
19
  n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
20
- raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
20
+ raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
21
21
 
22
22
  n32_char_by_number = {}
23
23
  n32_chars.each_with_index do |char, index|
@@ -48,7 +48,7 @@ class ULID
48
48
  map[encoding_char] = char_32
49
49
  end
50
50
  end.freeze
51
- raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
51
+ raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
52
52
 
53
53
  CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
54
54
 
@@ -5,15 +5,16 @@
5
5
 
6
6
  class ULID
7
7
  class MonotonicGenerator
8
- include MonitorMixin
8
+ include(MonitorMixin)
9
9
 
10
+ # @dynamic prev
10
11
  # @return [ULID, nil]
11
- attr_reader :prev
12
+ attr_reader(:prev)
12
13
 
13
- undef_method :instance_variable_set
14
+ undef_method(:instance_variable_set)
14
15
 
15
16
  def initialize
16
- super()
17
+ super
17
18
  @prev = nil
18
19
  end
19
20
 
@@ -21,7 +22,8 @@ class ULID
21
22
  def inspect
22
23
  "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
23
24
  end
24
- alias_method :to_s, :inspect
25
+ # @dynamic to_s
26
+ alias_method(:to_s, :inspect)
25
27
 
26
28
  # @param [Time, Integer] moment
27
29
  # @return [ULID]
@@ -30,23 +32,25 @@ class ULID
30
32
  # Basically will not happen. Just means this feature prefers error rather than invalid value.
31
33
  def generate(moment: ULID.current_milliseconds)
32
34
  synchronize do
33
- unless @prev
34
- @prev = ULID.generate(moment: moment)
35
- return @prev
35
+ prev_ulid = @prev
36
+ unless prev_ulid
37
+ ret = ULID.generate(moment: moment)
38
+ @prev = ret
39
+ return ret
36
40
  end
37
41
 
38
42
  milliseconds = ULID.milliseconds_from_moment(moment)
39
43
 
40
44
  ulid = (
41
- if @prev.milliseconds < milliseconds
45
+ if prev_ulid.milliseconds < milliseconds
42
46
  ULID.generate(moment: milliseconds)
43
47
  else
44
- ULID.from_milliseconds_and_entropy(milliseconds: @prev.milliseconds, entropy: @prev.entropy.succ)
48
+ ULID.from_milliseconds_and_entropy(milliseconds: prev_ulid.milliseconds, entropy: prev_ulid.entropy.succ)
45
49
  end
46
50
  )
47
51
 
48
- unless ulid > @prev
49
- base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{@prev.inspect}"
52
+ unless ulid > prev_ulid
53
+ base_message = "monotonicity broken from unexpected reasons # generated: #{ulid.inspect}, prev: #{prev_ulid.inspect}"
50
54
  additional_information = (
51
55
  if Thread.list == [Thread.main]
52
56
  '# NOTE: looks single thread only exist'
@@ -55,7 +59,7 @@ class ULID
55
59
  end
56
60
  )
57
61
 
58
- raise UnexpectedError, base_message + additional_information
62
+ raise(UnexpectedError, base_message + additional_information)
59
63
  end
60
64
 
61
65
  @prev = ulid
@@ -63,12 +67,12 @@ class ULID
63
67
  end
64
68
  end
65
69
 
66
- undef_method :freeze
70
+ undef_method(:freeze)
67
71
 
68
72
  # @raise [TypeError] always raises exception and does not freeze self
69
73
  # @return [void]
70
74
  def freeze
71
- raise TypeError, "cannot freeze #{self.class}"
75
+ raise(TypeError, "cannot freeze #{self.class}")
72
76
  end
73
77
  end
74
78
  end
data/lib/ulid/uuid.rb CHANGED
@@ -10,18 +10,18 @@
10
10
  class ULID
11
11
  # Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
12
12
  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
13
- private_constant :UUIDV4_PATTERN
13
+ private_constant(:UUIDV4_PATTERN)
14
14
 
15
15
  # @param [String, #to_str] uuid
16
16
  # @return [ULID]
17
17
  # @raise [ParserError] if the given format is not correct for UUIDv4 specs
18
18
  def self.from_uuidv4(uuid)
19
19
  uuid = String.try_convert(uuid)
20
- raise ArgumentError, 'ULID.from_uuidv4 takes only strings' unless uuid
20
+ raise(ArgumentError, 'ULID.from_uuidv4 takes only strings') unless uuid
21
21
 
22
22
  prefix_trimmed = uuid.delete_prefix('urn:uuid:')
23
23
  unless UUIDV4_PATTERN.match?(prefix_trimmed)
24
- raise ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`"
24
+ raise(ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`")
25
25
  end
26
26
 
27
27
  normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
@@ -32,8 +32,11 @@ class ULID
32
32
  def to_uuidv4
33
33
  # This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
34
34
  array = octets.pack('C*').unpack('NnnnnN')
35
- array[2] = (array[2] & 0x0fff) | 0x4000
36
- array[3] = (array[3] & 0x3fff) | 0x8000
35
+ ref2, ref3 = array[2], array[3]
36
+ raise unless Integer === ref2 && Integer === ref3
37
+
38
+ array[2] = (ref2 & 0x0fff) | 0x4000
39
+ array[3] = (ref3 & 0x3fff) | 0x8000
37
40
  ('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
38
41
  end
39
42
  end
data/lib/ulid/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.2.2'
5
+ VERSION = '0.3.0'
6
6
  end
data/lib/ulid.rb CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  # Copyright (C) 2021 Kenichi Kamiya
5
5
 
6
- require 'securerandom'
6
+ require('securerandom')
7
7
 
8
8
  # @see https://github.com/ulid/spec
9
9
  # @!attribute [r] milliseconds
@@ -11,7 +11,7 @@ require 'securerandom'
11
11
  # @!attribute [r] entropy
12
12
  # @return [Integer]
13
13
  class ULID
14
- include Comparable
14
+ include(Comparable)
15
15
 
16
16
  class Error < StandardError; end
17
17
  class OverflowError < Error; end
@@ -25,10 +25,12 @@ class ULID
25
25
 
26
26
  TIMESTAMP_ENCODED_LENGTH = 10
27
27
  RANDOMNESS_ENCODED_LENGTH = 16
28
- ENCODED_LENGTH = TIMESTAMP_ENCODED_LENGTH + RANDOMNESS_ENCODED_LENGTH
28
+ ENCODED_LENGTH = 26
29
+
29
30
  TIMESTAMP_OCTETS_LENGTH = 6
30
31
  RANDOMNESS_OCTETS_LENGTH = 10
31
- OCTETS_LENGTH = TIMESTAMP_OCTETS_LENGTH + RANDOMNESS_OCTETS_LENGTH
32
+ OCTETS_LENGTH = 16
33
+
32
34
  MAX_MILLISECONDS = 281474976710655
33
35
  MAX_ENTROPY = 1208925819614629174706175
34
36
  MAX_INTEGER = 340282366920938463463374607431768211455
@@ -43,11 +45,13 @@ class ULID
43
45
  # This can't contain `\b` for considering UTF-8 (e.g. Japanese), so intentional `false negative` definition.
44
46
  SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/i.freeze
45
47
 
46
- # Same as Time#inspect since Ruby 2.7, just to keep backward compatibility
48
+ # Similar as Time#inspect since Ruby 2.7, however it is NOT same.
49
+ # Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
47
50
  # @see https://bugs.ruby-lang.org/issues/15958
51
+ # @see https://github.com/ruby/ruby/blob/744d17ff6c33b09334508e8110007ea2a82252f5/time.c#L4026-L4078
48
52
  TIME_FORMAT_IN_INSPECT = '%Y-%m-%d %H:%M:%S.%3N %Z'
49
53
 
50
- private_class_method :new
54
+ private_class_method(:new)
51
55
 
52
56
  # @param [Integer, Time] moment
53
57
  # @param [Integer] entropy
@@ -60,7 +64,7 @@ class ULID
60
64
  # @param [Time] time
61
65
  # @return [ULID]
62
66
  def self.at(time)
63
- raise ArgumentError, 'ULID.at takes only `Time` instance' unless Time === time
67
+ raise(ArgumentError, 'ULID.at takes only `Time` instance') unless Time === time
64
68
 
65
69
  from_milliseconds_and_entropy(milliseconds: milliseconds_from_time(time), entropy: reasonable_entropy)
66
70
  end
@@ -92,14 +96,14 @@ class ULID
92
96
  # * Do not ensure the uniqueness
93
97
  # * Do not take random generator for the arguments
94
98
  # * Raising error instead of truncating elements for the given number
95
- def self.sample(*args, period: nil)
99
+ def self.sample(number=nil, period: nil)
96
100
  int_generator = (
97
101
  if period
98
102
  ulid_range = range(period)
99
103
  min, max, exclude_end = ulid_range.begin.to_i, ulid_range.end.to_i, ulid_range.exclude_end?
100
104
 
101
105
  possibilities = (max - min) + (exclude_end ? 0 : 1)
102
- raise ArgumentError, "given range `#{ulid_range.inspect}` does not have possibilities" unless possibilities.positive?
106
+ raise(ArgumentError, "given range `#{ulid_range.inspect}` does not have possibilities") unless possibilities.positive?
103
107
 
104
108
  -> {
105
109
  SecureRandom.random_number(possibilities) + min
@@ -109,24 +113,21 @@ class ULID
109
113
  end
110
114
  )
111
115
 
112
- case args.size
113
- when 0
116
+ case number
117
+ when nil
114
118
  from_integer(int_generator.call)
115
- when 1
116
- number = args.first
117
- raise ArgumentError, 'accepts no argument or integer only' unless Integer === number
118
-
119
+ when Integer
119
120
  if number > MAX_INTEGER || number.negative?
120
- raise ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative"
121
+ raise(ArgumentError, "given number `#{number}` is larger than ULID limit `#{MAX_INTEGER}` or negative")
121
122
  end
122
123
 
123
- if period && (number > possibilities)
124
- raise ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`"
124
+ if period && possibilities && (number > possibilities)
125
+ raise(ArgumentError, "given number `#{number}` is larger than given possibilities `#{possibilities}`")
125
126
  end
126
127
 
127
128
  Array.new(number) { from_integer(int_generator.call) }
128
129
  else
129
- raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..1)"
130
+ raise(ArgumentError, 'accepts no argument or integer only')
130
131
  end
131
132
  end
132
133
 
@@ -136,11 +137,13 @@ class ULID
136
137
  # @yieldreturn [self]
137
138
  def self.scan(string)
138
139
  string = String.try_convert(string)
139
- raise ArgumentError, 'ULID.scan takes only strings' unless string
140
- return to_enum(__callee__, string) unless block_given?
140
+ raise(ArgumentError, 'ULID.scan takes only strings') unless string
141
+ return to_enum(:scan, string) unless block_given?
141
142
 
142
143
  string.scan(SCANNING_PATTERN) do |matched|
143
- yield parse(matched)
144
+ if String === matched
145
+ yield(parse(matched))
146
+ end
144
147
  end
145
148
  self
146
149
  end
@@ -150,14 +153,16 @@ class ULID
150
153
  # @raise [OverflowError] if the given integer is larger than the ULID limit
151
154
  # @raise [ArgumentError] if the given integer is negative number
152
155
  def self.from_integer(integer)
153
- raise ArgumentError, 'ULID.from_integer takes only `Integer`' unless Integer === integer
154
- raise OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}" unless integer <= MAX_INTEGER
155
- raise ArgumentError, "integer should not be negative: given: #{integer}" if integer.negative?
156
+ raise(ArgumentError, 'ULID.from_integer takes only `Integer`') unless Integer === integer
157
+ raise(OverflowError, "integer overflow: given #{integer}, max: #{MAX_INTEGER}") unless integer <= MAX_INTEGER
158
+ raise(ArgumentError, "integer should not be negative: given: #{integer}") if integer.negative?
156
159
 
157
160
  n32encoded = integer.to_s(32).rjust(ENCODED_LENGTH, '0')
158
161
  n32encoded_timestamp = n32encoded.slice(0, TIMESTAMP_ENCODED_LENGTH)
159
162
  n32encoded_randomness = n32encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH)
160
163
 
164
+ raise(UnexpectedError) unless n32encoded_timestamp && n32encoded_randomness
165
+
161
166
  milliseconds = n32encoded_timestamp.to_i(32)
162
167
  entropy = n32encoded_randomness.to_i(32)
163
168
 
@@ -168,37 +173,43 @@ class ULID
168
173
  # @return [Range<ULID>]
169
174
  # @raise [ArgumentError] if the given period is not a `Range[Time]`, `Range[nil]` or `Range[ULID]`
170
175
  def self.range(period)
171
- raise ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`' unless Range === period
176
+ raise(ArgumentError, 'ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`') unless Range === period
172
177
 
173
178
  begin_element, end_element, exclude_end = period.begin, period.end, period.exclude_end?
174
- return period if self === begin_element && self === end_element
175
-
176
- case begin_element
177
- when Time
178
- begin_ulid = min(begin_element)
179
- when nil
180
- begin_ulid = MIN
181
- when self
182
- begin_ulid = begin_element
183
- else
184
- raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
185
- end
179
+ new_begin, new_end = false, false
180
+
181
+ begin_ulid = (
182
+ case begin_element
183
+ when Time
184
+ new_begin = true
185
+ min(begin_element)
186
+ when nil
187
+ MIN
188
+ when self
189
+ begin_element
190
+ else
191
+ raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
192
+ end
193
+ )
186
194
 
187
- case end_element
188
- when Time
189
- end_ulid = exclude_end ? min(end_element) : max(end_element)
190
- when nil
191
- # The end should be max and include end, because nil end means to cover endless ULIDs until the limit
192
- end_ulid = MAX
193
- exclude_end = false
194
- when self
195
- end_ulid = end_element
196
- else
197
- raise ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}"
198
- end
195
+ end_ulid = (
196
+ case end_element
197
+ when Time
198
+ new_end = true
199
+ exclude_end ? min(end_element) : max(end_element)
200
+ when nil
201
+ exclude_end = false
202
+ # The end should be max and include end, because nil end means to cover endless ULIDs until the limit
203
+ MAX
204
+ when self
205
+ end_element
206
+ else
207
+ raise(ArgumentError, "ULID.range takes only `Range[Time]`, `Range[nil]` or `Range[ULID]`, given: #{period.inspect}")
208
+ end
209
+ )
199
210
 
200
- begin_ulid.freeze
201
- end_ulid.freeze
211
+ begin_ulid.freeze if new_begin
212
+ end_ulid.freeze if new_end
202
213
 
203
214
  Range.new(begin_ulid, end_ulid, exclude_end)
204
215
  end
@@ -206,7 +217,7 @@ class ULID
206
217
  # @param [Time] time
207
218
  # @return [Time]
208
219
  def self.floor(time)
209
- raise ArgumentError, 'ULID.floor takes only `Time` instance' unless Time === time
220
+ raise(ArgumentError, 'ULID.floor takes only `Time` instance') unless Time === time
210
221
 
211
222
  time.floor(3)
212
223
  end
@@ -220,9 +231,9 @@ class ULID
220
231
  # @api private
221
232
  # @param [Time] time
222
233
  # @return [Integer]
223
- private_class_method def self.milliseconds_from_time(time)
234
+ private_class_method(def self.milliseconds_from_time(time)
224
235
  (time.to_r * 1000).to_i
225
- end
236
+ end)
226
237
 
227
238
  # @api private
228
239
  # @param [Time, Integer] moment
@@ -234,24 +245,24 @@ class ULID
234
245
  when Time
235
246
  milliseconds_from_time(moment)
236
247
  else
237
- raise ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`'
248
+ raise(ArgumentError, '`moment` should be a `Time` or `Integer as milliseconds`')
238
249
  end
239
250
  end
240
251
 
241
252
  # @return [Integer]
242
- private_class_method def self.reasonable_entropy
253
+ private_class_method(def self.reasonable_entropy
243
254
  SecureRandom.random_number(MAX_ENTROPY)
244
- end
255
+ end)
245
256
 
246
257
  # @param [String, #to_str] string
247
258
  # @return [ULID]
248
259
  # @raise [ParserError] if the given format is not correct for ULID specs
249
260
  def self.parse(string)
250
261
  string = String.try_convert(string)
251
- raise ArgumentError, 'ULID.parse takes only strings' unless string
262
+ raise(ArgumentError, 'ULID.parse takes only strings') unless string
252
263
 
253
264
  unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
254
- raise ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`"
265
+ raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
255
266
  end
256
267
 
257
268
  from_integer(CrockfordBase32.decode(string))
@@ -262,7 +273,7 @@ class ULID
262
273
  # @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
263
274
  def self.normalize(string)
264
275
  string = String.try_convert(string)
265
- raise ArgumentError, 'ULID.normalize takes only strings' unless string
276
+ raise(ArgumentError, 'ULID.normalize takes only strings') unless string
266
277
 
267
278
  normalized_in_crockford = CrockfordBase32.normalize(string)
268
279
  # Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
@@ -298,14 +309,14 @@ class ULID
298
309
  else
299
310
  object_class_name = safe_get_class_name(object)
300
311
  converted_class_name = safe_get_class_name(converted)
301
- raise TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})"
312
+ raise(TypeError, "can't convert #{object_class_name} to ULID (#{object_class_name}#to_ulid gives #{converted_class_name})")
302
313
  end
303
314
  end
304
315
  end
305
316
 
306
317
  # @param [BasicObject] object
307
318
  # @return [String]
308
- private_class_method def self.safe_get_class_name(object)
319
+ private_class_method(def self.safe_get_class_name(object)
309
320
  fallback = 'UnknownObject'
310
321
 
311
322
  # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
@@ -314,8 +325,12 @@ class ULID
314
325
  begin
315
326
  object.class
316
327
  rescue NoMethodError
328
+ # steep can't correctly handle singeton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
329
+ # So this annotation is hack for the type infer.
330
+ # @type var object: BasicObject
331
+ # @type var singleton_class: untyped
317
332
  singleton_class = class << object; self; end
318
- singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) }
333
+ (Class === singleton_class) ? singleton_class.ancestors.detect { |ancestor| !ancestor.equal?(singleton_class) } : fallback
319
334
  end
320
335
  )
321
336
 
@@ -326,7 +341,7 @@ class ULID
326
341
  else
327
342
  name || fallback
328
343
  end
329
- end
344
+ end)
330
345
 
331
346
  # @api private
332
347
  # @param [Integer] milliseconds
@@ -335,10 +350,10 @@ class ULID
335
350
  # @raise [OverflowError] if the given value is larger than the ULID limit
336
351
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
337
352
  def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
338
- raise ArgumentError, 'milliseconds and entropy should be an `Integer`' unless Integer === milliseconds && Integer === entropy
339
- raise OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}" unless milliseconds <= MAX_MILLISECONDS
340
- raise OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}" unless entropy <= MAX_ENTROPY
341
- raise ArgumentError, 'milliseconds and entropy should not be negative' if milliseconds.negative? || entropy.negative?
353
+ raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
354
+ raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
355
+ raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
356
+ raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
342
357
 
343
358
  n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
344
359
  n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
@@ -347,7 +362,8 @@ class ULID
347
362
  new(milliseconds: milliseconds, entropy: entropy, integer: integer)
348
363
  end
349
364
 
350
- attr_reader :milliseconds, :entropy
365
+ # @dynamic milliseconds, entropy
366
+ attr_reader(:milliseconds, :entropy)
351
367
 
352
368
  # @api private
353
369
  # @param [Integer] milliseconds
@@ -390,7 +406,8 @@ class ULID
390
406
  def eql?(other)
391
407
  equal?(other) || (ULID === other && @integer == other.to_i)
392
408
  end
393
- alias_method :==, :eql?
409
+ # @dynamic ==
410
+ alias_method(:==, :eql?)
394
411
 
395
412
  # @return [Boolean]
396
413
  def ===(other)
@@ -420,22 +437,22 @@ class ULID
420
437
 
421
438
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer)]
422
439
  def timestamp_octets
423
- octets.slice(0, TIMESTAMP_OCTETS_LENGTH)
440
+ octets.slice(0, TIMESTAMP_OCTETS_LENGTH) || raise(UnexpectedError)
424
441
  end
425
442
 
426
443
  # @return [Array(Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer)]
427
444
  def randomness_octets
428
- octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH)
445
+ octets.slice(TIMESTAMP_OCTETS_LENGTH, RANDOMNESS_OCTETS_LENGTH) || raise(UnexpectedError)
429
446
  end
430
447
 
431
448
  # @return [String]
432
449
  def timestamp
433
- @timestamp ||= to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze
450
+ @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
434
451
  end
435
452
 
436
453
  # @return [String]
437
454
  def randomness
438
- @randomness ||= to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze
455
+ @randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
439
456
  end
440
457
 
441
458
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -461,7 +478,8 @@ class ULID
461
478
  ULID.from_integer(succ_int)
462
479
  end
463
480
  end
464
- alias_method :next, :succ
481
+ # @dynamic next
482
+ alias_method(:next, :succ)
465
483
 
466
484
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
467
485
  def pred
@@ -513,7 +531,7 @@ class ULID
513
531
  self
514
532
  end
515
533
 
516
- undef_method :instance_variable_set
534
+ undef_method(:instance_variable_set)
517
535
 
518
536
  private
519
537
 
@@ -525,13 +543,13 @@ class ULID
525
543
  end
526
544
  end
527
545
 
528
- require_relative 'ulid/version'
529
- require_relative 'ulid/crockford_base32'
530
- require_relative 'ulid/monotonic_generator'
546
+ require_relative('ulid/version')
547
+ require_relative('ulid/crockford_base32')
548
+ require_relative('ulid/monotonic_generator')
531
549
 
532
550
  class ULID
533
551
  MIN = parse('00000000000000000000000000').freeze
534
552
  MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
535
553
 
536
- private_constant :TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR
554
+ private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
537
555
  end
data/sig/ulid.rbs CHANGED
@@ -36,7 +36,7 @@ class ULID < Object
36
36
  end
37
37
 
38
38
  module CrockfordBase32
39
- class SetupError < ScriptError
39
+ class SetupError < UnexpectedError
40
40
  end
41
41
 
42
42
  N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
@@ -97,9 +97,9 @@ class ULID < Object
97
97
  def to_ulid: () -> ULID
98
98
  end
99
99
 
100
- type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
101
- type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
102
- type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
100
+ type full_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
101
+ type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
102
+ type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
103
103
  type period = Range[Time] | Range[nil] | Range[ULID]
104
104
 
105
105
  @string: String?
@@ -318,7 +318,7 @@ class ULID < Object
318
318
  # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
319
319
  # ```
320
320
  def self.sample: (?period: period) -> ULID
321
- | (Integer number, ?period: period) -> Array[ULID]
321
+ | (Integer number, ?period: period?) -> Array[ULID]
322
322
  def self.valid?: (untyped) -> bool
323
323
 
324
324
  # Returns normalized string
@@ -409,7 +409,9 @@ class ULID < Object
409
409
  # ulid.to_i #=> 1957909092946624190749577070267409738
410
410
  # ```
411
411
  def to_i: -> Integer
412
- alias hash to_i
412
+
413
+ # Returns integer for making as a hash key use-case
414
+ def hash: -> Integer
413
415
 
414
416
  # Basically same as String based sort.
415
417
  #
@@ -462,7 +464,7 @@ class ULID < Object
462
464
  # Intentionally avoiding to use `Record type` ref: https://github.com/ruby/rbs/blob/4fb4c33b2325d1a73d79ff7aaeb49f21cec1e0e5/docs/syntax.md#record-type
463
465
  # Because the returning values are not fixed.
464
466
  def patterns: -> Hash[Symbol, Regexp | String]
465
- def octets: -> octets
467
+ def octets: -> full_octets
466
468
  def timestamp_octets: -> timestamp_octets
467
469
  def randomness_octets: -> randomness_octets
468
470
 
@@ -525,7 +527,7 @@ class ULID < Object
525
527
  private
526
528
 
527
529
  # A private API. Should not be used in your code.
528
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> self
530
+ def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
529
531
 
530
532
  # A private API. Should not be used in your code.
531
533
  def self.reasonable_entropy: -> Integer
metadata CHANGED
@@ -1,19 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.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-05-26 00:00:00.000000000 Z
11
+ date: 2022-06-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: |2
14
- The ULID(Universally Unique Lexicographically Sortable Identifier) has useful specs for applications (e.g. `Database key`), especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
15
- This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
16
- Also providing `ruby/rbs` signature files.
13
+ description: " generator, monotonic generator, parser and manipulations for ULID
14
+ (ruby/rbs signatures included)\n"
17
15
  email:
18
16
  - kachick1+ruby@gmail.com
19
17
  executables: []
@@ -46,7 +44,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
46
44
  requirements:
47
45
  - - ">="
48
46
  - !ruby/object:Gem::Version
49
- version: 2.7.0
47
+ version: 2.7.2
50
48
  required_rubygems_version: !ruby/object:Gem::Requirement
51
49
  requirements:
52
50
  - - ">="