ruby-ulid 0.4.0 → 0.6.1

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: 7b50cb3fee0b304a62ebd460e7d08b932d3c58c1f7810a1dc24b5166df10ceaf
4
- data.tar.gz: b84a2ad766640fa7c87e4bbb5b0fdd637bf831407f235989cdd043ea5b1189aa
3
+ metadata.gz: 5c7cf89034d0f74026278e2a4c113d57f8fc28b861b8bcf1d2210dbb76688e52
4
+ data.tar.gz: 340bfbea8fb1ff3ec0e49339e2a5a388a81552f8cd73a65ad9ff049448fc0c3f
5
5
  SHA512:
6
- metadata.gz: e44eadac5ad4c83f1c8da74fbfa502a3b3b7293dda8eb44855bb477070687afd815d46237a9cc15266223f7cd0981b69670165767b366156d23728cdb76c878e
7
- data.tar.gz: 349b2e8c2b16d5c9ae58270fdb5e87a3c364eef4b3ae2972680cb5de119ba737ed035db36bb7283fc96ff7c7a54663be5de8c0725e0de1975d8f66de34dd052c
6
+ metadata.gz: 74d26cb5dc49a8d79a5c933817c1c8913954d675d996a91057f29859fd1fdf0a32c99d8121ef954e1d7d400110fa1737be17a9084bfc03655082de96c9614392
7
+ data.tar.gz: 96f19838c4c90ee313722857f88693e88f247e5380293291b362eadc834f6adb1613c01034a0cfe430f24576b4fea55f252b79f689b00c3e05b739118db8906e
data/README.md CHANGED
@@ -7,8 +7,7 @@
7
7
 
8
8
  [ulid/spec](https://github.com/ulid/spec) is useful.
9
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 [RBS](https://github.com/ruby/rbs) signatures.
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).
12
11
 
13
12
  ---
14
13
 
@@ -50,7 +49,7 @@ Should be installed!
50
49
  Add this line in your Gemfile.
51
50
 
52
51
  ```ruby
53
- gem('ruby-ulid', '~> 0.4.0')
52
+ gem('ruby-ulid', '~> 0.6.1')
54
53
  ```
55
54
 
56
55
  ### How to use
@@ -58,34 +57,32 @@ gem('ruby-ulid', '~> 0.4.0')
58
57
  ```ruby
59
58
  require 'ulid'
60
59
 
61
- defined? ULID
62
- # => "constant"
60
+ ULID::VERSION
61
+ # => "0.6.1"
63
62
  ```
64
63
 
65
- ### Basic Generator
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).
66
65
 
67
- The generated `ULID` is an object not just a string.
66
+ ### Generator and Parser
67
+
68
+ `ULID.generate` returns `ULID` instance. It is not just a string.
68
69
 
69
70
  ```ruby
70
71
  ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
71
72
  ```
72
73
 
73
- ### Parser
74
-
75
- You can get the objects from exists encoded ULIDs.
74
+ `ULID.parse` returns `ULID` instance from exists encoded ULIDs.
76
75
 
77
76
  ```ruby
78
- ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
77
+ ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
79
78
  ```
80
79
 
81
- ### ULID object
82
-
83
- You can extract timestamps and binary formats.
80
+ It is helpful to inspect.
84
81
 
85
82
  ```ruby
86
- ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
87
83
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
88
84
  ulid.milliseconds #=> 1619544442826
85
+ ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
89
86
  ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
90
87
  ulid.timestamp #=> "01F4A5Y1YA"
91
88
  ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
@@ -93,6 +90,42 @@ ulid.to_i #=> 1957909092946624190749577070267409738
93
90
  ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
94
91
  ```
95
92
 
93
+ `ULID.generate` can take fixed `Time` instance. `ULID.at` is the shorthand.
94
+
95
+ ```ruby
96
+ time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
97
+ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
98
+ ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
99
+ ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
100
+ ```
101
+
102
+ Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
103
+
104
+ `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.
105
+ It can take same arguments as `ULID.generate`.
106
+
107
+ ```ruby
108
+ ULID.encode #=> "01G86M42Q6SJ9XQM2ZRM6JRDSF"
109
+ ULID.encode(moment: Time.at(946684800).utc) #=> "00VHNCZB00SYG7RCEXZC9DA4E1"
110
+ ```
111
+
112
+ `ULID.decode_time` returns Time. It can take `in` keyarg as same as `Time.at`.
113
+
114
+ ```ruby
115
+ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1') #=> 2000-01-01 00:00:00 UTC
116
+ ULID.decode_time('00VHNCZB00SYG7RCEXZC9DA4E1', in: '+09:00') #=> 2000-01-01 09:00:00 +0900
117
+ ```
118
+
119
+ This project does not prioritize the speed. However it actually works faster than others! :zap:
120
+
121
+ Snapshot on 0.6.1 is below
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)
126
+
127
+ You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
128
+
96
129
  ### Sortable with the timestamp
97
130
 
98
131
  ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
@@ -106,21 +139,7 @@ ulids.uniq(&:to_time).size #=> 1000
106
139
  ulids.sort == ulids #=> true
107
140
  ```
108
141
 
109
- `ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`.
110
-
111
- ```ruby
112
- time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
113
- ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
114
- ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
115
- ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
116
-
117
- ulids = 1000.times.map do |n|
118
- ULID.at(time + n)
119
- end
120
- ulids.sort == ulids #=> true
121
- ```
122
-
123
- The basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
142
+ Basic generator prefers `randomness`, it does not guarantee `sortable` for same milliseconds ULIDs.
124
143
 
125
144
  ```ruby
126
145
  ulids = 10000.times.map do
@@ -162,7 +181,7 @@ sample_ulids_by_the_time.take(5) #=>
162
181
  ulids.sort == ulids #=> true
163
182
  ```
164
183
 
165
- Same generator does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255).
184
+ Same instance of `ULID::MonotonicGenerator` does not generate duplicated ULIDs even in multi threads environment. It is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255).
166
185
 
167
186
  ### Filtering IDs with `Time`
168
187
 
@@ -202,7 +221,7 @@ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.12
202
221
  ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
203
222
  ```
204
223
 
205
- ### Some methods to help manipulations
224
+ ### Tools
206
225
 
207
226
  #### Scanner for string (e.g. `JSON`)
208
227
 
@@ -278,7 +297,7 @@ ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
278
297
  `ULID#next` and `ULID#succ` returns next(successor) ULID.
279
298
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
280
299
 
281
- NOTE: However basically `Range[ULID]#each` should not be used. Iincrementing 128 bits IDs are not reasonable operation in most cases.
300
+ NOTE: However basically `Range[ULID]#each` should not be used. Incrementing 128 bits IDs are not reasonable operation in most cases.
282
301
 
283
302
  ```ruby
284
303
  ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
@@ -341,31 +360,32 @@ ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
341
360
 
342
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.
343
362
 
363
+ >Case insensitive
364
+
365
+ I can understand it might be considered in actual use-case. So `ULID.parse` accepts upcase and downcase.
366
+ However it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
367
+
344
368
  >Uses Crockford's base32 for better efficiency and readability (5 bits per character)
345
369
 
346
370
  The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
347
371
  And accepts freestyle inserting `Hyphens (-)`.
348
372
  To consider this patterns or not is different in each implementations.
349
373
 
350
- Current parser/validator/matcher aims to cover `subset of Crockford's base32`.
351
- I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
374
+ I have suggested to clarify `subset of Crockford's base32` in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
352
375
 
353
- >Case insensitive
376
+ This gem provides some methods to handle the nasty possibilities.
354
377
 
355
- I can understand it might be considered in actual use-case.
356
- But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
357
-
358
- Be that as it may, this gem provides API for handling the nasty possibilities.
359
-
360
- `ULID.normalize` and `ULID.normalized?`
378
+ `ULID.normalize`, `ULID.normalized?`, `ULID.valid_as_variant_format?` and `ULID.parse_variant_format`
361
379
 
362
380
  ```ruby
363
- ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
364
- ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
365
- ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
381
+ ULID.normalize('01g70y0y7g-z1xwdarexergsddd') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
382
+ ULID.normalized?('01g70y0y7g-z1xwdarexergsddd') #=> false
383
+ ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true
384
+ ULID.valid_as_variant_format?('01g70y0y7g-z1xwdarexergsddd') #=> true
385
+ ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD') #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
366
386
  ```
367
387
 
368
- #### UUIDv4 converter for migration use-cases
388
+ #### UUIDv4 converter (experimental)
369
389
 
370
390
  `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
371
391
  The imported timestamp is meaningless. So ULID's benefit will lost.
@@ -398,68 +418,19 @@ ULID.min == reversed_min #=> false
398
418
  ULID.max == reversed_max #=> false
399
419
  ```
400
420
 
401
- ## How to migrate from other gems
402
-
403
- As far as I know, major prior arts are below
404
-
405
- ### [ulid gem](https://rubygems.org/gems/ulid) - [rafaelsales/ulid](https://github.com/rafaelsales/ulid)
406
-
407
- It is just providing basic `String` generator only.
408
- So you can replace the code as below
409
-
410
- ```diff
411
- -ULID.generate
412
- +ULID.generate.to_s
413
- ```
414
-
415
- NOTE: In version before `1.3.0`, timestamps might not be correct value.
416
-
417
- 1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
418
- 1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
419
- 1. [Released in 1.3.0](https://github.com/rafaelsales/ulid/compare/1.2.0...v1.3.0)
420
-
421
- ### [ulid-ruby gem](https://rubygems.org/gems/ulid-ruby) - [abachman/ulid-ruby](https://github.com/abachman/ulid-ruby)
422
-
423
- It is providing basic generator(except monotonic generator) and parser.
424
- Major methods can be replaced as below.
425
-
426
- ```diff
427
- -ULID.generate
428
- +ULID.generate.to_s
429
- -ULID.at(time)
430
- +ULID.at(time).to_s
431
- -ULID.time(string)
432
- +ULID.parse(string).to_time
433
- -ULID.min_ulid_at(time)
434
- +ULID.min(time).to_s
435
- -ULID.max_ulid_at(time)
436
- +ULID.max(time).to_s
437
- ```
438
-
439
- NOTE: In version before `1.0.2`, timestamps might not be correct value.
440
-
441
- 1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
442
- 1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
443
- 1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
444
- 1. [Released in 1.0.2](https://github.com/abachman/ulid-ruby/compare/v1.0.0...v1.0.2)
445
-
446
- ### Compare performance with them
447
-
448
- See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
421
+ ## Migration from other gems
449
422
 
450
- The results are not something to be proud of.
423
+ See [wiki page for gem migration](https://github.com/kachick/ruby-ulid/wiki/Gem-migration).
451
424
 
452
- ## How to use rbs
425
+ ## RBS
453
426
 
454
- See structure at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox)
427
+ Try at [examples/rbs_sandbox](https://github.com/kachick/ruby-ulid/tree/main/examples/rbs_sandbox).
455
428
 
456
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).
457
430
 
458
431
  * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
459
432
  * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
460
433
  * ![rbs ng-to_str](./assets/ulid-rbs-ng-to_str.png?raw=true.png)
461
- * ![rbs ok-at-time](./assets/ulid-rbs-ok-at-time.png?raw=true.png)
462
- * ![rbs ng-at-int](./assets/ulid-rbs-ng-at-int.png?raw=true.png)
463
434
 
464
435
  ## References
465
436
 
@@ -15,63 +15,66 @@ class ULID
15
15
  # * https://github.com/kachick/ruby-ulid/issues/57
16
16
  # * https://github.com/kachick/ruby-ulid/issues/78
17
17
  module CrockfordBase32
18
- class SetupError < UnexpectedError; end
19
-
20
- n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
21
- raise(SetupError, 'obvious bug exists in the mapping algorithm') unless n32_chars.size == 32
22
-
23
- n32_char_by_number = {}
24
- n32_chars.each_with_index do |char, index|
25
- n32_char_by_number[index] = char
26
- end
27
- n32_char_by_number.freeze
28
-
29
- crockford_base32_mappings = {
30
- 'J' => 18,
31
- 'K' => 19,
32
- 'M' => 20,
33
- 'N' => 21,
34
- 'P' => 22,
35
- 'Q' => 23,
36
- 'R' => 24,
37
- 'S' => 25,
38
- 'T' => 26,
39
- 'V' => 27,
40
- 'W' => 28,
41
- 'X' => 29,
42
- 'Y' => 30,
43
- 'Z' => 31
18
+ same_definitions = {
19
+ '0' => '0',
20
+ '1' => '1',
21
+ '2' => '2',
22
+ '3' => '3',
23
+ '4' => '4',
24
+ '5' => '5',
25
+ '6' => '6',
26
+ '7' => '7',
27
+ '8' => '8',
28
+ '9' => '9',
29
+ 'A' => 'A',
30
+ 'B' => 'B',
31
+ 'C' => 'C',
32
+ 'D' => 'D',
33
+ 'E' => 'E',
34
+ 'F' => 'F',
35
+ 'G' => 'G',
36
+ 'H' => 'H'
44
37
  }.freeze
45
38
 
46
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR = CROCKFORD_BASE32_ENCODING_STRING.chars.map(&:freeze).each_with_object({}) do |encoding_char, map|
47
- if n = crockford_base32_mappings[encoding_char]
48
- char_32 = n32_char_by_number.fetch(n)
49
- map[encoding_char] = char_32
50
- end
51
- end.freeze
52
- raise(SetupError, 'obvious bug exists in the mapping algorithm') unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
53
-
54
- CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
55
-
56
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
57
- N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
39
+ # Excluded I, L, O, U, - from Base32
40
+ base32_to_crockford = {
41
+ 'I' => 'J',
42
+ 'J' => 'K',
43
+ 'K' => 'M',
44
+ 'L' => 'N',
45
+ 'M' => 'P',
46
+ 'N' => 'Q',
47
+ 'O' => 'R',
48
+ 'P' => 'S',
49
+ 'Q' => 'T',
50
+ 'R' => 'V',
51
+ 'S' => 'W',
52
+ 'T' => 'X',
53
+ 'U' => 'Y',
54
+ 'V' => 'Z'
55
+ }.freeze
56
+ BASE32_TR_PATTERN = base32_to_crockford.keys.join.freeze
57
+ CROCKFORD_TR_PATTERN = base32_to_crockford.values.join.freeze
58
+ ENCODING_STRING = "#{same_definitions.values.join}#{CROCKFORD_TR_PATTERN}".freeze
58
59
 
59
- STANDARD_BY_VARIANT = {
60
+ variant_to_normarized = {
60
61
  'L' => '1',
61
62
  'l' => '1',
62
63
  'I' => '1',
63
64
  'i' => '1',
64
65
  'O' => '0',
65
- 'o' => '0',
66
- '-' => ''
66
+ 'o' => '0'
67
67
  }.freeze
68
- VARIANT_PATTERN = /[#{STANDARD_BY_VARIANT.keys.join}]/.freeze
68
+ VARIANT_TR_PATTERN = variant_to_normarized.keys.join.freeze
69
+ NORMALIZED_TR_PATTERN = variant_to_normarized.values.join.freeze
70
+
71
+ # @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
69
72
 
70
73
  # @api private
71
74
  # @param [String] string
72
75
  # @return [Integer]
73
76
  def self.decode(string)
74
- n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
77
+ n32encoded = string.upcase.tr(CROCKFORD_TR_PATTERN, BASE32_TR_PATTERN)
75
78
  n32encoded.to_i(32)
76
79
  end
77
80
 
@@ -80,14 +83,21 @@ class ULID
80
83
  # @return [String]
81
84
  def self.encode(integer)
82
85
  n32encoded = integer.to_s(32)
83
- n32encoded.upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0')
86
+ from_n32(n32encoded).rjust(ENCODED_LENGTH, '0')
84
87
  end
85
88
 
86
89
  # @api private
87
90
  # @param [String] string
88
91
  # @return [String]
89
92
  def self.normalize(string)
90
- string.gsub(VARIANT_PATTERN, STANDARD_BY_VARIANT)
93
+ string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
94
+ end
95
+
96
+ # @api private
97
+ # @param [String] n32encoded
98
+ # @return [String]
99
+ def self.from_n32(n32encoded)
100
+ n32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_TR_PATTERN)
91
101
  end
92
102
  end
93
103
  end
@@ -0,0 +1,10 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+ # shareable_constant_value: literal
4
+
5
+ class ULID
6
+ class Error < StandardError; end
7
+ class OverflowError < Error; end
8
+ class ParserError < Error; end
9
+ class UnexpectedError < Error; end
10
+ end
@@ -10,7 +10,6 @@ class ULID
10
10
  # However it is a C extention, I'm pending to use it for now.
11
11
  include(MonitorMixin)
12
12
 
13
- # @dynamic prev
14
13
  # @return [ULID, nil]
15
14
  attr_reader(:prev)
16
15
 
@@ -25,7 +24,6 @@ class ULID
25
24
  def inspect
26
25
  "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
27
26
  end
28
- # @dynamic to_s
29
27
  alias_method(:to_s, :inspect)
30
28
 
31
29
  # @param [Time, Integer] moment
@@ -70,6 +68,12 @@ class ULID
70
68
  end
71
69
  end
72
70
 
71
+ # @param [Time, Integer] moment
72
+ # @return [String]
73
+ def encode(moment: ULID.current_milliseconds)
74
+ generate(moment: moment).encode
75
+ end
76
+
73
77
  undef_method(:freeze)
74
78
 
75
79
  # @raise [TypeError] always raises exception and does not freeze self
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.4.0'
6
+ VERSION = '0.6.1'
7
7
  end
data/lib/ulid.rb CHANGED
@@ -6,6 +6,11 @@
6
6
 
7
7
  require('securerandom')
8
8
 
9
+ require_relative('ulid/version')
10
+ require_relative('ulid/errors')
11
+ require_relative('ulid/crockford_base32')
12
+ require_relative('ulid/monotonic_generator')
13
+
9
14
  # @see https://github.com/ulid/spec
10
15
  # @!attribute [r] milliseconds
11
16
  # @return [Integer]
@@ -14,16 +19,6 @@ require('securerandom')
14
19
  class ULID
15
20
  include(Comparable)
16
21
 
17
- class Error < StandardError; end
18
- class OverflowError < Error; end
19
- class ParserError < Error; end
20
- class UnexpectedError < Error; end
21
-
22
- # Excluded I, L, O, U, -.
23
- # This is the encoding patterns.
24
- # The decoding issue is written in ULID::CrockfordBase32
25
- CROCKFORD_BASE32_ENCODING_STRING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
26
-
27
22
  TIMESTAMP_ENCODED_LENGTH = 10
28
23
  RANDOMNESS_ENCODED_LENGTH = 16
29
24
  ENCODED_LENGTH = 26
@@ -38,12 +33,12 @@ class ULID
38
33
 
39
34
  # @see https://github.com/ulid/spec/pull/57
40
35
  # Currently not used as a constant, but kept as a reference for now.
41
- PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /(?<timestamp>[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}})(?<randomness>[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}})/i.freeze
36
+ PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /(?<timestamp>[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}})(?<randomness>[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}})/i.freeze
42
37
 
43
38
  STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /\A#{PATTERN_WITH_CROCKFORD_BASE32_SUBSET.source}\z/i.freeze
44
39
 
45
40
  # Optimized for `ULID.scan`, might be changed the definition with gathered `ULID.scan` spec changed.
46
- SCANNING_PATTERN = /\b[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i.freeze
41
+ SCANNING_PATTERN = /\b[0-7][#{CrockfordBase32::ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CrockfordBase32::ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}\b/i.freeze
47
42
 
48
43
  # Similar as Time#inspect since Ruby 2.7, however it is NOT same.
49
44
  # Time#inspect trancates needless digits. Keeping full milliseconds with "%3N" will fit for ULID.
@@ -60,6 +55,16 @@ class ULID
60
55
  from_milliseconds_and_entropy(milliseconds: milliseconds_from_moment(moment), entropy: entropy)
61
56
  end
62
57
 
58
+ # Almost same as [.generate] except directly returning String without needless object creation
59
+ #
60
+ # @param [Integer, Time] moment
61
+ # @param [Integer] entropy
62
+ # @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)
66
+ end
67
+
63
68
  # Short hand of `ULID.generate(moment: time)`
64
69
  # @param [Time] time
65
70
  # @return [ULID]
@@ -166,7 +171,12 @@ class ULID
166
171
  milliseconds = n32encoded_timestamp.to_i(32)
167
172
  entropy = n32encoded_randomness.to_i(32)
168
173
 
169
- new(milliseconds: milliseconds, entropy: entropy, integer: integer)
174
+ new(
175
+ milliseconds: milliseconds,
176
+ entropy: entropy,
177
+ integer: integer,
178
+ encoded: CrockfordBase32.from_n32(n32encoded).freeze
179
+ )
170
180
  end
171
181
 
172
182
  # @param [Range<Time>, Range<nil>, Range[ULID]] period
@@ -254,6 +264,17 @@ class ULID
254
264
  SecureRandom.random_number(MAX_ENTROPY)
255
265
  end
256
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
+
257
278
  # @param [String, #to_str] string
258
279
  # @return [ULID]
259
280
  # @raise [ParserError] if the given format is not correct for ULID specs
@@ -268,6 +289,36 @@ class ULID
268
289
  from_integer(CrockfordBase32.decode(string))
269
290
  end
270
291
 
292
+ # @param [String, #to_str] string
293
+ # @return [ULID]
294
+ # @raise [ParserError] if the given format is not correct for ULID specs
295
+ def self.parse_variant_format(string)
296
+ string = String.try_convert(string)
297
+ raise(ArgumentError, 'ULID.parse_variant_format takes only strings') unless string
298
+
299
+ normalized_in_crockford = CrockfordBase32.normalize(string)
300
+ parse(normalized_in_crockford)
301
+ end
302
+
303
+ # Almost same as `ULID.parse(string).to_time` except directly returning Time instance without needless object creation
304
+ #
305
+ # @param [String, #to_str] string
306
+ # @return [Time]
307
+ # @raise [ParserError] if the given format is not correct for ULID specs
308
+ def self.decode_time(string, in: 'UTC')
309
+ in_for_time_at = binding.local_variable_get(:in) # Needed because `in` is a reserved word.
310
+ string = String.try_convert(string)
311
+ raise(ArgumentError, 'ULID.decode_time takes only strings') unless string
312
+
313
+ unless STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string)
314
+ raise(ParserError, "given `#{string}` does not match to `#{STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.inspect}`")
315
+ end
316
+
317
+ timestamp = string.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError)
318
+
319
+ Time.at(0, CrockfordBase32.decode(timestamp), :millisecond, in: in_for_time_at)
320
+ end
321
+
271
322
  # @param [String, #to_str] string
272
323
  # @return [String]
273
324
  # @raise [ParserError] if the given format is not correct for ULID specs, even if ignored `orthographical variants of the format`
@@ -275,23 +326,40 @@ class ULID
275
326
  string = String.try_convert(string)
276
327
  raise(ArgumentError, 'ULID.normalize takes only strings') unless string
277
328
 
278
- normalized_in_crockford = CrockfordBase32.normalize(string)
279
329
  # Ensure the ULID correctness, because CrockfordBase32 does not always mean to satisfy ULID format
280
- parse(normalized_in_crockford).to_s
330
+ parse_variant_format(string).to_s
331
+ end
332
+
333
+ # @param [String, #to_str] string
334
+ # @return [Boolean]
335
+ def self.normalized?(string)
336
+ normalized = normalize(string)
337
+ rescue Exception
338
+ false
339
+ else
340
+ normalized == string
281
341
  end
282
342
 
343
+ # @param [String, #to_str] string
283
344
  # @return [Boolean]
284
- def self.normalized?(object)
285
- normalized = normalize(object)
345
+ def self.valid_as_variant_format?(string)
346
+ parse_variant_format(string)
286
347
  rescue Exception
287
348
  false
288
349
  else
289
- normalized == object
350
+ true
290
351
  end
291
352
 
353
+ # @deprecated Use [.valid_as_variant_format?] or [.normalized?] instead
354
+ #
355
+ # Returns `true` if it is normalized string.
356
+ # Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs.
357
+ #
292
358
  # @return [Boolean]
293
- def self.valid?(object)
294
- string = String.try_convert(object)
359
+ def self.valid?(string)
360
+ warn_kwargs = (RUBY_VERSION >= '3.0') ? { category: :deprecated } : {}
361
+ Warning.warn('ULID.valid? is deprecated. Use ULID.valid_as_variant_format? or ULID.normalized? instead.', **warn_kwargs)
362
+ string = String.try_convert(string)
295
363
  string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false
296
364
  end
297
365
 
@@ -350,37 +418,36 @@ class ULID
350
418
  # @raise [OverflowError] if the given value is larger than the ULID limit
351
419
  # @raise [ArgumentError] if the given milliseconds and/or entropy is negative number
352
420
  def self.from_milliseconds_and_entropy(milliseconds:, entropy:)
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?
357
-
358
- n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
359
- n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
360
- integer = (n32encoded_timestamp + n32encoded_randomness).to_i(32)
361
-
362
- new(milliseconds: milliseconds, entropy: entropy, integer: integer)
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
+ )
363
428
  end
364
429
 
365
- # @dynamic milliseconds, entropy
366
430
  attr_reader(:milliseconds, :entropy)
367
431
 
368
432
  # @api private
369
433
  # @param [Integer] milliseconds
370
434
  # @param [Integer] entropy
371
435
  # @param [Integer] integer
436
+ # @param [String] encoded
372
437
  # @return [void]
373
- def initialize(milliseconds:, entropy:, integer:)
438
+ def initialize(milliseconds:, entropy:, integer:, encoded:)
374
439
  # All arguments check should be done with each constructors, not here
375
440
  @integer = integer
441
+ @encoded = encoded
376
442
  @milliseconds = milliseconds
377
443
  @entropy = entropy
378
444
  end
379
445
 
380
446
  # @return [String]
381
- def to_s
382
- @string ||= CrockfordBase32.encode(@integer).freeze
447
+ def encode
448
+ @encoded
383
449
  end
450
+ alias_method(:to_s, :encode)
384
451
 
385
452
  # @return [Integer]
386
453
  def to_i
@@ -392,21 +459,20 @@ class ULID
392
459
  [ULID, @integer].hash
393
460
  end
394
461
 
395
- # @return [Integer, nil]
462
+ # @return [-1, 0, 1, nil]
396
463
  def <=>(other)
397
464
  (ULID === other) ? (@integer <=> other.to_i) : nil
398
465
  end
399
466
 
400
467
  # @return [String]
401
468
  def inspect
402
- @inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{to_s})".freeze
469
+ @inspect ||= "ULID(#{to_time.strftime(TIME_FORMAT_IN_INSPECT)}: #{@encoded})".freeze
403
470
  end
404
471
 
405
472
  # @return [Boolean]
406
473
  def eql?(other)
407
474
  equal?(other) || (ULID === other && @integer == other.to_i)
408
475
  end
409
- # @dynamic ==
410
476
  alias_method(:==, :eql?)
411
477
 
412
478
  # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
@@ -421,11 +487,9 @@ class ULID
421
487
  @integer == other.to_i
422
488
  when String
423
489
  begin
424
- normalized = ULID.normalize(other)
490
+ to_i == ULID.parse_variant_format(other).to_i
425
491
  rescue Exception
426
492
  false
427
- else
428
- to_s == normalized
429
493
  end
430
494
  when Time
431
495
  to_time == ULID.floor(other)
@@ -460,12 +524,12 @@ class ULID
460
524
 
461
525
  # @return [String]
462
526
  def timestamp
463
- @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
527
+ @timestamp ||= (@encoded.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
464
528
  end
465
529
 
466
530
  # @return [String]
467
531
  def randomness
468
- @randomness ||= (to_s.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
532
+ @randomness ||= (@encoded.slice(TIMESTAMP_ENCODED_LENGTH, RANDOMNESS_ENCODED_LENGTH).freeze || raise(UnexpectedError))
469
533
  end
470
534
 
471
535
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -491,7 +555,6 @@ class ULID
491
555
  ULID.from_integer(succ_int)
492
556
  end
493
557
  end
494
- # @dynamic next
495
558
  alias_method(:next, :succ)
496
559
 
497
560
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
@@ -526,7 +589,7 @@ class ULID
526
589
  # @return [void]
527
590
  def marshal_load(integer)
528
591
  unmarshaled = ULID.from_integer(integer)
529
- initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy)
592
+ initialize(integer: unmarshaled.to_i, milliseconds: unmarshaled.milliseconds, entropy: unmarshaled.entropy, encoded: unmarshaled.to_s)
530
593
  end
531
594
 
532
595
  # @return [self]
@@ -556,12 +619,9 @@ class ULID
556
619
  end
557
620
  end
558
621
 
559
- require_relative('ulid/version')
560
- require_relative('ulid/crockford_base32')
561
- require_relative('ulid/monotonic_generator')
562
622
  require_relative('ulid/ractor_unshareable_constants')
563
623
 
564
624
  class ULID
565
625
  # Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
566
- private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
626
+ private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR)
567
627
  end
data/sig/ulid.rbs CHANGED
@@ -1,6 +1,5 @@
1
1
  class ULID < Object
2
2
  VERSION: String
3
- CROCKFORD_BASE32_ENCODING_STRING: String
4
3
  TIMESTAMP_ENCODED_LENGTH: 10
5
4
  RANDOMNESS_ENCODED_LENGTH: 16
6
5
  ENCODED_LENGTH: 26
@@ -39,12 +38,11 @@ class ULID < Object
39
38
  class SetupError < UnexpectedError
40
39
  end
41
40
 
42
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
43
- CROCKFORD_BASE32_CHAR_PATTERN: Regexp
44
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
45
- N32_CHAR_PATTERN: Regexp
46
- STANDARD_BY_VARIANT: Hash[String, String]
47
- VARIANT_PATTERN: Regexp
41
+ ENCODING_STRING: String
42
+ CROCKFORD_TR_PATTERN: String
43
+ BASE32_TR_PATTERN: String
44
+ VARIANT_TR_PATTERN: String
45
+ NORMALIZED_TR_PATTERN: String
48
46
 
49
47
  # A private API. Should not be used in your code.
50
48
  def self.encode: (Integer integer) -> String
@@ -54,6 +52,9 @@ class ULID < Object
54
52
 
55
53
  # A private API. Should not be used in your code.
56
54
  def self.normalize: (String string) -> String
55
+
56
+ # A private API. Should not be used in your code.
57
+ def self.from_n32: (String n32encoded) -> String
57
58
  end
58
59
 
59
60
  class MonotonicGenerator
@@ -78,6 +79,9 @@ class ULID < Object
78
79
  # The `Thread-safety` is implemented with [Monitor](https://bugs.ruby-lang.org/issues/16255)
79
80
  def generate: (?moment: moment) -> ULID
80
81
 
82
+ # Just providing similar api as `ULID.generate` and `ULID.encode` relation. No performance benefit exists in monotonic generator's one.
83
+ def encode: (?moment: moment) -> String
84
+
81
85
  # Returned value is `basically not` Thread-safety
82
86
  # If you want to keep Thread-safety, keep to call {#generate} only in same {#synchronize} block
83
87
  #
@@ -102,8 +106,8 @@ class ULID < Object
102
106
  type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer] | Array[Integer]
103
107
  type period = Range[Time] | Range[nil] | Range[ULID]
104
108
 
105
- @string: String?
106
109
  @integer: Integer
110
+ @encoded: String
107
111
  @timestamp: String?
108
112
  @randomness: String?
109
113
  @inspect: String?
@@ -145,6 +149,16 @@ class ULID < Object
145
149
  #
146
150
  def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
147
151
 
152
+ # Retuns encoded and normalzied String.
153
+ # It has same arguments signatures as `.generate`. So can be used for just ID creation usecases without needless object creation.
154
+ #
155
+ # NOTE: Difference of ULID#encode, returned String is NOT frozen.
156
+ #
157
+ def self.encode: (?moment: moment, ?entropy: Integer) -> String
158
+
159
+ # A private API. Should not be used in your code.
160
+ def self.encode_n32: (milliseconds: Integer, entropy: Integer) -> String
161
+
148
162
  # Shorthand of `ULID.generate(moment: Time)`
149
163
  # See also [ULID.generate](https://kachick.github.io/ruby-ulid/ULID.html#generate-class_method)
150
164
  #
@@ -205,7 +219,7 @@ class ULID < Object
205
219
  # ```
206
220
  def self.floor: (Time time) -> Time
207
221
 
208
- # Get ULID instance from encoded String.
222
+ # Return ULID instance from encoded String.
209
223
  #
210
224
  # ```ruby
211
225
  # ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV')
@@ -213,6 +227,32 @@ class ULID < Object
213
227
  # ```
214
228
  def self.parse: (_ToStr string) -> ULID
215
229
 
230
+ # Return Time instance from encoded String.
231
+ # See also `ULID.encode` for similar purpose.
232
+ #
233
+ # NOTE: Difference of ULID#to_time, returned Time is NOT frozen.
234
+ #
235
+ # ```ruby
236
+ # time = ULID.decode_time('01ARZ3NDEKTSV4RRFFQ69G5FAV')
237
+ # #=> 2016-07-30 23:54:10.259 UTC
238
+ # ```
239
+ def self.decode_time: (_ToStr string, ?in: String | Integer | nil) -> Time
240
+
241
+ # Get ULID instance from unnormalized String that encoded in Crockford's base32.
242
+ #
243
+ # http://www.crockford.com/base32.html
244
+ #
245
+ # * Ignore Hyphens (-)
246
+ # * Mapping 0 O o => 0, 1 I i L l => 1
247
+ #
248
+ # ```ruby
249
+ # ulid = ULID.parse_variant_format('01G70Y0Y7G-ZLXWDIREXERGSDoD')
250
+ # #=> ULID(2022-07-03 02:25:22.672 UTC: 01G70Y0Y7GZ1XWD1REXERGSD0D)
251
+ # ```
252
+ #
253
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
254
+ def self.parse_variant_format: (_ToStr string) -> ULID
255
+
216
256
  # ```ruby
217
257
  # # Currently experimental feature, so needed to load the extension.
218
258
  # require 'ulid/uuid'
@@ -309,14 +349,13 @@ class ULID < Object
309
349
  # ```
310
350
  def self.sample: (?period: period) -> ULID
311
351
  | (Integer number, ?period: period?) -> Array[ULID]
312
- def self.valid?: (untyped) -> bool
313
352
 
314
353
  # Returns normalized string
315
354
  #
316
355
  # ```ruby
356
+ # ULID.normalize('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
317
357
  # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
318
- # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
319
- # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
358
+ # ULID.normalize('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> ULID::ParserError
320
359
  # ```
321
360
  #
322
361
  # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
@@ -325,13 +364,40 @@ class ULID < Object
325
364
  # Returns `true` if it is normalized string
326
365
  #
327
366
  # ```ruby
328
- # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
329
- # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
330
- # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
367
+ # ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true
368
+ # ULID.normalized?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> false
369
+ # ULID.normalized?(ULID.generate.to_s.downcase) #=> false
370
+ # ULID.normalized?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false (Not raising ULID::ParserError)
371
+ # ```
372
+ #
373
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
374
+ def self.normalized?: (_ToStr string) -> bool
375
+ | (untyped) -> false
376
+
377
+ # Returns `true` if it is valid in ULID format variants
378
+ #
379
+ # ```ruby
380
+ # ULID.valid_as_variant_format?(ULID.generate.to_s.downcase) #=> true
381
+ # ULID.valid_as_variant_format?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> true
382
+ # ULID.valid_as_variant_format?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false
331
383
  # ```
332
384
  #
333
385
  # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
334
- def self.normalized?: (untyped) -> bool
386
+ def self.valid_as_variant_format?: (_ToStr string) -> bool
387
+ | (untyped) -> false
388
+
389
+ # DEPRECATED Use valid_as_variant_format? instead
390
+ #
391
+ # Returns `true` if it is normalized string.
392
+ # Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs.
393
+ #
394
+ # ```ruby
395
+ # ULID.valid?(ULID.generate.to_s.downcase) #=> true
396
+ # ```
397
+ #
398
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
399
+ def self.valid?: (_ToStr string) -> bool
400
+ | (untyped) -> false
335
401
 
336
402
  # Returns parsed ULIDs from given String for rough operations.
337
403
  #
@@ -389,9 +455,12 @@ class ULID < Object
389
455
  # ```ruby
390
456
  # ulid = ULID.generate
391
457
  # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
392
- # ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
458
+ # ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
459
+ # ulid.encode.frozen? #=> true
460
+ # ulid.encode.equal?(ulid.to_s) #=> true
393
461
  # ```
394
- def to_s: -> String
462
+ def encode: -> String
463
+ alias to_s encode
395
464
 
396
465
  # ```ruby
397
466
  # ulid = ULID.generate
@@ -411,7 +480,12 @@ class ULID < Object
411
480
  # #=> true
412
481
  # ```
413
482
  #
414
- # To be precise, this sorting unaffected with `case sensitive or not` and might handle [ulid/spec#57](https://github.com/ulid/spec/pull/57) in future. So preferable than `lexicographically sortable` in actual case.
483
+ # To be precise, this sorting unaffected with `case sensitive or not` and might handle [ulid/spec#57](https://github.com/ulid/spec/pull/57) in future.
484
+ # So preferable than `lexicographically sortable` in actual case.
485
+ #
486
+ # This returns -1 | 0 | 1 for ULIDs. However defined as returning Integer. It is caused on ruby/rbs current definition.
487
+ # https://github.com/ruby/ruby/blob/cd34f56d450f2310cceaf4c5f34d23eddfda58e8/numeric.c#L4646-L4660
488
+ # https://github.com/ruby/rbs/blob/14abbbae8885a09a2ed82de2ef31d67a9c0a108d/core/integer.rbs#L461-L462
415
489
  #
416
490
  def <=>: (ULID other) -> Integer
417
491
  | (untyped other) -> nil
@@ -535,9 +609,6 @@ class ULID < Object
535
609
 
536
610
  private
537
611
 
538
- # A private API. Should not be used in your code.
539
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
540
-
541
612
  # A private API. Should not be used in your code.
542
613
  def self.reasonable_entropy: -> Integer
543
614
 
@@ -548,7 +619,7 @@ class ULID < Object
548
619
  def self.safe_get_class_name: (untyped object) -> String
549
620
 
550
621
  # A private API. Should not be used in your code.
551
- def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
622
+ def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer, encoded: String) -> void
552
623
 
553
624
  # A private API. Should not be used in your code.
554
625
  def cache_all_instance_variables: -> void
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-ulid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.1
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-02 00:00:00.000000000 Z
11
+ date: 2022-07-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: " generator, monotonic generator, parser and manipulations for ULID
14
- (RBS included)\n"
13
+ description: " generator, optional monotonicity, parser and tools for ULID (RBS
14
+ included)\n"
15
15
  email:
16
16
  - kachick1+ruby@gmail.com
17
17
  executables: []
@@ -23,6 +23,7 @@ files:
23
23
  - lib/ruby-ulid.rb
24
24
  - lib/ulid.rb
25
25
  - lib/ulid/crockford_base32.rb
26
+ - lib/ulid/errors.rb
26
27
  - lib/ulid/monotonic_generator.rb
27
28
  - lib/ulid/ractor_unshareable_constants.rb
28
29
  - lib/ulid/uuid.rb