ruby-ulid 0.0.14 → 0.0.19

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- if @latest_milliseconds < milliseconds
23
- @latest_milliseconds = milliseconds
24
- @latest_entropy = ULID.reasonable_entropy
25
- else
26
- @latest_entropy += 1
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
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class ULID
5
- VERSION = '0.0.14'
5
+ VERSION = '0.0.19'
6
6
  end
data/sig/ulid.rbs CHANGED
@@ -1,10 +1,10 @@
1
1
  # Classes
2
2
  class ULID
3
3
  VERSION: String
4
- ENCODING_CHARS: Array[String]
5
- TIMESTAMP_PART_LENGTH: 10
6
- RANDOMNESS_PART_LENGTH: 16
7
- ENCODED_ID_LENGTH: 26
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
- PATTERN: Regexp
16
- STRICT_PATTERN: Regexp
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
- MONOTONIC_GENERATOR: MonotonicGenerator
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.monotonic_generate: -> ULID
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: (Range[Time] time_range) -> Range[ULID]
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.octets_from_integer: (Integer integer, length: Integer) -> Array[Integer]
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) -> Integer?
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 pattern: -> Regexp
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 matchdata: -> MatchData
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.14
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-03 00:00:00.000000000 Z
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: " ULID(Universally Unique Lexicographically Sortable Identifier) has
88
- useful specs for applications (e.g. `Database key`). \n This gem aims to provide
89
- the generator, monotonic generator, parser and handy manipulation features around
90
- the ULID.\n Also providing `rbs` signature files.\n"
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.1.4
107
+ rubygems_version: 3.2.15
127
108
  signing_key:
128
109
  specification_version: 4
129
110
  summary: A handy ULID library
data/Steepfile DELETED
@@ -1,7 +0,0 @@
1
- target :lib do
2
- signature 'sig'
3
-
4
- check 'lib'
5
-
6
- library 'securerandom'
7
- end