ruby-ulid 0.3.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 358b03a503f8a12c87f3f7daaf3b0acf6e55dc44f1aba7a38b9f9a38985c2c68
4
- data.tar.gz: 15d25d443f4826b07fc9a74b092e35b9cd60dc34cc3615f03fee619d4fc39445
3
+ metadata.gz: 82f48c45c124521403b04938ec4498a85bf292645357d75f32173cc2b9613618
4
+ data.tar.gz: 9c6269cbef649b57df980d120116687cdcc33e505cb45585b4566f4b16f3cdf2
5
5
  SHA512:
6
- metadata.gz: 5b18d13597bd2f3dcd85a15a26d9086fb9a80b91775292c9da82d93144b6abd37585996f1bf64dfc4ac12b6d6d49683012dfea8e04624005358459667864114c
7
- data.tar.gz: d987aa9b60717577fae96eef68fca76b2395f11a580df4863a69dd0931d955e4cf68608c101959db18f5e3a26031fbe284ff90a0d107fa84bbebb63949bba66b
6
+ metadata.gz: 817d5589c2967d8d1b787ce20c147483cd12769eba22a0489ade4002285e1edc31e06f9e5d8dd590d3cf2342d2875c388818e55b95cbd1d72921b1848287483f
7
+ data.tar.gz: 6bcacebe7955bb247127ddc64df1fec2cc776954e7c86530750c200da5420fd4cbb73d5fd2f0888cb37ff5f097987dd12f372ee591adccb8b35f35e03e4f0538
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 [ruby/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
 
@@ -47,23 +46,43 @@ $ gem install ruby-ulid
47
46
  Should be installed!
48
47
  ```
49
48
 
50
- Add this line to your Gemfile`.
49
+ Add this line in your Gemfile.
51
50
 
52
51
  ```ruby
53
- gem('ruby-ulid', '~> 0.2.2')
52
+ gem('ruby-ulid', '~> 0.6.0')
54
53
  ```
55
54
 
56
- ### Generator and Parser
57
-
58
- The generated `ULID` is an object not just a string.
59
- It means easily get the timestamps and binary formats.
55
+ ### How to use
60
56
 
61
57
  ```ruby
62
58
  require 'ulid'
63
59
 
60
+ ULID::VERSION
61
+ # => "0.6.0"
62
+ ```
63
+
64
+ NOTE: This README includes info about development version. If you would see released version's one. [Look at the ref](https://github.com/kachick/ruby-ulid/tree/v0.6.0).
65
+
66
+ ### Generator and Parser
67
+
68
+ `ULID.generate` returns `ULID` instance. It is not just a string.
69
+
70
+ ```ruby
64
71
  ulid = ULID.generate #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
72
+ ```
73
+
74
+ `ULID.parse` returns `ULID` instance from exists encoded ULIDs.
75
+
76
+ ```ruby
77
+ ulid = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
78
+ ```
79
+
80
+ It is helpful to inspect.
81
+
82
+ ```ruby
65
83
  ulid.to_time #=> 2021-04-27 17:27:22.826 UTC
66
84
  ulid.milliseconds #=> 1619544442826
85
+ ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
67
86
  ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
68
87
  ulid.timestamp #=> "01F4A5Y1YA"
69
88
  ulid.randomness #=> "QCYAYCTC7GRMJ9AA"
@@ -71,41 +90,56 @@ ulid.to_i #=> 1957909092946624190749577070267409738
71
90
  ulid.octets #=> [1, 121, 20, 95, 7, 202, 187, 60, 175, 51, 76, 60, 49, 73, 37, 74]
72
91
  ```
73
92
 
74
- You can get the objects from exists encoded ULIDs
93
+ `ULID.generate` can take fixed `Time` instance. `ULID.at` is the shorthand.
75
94
 
76
95
  ```ruby
77
- ulid = ULID.parse('01ARZ3NDEKTSV4RRFFQ69G5FAV') #=> ULID(2016-07-30 23:54:10.259 UTC: 01ARZ3NDEKTSV4RRFFQ69G5FAV)
78
- ulid.to_time #=> 2016-07-30 23:54:10.259 UTC
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)
79
100
  ```
80
101
 
81
- ### Sortable with the timestamp
102
+ Also `ULID.encode` and `ULID.decode_time` can be used to get primitive values for most usecases.
82
103
 
83
- ULIDs are sortable when they are generated in different timestamp with milliseconds precision
104
+ `ULID.encode` returns [normalized](#variants-of-format) String without ULID object creation.
105
+ It can take same arguments as `ULID.generate`.
84
106
 
85
107
  ```ruby
86
- ulids = 1000.times.map do
87
- sleep(0.001)
88
- ULID.generate
89
- end
90
- ulids.uniq(&:to_time).size #=> 1000
91
- ulids.sort == ulids #=> true
108
+ ULID.encode #=> "01G86M42Q6SJ9XQM2ZRM6JRDSF"
109
+ ULID.encode(moment: Time.at(946684800).utc) #=> "00VHNCZB00SYG7RCEXZC9DA4E1"
92
110
  ```
93
111
 
94
- `ULID.generate` can take fixed `Time` instance. The shorthand is `ULID.at`
112
+ `ULID.decode_time` returns Time. It can take `in` keyarg as same as `Time.at`.
95
113
 
96
114
  ```ruby
97
- time = Time.at(946684800).utc #=> 2000-01-01 00:00:00 UTC
98
- ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB00N018DCPJA4H9379P)
99
- ULID.generate(moment: time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB006WQT3JTMN0T14EBP)
100
- ULID.at(time) #=> ULID(2000-01-01 00:00:00.000 UTC: 00VHNCZB002W5BGWWKN76N22H6)
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.0 is below
101
122
 
102
- ulids = 1000.times.map do |n|
103
- ULID.at(time + n)
123
+ * Generator is 1.4x faster than - [ulid gem](https://github.com/rafaelsales/ulid)
124
+ * Generator is 1.7x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
125
+ * Parser is 2.6x faster than - [ulid-ruby gem](https://github.com/abachman/ulid-ruby)
126
+
127
+ You can see further detail at [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
128
+
129
+ ### Sortable with the timestamp
130
+
131
+ ULIDs are sortable when they are generated in different timestamp with milliseconds precision.
132
+
133
+ ```ruby
134
+ ulids = 1000.times.map do
135
+ sleep(0.001)
136
+ ULID.generate
104
137
  end
138
+ ulids.uniq(&:to_time).size #=> 1000
105
139
  ulids.sort == ulids #=> true
106
140
  ```
107
141
 
108
- 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.
109
143
 
110
144
  ```ruby
111
145
  ulids = 10000.times.map do
@@ -147,11 +181,11 @@ sample_ulids_by_the_time.take(5) #=>
147
181
  ulids.sort == ulids #=> true
148
182
  ```
149
183
 
150
- 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).
151
185
 
152
186
  ### Filtering IDs with `Time`
153
187
 
154
- `ULID` can be element of the `Range`. If you generated the IDs in monotonic generator, ID based filtering is easy and reliable
188
+ `ULID` can be element of the `Range`. If they were generated with monotonic generator, ID based filtering is easy and reliable.
155
189
 
156
190
  ```ruby
157
191
  include_end = ulid1..ulid2
@@ -187,7 +221,9 @@ time = Time.at(946684800, Rational('123456.789')).utc #=> 2000-01-01 00:00:00.12
187
221
  ULID.floor(time) #=> 2000-01-01 00:00:00.123 UTC
188
222
  ```
189
223
 
190
- ### Scanner for string (e.g. `JSON`)
224
+ ### Tools
225
+
226
+ #### Scanner for string (e.g. `JSON`)
191
227
 
192
228
  For rough operations, `ULID.scan` might be useful.
193
229
 
@@ -230,7 +266,7 @@ ULID.scan(json).to_a
230
266
  ```
231
267
 
232
268
  `ULID#patterns` is a util for text based operations.
233
- The results and spec are not fixed. Should not be used except snippets/console operation
269
+ The results and spec are not fixed. Should not be used except snippets/console operation.
234
270
 
235
271
  ```ruby
236
272
  ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
@@ -241,7 +277,7 @@ ULID.parse('01F4GNBXW1AM2KWW52PVT3ZY9X').patterns
241
277
  }
242
278
  ```
243
279
 
244
- ### Some methods to help manipulations
280
+ #### Get boundary ULIDs
245
281
 
246
282
  `ULID.min` and `ULID.max` return termination values for ULID spec.
247
283
 
@@ -256,10 +292,12 @@ ULID.min(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3V0000000000000000)
256
292
  ULID.max(time) #=> ULID(2000-01-01 00:00:00.123 UTC: 00VHNCZB3VZZZZZZZZZZZZZZZZ)
257
293
  ```
258
294
 
295
+ #### As element in Enumerable
296
+
259
297
  `ULID#next` and `ULID#succ` returns next(successor) ULID.
260
298
  Especially `ULID#succ` makes it possible `Range[ULID]#each`.
261
299
 
262
- NOTE: But basically `Range[ULID]#each` should not be used, incrementing 128 bits IDs are not reasonable operation in most case
300
+ NOTE: However basically `Range[ULID]#each` should not be used. Incrementing 128 bits IDs are not reasonable operation in most cases.
263
301
 
264
302
  ```ruby
265
303
  ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZY').next.to_s #=> "01BX5ZZKBKZZZZZZZZZZZZZZZZ"
@@ -267,7 +305,7 @@ ULID.parse('01BX5ZZKBKZZZZZZZZZZZZZZZZ').next.to_s #=> "01BX5ZZKBM00000000000000
267
305
  ULID.parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').next #=> nil
268
306
  ```
269
307
 
270
- `ULID#pred` returns predecessor ULID
308
+ `ULID#pred` returns predecessor ULID.
271
309
 
272
310
  ```ruby
273
311
  ULID.parse('01BX5ZZKBK0000000000000001').pred.to_s #=> "01BX5ZZKBK0000000000000000"
@@ -275,6 +313,8 @@ ULID.parse('01BX5ZZKBK0000000000000000').pred.to_s #=> "01BX5ZZKBJZZZZZZZZZZZZZZ
275
313
  ULID.parse('00000000000000000000000000').pred #=> nil
276
314
  ```
277
315
 
316
+ #### Test helpers
317
+
278
318
  `ULID.sample` returns random ULIDs.
279
319
 
280
320
  Basically ignores generating time.
@@ -300,61 +340,52 @@ ulid1 = ULID.parse('01F4A5Y1YAQCYAYCTC7GRMJ9AA') #=> ULID(2021-04-27 17:27:22.82
300
340
  ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
301
341
  ulids = ULID.sample(1000, period: ulid1..ulid2)
302
342
  ulids.uniq.size #=> 1000
303
- ulids.take(10)
343
+ ulids.take(5)
304
344
  #=>
305
345
  #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
306
346
  # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
307
347
  # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
308
348
  # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
309
- # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
310
- # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
311
- # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
312
- # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
313
- # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
314
- # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
315
- ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
349
+ # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
350
+ ULID.sample(5, period: ulid1.to_time..ulid2.to_time)
316
351
  #=>
317
352
  # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
318
353
  # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
319
354
  # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
320
355
  # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
321
- # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
322
- # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
323
- # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
324
- # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
325
- # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
326
- # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
356
+ # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
327
357
  ```
328
358
 
329
- ### ULID specification ambiguity around orthographical variants of the format
359
+ #### Variants of format
330
360
 
331
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.
332
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
+
333
368
  >Uses Crockford's base32 for better efficiency and readability (5 bits per character)
334
369
 
335
370
  The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`.
336
371
  And accepts freestyle inserting `Hyphens (-)`.
337
372
  To consider this patterns or not is different in each implementations.
338
373
 
339
- Current parser/validator/matcher aims to cover `subset of Crockford's base32`.
340
- I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
341
-
342
- >Case insensitive
343
-
344
- I can understand it might be considered in actual use-case.
345
- But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ulid/spec/issues/3).
374
+ I have suggested to clarify `subset of Crockford's base32` in [ulid/spec#57](https://github.com/ulid/spec/pull/57).
346
375
 
347
- Be that as it may, this gem provides API for handling the nasty possibilities.
376
+ This gem provides some methods to handle the nasty possibilities.
348
377
 
349
- `ULID.normalize` and `ULID.normalized?`
378
+ `ULID.normalize`, `ULID.normalized?`, `ULID.valid_as_variant_format?` and `ULID.parse_variant_format`
350
379
 
351
380
  ```ruby
352
- ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
353
- ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
354
- 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)
355
386
  ```
356
387
 
357
- ### UUIDv4 converter for migration use-cases
388
+ #### UUIDv4 converter (experimental)
358
389
 
359
390
  `ULID.from_uuidv4` and `ULID#to_uuidv4` is the converter.
360
391
  The imported timestamp is meaningless. So ULID's benefit will lost.
@@ -387,68 +418,19 @@ ULID.min == reversed_min #=> false
387
418
  ULID.max == reversed_max #=> false
388
419
  ```
389
420
 
390
- ## How to migrate from other gems
391
-
392
- As far as I know, major prior arts are below
393
-
394
- ### [ulid gem](https://rubygems.org/gems/ulid) - [rafaelsales/ulid](https://github.com/rafaelsales/ulid)
395
-
396
- It is just providing basic `String` generator only.
397
- So you can replace the code as below
398
-
399
- ```diff
400
- -ULID.generate
401
- +ULID.generate.to_s
402
- ```
403
-
404
- NOTE: In version before `1.3.0`, timestamps might not be correct value.
405
-
406
- 1. [Sort order does not respect millisecond ordering](https://github.com/rafaelsales/ulid/issues/22)
407
- 1. [Fixed in this PR](https://github.com/rafaelsales/ulid/pull/23)
408
- 1. [Released in 1.3.0](https://github.com/rafaelsales/ulid/compare/1.2.0...v1.3.0)
409
-
410
- ### [ulid-ruby gem](https://rubygems.org/gems/ulid-ruby) - [abachman/ulid-ruby](https://github.com/abachman/ulid-ruby)
411
-
412
- It is providing basic generator(except monotonic generator) and parser.
413
- Major methods can be replaced as below.
414
-
415
- ```diff
416
- -ULID.generate
417
- +ULID.generate.to_s
418
- -ULID.at(time)
419
- +ULID.at(time).to_s
420
- -ULID.time(string)
421
- +ULID.parse(string).to_time
422
- -ULID.min_ulid_at(time)
423
- +ULID.min(time).to_s
424
- -ULID.max_ulid_at(time)
425
- +ULID.max(time).to_s
426
- ```
427
-
428
- NOTE: In version before `1.0.2`, timestamps might not be correct value.
429
-
430
- 1. [Parsed time object has more than milliseconds](https://github.com/abachman/ulid-ruby/issues/3)
431
- 1. [Fix to handle timestamp precision in parser](https://github.com/abachman/ulid-ruby/pull/5)
432
- 1. [Fix to handle timestamp precision in generator](https://github.com/abachman/ulid-ruby/pull/4)
433
- 1. [Released in 1.0.2](https://github.com/abachman/ulid-ruby/compare/v1.0.0...v1.0.2)
434
-
435
- ### Compare performance with them
436
-
437
- See [Benchmark](https://github.com/kachick/ruby-ulid/wiki/Benchmark).
421
+ ## Migration from other gems
438
422
 
439
- 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).
440
424
 
441
- ## How to use rbs
425
+ ## RBS
442
426
 
443
- 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).
444
428
 
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).
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).
446
430
 
447
431
  * ![rbs overview](./assets/ulid-rbs-overview.png?raw=true.png)
448
432
  * ![rbs mix](./assets/ulid-rbs-mix.png?raw=true.png)
449
433
  * ![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
434
 
453
435
  ## References
454
436
 
data/lib/ruby-ulid.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
@@ -14,63 +15,62 @@ class ULID
14
15
  # * https://github.com/kachick/ruby-ulid/issues/57
15
16
  # * https://github.com/kachick/ruby-ulid/issues/78
16
17
  module CrockfordBase32
17
- class SetupError < UnexpectedError; end
18
-
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
21
-
22
- n32_char_by_number = {}
23
- n32_chars.each_with_index do |char, index|
24
- n32_char_by_number[index] = char
25
- end
26
- n32_char_by_number.freeze
27
-
28
- crockford_base32_mappings = {
29
- 'J' => 18,
30
- 'K' => 19,
31
- 'M' => 20,
32
- 'N' => 21,
33
- 'P' => 22,
34
- 'Q' => 23,
35
- 'R' => 24,
36
- 'S' => 25,
37
- 'T' => 26,
38
- 'V' => 27,
39
- 'W' => 28,
40
- 'X' => 29,
41
- 'Y' => 30,
42
- 'Z' => 31
18
+ # Excluded I, L, O, U, - from Base32
19
+ base32_to_crockford = {
20
+ '0' => '0',
21
+ '1' => '1',
22
+ '2' => '2',
23
+ '3' => '3',
24
+ '4' => '4',
25
+ '5' => '5',
26
+ '6' => '6',
27
+ '7' => '7',
28
+ '8' => '8',
29
+ '9' => '9',
30
+ 'A' => 'A',
31
+ 'B' => 'B',
32
+ 'C' => 'C',
33
+ 'D' => 'D',
34
+ 'E' => 'E',
35
+ 'F' => 'F',
36
+ 'G' => 'G',
37
+ 'H' => 'H',
38
+ 'I' => 'J',
39
+ 'J' => 'K',
40
+ 'K' => 'M',
41
+ 'L' => 'N',
42
+ 'M' => 'P',
43
+ 'N' => 'Q',
44
+ 'O' => 'R',
45
+ 'P' => 'S',
46
+ 'Q' => 'T',
47
+ 'R' => 'V',
48
+ 'S' => 'W',
49
+ 'T' => 'X',
50
+ 'U' => 'Y',
51
+ 'V' => 'Z'
43
52
  }.freeze
53
+ BASE32_TR_PATTERN = base32_to_crockford.keys.join.freeze
54
+ ENCODING_STRING = CROCKFORD_BASE32_TR_PATTERN = base32_to_crockford.values.freeze.join.freeze
44
55
 
45
- N32_CHAR_BY_CROCKFORD_BASE32_CHAR = CROCKFORD_BASE32_ENCODING_STRING.chars.map(&:freeze).each_with_object({}) do |encoding_char, map|
46
- if n = crockford_base32_mappings[encoding_char]
47
- char_32 = n32_char_by_number.fetch(n)
48
- map[encoding_char] = char_32
49
- end
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
52
-
53
- CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
54
-
55
- CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
56
- N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
57
-
58
- STANDARD_BY_VARIANT = {
56
+ normarized_by_variant = {
59
57
  'L' => '1',
60
58
  'l' => '1',
61
59
  'I' => '1',
62
60
  'i' => '1',
63
61
  'O' => '0',
64
- 'o' => '0',
65
- '-' => ''
62
+ 'o' => '0'
66
63
  }.freeze
67
- VARIANT_PATTERN = /[#{STANDARD_BY_VARIANT.keys.join}]/.freeze
64
+ VARIANT_TR_PATTERN = normarized_by_variant.keys.join.freeze
65
+ NORMALIZED_TR_PATTERN = normarized_by_variant.values.join.freeze
66
+
67
+ # @note Avoid to depend regex as possible. `tr(string, string)` is almost 2x Faster than `gsub(regex, hash)` in Ruby 3.1
68
68
 
69
69
  # @api private
70
70
  # @param [String] string
71
71
  # @return [Integer]
72
72
  def self.decode(string)
73
- n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
73
+ n32encoded = string.upcase.tr(CROCKFORD_BASE32_TR_PATTERN, BASE32_TR_PATTERN)
74
74
  n32encoded.to_i(32)
75
75
  end
76
76
 
@@ -79,14 +79,21 @@ class ULID
79
79
  # @return [String]
80
80
  def self.encode(integer)
81
81
  n32encoded = integer.to_s(32)
82
- n32encoded.upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0')
82
+ from_n32(n32encoded).rjust(ENCODED_LENGTH, '0')
83
83
  end
84
84
 
85
85
  # @api private
86
86
  # @param [String] string
87
87
  # @return [String]
88
88
  def self.normalize(string)
89
- string.gsub(VARIANT_PATTERN, STANDARD_BY_VARIANT)
89
+ string.delete('-').tr(VARIANT_TR_PATTERN, NORMALIZED_TR_PATTERN)
90
+ end
91
+
92
+ # @api private
93
+ # @param [String] n32encoded
94
+ # @return [String]
95
+ def self.from_n32(n32encoded)
96
+ n32encoded.upcase.tr(BASE32_TR_PATTERN, CROCKFORD_BASE32_TR_PATTERN)
90
97
  end
91
98
  end
92
99
  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
@@ -1,13 +1,15 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
7
  class ULID
7
8
  class MonotonicGenerator
9
+ # @note When use https://github.com/ko1/ractor-tvar might realize Ractor based thread safe monotonic generator.
10
+ # However it is a C extention, I'm pending to use it for now.
8
11
  include(MonitorMixin)
9
12
 
10
- # @dynamic prev
11
13
  # @return [ULID, nil]
12
14
  attr_reader(:prev)
13
15
 
@@ -22,7 +24,6 @@ class ULID
22
24
  def inspect
23
25
  "ULID::MonotonicGenerator(prev: #{@prev.inspect})"
24
26
  end
25
- # @dynamic to_s
26
27
  alias_method(:to_s, :inspect)
27
28
 
28
29
  # @param [Time, Integer] moment
@@ -67,6 +68,12 @@ class ULID
67
68
  end
68
69
  end
69
70
 
71
+ # @param [Time, Integer] moment
72
+ # @return [String]
73
+ def encode(moment: ULID.current_milliseconds)
74
+ generate(moment: moment).encode
75
+ end
76
+
70
77
  undef_method(:freeze)
71
78
 
72
79
  # @raise [TypeError] always raises exception and does not freeze self
@@ -0,0 +1,12 @@
1
+ # coding: us-ascii
2
+ # frozen_string_literal: true
3
+
4
+ class ULID
5
+ min = parse('00000000000000000000000000').freeze
6
+ max = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
7
+
8
+ ractor_can_make_shareable_time = RUBY_VERSION >= '3.1'
9
+
10
+ MIN = ractor_can_make_shareable_time ? Ractor.make_shareable(min) : min
11
+ MAX = ractor_can_make_shareable_time ? Ractor.make_shareable(max) : max
12
+ end
data/lib/ulid/uuid.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
data/lib/ulid/version.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: literal
3
4
 
4
5
  class ULID
5
- VERSION = '0.3.0'
6
+ VERSION = '0.6.0'
6
7
  end
data/lib/ulid.rb CHANGED
@@ -1,10 +1,16 @@
1
1
  # coding: us-ascii
2
2
  # frozen_string_literal: true
3
+ # shareable_constant_value: experimental_everything
3
4
 
4
5
  # Copyright (C) 2021 Kenichi Kamiya
5
6
 
6
7
  require('securerandom')
7
8
 
9
+ require_relative('ulid/version')
10
+ require_relative('ulid/errors')
11
+ require_relative('ulid/crockford_base32')
12
+ require_relative('ulid/monotonic_generator')
13
+
8
14
  # @see https://github.com/ulid/spec
9
15
  # @!attribute [r] milliseconds
10
16
  # @return [Integer]
@@ -13,16 +19,6 @@ require('securerandom')
13
19
  class ULID
14
20
  include(Comparable)
15
21
 
16
- class Error < StandardError; end
17
- class OverflowError < Error; end
18
- class ParserError < Error; end
19
- class UnexpectedError < Error; end
20
-
21
- # Excluded I, L, O, U, -.
22
- # This is the encoding patterns.
23
- # The decoding issue is written in ULID::CrockfordBase32
24
- CROCKFORD_BASE32_ENCODING_STRING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'
25
-
26
22
  TIMESTAMP_ENCODED_LENGTH = 10
27
23
  RANDOMNESS_ENCODED_LENGTH = 16
28
24
  ENCODED_LENGTH = 26
@@ -37,13 +33,12 @@ class ULID
37
33
 
38
34
  # @see https://github.com/ulid/spec/pull/57
39
35
  # Currently not used as a constant, but kept as a reference for now.
40
- 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
41
37
 
42
38
  STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET = /\A#{PATTERN_WITH_CROCKFORD_BASE32_SUBSET.source}\z/i.freeze
43
39
 
44
40
  # Optimized for `ULID.scan`, might be changed the definition with gathered `ULID.scan` spec changed.
45
- # This can't contain `\b` for considering UTF-8 (e.g. Japanese), so intentional `false negative` definition.
46
- SCANNING_PATTERN = /[0-7][#{CROCKFORD_BASE32_ENCODING_STRING}]{#{TIMESTAMP_ENCODED_LENGTH - 1}}[#{CROCKFORD_BASE32_ENCODING_STRING}]{#{RANDOMNESS_ENCODED_LENGTH}}/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]
@@ -83,7 +88,7 @@ class ULID
83
88
 
84
89
  RANDOM_INTEGER_GENERATOR = -> {
85
90
  SecureRandom.random_number(MAX_INTEGER)
86
- }
91
+ }.freeze
87
92
 
88
93
  # @param [Range<Time>, Range<nil>, Range[ULID], nil] period
89
94
  # @overload sample(number, period: nil)
@@ -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
@@ -231,9 +241,9 @@ class ULID
231
241
  # @api private
232
242
  # @param [Time] time
233
243
  # @return [Integer]
234
- private_class_method(def self.milliseconds_from_time(time)
244
+ private_class_method def self.milliseconds_from_time(time)
235
245
  (time.to_r * 1000).to_i
236
- end)
246
+ end
237
247
 
238
248
  # @api private
239
249
  # @param [Time, Integer] moment
@@ -250,9 +260,20 @@ class ULID
250
260
  end
251
261
 
252
262
  # @return [Integer]
253
- private_class_method(def self.reasonable_entropy
263
+ private_class_method def self.reasonable_entropy
254
264
  SecureRandom.random_number(MAX_ENTROPY)
255
- end)
265
+ end
266
+
267
+ private_class_method def self.encode_n32(milliseconds:, entropy:)
268
+ raise(ArgumentError, 'milliseconds and entropy should be an `Integer`') unless Integer === milliseconds && Integer === entropy
269
+ raise(OverflowError, "timestamp overflow: given #{milliseconds}, max: #{MAX_MILLISECONDS}") unless milliseconds <= MAX_MILLISECONDS
270
+ raise(OverflowError, "entropy overflow: given #{entropy}, max: #{MAX_ENTROPY}") unless entropy <= MAX_ENTROPY
271
+ raise(ArgumentError, 'milliseconds and entropy should not be negative') if milliseconds.negative? || entropy.negative?
272
+
273
+ n32encoded_timestamp = milliseconds.to_s(32).rjust(TIMESTAMP_ENCODED_LENGTH, '0')
274
+ n32encoded_randomness = entropy.to_s(32).rjust(RANDOMNESS_ENCODED_LENGTH, '0')
275
+ "#{n32encoded_timestamp}#{n32encoded_randomness}"
276
+ end
256
277
 
257
278
  # @param [String, #to_str] string
258
279
  # @return [ULID]
@@ -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
281
331
  end
282
332
 
333
+ # @param [String, #to_str] string
283
334
  # @return [Boolean]
284
- def self.normalized?(object)
285
- normalized = normalize(object)
335
+ def self.normalized?(string)
336
+ normalized = normalize(string)
286
337
  rescue Exception
287
338
  false
288
339
  else
289
- normalized == object
340
+ normalized == string
290
341
  end
291
342
 
343
+ # @param [String, #to_str] string
292
344
  # @return [Boolean]
293
- def self.valid?(object)
294
- string = String.try_convert(object)
345
+ def self.valid_as_variant_format?(string)
346
+ parse_variant_format(string)
347
+ rescue Exception
348
+ false
349
+ else
350
+ true
351
+ end
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
+ #
358
+ # @return [Boolean]
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
 
@@ -316,7 +384,7 @@ class ULID
316
384
 
317
385
  # @param [BasicObject] object
318
386
  # @return [String]
319
- private_class_method(def self.safe_get_class_name(object)
387
+ private_class_method def self.safe_get_class_name(object)
320
388
  fallback = 'UnknownObject'
321
389
 
322
390
  # This class getter implementation used https://github.com/rspec/rspec-support/blob/4ad8392d0787a66f9c351d9cf6c7618e18b3d0f2/lib/rspec/support.rb#L83-L89 as a reference, thank you!
@@ -325,7 +393,7 @@ class ULID
325
393
  begin
326
394
  object.class
327
395
  rescue NoMethodError
328
- # steep can't correctly handle singeton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
396
+ # steep can't correctly handle singleton class assign. See https://github.com/soutaro/steep/pull/586 for further detail
329
397
  # So this annotation is hack for the type infer.
330
398
  # @type var object: BasicObject
331
399
  # @type var singleton_class: untyped
@@ -341,7 +409,7 @@ class ULID
341
409
  else
342
410
  name || fallback
343
411
  end
344
- end)
412
+ end
345
413
 
346
414
  # @api private
347
415
  # @param [Integer] milliseconds
@@ -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,30 +459,40 @@ 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
 
478
+ # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
479
+ # Do not consider integer, octets and partial strings, then returns `false`.
480
+ #
412
481
  # @return [Boolean]
482
+ # @see .normalize
483
+ # @see .floor
413
484
  def ===(other)
414
485
  case other
415
486
  when ULID
416
487
  @integer == other.to_i
417
488
  when String
418
- to_s == other.upcase
489
+ begin
490
+ to_i == ULID.parse_variant_format(other).to_i
491
+ rescue Exception
492
+ false
493
+ end
494
+ when Time
495
+ to_time == ULID.floor(other)
419
496
  else
420
497
  false
421
498
  end
@@ -447,12 +524,12 @@ class ULID
447
524
 
448
525
  # @return [String]
449
526
  def timestamp
450
- @timestamp ||= (to_s.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
527
+ @timestamp ||= (@encoded.slice(0, TIMESTAMP_ENCODED_LENGTH).freeze || raise(UnexpectedError))
451
528
  end
452
529
 
453
530
  # @return [String]
454
531
  def randomness
455
- @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))
456
533
  end
457
534
 
458
535
  # @note Providing for rough operations. The keys and values is not fixed.
@@ -478,7 +555,6 @@ class ULID
478
555
  ULID.from_integer(succ_int)
479
556
  end
480
557
  end
481
- # @dynamic next
482
558
  alias_method(:next, :succ)
483
559
 
484
560
  # @return [ULID, nil] when called on ULID as `00000000000000000000000000`, returns `nil` instead of ULID
@@ -513,7 +589,7 @@ class ULID
513
589
  # @return [void]
514
590
  def marshal_load(integer)
515
591
  unmarshaled = ULID.from_integer(integer)
516
- 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)
517
593
  end
518
594
 
519
595
  # @return [self]
@@ -543,13 +619,9 @@ class ULID
543
619
  end
544
620
  end
545
621
 
546
- require_relative('ulid/version')
547
- require_relative('ulid/crockford_base32')
548
- require_relative('ulid/monotonic_generator')
622
+ require_relative('ulid/ractor_unshareable_constants')
549
623
 
550
624
  class ULID
551
- MIN = parse('00000000000000000000000000').freeze
552
- MAX = parse('7ZZZZZZZZZZZZZZZZZZZZZZZZZ').freeze
553
-
554
- private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR, :CROCKFORD_BASE32_ENCODING_STRING)
625
+ # Do not write as `ULID.private_constant` for avoiding YARD warnings `[warn]: in YARD::Handlers::Ruby::PrivateConstantHandler: Undocumentable private constants:`
626
+ private_constant(:TIME_FORMAT_IN_INSPECT, :MIN, :MAX, :RANDOM_INTEGER_GENERATOR)
555
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_BASE32_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'
@@ -292,41 +332,30 @@ class ULID < Object
292
332
  # ulid2 = ULID.parse('01F4PTVCSN9ZPFKYTY2DDJVRK4') #=> ULID(2021-05-02 15:23:48.917 UTC: 01F4PTVCSN9ZPFKYTY2DDJVRK4)
293
333
  # ulids = ULID.sample(1000, period: ulid1..ulid2)
294
334
  # ulids.uniq.size #=> 1000
295
- # ulids.take(10)
335
+ # ulids.take(5)
296
336
  # #=>
297
337
  # #[ULID(2021-05-02 06:57:19.954 UTC: 01F4NXW02JNB8H0J0TK48JD39X),
298
338
  # # ULID(2021-05-02 07:06:07.458 UTC: 01F4NYC372GVP7NS0YAYQGT4VZ),
299
339
  # # ULID(2021-05-01 06:16:35.791 UTC: 01F4K94P6F6P68K0H64WRDSFKW),
300
340
  # # ULID(2021-04-27 22:17:37.844 UTC: 01F4APHGSMFJZQTGXKZBFFBPJP),
301
- # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3),
302
- # # ULID(2021-04-30 07:18:54.307 UTC: 01F4GTA2332AS2VPHC4FMKC7R5),
303
- # # ULID(2021-05-02 12:26:03.480 UTC: 01F4PGNXARG554Y3HYVBDW4T9S),
304
- # # ULID(2021-04-29 09:52:15.107 UTC: 01F4EGP483ZX2747FQPWQNPPMW),
305
- # # ULID(2021-04-29 03:18:24.152 UTC: 01F4DT4Z4RA0QV8WFQGRAG63EH),
306
- # # ULID(2021-05-02 13:27:16.394 UTC: 01F4PM605ABF5SDVMEHBH8JJ9R)]
341
+ # # ULID(2021-04-28 20:17:55.357 UTC: 01F4D231MXQJXAR8G2JZHEJNH3)]
307
342
  # ULID.sample(10, period: ulid1.to_time..ulid2.to_time)
308
343
  # #=>
309
344
  # # [ULID(2021-04-29 06:44:41.513 UTC: 01F4E5YPD9XQ3MYXWK8ZJKY8SW),
310
345
  # # ULID(2021-05-01 00:35:06.629 UTC: 01F4JNKD85SVK1EAEYSJGF53A2),
311
346
  # # ULID(2021-05-02 12:45:28.408 UTC: 01F4PHSEYRG9BWBEWMRW1XE6WW),
312
347
  # # ULID(2021-05-01 03:06:09.130 UTC: 01F4JY7ZBABCBMX16XH2Q4JW4W),
313
- # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G),
314
- # # ULID(2021-04-29 17:14:14.116 UTC: 01F4F9ZDQ449BE8BBZFEHYQWG2),
315
- # # ULID(2021-04-30 16:18:08.205 UTC: 01F4HS5DPD1HWDVJNJ6YKJXKSK),
316
- # # ULID(2021-04-30 10:31:33.602 UTC: 01F4H5ATF2A1CSQF0XV5NKZ288),
317
- # # ULID(2021-04-28 16:49:06.484 UTC: 01F4CP4PDM214Q6H3KJP7DYJRR),
318
- # # ULID(2021-04-28 15:05:06.808 UTC: 01F4CG68ZRST94T056KRZ5K9S4)]
348
+ # # ULID(2021-04-29 21:38:58.109 UTC: 01F4FS45DX4049JEQK4W6TER6G)]
319
349
  # ```
320
350
  def self.sample: (?period: period) -> ULID
321
351
  | (Integer number, ?period: period?) -> Array[ULID]
322
- def self.valid?: (untyped) -> bool
323
352
 
324
353
  # Returns normalized string
325
354
  #
326
355
  # ```ruby
356
+ # ULID.normalize('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD"
327
357
  # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
328
- # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
329
- # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true
358
+ # ULID.normalize('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> ULID::ParserError
330
359
  # ```
331
360
  #
332
361
  # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
@@ -335,13 +364,40 @@ class ULID < Object
335
364
  # Returns `true` if it is normalized string
336
365
  #
337
366
  # ```ruby
338
- # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV"
339
- # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false
340
- # 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)
341
371
  # ```
342
372
  #
343
373
  # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
344
- def self.normalized?: (untyped) -> bool
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
383
+ # ```
384
+ #
385
+ # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3)
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
345
401
 
346
402
  # Returns parsed ULIDs from given String for rough operations.
347
403
  #
@@ -399,9 +455,12 @@ class ULID < Object
399
455
  # ```ruby
400
456
  # ulid = ULID.generate
401
457
  # #=> ULID(2021-04-27 17:27:22.826 UTC: 01F4A5Y1YAQCYAYCTC7GRMJ9AA)
402
- # ulid.to_s #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
458
+ # ulid.encode #=> "01F4A5Y1YAQCYAYCTC7GRMJ9AA"
459
+ # ulid.encode.frozen? #=> true
460
+ # ulid.encode.equal?(ulid.to_s) #=> true
403
461
  # ```
404
- def to_s: -> String
462
+ def encode: -> String
463
+ alias to_s encode
405
464
 
406
465
  # ```ruby
407
466
  # ulid = ULID.generate
@@ -421,7 +480,12 @@ class ULID < Object
421
480
  # #=> true
422
481
  # ```
423
482
  #
424
- # 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
425
489
  #
426
490
  def <=>: (ULID other) -> Integer
427
491
  | (untyped other) -> nil
@@ -436,9 +500,28 @@ class ULID < Object
436
500
  # ULID.parse('4NNB20D9C1ME2NGMTX51ERZJX0') == ULID.parse('4nnb20d9c1me2ngmtx51erzjx0')
437
501
  # #=> true
438
502
  # ```
439
- def eql?: (untyped other) -> bool
503
+ def eql?: (ULID other) -> bool
504
+ | (untyped other) -> false
440
505
  alias == eql?
441
- def ===: (untyped other) -> bool
506
+
507
+ # Return `true` for same value of ULID, variant formats of strings, same Time in ULID precision(msec).
508
+ # Do not consider integer, octets and partial strings, then returns `false`.
509
+ #
510
+ # ```ruby
511
+ # ulid = ULID.parse('01G6Z7Q4RSH97E6QHAC7VK19G2')
512
+ # ulid === ULID.parse(ulid.to_s)
513
+ # #=> true
514
+ # ulid === ulid.to_s.downcase
515
+ # #=> true
516
+ # ulid === ulid.to_time
517
+ # #=> true
518
+ # ulid === ulid.to_i
519
+ # #=> false
520
+ # ulid === ulid.next
521
+ # #=> false
522
+ # ```
523
+ def ===: (ULID | String | Time other) -> bool
524
+ | (untyped other) -> false
442
525
 
443
526
  # ```ruby
444
527
  # ulid = ULID.generate
@@ -526,9 +609,6 @@ class ULID < Object
526
609
 
527
610
  private
528
611
 
529
- # A private API. Should not be used in your code.
530
- def self.new: (milliseconds: Integer, entropy: Integer, integer: Integer) -> ULID
531
-
532
612
  # A private API. Should not be used in your code.
533
613
  def self.reasonable_entropy: -> Integer
534
614
 
@@ -539,7 +619,7 @@ class ULID < Object
539
619
  def self.safe_get_class_name: (untyped object) -> String
540
620
 
541
621
  # A private API. Should not be used in your code.
542
- def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer) -> void
622
+ def initialize: (milliseconds: Integer, entropy: Integer, integer: Integer, encoded: String) -> void
543
623
 
544
624
  # A private API. Should not be used in your code.
545
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.3.0
4
+ version: 0.6.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-06-22 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
- (ruby/rbs signatures 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,7 +23,9 @@ 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
28
+ - lib/ulid/ractor_unshareable_constants.rb
27
29
  - lib/ulid/uuid.rb
28
30
  - lib/ulid/version.rb
29
31
  - sig/ulid.rbs