uuid 1.0.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (9) hide show
  1. data/CHANGELOG +32 -19
  2. data/MIT-LICENSE +1 -1
  3. data/README.rdoc +103 -0
  4. data/Rakefile +65 -185
  5. data/lib/uuid.rb +212 -326
  6. data/test/test-uuid.rb +82 -52
  7. metadata +53 -36
  8. data/README +0 -81
  9. data/bin/uuid-setup +0 -7
data/CHANGELOG CHANGED
@@ -1,19 +1,32 @@
1
- 1.0.4 (1/2/2006)
2
- * Changed: By default creates the uuid.state file in the working directory,
3
- not in the installation directory, which requires sudo privileges (e.g. gem).
4
-
5
- 1.0.3 (1/8/2006)
6
- * Fixed: Work around YAML bug in serializing that occurs when MAC address
7
- consists only of decimal digits. Credit: ebuprofen"
8
-
9
- 1.0.2 (1/28/2006)
10
- * Changed: Constants are not conditionally defined (removes warnings when
11
- using in Rails.
12
-
13
- 1.0.1 (1/26/2006)
14
- * Added: Regular expressions to test if a string is a UUID.
15
- * Changed: When used in ActiveRecord, adds as callback instead of overriding
16
- save.
17
-
18
- 1.0.0 (1/20/2005)
19
- * Changed: Separated form reliable-msg into its own package.
1
+ 2.0.0 (2008-08-28)
2
+ * Changed: API. UUID.generate still works as it always did, but the rest of
3
+ the API is brand spanking new, so if you rely on anything besides
4
+ UUID.generate, or just curious, check out the rdocs.
5
+ * Changed: uuid.state replaced by ruby-uuid file. By default stored in
6
+ /var/tmp, or if that path is not accessible, as .ruby-uuid in the
7
+ home directory.
8
+ * Changed: ruby-uuid is now stored as binary file (faster read/write), if you
9
+ need to have a peek, open irb and type UUID.new.inspect.
10
+ * Changed: Source code and documentation for this release hosted on the
11
+ wonderful Github: http://github.com/assaf/uuid
12
+
13
+ 1.0.4 (2007-08-28)
14
+ * Changed: By default creates the uuid.state file in the working directory,
15
+ not in the installation directory, which requires sudo privileges
16
+ (e.g. gem).
17
+
18
+ 1.0.3 (2006-11-08)
19
+ * Fixed: Work around YAML bug in serializing that occurs when MAC address
20
+ consists only of decimal digits. Credit: ebuprofen"
21
+
22
+ 1.0.2 (2006-08-19)
23
+ * Changed: Constants are not conditionally defined (removes warnings when
24
+ using in Rails.
25
+
26
+ 1.0.1 (2006-07-27)
27
+ * Added: Regular expressions to test if a string is a UUID.
28
+ * Changed: When used in ActiveRecord, adds as callback instead of overriding
29
+ save.
30
+
31
+ 1.0.0 (2005-11-20)
32
+ * Changed: Separated form reliable-msg into its own package.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2005,2007 Assaf Arkin
1
+ Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,103 @@
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
+ three 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
+ For example:
16
+
17
+ uuid = UUID.new
18
+ 10.times do
19
+ p uuid.generate
20
+ end
21
+
22
+
23
+ == UUIDs in Brief
24
+
25
+ UUID (universally unique identifier) are guaranteed to be unique across time
26
+ and space.
27
+
28
+ A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit
29
+ sequence number and a 48-bit node identifier.
30
+
31
+ The time value is taken from the system clock, and is monotonically
32
+ incrementing. However, since it is possible to set the system clock
33
+ backward, a sequence number is added. The sequence number is incremented
34
+ each time the UUID generator is started. The combination guarantees that
35
+ identifiers created on the same machine are unique with a high degree of
36
+ probability.
37
+
38
+ Note that due to the structure of the UUID and the use of sequence number,
39
+ there is no guarantee that UUID values themselves are monotonically
40
+ incrementing. The UUID value cannot itself be used to sort based on order
41
+ of creation.
42
+
43
+ To guarantee that UUIDs are unique across all machines in the network,
44
+ the IEEE 802 MAC address of the machine's network interface card is used as
45
+ the node identifier.
46
+
47
+ For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
48
+
49
+
50
+ == UUID State File
51
+
52
+ The UUID generator uses a state file to hold the MAC address and sequence
53
+ number.
54
+
55
+ The MAC address is used to generate identifiers that are unique to your
56
+ machine, preventing conflicts in distributed applications. The MAC
57
+ address is six bytes (48 bit) long. It is automatically extracted from
58
+ one of the network cards on your machine.
59
+
60
+ The sequence number is incremented each time the UUID generator is
61
+ first used by the application, to prevent multiple processes from
62
+ generating the same set of identifiers, and deal with changes to the
63
+ system clock.
64
+
65
+ The UUID state file is created in <tt>/var/tmp/ruby-uuid</tt> or the Windows
66
+ common application data directory using mode 0644. If that directory is not
67
+ writable, the file is created as <tt>.ruby-uuid</tt> in the home directory.
68
+ If you need to create the file with a different mode, use UUID#state_file
69
+ before running the UUID generator.
70
+
71
+ State files are not portable across machines.
72
+
73
+
74
+ == Latest and Greatest
75
+
76
+ Stable release are made through RubyForge, to upgrade simply:
77
+
78
+ gem upgrade uuid
79
+
80
+ You can subscribe for notifications of new releases through the reliable-msg
81
+ project page at http://rubyforge.org/projects/reliable-msg
82
+
83
+ Source code and documentation hosted on Github:
84
+
85
+ http://github.com/assaf/uuid
86
+
87
+ To get UUID from source:
88
+
89
+ git clone git://github.com/assaf/uuid.git
90
+
91
+
92
+ == Change Log
93
+
94
+ :include: CHANGELOG
95
+
96
+
97
+ == License
98
+
99
+ This package is licensed under the MIT license and/or the {Creative
100
+ Commons Attribution-ShareAlike}[http://creativecommons.org/licenses/by-sa/2.5/legalcode].
101
+
102
+ :include: MIT-LICENSE
103
+
data/Rakefile CHANGED
@@ -1,220 +1,100 @@
1
1
  # Adapted from the rake Rakefile.
2
2
 
3
- require "rubygems"
4
- require "rake/testtask"
5
- require "rake/rdoctask"
6
- require "rake/gempackagetask"
7
-
8
- VERSION_FILE = "lib/uuid.rb"
9
- # Create the GEM package.
10
- spec = Gem::Specification.new do |spec|
11
- spec.name = "uuid"
12
- spec.version = File.read(__FILE__.pathmap("%d/#{VERSION_FILE}")).scan(/VERSION\s*=\s*(['"])(.*)\1/)[0][1]
13
- spec.summary = "UUID generator"
14
- spec.description = <<-EOF
15
- UUID generator for producing universally unique identifiers based
16
- on RFC 4122 (http://www.ietf.org/rfc/rfc4122.txt).
17
- EOF
18
- spec.author = "Assaf Arkin"
19
- spec.email = "assaf@labnotes.org"
20
- spec.homepage = "http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/UuidGenerator"
21
- spec.files = FileList["{bin,test,lib,docs}/**/*", "README", "MIT-LICENSE", "Rakefile", "CHANGELOG"].to_a
22
- spec.require_path = "lib"
23
- spec.autorequire = "uuid.rb"
24
- spec.bindir = "bin"
25
- spec.executables = ["uuid-setup"]
26
- spec.default_executable = "uuid-setup"
27
- spec.has_rdoc = true
28
- spec.rdoc_options << "--main" << "README" << "--title" << "UUID generator" << "--line-numbers"
29
- spec.extra_rdoc_files = ["README"]
30
- spec.rubyforge_project = "reliable-msg"
31
- end
3
+ require 'rubygems'
4
+ require 'rake/testtask'
5
+ require 'rake/rdoctask'
6
+ require 'rake/gempackagetask'
7
+ require 'rubygems/source_info_cache'
8
+
32
9
 
10
+ spec = Gem::Specification.load(File.join(File.dirname(__FILE__), 'uuid.gemspec'))
33
11
 
34
12
  desc "Default Task"
35
- task :default => [:test, :rdoc]
13
+ task 'default' => ['test', 'rdoc']
14
+
15
+
16
+ desc "If you're building from sources, run this task first to setup the necessary dependencies"
17
+ task 'setup' do
18
+ windows = Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i
19
+ rb_bin = File.expand_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir'])
20
+ spec.dependencies.select { |dep| Gem::SourceIndex.from_installed_gems.search(dep).empty? }.each do |missing|
21
+ dep = Gem::Dependency.new(missing.name, missing.version_requirements)
22
+ spec = Gem::SourceInfoCache.search(dep, true, true).last
23
+ fail "#{dep} not found in local or remote repository!" unless spec
24
+ puts "Installing #{spec.full_name} ..."
25
+ args = [rb_bin, '-S', 'gem', 'install', spec.name, '-v', spec.version.to_s]
26
+ args.unshift('sudo') unless windows || ENV['GEM_HOME']
27
+ sh args.map{ |a| a.inspect }.join(' ')
28
+ end
29
+ end
36
30
 
37
31
 
38
32
  desc "Run all test cases"
39
33
  Rake::TestTask.new do |test|
40
34
  test.verbose = true
41
- test.test_files = ["test/*.rb"]
42
- #test.warning = true
35
+ test.test_files = ['test/*.rb']
36
+ test.warning = true
43
37
  end
44
38
 
45
39
  # Create the documentation.
46
40
  Rake::RDocTask.new do |rdoc|
47
- rdoc.main = "README"
48
- rdoc.rdoc_files.include("README", "lib/**/*.rb")
41
+ rdoc.main = 'README.rdoc'
42
+ rdoc.rdoc_files.include('README.rdoc', 'lib/**/*.rb')
49
43
  rdoc.title = "UUID generator"
50
44
  end
51
45
 
46
+
52
47
  gem = Rake::GemPackageTask.new(spec) do |pkg|
53
48
  pkg.need_tar = true
54
49
  pkg.need_zip = true
55
50
  end
56
51
 
57
52
  desc "Install the package locally"
58
- task :install=>:package do |task|
59
- system "gem", "install", "pkg/#{spec.name}-#{spec.version}.gem"
53
+ task 'install'=>['setup', 'package'] do |task|
54
+ rb_bin = File.expand_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir'])
55
+ args = [rb_bin, '-S', 'gem', 'install', "pkg/#{spec.name}-#{spec.version}.gem"]
56
+ windows = Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i
57
+ args.unshift('sudo') unless windows || ENV['GEM_HOME']
58
+ sh args.map{ |a| a.inspect }.join(' ')
60
59
  end
61
60
 
62
61
  desc "Uninstall previously installed packaged"
63
- task :uninstall do |task|
64
- system "gem", "uninstall", spec.name, "-v", spec.version.to_s
65
- end
66
-
67
-
68
- desc "Look for TODO and FIXME tags in the code"
69
- task :todo do
70
- FileList["**/*.rb"].egrep /#.*(FIXME|TODO|TBD)/
71
- end
72
-
73
-
74
- # --------------------------------------------------------------------
75
- # Creating a release
76
-
77
-
78
- namespace :upload do
79
- task :packages=>["rake:rdoc", "rake:package"] do |task|
80
- require 'rubyforge'
81
-
82
- # Read the changes for this release.
83
- pattern = /(^(\d+\.\d+(?:\.\d+)?)\s+\(\d+\/\d+\/\d+\)\s*((:?^[^\n]+\n)*))/
84
- changelog = File.read(__FILE__.pathmap("%d/CHANGELOG"))
85
- changes = changelog.scan(pattern).inject({}) { |hash, set| hash[set[1]] = set[2] ; hash }
86
- current = changes[spec.version.to_s]
87
- if !current && spec.version.to_s =~ /\.0$/
88
- current = changes[spec.version.to_s.split(".")[0..-2].join(".")]
89
- end
90
- fail "No changeset found for version #{spec.version}" unless current
91
-
92
- puts "Uploading #{spec.name} #{spec.version}"
93
- files = %w( gem tgz zip ).map { |ext| "pkg/#{spec.name}-#{spec.version}.#{ext}" }
94
- rubyforge = RubyForge.new
95
- rubyforge.login
96
- File.open(".changes", 'w'){|f| f.write(current)}
97
- rubyforge.userconfig.merge!("release_changes" => ".changes", "preformatted" => true)
98
- rubyforge.add_release spec.rubyforge_project.downcase, spec.name.downcase, spec.version, *files
99
- rm ".changes"
100
- puts "Release #{spec.version} uploaded"
101
- end
62
+ task 'uninstall' do |task|
63
+ rb_bin = File.expand_path(Config::CONFIG['ruby_install_name'], Config::CONFIG['bindir'])
64
+ args = [rb_bin, '-S', 'gem', 'install', spec.name, '-v', spec.version.to_s]
65
+ windows = Config::CONFIG['host_os'] =~ /windows|cygwin|bccwin|cygwin|djgpp|mingw|mswin|wince/i
66
+ args.unshift('sudo') unless windows || ENV['GEM_HOME']
67
+ sh args.map{ |a| a.inspect }.join(' ')
102
68
  end
103
69
 
104
- namespace :release do
105
- task :ready? do
106
- require 'highline'
107
- require 'highline/import'
108
-
109
- puts "This version: #{spec.version}"
110
- puts
111
- puts "Top 4 lines form CHANGELOG:"
112
- puts File.readlines("CHANGELOG")[0..3].map { |l| " #{l}" }
113
- puts
114
- ask("Top-entry in CHANGELOG file includes today's date?") =~ /yes/i or
115
- fail "Please update CHANGELOG to include the right date"
116
- end
117
70
 
118
- task :post do
119
- # Practical example of functional read but not comprehend code:
120
- next_version = spec.version.to_ints.zip([0, 0, 1]).map { |a| a.inject(0) { |t,i| t + i } }.join(".")
121
- puts "Updating #{VERSION_FILE} to next version number: #{next_version}"
122
- ver_file = File.read(__FILE__.pathmap("%d/#{VERSION_FILE}")).
123
- sub(/(VERSION\s*=\s*)(['"])(.*)\2/) { |line| "#{$1}#{$2}#{next_version}#{$2}" }
124
- File.open(__FILE__.pathmap("%d/#{VERSION_FILE}"), "w") { |file| file.write ver_file }
125
- puts "Adding entry to CHANGELOG"
126
- changelog = File.read(__FILE__.pathmap("%d/CHANGELOG"))
127
- File.open(__FILE__.pathmap("%d/CHANGELOG"), "w") { |file| file.write "#{next_version} (Pending)\n\n#{changelog}" }
128
- end
129
-
130
- task :meat=>["clobber", "test", "upload:packages"]
131
- end
132
-
133
- desc "Upload release to RubyForge including docs, tag SVN"
134
- task :release=>[ "release:ready?", "release:meat", "release:post" ]
135
-
136
- =begin
137
-
138
- # Handle version number.
139
- class Version
140
-
141
- PATTERN = /(\s*)VERSION.*(\d+\.\d+\.\d+)/
142
-
143
- def initialize file, new_version
144
- @file = file
145
- @version = File.open @file, "r" do |file|
146
- version = nil
147
- file.each_line do |line|
148
- match = line.match PATTERN
149
- if match
150
- version = match[2]
151
- break
152
- end
153
- end
154
- version
155
- end
156
- fail "Can't determine version number" unless @version
157
- @new_version = new_version || @version
158
- end
71
+ task 'release'=>['setup', 'test', 'package'] do
72
+
73
+ require 'rubyforge'
74
+ =begin
75
+ header = File.readlines('CHANGELOG').first
76
+ version, date = header.scan(/(\d+\.\d+\.\d+) \((\d{4}-\d{2}-\d{2})\)/).first
77
+ fail "CHANGELOG and spec version numbers do not match: #{version} != #{spec.version}" unless version == spec.version.to_s
78
+ today = Time.now.strftime('%Y-%m-%d')
79
+ fail "CHANGELOG entry not using today's date: #{date} != #{today}" unless date = today
159
80
 
160
- def changed?
161
- @version != @new_version
162
- end
163
-
164
- def number
165
- @version
166
- end
167
-
168
- def next
169
- @new_version
170
- end
171
-
172
- def update
173
- puts "Updating to version #{@new_version}"
174
- copy = "#{@file}.new"
175
- open @file, "r" do |input|
176
- open copy, "w" do |output|
177
- input.each_line do |line|
178
- match = line.match PATTERN
179
- if match
180
- output.puts "#{match[1]}VERSION = '#{@new_version}'"
181
- else
182
- output.puts line
183
- end
184
- end
185
- end
186
- end
187
- mv copy, @file
188
- @version = @new_version
81
+ =end
82
+ changes = File.read('CHANGELOG')[/\d+.\d+.\d+.*\n((:?^[^\n]+\n)*)/]
83
+ File.open '.changes', 'w' do |file|
84
+ file.write changes
189
85
  end
190
86
 
87
+ puts "Uploading #{spec.name} #{spec.version}"
88
+ files = Dir['pkg/*.{gem,tgz,zip}']
89
+ rubyforge = RubyForge.new
90
+ rubyforge.configure
91
+ rubyforge.login
92
+ rubyforge.userconfig.merge! 'release_changes'=>'.changes', 'preformatted'=>true
93
+ rubyforge.add_release spec.rubyforge_project.downcase, spec.name.downcase, spec.version, *files
94
+ rm_f '.changes'
95
+ puts "Release #{spec.version} uploaded"
191
96
  end
192
- version = Version.new "lib/uuid.rb", ENV["version"]
193
-
194
-
195
97
 
196
-
197
- desc "Make a new release"
198
- task :release => [:test, :prerelease, :clobber, :update_version, :package] do
199
- puts
200
- puts "**************************************************************"
201
- puts "* Release #{version.number} Complete."
202
- puts "* Packages ready to upload."
203
- puts "**************************************************************"
204
- puts
205
- end
206
-
207
- task :prerelease do
208
- if !version.changed? && ENV["reuse"] != version.number
209
- fail "Current version is #{version.number}, must specify reuse=ver to reuse existing version"
210
- end
98
+ task 'clobber' do
99
+ rm_f '.changes'
211
100
  end
212
-
213
- task :update_version => [:prerelease] do
214
- if !version.changed?
215
- puts "No version change ... skipping version update"
216
- else
217
- version.update
218
- end
219
- end
220
- =end
@@ -2,394 +2,280 @@
2
2
  # = uuid.rb - UUID generator
3
3
  #
4
4
  # Author:: Assaf Arkin assaf@labnotes.org
5
+ # Eric Hodel drbrain@segment7.net
5
6
  # Documentation:: http://trac.labnotes.org/cgi-bin/trac.cgi/wiki/Ruby/UuidGenerator
6
- # Copyright:: Copyright (c) 2005,2007 Assaf Arkin
7
+ # Copyright:: Copyright (c) 2005-2008 Assaf Arkin, Eric Hodel
7
8
  # License:: MIT and/or Creative Commons Attribution-ShareAlike
8
9
 
9
-
10
+ require 'fileutils'
10
11
  require 'thread'
11
- require 'yaml'
12
- require 'singleton'
13
- require 'logger'
12
+ require 'tmpdir'
13
+
14
+ require 'rubygems'
15
+ require 'macaddr'
14
16
 
15
17
 
16
- # == Generating UUIDs
18
+ ##
19
+ # = Generating UUIDs
17
20
  #
18
- # Call UUID.new to generate and return a new UUID. The method returns a string in one of three
19
- # formats. The default format is 36 characters long, and contains the 32 hexadecimal octets and
20
- # hyphens separating the various value parts. The <tt>:compact</tt> format omits the hyphens,
21
- # while the <tt>:urn</tt> format adds the <tt>:urn:uuid</tt> prefix.
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.
22
26
  #
23
27
  # For example:
24
- # 10.times do
25
- # p UUID.new
26
- # end
27
28
  #
28
- # ---
29
- # == UUIDs in Brief
29
+ # uuid = UUID.new
30
+ #
31
+ # 10.times do
32
+ # p uuid.generate
33
+ # end
34
+ #
35
+ # = UUIDs in Brief
30
36
  #
31
- # UUID (universally unique identifier) are guaranteed to be unique across time and space.
37
+ # UUID (universally unique identifier) are guaranteed to be unique across time
38
+ # and space.
32
39
  #
33
- # A UUID is 128 bit long, and consists of a 60-bit time value, a 16-bit sequence number and
34
- # a 48-bit node identifier.
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.
35
42
  #
36
- # The time value is taken from the system clock, and is monotonically incrementing. However,
37
- # since it is possible to set the system clock backward, a sequence number is added. The
38
- # sequence number is incremented each time the UUID generator is started. The combination
39
- # guarantees that identifiers created on the same machine are unique with a high degree of
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
40
48
  # probability.
41
49
  #
42
- # Note that due to the structure of the UUID and the use of sequence number, there is no
43
- # guarantee that UUID values themselves are monotonically incrementing. The UUID value
44
- # cannot itself be used to sort based on order of creation.
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.
45
54
  #
46
- # To guarantee that UUIDs are unique across all machines in the network, use the IEEE 802
47
- # MAC address of the machine's network interface card as the node identifier. Network interface
48
- # cards have unique MAC address that are 47-bit long (the last bit acts as a broadcast flag).
49
- # Use +ipconfig+ (Windows), or +ifconfig+ (Unix) to find the MAC address (aka physical address)
50
- # of a network card. It takes the form of six pairs of hexadecimal digits, separated by hypen or
51
- # colon, e.g. '<tt>08-0E-46-21-4B-35</tt>'
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.
52
58
  #
53
59
  # For more information see {RFC 4122}[http://www.ietf.org/rfc/rfc4122.txt].
54
- #
55
- # == Configuring the UUID generator
56
- #
57
- # The UUID generator requires a state file which maintains the MAC address and next sequence
58
- # number to use. By default, the UUID generator will use the file <tt>uuid.state</tt> contained
59
- # in the current directory.
60
- #
61
- # Use UUID.config to specify a different location for the UUID state file. If the UUID state file
62
- # does not exist, you can create one manually, or use UUID.config with the options <tt>:sequence</tt>
63
- # and <tt>:mac_addr</tt>.
64
- #
65
- # A UUID state file looks like:
66
- # ---
67
- # last_clock: "0x28227f76122d80"
68
- # mac_addr: 08-0E-46-21-4B-35
69
- # sequence: "0x1639"
70
- #
71
- #--
72
- # === Time-based UUID
73
- #
74
- # The UUID specification prescribes the following format for representing UUIDs. Four octets encode
75
- # the low field of the time stamp, two octects encode the middle field of the timestamp, and two
76
- # octets encode the high field of the timestamp with the version number. Two octets encode the
77
- # clock sequence number and six octets encode the unique node identifier.
78
- #
79
- # The timestamp is a 60 bit value holding UTC time as a count of 100 nanosecond intervals since
80
- # October 15, 1582. UUIDs generated in this manner are guaranteed not to roll over until 3400 AD.
81
- #
82
- # The clock sequence is used to help avoid duplicates that could arise when the clock is set backward
83
- # in time or if the node ID changes. Although the system clock is guaranteed to be monotonic, the
84
- # system clock is not guaranteed to be monotonic across system failures. The UUID cannot be sure
85
- # that no UUIDs were generated with timestamps larger than the current timestamp.
86
- #
87
- # If the clock sequence can be determined at initialization, it is incremented by one. The clock sequence
88
- # MUST be originally (i.e. once in the lifetime of a system) initialized to a random number to minimize the
89
- # correlation across systems. The initial value must not be correlated to the node identifier.
90
- #
91
- # The node identifier must be unique for each UUID generator. This is accomplished using the IEEE 802
92
- # network card address. For systems with multiple IEEE 802 addresses, any available address can be used.
93
- # For systems with no IEEE address, a 47 bit random value is used and the multicast bit is set so it will
94
- # never conflict with addresses obtained from network cards.
95
- #
96
- # === UUID state file
97
- #
98
- # The UUID state is contained in the UUID state file. The file name can be specified when configuring
99
- # the UUID generator with UUID.config. The default is to use the file +uuid.state+ in the current directory.
100
- #
101
- # The UUID state file is read once when the UUID generator is first used (or configured). The sequence
102
- # number contained in the UUID is read and used, and the state file is updated to the next sequence
103
- # number. The MAC address is also read from the state file. The current clock time (in 100ns resolution)
104
- # is stored in the state file whenever the sequence number is updated, but is never read.
105
- #
106
- # If the UUID generator detects that the system clock has been moved backwards, it will obtain a new
107
- # sequence in the same manner. So it is possible that the UUID state file will be updated while the
108
- # application is running.
109
- #++
110
- module UUID
111
-
112
- VERSION = '1.0.4'
113
-
114
- # Regular expression to identify a 36 character UUID. Can be used for a partial match.
115
- REGEXP = /[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}/
116
60
 
117
- # Regular expression to identify a 36 character UUID. Can only be used for a full match.
118
- REGEXP_FULL = /^[[:xdigit:]]{8}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{4}[:-][[:xdigit:]]{12}$/
61
+ class UUID
119
62
 
120
- # Regular expression to identify a 32 character UUID, no hyphens (compact) full match.
121
- REGEXP_COMPACT = /^[[:xdigit:]]{32}$/
63
+ VERSION = '2.0.0'
122
64
 
123
- # Default state file.
124
- STATE_FILE = 'uuid.state'
65
+ ##
66
+ # Clock multiplier. Converts Time (resolution: seconds) to UUID clock
67
+ # (resolution: 10ns)
68
+ CLOCK_MULTIPLIER = 10000000
125
69
 
126
- # Clock multiplier. Converts Time (resolution: seconds) to UUID clock (resolution: 10ns)
127
- CLOCK_MULTIPLIER = 10000000 #:nodoc:
128
-
129
- # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time ticks.
130
- CLOCK_GAPS = 100000 #:nodoc:
70
+ ##
71
+ # Clock gap is the number of ticks (resolution: 10ns) between two Ruby Time
72
+ # ticks.
73
+ CLOCK_GAPS = 100000
131
74
 
75
+ ##
132
76
  # Version number stamped into the UUID to identify it as time-based.
133
- VERSION_CLOCK = 0x0100 #:nodoc:
77
+ VERSION_CLOCK = 0x0100
134
78
 
79
+ ##
135
80
  # Formats supported by the UUID generator.
81
+ #
82
+ # <tt>:default</tt>:: Produces 36 characters, including hyphens separating
83
+ # the UUID value parts
84
+ # <tt>:compact</tt>:: Produces a 32 digits (hexadecimal) value with no
85
+ # hyphens
86
+ # <tt>:urn</tt>:: Adds the prefix <tt>urn:uuid:</tt> to the default format
136
87
  FORMATS = {
137
- :compact=>'%08x%04x%04x%04x%012x',
138
- :default=>'%08x-%04x-%04x-%04x-%012x', :urn=>'urn:uuid:%08x-%04x-%04x-%04x-%012x' } #:nodoc:
88
+ :compact => '%08x%04x%04x%04x%012x',
89
+ :default => '%08x-%04x-%04x-%04x-%012x',
90
+ :urn => 'urn:uuid:%08x-%04x-%04x-%04x-%012x',
91
+ }
139
92
 
140
- # Length (in characters) of UUIDs generated for each of the formats.
141
- FORMATS_LENGTHS = { :compact=>32, :default=>36, :urn=>45 } #:nodoc:
93
+ ##
94
+ # MAC address (48 bits), sequence number and last clock
95
+ STATE_FILE_FORMAT = 'SLLQ'
142
96
 
143
- ERROR_INVALID_SEQUENCE = "Invalid sequence number: found '%s', expected 4 hexdecimal digits" #:nodoc:
97
+ @state_file = nil
98
+ @mode = nil
99
+ @uuid = nil
144
100
 
145
- ERROR_NOT_A_SEQUENCE = "Not a sequence number: expected integer between 0 and 0xFFFF" #:nodoc:
101
+ ##
102
+ # The access mode of the state file. Set it with state_file.
146
103
 
147
- ERROR_INVALID_MAC_ADDR = "Invalid MAC address: found '%s', expected a number in the format XX-XX-XX-XX-XX-XX" #:nodoc:
104
+ def self.mode
105
+ @mode
106
+ end
148
107
 
149
- INFO_INITIALIZED = "Initialized UUID generator with sequence number 0x%04x and MAC address %s" #:nodoc:
108
+ ##
109
+ # Generates a new UUID string using +format+. See FORMATS for a list of
110
+ # supported formats.
150
111
 
151
- ERROR_INITIALIZED_RANDOM_1 = "Initialized UUID generator with random sequence number/MAC address." #:nodoc:
112
+ def self.generate(format = :default)
113
+ @uuid ||= new
114
+ @uuid.generate format
115
+ end
152
116
 
153
- ERROR_INITIALIZED_RANDOM_2 = "UUIDs are not guaranteed to be unique. Please create a uuid.state file as soon as possible." #:nodoc:
117
+ ##
118
+ # Creates an empty state file in /var/tmp/ruby-uuid or the windows common
119
+ # application data directory using mode 0644. Call with a different mode
120
+ # before creating a UUID generator if you want to open access beyond your
121
+ # user by default.
122
+ #
123
+ # If the default state dir is not writable, UUID falls back to ~/.ruby-uuid.
124
+ #
125
+ # State files are not portable across machines.
126
+ def self.state_file(mode = 0644)
127
+ return @state_file if @state_file
154
128
 
155
- IFCONFIG_PATTERN = /[^:\-](?:[0-9A-Za-z][0-9A-Za-z][:\-]){5}[0-9A-Za-z][0-9A-Za-z][^:\-]/ #:nodoc:
129
+ @mode = mode
156
130
 
157
- class << self
131
+ begin
132
+ require 'Win32API'
158
133
 
159
- # Configures the UUID generator. Use this method to specify the UUID state file, logger, etc.
160
- #
161
- # The method accepts the following options:
162
- # * <tt>:state_file</tt> -- Specifies the location of the state file. If missing, the default
163
- # is <tt>uuid.state</tt>
164
- # * <tt>:logger<tt> -- The UUID generator will use this logger to report the state information (optional).
165
- # * <tt>:sequence</tt> -- Specifies the sequence number (0 to 0xFFFF) to use. Required to
166
- # create a new state file, ignored if the state file already exists.
167
- # * <tt>:mac_addr</tt> -- Specifies the MAC address (xx-xx-xx-xx-xx) to use. Required to
168
- # create a new state file, ignored if the state file already exists.
169
- #
170
- # For example, to create a new state file:
171
- # UUID.config :state_file=>'my-uuid.state', :sequence=>rand(0x10000), :mac_addr=>'0C-0E-35-41-60-65'
172
- # To use an existing state file and log to +STDOUT+:
173
- # UUID.config :state_file=>'my-uuid.state', :logger=>Logger.new(STDOUT)
174
- #
175
- # :call-seq:
176
- # UUID.config(config)
177
- #
178
- def config(options)
179
- options ||= {}
180
- @@mutex.synchronize do
181
- @@logger = options[:logger]
182
- next_sequence options
183
- end
184
- end
134
+ csidl_common_appdata = 0x0023
135
+ path = 0.chr * 260
136
+ get_folder_path = Win32API.new('shell32', 'SHGetFolderPath', 'LLLLP', 'L')
137
+ get_folder_path.call 0, csidl_common_appdata, 0, 1, path
185
138
 
186
- # Create a uuid.state file by finding the IEEE 802 NIC MAC address for this machine.
187
- # Works for UNIX (ifconfig) and Windows (ipconfig). Creates the uuid.state file in the
188
- # current directory.
189
- def setup()
190
- file = File.expand_path(File.join(Dir.pwd, STATE_FILE))
191
- if File.exist? file
192
- puts "UUID: Found an existing UUID state file: #{file}"
193
- else
194
- puts "UUID: No UUID state file found, attempting to create one for you:"
195
- # Run ifconfig for UNIX, or ipconfig for Windows.
196
- config = ""
197
- Kernel.open("|ifconfig") { |input| input.each_line { |line| config << line } } rescue nil
198
- Kernel.open("|ipconfig /all") { |input| input.each_line { |line| config << line } } rescue nil
199
-
200
- addresses = config.scan(IFCONFIG_PATTERN).map { |addr| addr[1..-2] }
201
- if addresses.empty?
202
- puts "Could not find any IEEE 802 NIC MAC addresses for this machine."
203
- puts "You need to create the uuid.state file manually."
204
- else
205
- puts "Found the following IEEE 802 NIC MAC addresses on your computer:"
206
- addresses.each { |addr| puts " #{addr}" }
207
- puts "Selecting the first address #{addresses[0]} for use in your UUID state file."
208
- File.open file, "w" do |output|
209
- output.puts "mac_addr: \"#{addresses[0]}\""
210
- output.puts format("sequence: \"0x%04x\"", rand(0x10000))
211
- end
212
- puts "Created a new UUID state file: #{file}"
213
- end
214
- end
215
- file
139
+ state_dir = File.join(path.strip)
140
+ rescue LoadError
141
+ state_dir = File.join('', 'var', 'tmp')
216
142
  end
217
143
 
218
- private
219
-
220
- def next_sequence(config = nil)
221
- # If called to advance the sequence number (config is nil), we have a state file that we're able to use.
222
- # If called from configuration, use the specified or default state file.
223
- state_file = (config && config[:state_file]) || @@state_file
224
-
225
- unless state_file
226
- if File.exist?(STATE_FILE)
227
- state_file = STATE_FILE
228
- else
229
- file = File.expand_path(File.join(Dir.pwd, STATE_FILE))
230
- state_file = File.exist?(file) ? file : setup
231
- end
232
- end
233
- begin
234
- File.open state_file, "r+" do |file|
235
- # Lock the file for exclusive access, just to make sure it's not being read while we're
236
- # updating its contents.
237
- file.flock(File::LOCK_EX)
238
- state = YAML::load file
239
- # Get the sequence number. Must be a valid 16-bit hexadecimal value.
240
- sequence = state['sequence']
241
- if sequence
242
- raise RuntimeError, format(ERROR_INVALID_SEQUENCE, sequence) unless sequence.is_a?(String) && sequence =~ /[0-9a-fA-F]{4}/
243
- sequence = sequence.hex & 0xFFFF
244
- else
245
- sequence = rand(0x10000)
246
- end
247
- # Get the MAC address. Must be 6 pairs of hexadecimal octets. Convert MAC address into
248
- # a 48-bit value with the higher bit being zero.
249
- mac_addr = state['mac_addr']
250
- raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless
251
- mac_addr.is_a?(String) && mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
252
- mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
253
-
254
- # If everything is OK, proceed to the next step. Grab the sequence number and store
255
- # the new state. Start at beginning of file, and truncate file when done.
256
- @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
257
- file.pos = 0
258
- dump file, true
259
- file.truncate file.pos
260
- end
261
- # Initialized.
262
- if @@logger
263
- @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
264
- else
265
- warn "UUID: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
266
- end
267
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
268
- rescue Errno::ENOENT=>error
269
- if !config
270
- # Generate random values.
271
- @@mac_hex, @@sequence, @@state_file = rand(0x800000000000) | 0xF00000000000, rand(0x10000), nil
272
- # Initialized.
273
- if @@logger
274
- @@logger.error ERROR_INITIALIZED_RANDOM_1
275
- @@logger.error ERROR_INITIALIZED_RANDOM_2
276
- else
277
- warn "UUID: " + ERROR_INITIALIZED_RANDOM_1
278
- warn "UUID: " + ERROR_INITIALIZED_RANDOM_2
279
- end
280
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
281
- else
282
- # No state file. If we were called for configuration with valid sequence number and MAC address,
283
- # attempt to create state file. See code above for how we interpret these values.
284
- sequence = config[:sequence]
285
- raise RuntimeError, format(ERROR_NOT_A_SEQUENCE, sequence) unless sequence.is_a?(Integer)
286
- sequence &= 0xFFFF
287
- mac_addr = config[:mac_addr]
288
- raise RuntimeError, format(ERROR_INVALID_MAC_ADDR, mac_addr) unless
289
- mac_addr.is_a?(String) && mac_addr =~ /([0-9a-fA-F]{2}[:\-]){5}[0-9a-fA-F]{2}/
290
- mac_hex = mac_addr.scan(/[0-9a-fA-F]{2}/).join.hex & 0x7FFFFFFFFFFF
291
- File.open state_file, "w" do |file|
292
- file.flock(File::LOCK_EX)
293
- @@mac_addr, @@mac_hex, @@sequence, @@state_file = mac_addr, mac_hex, sequence, state_file
294
- file.pos = 0
295
- dump file, true
296
- file.truncate file.pos
297
- end
298
- # Initialized.
299
- if @@logger
300
- @@logger.info format(INFO_INITIALIZED, @@sequence, @@mac_addr)
301
- else
302
- warn "UUID: " + format(INFO_INITIALIZED, @@sequence, @@mac_addr)
303
- end
304
- @@last_clock, @@drift = (Time.new.to_f * CLOCK_MULTIPLIER).to_i, 0
305
- end
306
- rescue Exception=>error
307
- @@last_clock = nil
308
- raise error
309
- end
144
+ if File.writable?(state_dir) then
145
+ @state_file = File.join(state_dir, 'ruby-uuid')
146
+ else
147
+ @state_file = File.expand_path(File.join('~', '.ruby-uuid'))
310
148
  end
311
149
 
312
- def dump(file, plus_one)
313
- # Gets around YAML weirdness, like ont storing the MAC address as a string.
314
- if @@sequence && @@mac_addr
315
- file.puts "mac_addr: \"#{@@mac_addr}\""
316
- file.puts "sequence: \"0x%04x\"" % ((plus_one ? @@sequence + 1 : @@sequence) & 0xFFFF)
317
- file.puts "last_clock: \"0x%x\"" % (@@last_clock || (Time.new.to_f * CLOCK_MULTIPLIER).to_i)
150
+ @state_file
151
+ end
152
+
153
+ ##
154
+ # Create a new UUID generator. You really only need to do this once.
155
+ def initialize
156
+ @drift = 0
157
+ @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
158
+ @mutex = Mutex.new
159
+
160
+ if File.exist?(self.class.state_file) then
161
+ next_sequence
162
+ else
163
+ @mac = Mac.addr.gsub(':', '').hex & 0x7FFFFFFFFFFF
164
+ @sequence = rand 0x10000
165
+
166
+ open_lock 'w' do |io|
167
+ write_state io
318
168
  end
319
169
  end
320
170
  end
321
171
 
322
- @@mutex = Mutex.new
323
- @@last_clock = nil
324
- @@logger = nil
325
- @@state_file = nil
172
+ ##
173
+ # Generates a new UUID string using +format+. See FORMATS for a list of
174
+ # supported formats.
175
+ def generate(format = :default)
176
+ template = FORMATS[format]
326
177
 
327
- # Generates and returns a new UUID string.
328
- #
329
- # The argument +format+ specifies which formatting template to use:
330
- # * <tt>:default</tt> -- Produces 36 characters, including hyphens separating the UUID value parts
331
- # * <tt>:compact</tt> -- Produces a 32 digits (hexadecimal) value with no hyphens
332
- # * <tt>:urn</tt> -- Aadds the prefix <tt>urn:uuid:</tt> to the <tt>:default</tt> format
333
- #
334
- # For example:
335
- # print UUID.new :default
336
- # or just
337
- # print UUID.new
338
- #
339
- # :call-seq:
340
- # UUID.new([format]) -> string
341
- #
342
- def new(format = nil)
343
- # Determine which format we're using for the UUID string.
344
- template = FORMATS[format || :default] or raise RuntimeError, "I don't know the format '#{format}'"
345
-
346
- # The clock must be monotonically increasing. The clock resolution is at best 100 ns
347
- # (UUID spec), but practically may be lower (on my setup, around 1ms). If this method
348
- # is called too fast, we don't have a monotonically increasing clock, so the solution is
349
- # to just wait.
350
- # It is possible for the clock to be adjusted backwards, in which case we would end up
351
- # blocking for a long time. When backward clock is detected, we prevent duplicates by
352
- # asking for a new sequence number and continue with the new clock.
353
- clock = @@mutex.synchronize do
354
- # Initialize UUID generator if not already initialized. Uninitizlied UUID generator has no
355
- # last known clock.
356
- next_sequence unless @@last_clock
178
+ raise ArgumentError, "invalid UUID format #{format.inspect}" unless template
179
+
180
+ # The clock must be monotonically increasing. The clock resolution is at
181
+ # best 100 ns (UUID spec), but practically may be lower (on my setup,
182
+ # around 1ms). If this method is called too fast, we don't have a
183
+ # monotonically increasing clock, so the solution is to just wait.
184
+ #
185
+ # It is possible for the clock to be adjusted backwards, in which case we
186
+ # would end up blocking for a long time. When backward clock is detected,
187
+ # we prevent duplicates by asking for a new sequence number and continue
188
+ # with the new clock.
189
+
190
+ clock = @mutex.synchronize do
357
191
  clock = (Time.new.to_f * CLOCK_MULTIPLIER).to_i & 0xFFFFFFFFFFFFFFF0
358
- if clock > @@last_clock
359
- @@drift = 0
360
- @@last_clock = clock
361
- elsif clock = @@last_clock
362
- drift = @@drift += 1
363
- if drift < 10000
364
- @@last_clock += 1
192
+
193
+ if clock > @last_clock then
194
+ @drift = 0
195
+ @last_clock = clock
196
+ elsif clock == @last_clock then
197
+ drift = @drift += 1
198
+
199
+ if drift < 10000 then
200
+ @last_clock += 1
365
201
  else
366
202
  Thread.pass
367
203
  nil
368
204
  end
369
205
  else
370
206
  next_sequence
371
- @@last_clock = clock
207
+ @last_clock = clock
372
208
  end
373
- end while not clock
374
- sprintf template, clock & 0xFFFFFFFF, (clock >> 32)& 0xFFFF, ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
375
- @@sequence & 0xFFFF, @@mac_hex & 0xFFFFFFFFFFFF
209
+ end until clock
210
+
211
+ template % [
212
+ clock & 0xFFFFFFFF,
213
+ (clock >> 32) & 0xFFFF,
214
+ ((clock >> 48) & 0xFFFF | VERSION_CLOCK),
215
+ @sequence & 0xFFFF,
216
+ @mac & 0xFFFFFFFFFFFF
217
+ ]
376
218
  end
377
219
 
378
- alias uuid new
379
- module_function :uuid, :new
220
+ ##
221
+ # Updates the state file with a new sequence number.
222
+ def next_sequence
223
+ open_lock 'r+' do |io|
224
+ @mac, @sequence, @last_clock = read_state(io)
380
225
 
381
- end
226
+ io.rewind
227
+ io.truncate 0
228
+
229
+ @sequence += 1
230
+
231
+ write_state io
232
+ end
233
+ rescue Errno::ENOENT
234
+ open_lock 'w' do |io|
235
+ write_state io
236
+ end
237
+ ensure
238
+ @last_clock = (Time.now.to_f * CLOCK_MULTIPLIER).to_i
239
+ @drift = 0
240
+ end
382
241
 
242
+ def inspect
243
+ mac = ("%08x" % @mac).scan(/[0-9a-f]{2}/).join(':')
244
+ "MAC: #{mac} Sequence: #{@sequence}"
245
+ end
246
+
247
+ protected
383
248
 
384
- class ActiveRecord::Base
385
- class << self
386
- def uuid_primary_key()
387
- before_create { |record| record.id = UUID.new unless record.id }
249
+ ##
250
+ # Open the state file with an exclusive lock and access mode +mode+.
251
+ def open_lock(mode)
252
+ File.open self.class.state_file, mode, self.class.mode do |io|
253
+ begin
254
+ io.flock File::LOCK_EX
255
+ yield io
256
+ ensure
257
+ io.flock File::LOCK_UN
258
+ end
388
259
  end
389
260
  end
390
- end if defined?(ActiveRecord)
391
261
 
262
+ ##
263
+ # Read the state from +io+
264
+ def read_state(io)
265
+ mac1, mac2, seq, last_clock = io.read(32).unpack(STATE_FILE_FORMAT)
266
+ mac = (mac1 << 32) + mac2
392
267
 
393
- if __FILE__ == $0
394
- UUID.setup
268
+ return mac, seq, last_clock
269
+ end
270
+
271
+
272
+ ##
273
+ # Write that state to +io+
274
+ def write_state(io)
275
+ mac2 = @mac & 0xffffffff
276
+ mac1 = (@mac >> 32) & 0xffff
277
+
278
+ io.write [mac1, mac2, @sequence, @last_clock].pack(STATE_FILE_FORMAT)
279
+ end
280
+
395
281
  end