ruby-ulid 0.0.14 → 0.0.19
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 +4 -4
- data/README.md +219 -33
- data/lib/ulid.rb +210 -155
- data/lib/ulid/crockford_base32.rb +68 -0
- data/lib/ulid/monotonic_generator.rb +9 -7
- data/lib/ulid/uuid.rb +38 -0
- data/lib/ulid/version.rb +1 -1
- data/sig/ulid.rbs +36 -24
- metadata +9 -28
- data/Steepfile +0 -7
@@ -0,0 +1,68 @@
|
|
1
|
+
# coding: us-ascii
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# Copyright (C) 2021 Kenichi Kamiya
|
4
|
+
|
5
|
+
class ULID
|
6
|
+
# Currently supporting only for `subset` for actual use-case`
|
7
|
+
# Original decoding spec allows other characters.
|
8
|
+
# But I think ULID should allow `subset` of Crockford's Base32.
|
9
|
+
# See below
|
10
|
+
# * https://github.com/ulid/spec/pull/57
|
11
|
+
# * https://github.com/kachick/ruby-ulid/issues/57
|
12
|
+
# * https://github.com/kachick/ruby-ulid/issues/78
|
13
|
+
module CrockfordBase32
|
14
|
+
class SetupError < ScriptError; end
|
15
|
+
|
16
|
+
n32_chars = [*'0'..'9', *'A'..'V'].map(&:freeze).freeze
|
17
|
+
raise SetupError, 'obvious bug exists in the mapping algorithm' unless n32_chars.size == 32
|
18
|
+
|
19
|
+
n32_char_by_number = {}
|
20
|
+
n32_chars.each_with_index do |char, index|
|
21
|
+
n32_char_by_number[index] = char
|
22
|
+
end
|
23
|
+
n32_char_by_number.freeze
|
24
|
+
|
25
|
+
crockford_base32_mappings = {
|
26
|
+
'J' => 18,
|
27
|
+
'K' => 19,
|
28
|
+
'M' => 20,
|
29
|
+
'N' => 21,
|
30
|
+
'P' => 22,
|
31
|
+
'Q' => 23,
|
32
|
+
'R' => 24,
|
33
|
+
'S' => 25,
|
34
|
+
'T' => 26,
|
35
|
+
'V' => 27,
|
36
|
+
'W' => 28,
|
37
|
+
'X' => 29,
|
38
|
+
'Y' => 30,
|
39
|
+
'Z' => 31
|
40
|
+
}.freeze
|
41
|
+
|
42
|
+
N32_CHAR_BY_CROCKFORD_BASE32_CHAR = CROCKFORD_BASE32_ENCODING_STRING.chars.map(&:freeze).each_with_object({}) do |encoding_char, map|
|
43
|
+
if n = crockford_base32_mappings[encoding_char]
|
44
|
+
char_32 = n32_char_by_number.fetch(n)
|
45
|
+
map[encoding_char] = char_32
|
46
|
+
end
|
47
|
+
end.freeze
|
48
|
+
raise SetupError, 'obvious bug exists in the mapping algorithm' unless N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys == crockford_base32_mappings.keys
|
49
|
+
CROCKFORD_BASE32_CHAR_PATTERN = /[#{N32_CHAR_BY_CROCKFORD_BASE32_CHAR.keys.join}]/.freeze
|
50
|
+
|
51
|
+
CROCKFORD_BASE32_CHAR_BY_N32_CHAR = N32_CHAR_BY_CROCKFORD_BASE32_CHAR.invert.freeze
|
52
|
+
N32_CHAR_PATTERN = /[#{CROCKFORD_BASE32_CHAR_BY_N32_CHAR.keys.join}]/.freeze
|
53
|
+
|
54
|
+
# @param [String] string
|
55
|
+
# @return [Integer]
|
56
|
+
def self.decode(string)
|
57
|
+
n32encoded = string.upcase.gsub(CROCKFORD_BASE32_CHAR_PATTERN, N32_CHAR_BY_CROCKFORD_BASE32_CHAR)
|
58
|
+
n32encoded.to_i(32)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [Integer] integer
|
62
|
+
# @return [String]
|
63
|
+
def self.encode(integer)
|
64
|
+
n32encoded = integer.to_s(32)
|
65
|
+
n32encoded.upcase.gsub(N32_CHAR_PATTERN, CROCKFORD_BASE32_CHAR_BY_N32_CHAR).rjust(ENCODED_LENGTH, '0')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -8,6 +8,7 @@ class ULID
|
|
8
8
|
attr_accessor :latest_milliseconds, :latest_entropy
|
9
9
|
|
10
10
|
def initialize
|
11
|
+
@mutex = Thread::Mutex.new
|
11
12
|
reset
|
12
13
|
end
|
13
14
|
|
@@ -19,14 +20,15 @@ class ULID
|
|
19
20
|
milliseconds = ULID.milliseconds_from_moment(moment)
|
20
21
|
raise ArgumentError, "milliseconds should not be negative: given: #{milliseconds}" if milliseconds.negative?
|
21
22
|
|
22
|
-
|
23
|
-
@latest_milliseconds
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
@mutex.synchronize do
|
24
|
+
if @latest_milliseconds < milliseconds
|
25
|
+
@latest_milliseconds = milliseconds
|
26
|
+
@latest_entropy = ULID.reasonable_entropy
|
27
|
+
else
|
28
|
+
@latest_entropy += 1
|
29
|
+
end
|
30
|
+
ULID.from_monotonic_generator(self)
|
27
31
|
end
|
28
|
-
|
29
|
-
ULID.new milliseconds: @latest_milliseconds, entropy: @latest_entropy
|
30
32
|
end
|
31
33
|
|
32
34
|
# @api private
|
data/lib/ulid/uuid.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: us-ascii
|
2
|
+
# frozen_string_literal: true
|
3
|
+
# Copyright (C) 2021 Kenichi Kamiya
|
4
|
+
|
5
|
+
# Extracted features around UUID from some reasons
|
6
|
+
# ref:
|
7
|
+
# * https://github.com/kachick/ruby-ulid/issues/105
|
8
|
+
# * https://github.com/kachick/ruby-ulid/issues/76
|
9
|
+
class ULID
|
10
|
+
# Imported from https://stackoverflow.com/a/38191104/1212807, thank you!
|
11
|
+
UUIDV4_PATTERN = /\A[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}\z/i.freeze
|
12
|
+
private_constant :UUIDV4_PATTERN
|
13
|
+
|
14
|
+
# @param [String, #to_str] uuid
|
15
|
+
# @return [ULID]
|
16
|
+
# @raise [ParserError] if the given format is not correct for UUIDv4 specs
|
17
|
+
def self.from_uuidv4(uuid)
|
18
|
+
uuid = String.try_convert(uuid)
|
19
|
+
raise ArgumentError, 'ULID.from_uuidv4 takes only strings' unless uuid
|
20
|
+
|
21
|
+
prefix_trimmed = uuid.sub(/\Aurn:uuid:/, '')
|
22
|
+
unless UUIDV4_PATTERN.match?(prefix_trimmed)
|
23
|
+
raise ParserError, "given `#{uuid}` does not match to `#{UUIDV4_PATTERN.inspect}`"
|
24
|
+
end
|
25
|
+
|
26
|
+
normalized = prefix_trimmed.gsub(/[^0-9A-Fa-f]/, '')
|
27
|
+
from_integer(normalized.to_i(16))
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [String]
|
31
|
+
def to_uuidv4
|
32
|
+
# This code referenced https://github.com/ruby/ruby/blob/121fa24a3451b45c41ac0a661b64e9fc8600e589/lib/securerandom.rb#L221-L241
|
33
|
+
array = octets.pack('C*').unpack('NnnnnN')
|
34
|
+
array[2] = (array[2] & 0x0fff) | 0x4000
|
35
|
+
array[3] = (array[3] & 0x3fff) | 0x8000
|
36
|
+
('%08x-%04x-%04x-%04x-%04x%08x' % array).freeze
|
37
|
+
end
|
38
|
+
end
|
data/lib/ulid/version.rb
CHANGED
data/sig/ulid.rbs
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# Classes
|
2
2
|
class ULID
|
3
3
|
VERSION: String
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
CROCKFORD_BASE32_ENCODING_STRING: String
|
5
|
+
TIMESTAMP_ENCODED_LENGTH: 10
|
6
|
+
RANDOMNESS_ENCODED_LENGTH: 16
|
7
|
+
ENCODED_LENGTH: 26
|
8
8
|
TIMESTAMP_OCTETS_LENGTH: 6
|
9
9
|
RANDOMNESS_OCTETS_LENGTH: 10
|
10
10
|
OCTETS_LENGTH: 16
|
@@ -12,12 +12,16 @@ class ULID
|
|
12
12
|
MAX_ENTROPY: 1208925819614629174706175
|
13
13
|
MAX_INTEGER: 340282366920938463463374607431768211455
|
14
14
|
TIME_FORMAT_IN_INSPECT: '%Y-%m-%d %H:%M:%S.%3N %Z'
|
15
|
-
|
16
|
-
|
15
|
+
RANDOM_INTEGER_GENERATOR: ^() -> Integer
|
16
|
+
PATTERN_WITH_CROCKFORD_BASE32_SUBSET: Regexp
|
17
|
+
STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET: Regexp
|
18
|
+
SCANNING_PATTERN: Regexp
|
17
19
|
UUIDV4_PATTERN: Regexp
|
18
|
-
|
20
|
+
MIN: ULID
|
21
|
+
MAX: ULID
|
19
22
|
include Comparable
|
20
23
|
|
24
|
+
# The `moment` is a `Time` or `Intger of the milliseconds`
|
21
25
|
type moment = Time | Integer
|
22
26
|
|
23
27
|
class Error < StandardError
|
@@ -29,6 +33,19 @@ class ULID
|
|
29
33
|
class ParserError < Error
|
30
34
|
end
|
31
35
|
|
36
|
+
module CrockfordBase32
|
37
|
+
class SetupError < ScriptError
|
38
|
+
end
|
39
|
+
|
40
|
+
N32_CHAR_BY_CROCKFORD_BASE32_CHAR: Hash[String, String]
|
41
|
+
CROCKFORD_BASE32_CHAR_PATTERN: Regexp
|
42
|
+
CROCKFORD_BASE32_CHAR_BY_N32_CHAR: Hash[String, String]
|
43
|
+
N32_CHAR_PATTERN: Regexp
|
44
|
+
|
45
|
+
def self.encode: (Integer integer) -> String
|
46
|
+
def self.decode: (String string) -> Integer
|
47
|
+
end
|
48
|
+
|
32
49
|
class MonotonicGenerator
|
33
50
|
attr_accessor latest_milliseconds: Integer
|
34
51
|
attr_accessor latest_entropy: Integer
|
@@ -41,29 +58,22 @@ class ULID
|
|
41
58
|
type octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
42
59
|
type timestamp_octets = [Integer, Integer, Integer, Integer, Integer, Integer]
|
43
60
|
type randomness_octets = [Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer, Integer]
|
61
|
+
type period = Range[Time] | Range[nil] | Range[ULID]
|
44
62
|
|
45
63
|
@milliseconds: Integer
|
46
64
|
@entropy: Integer
|
47
65
|
@string: String?
|
48
66
|
@integer: Integer?
|
49
|
-
@octets: octets?
|
50
|
-
@timestamp_octets: timestamp_octets?
|
51
|
-
@randomness_octets: randomness_octets?
|
52
67
|
@timestamp: String?
|
53
68
|
@randomness: String?
|
54
69
|
@inspect: String?
|
55
70
|
@time: Time?
|
56
|
-
@next: ULID?
|
57
|
-
@pattern: Regexp?
|
58
|
-
@strict_pattern: Regexp?
|
59
|
-
@matchdata: MatchData?
|
60
71
|
|
61
72
|
def self.generate: (?moment: moment, ?entropy: Integer) -> ULID
|
62
|
-
def self.
|
73
|
+
def self.at: (Time time) -> ULID
|
63
74
|
def self.current_milliseconds: -> Integer
|
64
|
-
def self.milliseconds_from_time: (Time time) -> Integer
|
65
75
|
def self.milliseconds_from_moment: (moment moment) -> Integer
|
66
|
-
def self.range: (
|
76
|
+
def self.range: (period period) -> Range[ULID]
|
67
77
|
def self.floor: (Time time) -> Time
|
68
78
|
def self.reasonable_entropy: -> Integer
|
69
79
|
def self.parse: (String string) -> ULID
|
@@ -71,19 +81,20 @@ class ULID
|
|
71
81
|
def self.from_integer: (Integer integer) -> ULID
|
72
82
|
def self.min: (?moment: moment) -> ULID
|
73
83
|
def self.max: (?moment: moment) -> ULID
|
84
|
+
def self.sample: (?period: period) -> ULID
|
85
|
+
| (Integer number, ?period: period) -> Array[ULID]
|
74
86
|
def self.valid?: (untyped string) -> bool
|
75
87
|
def self.scan: (String string) -> Enumerator[ULID, singleton(ULID)]
|
76
88
|
| (String string) { (ULID ulid) -> void } -> singleton(ULID)
|
77
|
-
def self.
|
78
|
-
def self.inverse_of_digits: (Array[Integer] reversed_digits) -> Integer
|
89
|
+
def self.from_monotonic_generator: (MonotonicGenerator generator) -> ULID
|
79
90
|
attr_reader milliseconds: Integer
|
80
91
|
attr_reader entropy: Integer
|
81
|
-
def initialize: (milliseconds: Integer, entropy: Integer) -> void
|
92
|
+
def initialize: (milliseconds: Integer, entropy: Integer, ?integer: Integer) -> void
|
82
93
|
def to_s: -> String
|
83
94
|
def to_i: -> Integer
|
84
95
|
alias hash to_i
|
85
96
|
def <=>: (ULID other) -> Integer
|
86
|
-
| (untyped other) ->
|
97
|
+
| (untyped other) -> nil
|
87
98
|
def inspect: -> String
|
88
99
|
def eql?: (untyped other) -> bool
|
89
100
|
alias == eql?
|
@@ -91,16 +102,17 @@ class ULID
|
|
91
102
|
def to_time: -> Time
|
92
103
|
def timestamp: -> String
|
93
104
|
def randomness: -> String
|
94
|
-
def
|
95
|
-
def strict_pattern: -> Regexp
|
105
|
+
def patterns: -> Hash[Symbol, Regexp | String]
|
96
106
|
def octets: -> octets
|
97
107
|
def timestamp_octets: -> timestamp_octets
|
98
108
|
def randomness_octets: -> randomness_octets
|
109
|
+
def to_uuidv4: -> String
|
99
110
|
def next: -> ULID?
|
100
111
|
alias succ next
|
101
112
|
def pred: -> ULID?
|
102
113
|
def freeze: -> self
|
103
114
|
|
104
115
|
private
|
105
|
-
def
|
116
|
+
def self.milliseconds_from_time: (Time time) -> Integer
|
117
|
+
def cache_all_instance_variables: -> void
|
106
118
|
end
|
metadata
CHANGED
@@ -1,35 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-ulid
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.19
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kenichi Kamiya
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-05-
|
11
|
+
date: 2021-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: integer-base
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - ">="
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 0.1.2
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: 0.2.0
|
23
|
-
type: :runtime
|
24
|
-
prerelease: false
|
25
|
-
version_requirements: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - ">="
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 0.1.2
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: 0.2.0
|
33
13
|
- !ruby/object:Gem::Dependency
|
34
14
|
name: rbs
|
35
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,10 +64,10 @@ dependencies:
|
|
84
64
|
- - "<"
|
85
65
|
- !ruby/object:Gem::Version
|
86
66
|
version: '2'
|
87
|
-
description:
|
88
|
-
|
89
|
-
|
90
|
-
|
67
|
+
description: |2
|
68
|
+
The ULID(Universally Unique Lexicographically Sortable Identifier) has useful specs for applications (e.g. `Database key`), especially possess all `uniqueness`, `randomness`, `extractable timestamps` and `sortable` features.
|
69
|
+
This gem aims to provide the generator, monotonic generator, parser and handy manipulation features around the ULID.
|
70
|
+
Also providing `ruby/rbs` signature files.
|
91
71
|
email:
|
92
72
|
- kachick1+ruby@gmail.com
|
93
73
|
executables: []
|
@@ -96,9 +76,10 @@ extra_rdoc_files: []
|
|
96
76
|
files:
|
97
77
|
- LICENSE
|
98
78
|
- README.md
|
99
|
-
- Steepfile
|
100
79
|
- lib/ulid.rb
|
80
|
+
- lib/ulid/crockford_base32.rb
|
101
81
|
- lib/ulid/monotonic_generator.rb
|
82
|
+
- lib/ulid/uuid.rb
|
102
83
|
- lib/ulid/version.rb
|
103
84
|
- sig/ulid.rbs
|
104
85
|
homepage: https://github.com/kachick/ruby-ulid
|
@@ -123,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
104
|
- !ruby/object:Gem::Version
|
124
105
|
version: '0'
|
125
106
|
requirements: []
|
126
|
-
rubygems_version: 3.
|
107
|
+
rubygems_version: 3.2.15
|
127
108
|
signing_key:
|
128
109
|
specification_version: 4
|
129
110
|
summary: A handy ULID library
|