ruby-ulid 0.4.0 → 0.6.1

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: 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