gn0m30-uuid 2.1.2
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.
- data/CHANGELOG +49 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +97 -0
- data/Rakefile +60 -0
- data/gn0m30-uuid.gemspec +22 -0
- data/lib/uuid.rb +326 -0
- data/test/test-uuid.rb +118 -0
- metadata +90 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
2.1.1 (2010-01-25)
|
2
|
+
* added the :teenie format
|
3
|
+
|
4
|
+
2.1.0 (2009-12-16)
|
5
|
+
* Added uuid.validate -- easier to implement than explain why it's wrong.
|
6
|
+
|
7
|
+
2.0.2 (2009-06-10)
|
8
|
+
* Maintenance release. Added uuid.gemspec file in packaging, tested against
|
9
|
+
Ruby 1.9.1.
|
10
|
+
|
11
|
+
2.0.1 (2008-08-28)
|
12
|
+
* Fixed: MAC address parses correctly when using colon as separator, not
|
13
|
+
when using hyphen (ruby-mingw32). If your MAC address is all zero
|
14
|
+
(check with UUID.new.inspect), remove the ruby-uuid file, and it
|
15
|
+
will reset to the actual MAC address. (Rasha)
|
16
|
+
* Fixed: UUID.new.inspect not showing full MAC address.
|
17
|
+
|
18
|
+
2.0.0 (2008-08-28)
|
19
|
+
* Changed: API. UUID.generate still works as it always did, but the rest of
|
20
|
+
the API is brand spanking new, so if you rely on anything besides
|
21
|
+
UUID.generate, or just curious, check out the rdocs.
|
22
|
+
* Changed: uuid.state replaced by ruby-uuid file. By default stored in
|
23
|
+
/var/tmp, or if that path is not accessible, as .ruby-uuid in the
|
24
|
+
home directory.
|
25
|
+
* Changed: ruby-uuid is now stored as binary file (faster read/write), if you
|
26
|
+
need to have a peek, open irb and type UUID.new.inspect.
|
27
|
+
* Changed: Source code and documentation for this release hosted on the
|
28
|
+
wonderful Github: http://github.com/assaf/uuid
|
29
|
+
|
30
|
+
1.0.4 (2007-08-28)
|
31
|
+
* Changed: By default creates the uuid.state file in the working directory,
|
32
|
+
not in the installation directory, which requires sudo privileges
|
33
|
+
(e.g. gem).
|
34
|
+
|
35
|
+
1.0.3 (2006-11-08)
|
36
|
+
* Fixed: Work around YAML bug in serializing that occurs when MAC address
|
37
|
+
consists only of decimal digits. Credit: ebuprofen"
|
38
|
+
|
39
|
+
1.0.2 (2006-08-19)
|
40
|
+
* Changed: Constants are not conditionally defined (removes warnings when
|
41
|
+
using in Rails.
|
42
|
+
|
43
|
+
1.0.1 (2006-07-27)
|
44
|
+
* Added: Regular expressions to test if a string is a UUID.
|
45
|
+
* Changed: When used in ActiveRecord, adds as callback instead of overriding
|
46
|
+
save.
|
47
|
+
|
48
|
+
1.0.0 (2005-11-20)
|
49
|
+
* Changed: Separated form reliable-msg into its own package.
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
= UUID Generator
|
2
|
+
|
3
|
+
Generates universally unique identifiers (UUIDs) for use in distributed
|
4
|
+
applications. Based on {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
5
|
+
|
6
|
+
|
7
|
+
== Generating UUIDs
|
8
|
+
|
9
|
+
Call #generate to generate a new UUID. The method returns a string in one of
|
10
|
+
four formats. The default format is 36 characters long, and contains the 32
|
11
|
+
hexadecimal octets and hyphens separating the various value parts. The
|
12
|
+
<tt>:compact</tt> format omits the hyphens, while the <tt>:urn</tt> format
|
13
|
+
adds the <tt>:urn:uuid</tt> prefix.
|
14
|
+
|
15
|
+
The gn0m30 fork of uuid adds another format, the <tt>:teenie</tt> format
|
16
|
+
converts the numeric portions of the default format to base 62 representations
|
17
|
+
for use in situations where case sensitive keys might be useful like Amazon S3.
|
18
|
+
|
19
|
+
|
20
|
+
For example:
|
21
|
+
|
22
|
+
uuid = UUID.new
|
23
|
+
10.times do
|
24
|
+
p uuid.generate
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
== UUIDs in Brief
|
29
|
+
|
30
|
+
UUID (universally unique identifier) are guaranteed to be unique across time
|
31
|
+
and space.
|
32
|
+
|
33
|
+
A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
|
34
|
+
sequence number and a 48-bit node identifier.
|
35
|
+
|
36
|
+
The time value is taken from the system clock, and is monotonically
|
37
|
+
incrementing. However, since it is possible to set the system clock
|
38
|
+
backward, a sequence number is added. The sequence number is incremented
|
39
|
+
each time the UUID generator is started. The combination guarantees that
|
40
|
+
identifiers created on the same machine are unique with a high degree of
|
41
|
+
probability.
|
42
|
+
|
43
|
+
Note that due to the structure of the UUID and the use of sequence number,
|
44
|
+
there is no guarantee that UUID values themselves are monotonically
|
45
|
+
incrementing. The UUID value cannot itself be used to sort based on order
|
46
|
+
of creation.
|
47
|
+
|
48
|
+
To guarantee that UUIDs are unique across all machines in the network,
|
49
|
+
the IEEE 802 MAC address of the machine's network interface card is used as
|
50
|
+
the node identifier.
|
51
|
+
|
52
|
+
For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
53
|
+
|
54
|
+
|
55
|
+
== UUID State File
|
56
|
+
|
57
|
+
The UUID generator uses a state file to hold the MAC address and sequence
|
58
|
+
number.
|
59
|
+
|
60
|
+
The MAC address is used to generate identifiers that are unique to your
|
61
|
+
machine, preventing conflicts in distributed applications. The MAC
|
62
|
+
address is six bytes (48 bit) long. It is automatically extracted from
|
63
|
+
one of the network cards on your machine.
|
64
|
+
|
65
|
+
The sequence number is incremented each time the UUID generator is
|
66
|
+
first used by the application, to prevent multiple processes from
|
67
|
+
generating the same set of identifiers, and deal with changes to the
|
68
|
+
system clock.
|
69
|
+
|
70
|
+
The UUID state file is created in <tt>/var/tmp/ruby-uuid</tt> or the Windows
|
71
|
+
common application data directory using mode 0644. If that directory is not
|
72
|
+
writable, the file is created as <tt>.ruby-uuid</tt> in the home directory.
|
73
|
+
If you need to create the file with a different mode, use UUID#state_file
|
74
|
+
before running the UUID generator.
|
75
|
+
|
76
|
+
State files are not portable across machines.
|
77
|
+
|
78
|
+
|
79
|
+
== Latest and Greatest
|
80
|
+
|
81
|
+
Stable release are made through Gemcutter, to upgrade simply:
|
82
|
+
|
83
|
+
gem upgrade gn0m30-uuid
|
84
|
+
|
85
|
+
|
86
|
+
gn0m30-uuid is forked from UUID available here: http://github.com/assaf/uuid
|
87
|
+
|
88
|
+
Source code and documentation hosted on Github: http://github.com/gn0m30/uuid
|
89
|
+
|
90
|
+
|
91
|
+
== License
|
92
|
+
|
93
|
+
This package is licensed under the MIT license and/or the Creative
|
94
|
+
Commons Attribution-ShareAlike.
|
95
|
+
|
96
|
+
:include: MIT-LICENSE
|
97
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
|
4
|
+
|
5
|
+
spec = Gem::Specification.load(File.expand_path("gn0m30-uuid.gemspec", File.dirname(__FILE__)))
|
6
|
+
|
7
|
+
desc "Default Task"
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
|
11
|
+
desc "If you're building from sources, run this task first to setup the necessary dependencies"
|
12
|
+
task 'setup' do
|
13
|
+
missing = spec.dependencies.select { |dep| Gem::SourceIndex.from_installed_gems.search(dep).empty? }
|
14
|
+
missing.each do |dep|
|
15
|
+
if Gem::SourceIndex.from_installed_gems.search(dep).empty?
|
16
|
+
puts "Installing #{dep.name} ..."
|
17
|
+
rb_bin = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
|
18
|
+
args = []
|
19
|
+
args << rb_bin << '-S' << 'gem' << 'install' << dep.name
|
20
|
+
args << '--version' << dep.version_requirements.to_s
|
21
|
+
args << '--source' << 'http://gems.rubyforge.org'
|
22
|
+
args << '--install-dir' << ENV['GEM_HOME'] if ENV['GEM_HOME']
|
23
|
+
sh *args
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
desc "Run all test cases"
|
30
|
+
Rake::TestTask.new do |test|
|
31
|
+
test.verbose = true
|
32
|
+
test.test_files = ['test/*.rb']
|
33
|
+
test.warning = true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create the documentation.
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
rdoc.rdoc_files.include "README.rdoc", "lib/**/*.rb"
|
39
|
+
rdoc.options = spec.rdoc_options
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
desc "Push new release to gemcutter and git tag"
|
45
|
+
task :push do
|
46
|
+
sh "git push"
|
47
|
+
puts "Tagging version #{spec.version} .."
|
48
|
+
sh "git tag v#{spec.version}"
|
49
|
+
sh "git push --tag"
|
50
|
+
puts "Building and pushing gem .."
|
51
|
+
sh "gem build #{spec.name}.gemspec"
|
52
|
+
sh "gem push #{spec.name}-#{spec.version}.gem"
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Install #{spec.name} locally"
|
56
|
+
task :install do
|
57
|
+
sh "gem build #{spec.name}.gemspec"
|
58
|
+
sudo = "sudo" unless File.writable?( Gem::ConfigMap[:bindir])
|
59
|
+
sh "#{sudo} gem install #{spec.name}-#{spec.version}.gem"
|
60
|
+
end
|
data/gn0m30-uuid.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
spec = Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'gn0m30-uuid'
|
3
|
+
spec.version = '2.1.2'
|
4
|
+
spec.summary = "UUID generator with teenie format"
|
5
|
+
spec.description = <<-EOF
|
6
|
+
UUID generator for producing universally unique identifiers based on RFC 4122
|
7
|
+
(http://www.ietf.org/rfc/rfc4122.txt).
|
8
|
+
EOF
|
9
|
+
|
10
|
+
spec.authors << 'Assaf Arkin' << 'Eric Hodel' << 'jim nist'
|
11
|
+
spec.email = 'reggie@loco8.org'
|
12
|
+
spec.homepage = 'http://github.com/gn0m30/uuid'
|
13
|
+
|
14
|
+
spec.files = Dir['{bin,test,lib,docs}/**/*'] + ['README.rdoc', 'MIT-LICENSE', 'Rakefile', 'CHANGELOG', 'gn0m30-uuid.gemspec']
|
15
|
+
spec.has_rdoc = true
|
16
|
+
spec.rdoc_options << '--main' << 'README.rdoc' << '--title' << 'UUID generator' << '--line-numbers'
|
17
|
+
'--webcvs' << 'http://github.com/assaf/uuid'
|
18
|
+
spec.extra_rdoc_files = ['README.rdoc', 'MIT-LICENSE']
|
19
|
+
|
20
|
+
spec.add_dependency 'macaddr', ['~>1.0']
|
21
|
+
spec.add_dependency 'base62', ['~>0.1.0']
|
22
|
+
end
|
data/lib/uuid.rb
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
#
|
2
|
+
# = uuid.rb - UUID generator
|
3
|
+
#
|
4
|
+
# Author:: Assaf Arkin assaf@labnotes.org
|
5
|
+
# Eric Hodel drbrain@segment7.net
|
6
|
+
# Copyright:: Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
|
7
|
+
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
8
|
+
|
9
|
+
require 'fileutils'
|
10
|
+
require 'thread'
|
11
|
+
require 'tmpdir'
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'macaddr'
|
15
|
+
require 'base62'
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
# = Generating UUIDs
|
20
|
+
#
|
21
|
+
# Call #generate to generate a new UUID. The method returns a string in one of
|
22
|
+
# three formats. The default format is 36 characters long, and contains the 32
|
23
|
+
# hexadecimal octets and hyphens separating the various value parts. The
|
24
|
+
# <tt>:compact</tt> format omits the hyphens, while the <tt>:urn</tt> format
|
25
|
+
# adds the <tt>:urn:uuid</tt> prefix.
|
26
|
+
#
|
27
|
+
# For example:
|
28
|
+
#
|
29
|
+
# uuid = UUID.new
|
30
|
+
#
|
31
|
+
# 10.times do
|
32
|
+
# p uuid.generate
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# = UUIDs in Brief
|
36
|
+
#
|
37
|
+
# UUID (universally unique identifier) are guaranteed to be unique across time
|
38
|
+
# and space.
|
39
|
+
#
|
40
|
+
# A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
|
41
|
+
# sequence number and a 48-bit node identifier.
|
42
|
+
#
|
43
|
+
# The time value is taken from the system clock, and is monotonically
|
44
|
+
# incrementing. However, since it is possible to set the system clock
|
45
|
+
# backward, a sequence number is added. The sequence number is incremented
|
46
|
+
# each time the UUID generator is started. The combination guarantees that
|
47
|
+
# identifiers created on the same machine are unique with a high degree of
|
48
|
+
# probability.
|
49
|
+
#
|
50
|
+
# Note that due to the structure of the UUID and the use of sequence number,
|
51
|
+
# there is no guarantee that UUID values themselves are monotonically
|
52
|
+
# incrementing. The UUID value cannot itself be used to sort based on order
|
53
|
+
# of creation.
|
54
|
+
#
|
55
|
+
# To guarantee that UUIDs are unique across all machines in the network,
|
56
|
+
# the IEEE 802 MAC address of the machine's network interface card is used as
|
57
|
+
# the node identifier.
|
58
|
+
#
|
59
|
+
# For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
|
60
|
+
|
61
|
+
class UUID
|
62
|
+
|
63
|
+
# Version number.
|
64
|
+
module Version
|
65
|
+
version = Gem::Specification.load(File.expand_path("../gn0m30-uuid.gemspec", File.dirname(__FILE__))).version.to_s.split(".").map { |i| i.to_i }
|
66
|
+
MAJOR = version[0]
|
67
|
+
MINOR = version[1]
|
68
|
+
PATCH = version[2]
|
69
|
+
STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
|
70
|
+
end
|
71
|
+
|
72
|
+
VERSION = Version::STRING
|
73
|
+
|
74
|
+
##
|
75
|
+
# Clock multiplier. Converts Time (resolution: seconds) to UUID clock
|
76
|
+
# (resolution: 10ns)
|
77
|
+
CLOCK_MULTIPLIER = 10000000
|
78
|
+
|
79
|
+
##
|
80
|
+
# Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time
|
81
|
+
# ticks.
|
82
|
+
CLOCK_GAPS = 100000
|
83
|
+
|
84
|
+
##
|
85
|
+
# Version number stamped into the UUID to identify it as time-based.
|
86
|
+
VERSION_CLOCK = 0x0100
|
87
|
+
|
88
|
+
##
|
89
|
+
# Formats supported by the UUID generator.
|
90
|
+
#
|
91
|
+
# <tt>:default</tt>:: Produces 36 characters, including hyphens separating
|
92
|
+
# the UUID value parts
|
93
|
+
# <tt>:compact</tt>:: Produces a 32 digits (hexadecimal) value with no
|
94
|
+
# hyphens
|
95
|
+
# <tt>:urn</tt>:: Adds the prefix <tt>urn:uuid:</tt> to the default format
|
96
|
+
# <tt>:teenie</tt>:: converts numeric portions of default format to base62
|
97
|
+
FORMATS = {
|
98
|
+
:compact => '%08x%04x%04x%04x%012x',
|
99
|
+
:default => '%08x-%04x-%04x-%04x-%012x',
|
100
|
+
:urn => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
|
101
|
+
:teenie => '%6.6s%3.3s%3.3s%3.3s%9.9s',
|
102
|
+
}
|
103
|
+
|
104
|
+
##
|
105
|
+
# MAC address (48 bits), sequence number and last clock
|
106
|
+
STATE_FILE_FORMAT = 'SLLQ'
|
107
|
+
|
108
|
+
@state_file = nil
|
109
|
+
@mode = nil
|
110
|
+
@uuid = nil
|
111
|
+
|
112
|
+
##
|
113
|
+
# The access mode of the state file. Set it with state_file.
|
114
|
+
|
115
|
+
def self.mode
|
116
|
+
@mode
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Generates a new UUID string using +format+. See FORMATS for a list of
|
121
|
+
# supported formats.
|
122
|
+
|
123
|
+
def self.generate(format = :default)
|
124
|
+
@uuid ||= new
|
125
|
+
@uuid.generate format
|
126
|
+
end
|
127
|
+
|
128
|
+
##
|
129
|
+
# Creates an empty state file in /var/tmp/ruby-uuid or the windows common
|
130
|
+
# application data directory using mode 0644. Call with a different mode
|
131
|
+
# before creating a UUID generator if you want to open access beyond your
|
132
|
+
# user by default.
|
133
|
+
#
|
134
|
+
# If the default state dir is not writable, UUID falls back to ~/.ruby-uuid.
|
135
|
+
#
|
136
|
+
# State files are not portable across machines.
|
137
|
+
def self.state_file(mode = 0644)
|
138
|
+
return @state_file if @state_file
|
139
|
+
|
140
|
+
@mode = mode
|
141
|
+
|
142
|
+
begin
|
143
|
+
require 'Win32API'
|
144
|
+
|
145
|
+
csidl_common_appdata = 0x0023
|
146
|
+
path = 0.chr * 260
|
147
|
+
get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L')
|
148
|
+
get_folder_path.call 0, csidl_common_appdata, 0, 1, path
|
149
|
+
|
150
|
+
state_dir = File.join(path.strip)
|
151
|
+
rescue LoadError
|
152
|
+
state_dir = File.join('', 'var', 'tmp')
|
153
|
+
end
|
154
|
+
|
155
|
+
if File.writable?(state_dir) then
|
156
|
+
@state_file = File.join(state_dir, 'ruby-uuid')
|
157
|
+
else
|
158
|
+
@state_file = File.expand_path(File.join('~', '.ruby-uuid'))
|
159
|
+
end
|
160
|
+
|
161
|
+
@state_file
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Specify the path of the state file.
|
166
|
+
def self.state_file=(path)
|
167
|
+
@state_file = path
|
168
|
+
end
|
169
|
+
|
170
|
+
##
|
171
|
+
# Returns true if +uuid+ is in compact, default or urn formats. Does not
|
172
|
+
# validate the layout (RFC 4122 section 4) of the UUID.
|
173
|
+
# does not validate :teenie format. see validate_teenie
|
174
|
+
def self.validate(uuid)
|
175
|
+
return true if uuid =~ /\A[\da-f]{32}\z/i
|
176
|
+
return true if
|
177
|
+
uuid =~ /\A(urn:uuid:)?[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}\z/i
|
178
|
+
end
|
179
|
+
|
180
|
+
##
|
181
|
+
# Returns true if +uuid+ is in teenie format
|
182
|
+
def self.validate_teenie(uuid)
|
183
|
+
return true if uuid =~ /\A[\da-fA-f]{24}\z/i
|
184
|
+
end
|
185
|
+
|
186
|
+
##
|
187
|
+
# Create a new UUID generator. You really only need to do this once.
|
188
|
+
def initialize
|
189
|
+
@drift = 0
|
190
|
+
@last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
|
191
|
+
@mutex = Mutex.new
|
192
|
+
|
193
|
+
if File.exist?(self.class.state_file) then
|
194
|
+
next_sequence
|
195
|
+
else
|
196
|
+
@mac = Mac.addr.gsub(/:|-/, '').hex & 0x7FFFFFFFFFFF
|
197
|
+
fail "Cannot determine MAC address from any available interface, tried with #{Mac.addr}" if @mac == 0
|
198
|
+
@sequence = rand 0x10000
|
199
|
+
|
200
|
+
open_lock 'w' do |io|
|
201
|
+
write_state io
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Generates a new UUID string using +format+. See FORMATS for a list of
|
208
|
+
# supported formats.
|
209
|
+
def generate(format = :default)
|
210
|
+
template = FORMATS[format]
|
211
|
+
|
212
|
+
raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
|
213
|
+
|
214
|
+
# The clock must be monotonically increasing. The clock resolution is at
|
215
|
+
# best 100 ns (UUID spec), but practically may be lower (on my setup,
|
216
|
+
# around 1ms). If this method is called too fast, we don't have a
|
217
|
+
# monotonically increasing clock, so the solution is to just wait.
|
218
|
+
#
|
219
|
+
# It is possible for the clock to be adjusted backwards, in which case we
|
220
|
+
# would end up blocking for a long time. When backward clock is detected,
|
221
|
+
# we prevent duplicates by asking for a new sequence number and continue
|
222
|
+
# with the new clock.
|
223
|
+
|
224
|
+
clock = @mutex.synchronize do
|
225
|
+
clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
|
226
|
+
|
227
|
+
if clock > @last_clock then
|
228
|
+
@drift = 0
|
229
|
+
@last_clock = clock
|
230
|
+
elsif clock == @last_clock then
|
231
|
+
drift = @drift += 1
|
232
|
+
|
233
|
+
if drift < 10000 then
|
234
|
+
@last_clock += 1
|
235
|
+
else
|
236
|
+
Thread.pass
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
else
|
240
|
+
next_sequence
|
241
|
+
@last_clock = clock
|
242
|
+
end
|
243
|
+
end until clock
|
244
|
+
|
245
|
+
part1 = clock & 0xFFFFFFFF
|
246
|
+
part2 = (clock >> 32) & 0xFFFF
|
247
|
+
part3 = ((clock >> 48) & 0xFFFF | VERSION_CLOCK)
|
248
|
+
part4 = @sequence & 0xFFFF
|
249
|
+
part5 = @mac & 0xFFFFFFFFFFFF
|
250
|
+
|
251
|
+
# for this special case, the parts are going to be strings which we will 0 pad
|
252
|
+
if format == :teenie
|
253
|
+
part1 = part1.base62_encode
|
254
|
+
part2 = part2.base62_encode
|
255
|
+
part3 = part3.base62_encode
|
256
|
+
part4 = part4.base62_encode
|
257
|
+
part5 = part5.base62_encode
|
258
|
+
|
259
|
+
(template % [part1, part2, part3, part4, part5]).gsub(' ', '0')
|
260
|
+
else
|
261
|
+
template % [part1, part2, part3, part4, part5]
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
##
|
266
|
+
# Updates the state file with a new sequence number.
|
267
|
+
def next_sequence
|
268
|
+
open_lock 'r+' do |io|
|
269
|
+
@mac, @sequence, @last_clock = read_state(io)
|
270
|
+
|
271
|
+
io.rewind
|
272
|
+
io.truncate 0
|
273
|
+
|
274
|
+
@sequence += 1
|
275
|
+
|
276
|
+
write_state io
|
277
|
+
end
|
278
|
+
rescue Errno::ENOENT
|
279
|
+
open_lock 'w' do |io|
|
280
|
+
write_state io
|
281
|
+
end
|
282
|
+
ensure
|
283
|
+
@last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
|
284
|
+
@drift = 0
|
285
|
+
end
|
286
|
+
|
287
|
+
def inspect
|
288
|
+
mac = ("%012x" % @mac).scan(/[0-9a-f]{2}/).join(':')
|
289
|
+
"MAC: #{mac} Sequence: #{@sequence}"
|
290
|
+
end
|
291
|
+
|
292
|
+
protected
|
293
|
+
|
294
|
+
##
|
295
|
+
# Open the state file with an exclusive lock and access mode +mode+.
|
296
|
+
def open_lock(mode)
|
297
|
+
File.open self.class.state_file, mode, self.class.mode do |io|
|
298
|
+
begin
|
299
|
+
io.flock File::LOCK_EX
|
300
|
+
yield io
|
301
|
+
ensure
|
302
|
+
io.flock File::LOCK_UN
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
##
|
308
|
+
# Read the state from +io+
|
309
|
+
def read_state(io)
|
310
|
+
mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT)
|
311
|
+
mac = (mac1 << 32) + mac2
|
312
|
+
|
313
|
+
return mac, seq, last_clock
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
##
|
318
|
+
# Write that state to +io+
|
319
|
+
def write_state(io)
|
320
|
+
mac2 = @mac & 0xffffffff
|
321
|
+
mac1 = (@mac >> 32) & 0xffff
|
322
|
+
|
323
|
+
io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
data/test/test-uuid.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# Author:: Assaf Arkin assaf@labnotes.org
|
2
|
+
# Eric Hodel drbrain@segment7.net
|
3
|
+
# Copyright:: Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
|
4
|
+
# License:: MIT and/or Creative Commons Attribution-ShareAlike
|
5
|
+
|
6
|
+
require 'test/unit'
|
7
|
+
require 'uuid'
|
8
|
+
|
9
|
+
class TestUUID < Test::Unit::TestCase
|
10
|
+
|
11
|
+
def test_state_file_creation
|
12
|
+
path = UUID.state_file
|
13
|
+
File.delete path if File.exist?(path)
|
14
|
+
UUID.new.generate
|
15
|
+
File.exist?(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_state_file_specify
|
19
|
+
path = File.join("path", "to", "ruby-uuid")
|
20
|
+
UUID.state_file = path
|
21
|
+
assert_equal path, UUID.state_file
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_instance_generate
|
25
|
+
uuid = UUID.new
|
26
|
+
assert_match(/\A[\da-f]{32}\z/i, uuid.generate(:compact))
|
27
|
+
|
28
|
+
assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
|
29
|
+
uuid.generate(:default))
|
30
|
+
|
31
|
+
assert_match(/^urn:uuid:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
|
32
|
+
uuid.generate(:urn))
|
33
|
+
|
34
|
+
assert_match(/\A[\da-fA-f]{24}\z/i, uuid.generate(:teenie))
|
35
|
+
|
36
|
+
e = assert_raise ArgumentError do
|
37
|
+
uuid.generate :unknown
|
38
|
+
end
|
39
|
+
|
40
|
+
assert_equal 'invalid UUID format :unknown', e.message
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_class_generate
|
44
|
+
assert_match(/\A[\da-f]{32}\z/i, UUID.generate(:compact))
|
45
|
+
|
46
|
+
assert_match(/\A[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
|
47
|
+
UUID.generate(:default))
|
48
|
+
|
49
|
+
assert_match(/^urn:uuid:[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\z/i,
|
50
|
+
UUID.generate(:urn))
|
51
|
+
|
52
|
+
assert_match(/\A[\da-fA-f]{24}\z/i, UUID.generate(:teenie))
|
53
|
+
|
54
|
+
e = assert_raise ArgumentError do
|
55
|
+
UUID.generate :unknown
|
56
|
+
end
|
57
|
+
assert_equal 'invalid UUID format :unknown', e.message
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_class_validate
|
61
|
+
assert !UUID.validate('')
|
62
|
+
|
63
|
+
assert UUID.validate('01234567abcd8901efab234567890123'), 'compact'
|
64
|
+
assert UUID.validate('01234567-abcd-8901-efab-234567890123'), 'default'
|
65
|
+
assert UUID.validate('urn:uuid:01234567-abcd-8901-efab-234567890123'),
|
66
|
+
'urn'
|
67
|
+
assert UUID.validate_teenie('4etJlQGyu04qEJs002f129iS'), 'teenie'
|
68
|
+
|
69
|
+
assert UUID.validate('01234567ABCD8901EFAB234567890123'), 'compact'
|
70
|
+
assert UUID.validate('01234567-ABCD-8901-EFAB-234567890123'), 'default'
|
71
|
+
assert UUID.validate('urn:uuid:01234567-ABCD-8901-EFAB-234567890123'),
|
72
|
+
'urn'
|
73
|
+
assert UUID.validate_teenie('1ldIDgGyv04qEJs002f129iS'), 'teenie'
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_class_invalids
|
77
|
+
assert_nil UUID.validate('01234567abcd8901efab234567890123000'), 'compact - too long'
|
78
|
+
assert_nil UUID.validate('01234567abcd8901efab23456789012'), 'compact - too short'
|
79
|
+
assert_nil UUID.validate('01234567abcd8901efzb234567890123'), 'compact - bad chars'
|
80
|
+
|
81
|
+
assert_nil UUID.validate_teenie('1ldIDgGyv04qEJs002f129iSaaaaa'), 'teenie - too long'
|
82
|
+
assert_nil UUID.validate_teenie('4etJlQGyu04qEJs02f129iS'), 'teenie - too short'
|
83
|
+
assert_nil UUID.validate_teenie('1ldIDgGyv04qEJs002f1-9iS'), 'teenie - bad chars'
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_monotonic
|
87
|
+
seen = {}
|
88
|
+
uuid_gen = UUID.new
|
89
|
+
|
90
|
+
20_000.times do
|
91
|
+
uuid = uuid_gen.generate
|
92
|
+
assert !seen.has_key?(uuid), "UUID repeated"
|
93
|
+
seen[uuid] = true
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_same_mac
|
98
|
+
class << foo = UUID.new
|
99
|
+
attr_reader :mac
|
100
|
+
end
|
101
|
+
class << bar = UUID.new
|
102
|
+
attr_reader :mac
|
103
|
+
end
|
104
|
+
assert_equal foo.mac, bar.mac
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_increasing_sequence
|
108
|
+
class << foo = UUID.new
|
109
|
+
attr_reader :sequence
|
110
|
+
end
|
111
|
+
class << bar = UUID.new
|
112
|
+
attr_reader :sequence
|
113
|
+
end
|
114
|
+
assert_equal foo.sequence + 1, bar.sequence
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gn0m30-uuid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Assaf Arkin
|
8
|
+
- Eric Hodel
|
9
|
+
- jim nist
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2010-02-18 00:00:00 -05:00
|
15
|
+
default_executable:
|
16
|
+
dependencies:
|
17
|
+
- !ruby/object:Gem::Dependency
|
18
|
+
name: macaddr
|
19
|
+
type: :runtime
|
20
|
+
version_requirement:
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ~>
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: "1.0"
|
26
|
+
version:
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: base62
|
29
|
+
type: :runtime
|
30
|
+
version_requirement:
|
31
|
+
version_requirements: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.1.0
|
36
|
+
version:
|
37
|
+
description: |
|
38
|
+
UUID generator for producing universally unique identifiers based on RFC 4122
|
39
|
+
(http://www.ietf.org/rfc/rfc4122.txt).
|
40
|
+
|
41
|
+
email: reggie@loco8.org
|
42
|
+
executables: []
|
43
|
+
|
44
|
+
extensions: []
|
45
|
+
|
46
|
+
extra_rdoc_files:
|
47
|
+
- README.rdoc
|
48
|
+
- MIT-LICENSE
|
49
|
+
files:
|
50
|
+
- test/test-uuid.rb
|
51
|
+
- lib/uuid.rb
|
52
|
+
- README.rdoc
|
53
|
+
- MIT-LICENSE
|
54
|
+
- Rakefile
|
55
|
+
- CHANGELOG
|
56
|
+
- gn0m30-uuid.gemspec
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/gn0m30/uuid
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --main
|
64
|
+
- README.rdoc
|
65
|
+
- --title
|
66
|
+
- UUID generator
|
67
|
+
- --line-numbers
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
version:
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: "0"
|
81
|
+
version:
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.5
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: UUID generator with teenie format
|
89
|
+
test_files: []
|
90
|
+
|