lazy-uuid 0.5.0
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 +7 -0
- data/.codeclimate.yml +18 -0
- data/.gitignore +135 -0
- data/.rspec +3 -0
- data/.rubocop.yml +1156 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.md +17 -0
- data/README.md +100 -0
- data/Rakefile +28 -0
- data/bin/console +14 -0
- data/bin/generate-uuid +7 -0
- data/bin/setup +7 -0
- data/lazy-uuid.gemspec +32 -0
- data/lib/lazy-uuid.rb +6 -0
- data/lib/lazy-uuid/uuid.rb +251 -0
- data/lib/lazy-uuid/version.rb +3 -0
- data/spec/factories/uuid_factory.rb +43 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/uuid_spec.rb +457 -0
- metadata +183 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
=====================
|
3
|
+
|
4
|
+
*Copyright (c) 2016*
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
7
|
+
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
8
|
+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
9
|
+
persons to whom the Software is furnished to do so, subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
14
|
+
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
15
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
16
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
17
|
+
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
Lazy UUID
|
2
|
+
=========
|
3
|
+
|
4
|
+
[](https://travis-ci.org/bluepixelmike/lazy-uuid)
|
5
|
+
[](https://codeclimate.com/github/bluepixelmike/lazy-uuid)
|
6
|
+
[](https://codeclimate.com/github/bluepixelmike/lazy-uuid/coverage)
|
7
|
+
[](https://codeclimate.com/github/bluepixelmike/lazy-uuid)
|
8
|
+
[](http://www.rubydoc.info/github/bluepixelmike/lazy-uuid/master)
|
9
|
+
|
10
|
+
Small gem for creating and using UUIDs (universally unique identifier).
|
11
|
+
It has no external dependencies.
|
12
|
+
|
13
|
+
Installation
|
14
|
+
------------
|
15
|
+
|
16
|
+
Add this line to your application's Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem 'lazy-uuid'
|
20
|
+
```
|
21
|
+
|
22
|
+
Or to your gemspec:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
spec.add_dependency 'lazy-uuid'
|
26
|
+
```
|
27
|
+
|
28
|
+
And then execute:
|
29
|
+
|
30
|
+
$ bundle
|
31
|
+
|
32
|
+
Or install it yourself as:
|
33
|
+
|
34
|
+
$ gem install lazy-uuid
|
35
|
+
|
36
|
+
Usage
|
37
|
+
-----
|
38
|
+
|
39
|
+
### Script Usage
|
40
|
+
|
41
|
+
Include the `Uuid` class by doing:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
require 'lazy-uuid'
|
45
|
+
```
|
46
|
+
|
47
|
+
To generate a new UUID:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
uuid = Uuid.generate
|
51
|
+
```
|
52
|
+
|
53
|
+
To create a new UUID from a value:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
value = "\xde\x30\x5d\x54\x75\xb4\x43\x1b\xad\xb2\xeb\x6b\x9e\x54\x60\x14"
|
57
|
+
uuid = Uuid.new(value)
|
58
|
+
```
|
59
|
+
|
60
|
+
To parse an existing UUID string:
|
61
|
+
```ruby
|
62
|
+
str = 'de305d54-75b4-431b-adb2-eb6b9e546014'
|
63
|
+
uuid = Uuid.parse(str)
|
64
|
+
```
|
65
|
+
|
66
|
+
Use `#to_s` to generate a human-readable representation of the UUID.
|
67
|
+
|
68
|
+
### Command-line Usage
|
69
|
+
|
70
|
+
Run the `generate-uuid` script to generate and output a UUID.
|
71
|
+
|
72
|
+
$ generate-uuid
|
73
|
+
c06b98c9-4cd6-4265-97ef-3299027cde88
|
74
|
+
|
75
|
+
Or pass a number as an argument to generate multiple UUIDs.
|
76
|
+
|
77
|
+
$ generate-uuid 5
|
78
|
+
cf803c2a-a04b-45ff-be3b-294b70a5d8d5
|
79
|
+
4e5a5172-79f4-4cd2-9649-fc5f6b682679
|
80
|
+
62b4226d-23fb-4e5f-856b-1dfbf0448719
|
81
|
+
2d259f9d-bd4d-4c56-b3d2-71305f5f73d9
|
82
|
+
6d5755b0-78fa-4bfe-a8e5-0b16fb941e8a
|
83
|
+
|
84
|
+
Development
|
85
|
+
-----------
|
86
|
+
|
87
|
+
After checking out the repo, run `bin/setup` to install developer dependencies.
|
88
|
+
Then, run `bundle exec rake test` to run the tests.
|
89
|
+
It's recommended that you run `bundle exec rake inspect` to run inspections.
|
90
|
+
You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
91
|
+
`bundle exec rake doc` will generate documentation.
|
92
|
+
|
93
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
94
|
+
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`.
|
95
|
+
This will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
96
|
+
|
97
|
+
Contributing
|
98
|
+
------------
|
99
|
+
|
100
|
+
Bug reports and pull requests are welcome on [GitHub](https://github.com/bluepixelmike/lazy-uuid).
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'reek/rake/task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'rubocop'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'yard'
|
7
|
+
|
8
|
+
task :default => [:build, :test]
|
9
|
+
task :inspect => [:reek, :rubocop]
|
10
|
+
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
task :test => :spec
|
13
|
+
|
14
|
+
Reek::Rake::Task.new do |task| # :reek
|
15
|
+
task.name = :reek
|
16
|
+
task.source_files = 'lib/**/*.rb'
|
17
|
+
task.fail_on_error = false
|
18
|
+
end
|
19
|
+
|
20
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
21
|
+
task.patterns = ['lib/**/*.rb']
|
22
|
+
task.fail_on_error = false
|
23
|
+
end
|
24
|
+
|
25
|
+
YARD::Rake::YardocTask.new do |task| # :yard
|
26
|
+
task.files = %w(lib/**/*.rb - *.md)
|
27
|
+
end
|
28
|
+
task :doc => :yard
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'lazy-uuid'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/generate-uuid
ADDED
data/bin/setup
ADDED
data/lazy-uuid.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'lazy-uuid/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'lazy-uuid'
|
8
|
+
spec.version = LazyUuid::VERSION
|
9
|
+
spec.authors = ['Michael Miller']
|
10
|
+
spec.email = ['bluepixelmike@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = %q{Small gem for creating and using UUIDs (universally unique identifier).}
|
13
|
+
spec.description = %q{Generates RFC 4122 compliant UUIDs.
|
14
|
+
These UUIDs can be treated as their raw byte representation or as human-readable strings.}
|
15
|
+
spec.homepage = 'https://github.com/bluepixelmike/lazy-uuid'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
19
|
+
spec.bindir = 'bin'
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/.*uuid}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler'
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
27
|
+
spec.add_development_dependency 'factory_girl', '~> 4.0'
|
28
|
+
spec.add_development_dependency 'reek', '~> 3.0'
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0.38.0'
|
30
|
+
spec.add_development_dependency 'codeclimate-test-reporter' unless RUBY_PLATFORM == 'java'
|
31
|
+
spec.add_development_dependency 'yard'
|
32
|
+
end
|
data/lib/lazy-uuid.rb
ADDED
@@ -0,0 +1,251 @@
|
|
1
|
+
require 'securerandom' # For generating new UUIDs.
|
2
|
+
|
3
|
+
# Universally unique identifier.
|
4
|
+
# Items with distinct different UUIDs should be different entities.
|
5
|
+
# Items with the same UUID should be the same entity.
|
6
|
+
# @note The naming scheme used for this class is as follows:
|
7
|
+
# * +uuid+ - An instance of this class.
|
8
|
+
# * +value+ - Packed string, not human readable. 16 bytes in length.
|
9
|
+
# * +uuid_str+ - Formatted string, human readable. Formatted as xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12).
|
10
|
+
class Uuid
|
11
|
+
include Comparable
|
12
|
+
|
13
|
+
# Raw, packed byte string containing the UUID's value.
|
14
|
+
# @return [String] Packed string, 16 bytes in length.
|
15
|
+
attr_reader :value
|
16
|
+
|
17
|
+
# Creates a UUID or one from an existing value.
|
18
|
+
# @param value [String] Packed bytes with the UUID's value.
|
19
|
+
# The value must be a string of 16 bytes (128 bits).
|
20
|
+
# @see generate
|
21
|
+
def initialize(value)
|
22
|
+
value_str = value.to_s
|
23
|
+
fail ArgumentError, 'Packed UUID value must be 16 bytes.' unless value_str.length == 16
|
24
|
+
|
25
|
+
# Get a copy to prevent external processes modifying the value.
|
26
|
+
@value = value_str.dup
|
27
|
+
|
28
|
+
# Prevent modification.
|
29
|
+
@value.freeze
|
30
|
+
end
|
31
|
+
|
32
|
+
class << self
|
33
|
+
|
34
|
+
# Generates a new (and random) UUID
|
35
|
+
# @return [UUID] Newly generated UUID.
|
36
|
+
def generate
|
37
|
+
# A built-in method from Ruby to generate a valid UUID is SecureRandom.uuid.
|
38
|
+
# However, it returns it as a formatted string.
|
39
|
+
# The formatted string has to be converted to a packed string before it can be used.
|
40
|
+
uuid_str = SecureRandom.uuid
|
41
|
+
value = pack_uuid_str(uuid_str)
|
42
|
+
Uuid.new(value)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Determines whether a string contains a valid formatted UUID.
|
46
|
+
# @param uuid_str [String] String to check.
|
47
|
+
# @return [true] The string contains a valid formatted UUID.
|
48
|
+
# @return [false] The string does not contain a valid UUID.
|
49
|
+
def valid_str?(uuid_str)
|
50
|
+
if uuid_str.is_a? String
|
51
|
+
# Check the formatting.
|
52
|
+
# Note that the validity of the UUID isn't checked.
|
53
|
+
!!/^\A[0-9a-f]{8}(-?)[0-9a-f]{4}\1[0-9a-f]{4}\1[0-9a-f]{4}\1[0-9a-f]{12}\z$/i.match(uuid_str)
|
54
|
+
else
|
55
|
+
# A string wasn't passed in.
|
56
|
+
fail ArgumentError
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Converts a UUID formatted as a string into its packed byte representation.
|
61
|
+
# @param uuid_str [String] UUID in string form.
|
62
|
+
# @return [String] Packed string, 16 bytes in length.
|
63
|
+
# @!visibility private
|
64
|
+
def pack_uuid_str(uuid_str)
|
65
|
+
# 1) Strip hyphens.
|
66
|
+
# 2) Collect 2 characters.
|
67
|
+
# 3) Convert 2 characters from hex to numeric.
|
68
|
+
# 4) Pack the numeric values into bytes in a string.
|
69
|
+
uuid_str.delete('-').scan(/../).map(&:hex).pack('C16')
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates a UUID object from its string representation.
|
73
|
+
# @param uuid_str [String] UUID in string form.
|
74
|
+
# The string must be in the form: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
|
75
|
+
# @note Casing does not matter for a-f.
|
76
|
+
# @return [Uuid] Parsed UUID from the string.
|
77
|
+
# @return [nil] The string doesn't have a properly formatted UUID.
|
78
|
+
# @note Dashes may be omitted from the string.
|
79
|
+
# This allows strings of 32 hexadecimal characters.
|
80
|
+
def parse(uuid_str)
|
81
|
+
if valid_str?(uuid_str)
|
82
|
+
# Properly formatted UUID string.
|
83
|
+
# Pack the UUID into a byte string and return a new instance.
|
84
|
+
value = pack_uuid_str(uuid_str)
|
85
|
+
Uuid.new(value)
|
86
|
+
else
|
87
|
+
# Not properly formatted.
|
88
|
+
nil
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Checks for hash equality of two UUIDs.
|
94
|
+
# @param other [Uuid] Other UUID to compare against.
|
95
|
+
# @return [true] The UUIDs are equal.
|
96
|
+
# @return [false] The UUIDs are different.
|
97
|
+
# @note This method compares only {Uuid} instances.
|
98
|
+
# +false+ will be returned if +other+ is not a {Uuid}.
|
99
|
+
def eql?(other)
|
100
|
+
other.is_a?(Uuid) && eq_packed(other.value)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Checks if two UUIDs have the same value.
|
104
|
+
# @param other [Uuid, String] Other object to check against.
|
105
|
+
# @return [true] The UUIDs are equal.
|
106
|
+
# @return [false] The UUIDs are different.
|
107
|
+
def ==(other)
|
108
|
+
case other
|
109
|
+
when Uuid
|
110
|
+
# Compare two UUID instances.
|
111
|
+
eq_packed(other.value)
|
112
|
+
|
113
|
+
when String
|
114
|
+
if other.length == 16
|
115
|
+
# Compare against a packed string
|
116
|
+
eq_packed(other)
|
117
|
+
|
118
|
+
else
|
119
|
+
# Compare against a formatted string
|
120
|
+
eq_formatted(other)
|
121
|
+
end
|
122
|
+
|
123
|
+
else
|
124
|
+
# Everything else can't be equated.
|
125
|
+
false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Compares two UUIDs to each other to determine which is lower.
|
130
|
+
# @param other [Uuid, String] Other object to compare against.
|
131
|
+
# @return [-1] The left UUID is smaller.
|
132
|
+
# @return [0] The UUIDs are the same.
|
133
|
+
# @return [1] The right UUID is smaller.
|
134
|
+
# @return [nil] The right side isn't a UUID.
|
135
|
+
def <=>(other)
|
136
|
+
case other
|
137
|
+
when Uuid
|
138
|
+
# Compare two UUID instances.
|
139
|
+
cmp_packed(other.value)
|
140
|
+
|
141
|
+
when String
|
142
|
+
if other.length == 16
|
143
|
+
# Compare against a packed string
|
144
|
+
cmp_packed(other)
|
145
|
+
|
146
|
+
else
|
147
|
+
# Compare against a formatted string
|
148
|
+
cmp_formatted(other)
|
149
|
+
end
|
150
|
+
|
151
|
+
else
|
152
|
+
# Everything else can't be compared.
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Produces a hash value of the UUID.
|
158
|
+
# @return [Fixnum]
|
159
|
+
def hash
|
160
|
+
@value.hash
|
161
|
+
end
|
162
|
+
|
163
|
+
# Produces the string representation of the UUID.
|
164
|
+
# @param dashes [Boolean] +true+ to put dashes in the output (standard format),
|
165
|
+
# +false+ to not put dashes.
|
166
|
+
# @return [String] UUID in string form: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12)
|
167
|
+
def to_s(dashes = true)
|
168
|
+
bytes = @value.bytes
|
169
|
+
if dashes
|
170
|
+
sprintf('%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x', *bytes)
|
171
|
+
else
|
172
|
+
sprintf('%02x' * 16, *bytes)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Empty UUID.
|
177
|
+
# @return [Uuid] UUID with the value 00000000-0000-0000-0000-000000000000.
|
178
|
+
DEFAULT = Uuid.new("\x0" * 16).freeze
|
179
|
+
|
180
|
+
private
|
181
|
+
|
182
|
+
# Checks for equality with a packed string.
|
183
|
+
# @param value [String] Packed string.
|
184
|
+
# @return [true] The packed string contains an identical value.
|
185
|
+
# @return [false] The packed string contains a different value.
|
186
|
+
def eq_packed(value)
|
187
|
+
# Compare the bytes in each string.
|
188
|
+
# Byte comparison *must* be used, since == uses character comparison.
|
189
|
+
# Bytes != characters when encoding is involved.
|
190
|
+
@value.each_byte.with_index do |byte_a, index|
|
191
|
+
byte_b = value.getbyte(index)
|
192
|
+
break false if byte_a != byte_b # Abort enumeration with false value.
|
193
|
+
break true if index == 15 # Return true on last iteration when bytes are the same.
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Checks for equality with a UUID formatted as a string.
|
198
|
+
# @param uuid_str [String] UUID formatted as a string.
|
199
|
+
# @return [true] The formatted string contains an identical value.
|
200
|
+
# @return [false] The formatted string contains a different value.
|
201
|
+
def eq_formatted(uuid_str)
|
202
|
+
klass = self.class
|
203
|
+
if klass.valid_str?(uuid_str)
|
204
|
+
# Format is valid.
|
205
|
+
# Compare it to packed string.
|
206
|
+
value = klass.pack_uuid_str(uuid_str)
|
207
|
+
eq_packed(value)
|
208
|
+
else
|
209
|
+
# Invalid format can't be equal.
|
210
|
+
false
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# Compares the ordering with a packed string.
|
215
|
+
# @param value [String] Packed string.
|
216
|
+
# @return [-1] The left UUID is smaller.
|
217
|
+
# @return [0] The UUIDs are the same.
|
218
|
+
# @return [1] The right UUID is smaller.
|
219
|
+
# @return [nil] The right side isn't a UUID.
|
220
|
+
def cmp_packed(value)
|
221
|
+
# Compare the bytes in each string.
|
222
|
+
# Byte comparison *must* be used, since <=> uses character comparison.
|
223
|
+
# Bytes != characters when encoding is involved.
|
224
|
+
@value.bytes.each_with_index do |byte_a, index|
|
225
|
+
byte_b = value.getbyte(index)
|
226
|
+
result = byte_a <=> byte_b
|
227
|
+
break result if result != 0 # Abort enumeration with result.
|
228
|
+
break 0 if index == 15 # Return 0 on last byte if they're the same.
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Checks for equality with a UUID formatted as a string.
|
233
|
+
# @param uuid_str [String] UUID formatted as a string.
|
234
|
+
# @return [-1] The left UUID is smaller.
|
235
|
+
# @return [0] The UUIDs are the same.
|
236
|
+
# @return [1] The right UUID is smaller.
|
237
|
+
# @return [nil] The right side isn't a UUID.
|
238
|
+
def cmp_formatted(uuid_str)
|
239
|
+
klass = self.class
|
240
|
+
if klass.valid_str?(uuid_str)
|
241
|
+
# Format is valid.
|
242
|
+
# Compare it to packed string.
|
243
|
+
value = klass.pack_uuid_str(uuid_str)
|
244
|
+
cmp_packed(value)
|
245
|
+
else
|
246
|
+
# Invalid format can't be equal.
|
247
|
+
nil
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|