torid 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +46 -0
- data/HISTORY.md +5 -0
- data/LICENSE +16 -0
- data/Manifest.txt +18 -0
- data/README.md +100 -0
- data/Rakefile +17 -0
- data/lib/torid.rb +22 -0
- data/lib/torid/clock.rb +65 -0
- data/lib/torid/generator.rb +82 -0
- data/lib/torid/uuid.rb +138 -0
- data/tasks/default.rake +276 -0
- data/tasks/this.rb +214 -0
- data/test/test_clock.rb +20 -0
- data/test/test_generator.rb +42 -0
- data/test/test_helper.rb +5 -0
- data/test/test_torid.rb +12 -0
- data/test/test_uuid.rb +58 -0
- data/test/test_version.rb +11 -0
- metadata +135 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e6dacf1ffa7be13c30286d37766f06b04cfef520
|
4
|
+
data.tar.gz: eb17f8af92781af966f8575148346990b0c055a5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ab397dcae732b089f5f5eba45c17dd1362fb4928a48fa30b9275c226e91cacd37f6c6248dd6dc26cc1d6ebe56b188f4d6e313670312a8f6811cea60f6c7b5f7e
|
7
|
+
data.tar.gz: 0265870e23051dfce0d1953c336447bd82e45129d34ca3156fcc4f4f701d329faad1e0f0b03612b4188189481734ba625f94ac2895f8dae54a1276fcff9048e2
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Hi there!
|
2
|
+
|
3
|
+
I see you are interested in contributing. That is wonderful. I love
|
4
|
+
contributions.
|
5
|
+
|
6
|
+
I guarantee that there are bugs in this software. And I guarantee that there is
|
7
|
+
a feature you want that is not in here yet. As such, any and all bugs reports
|
8
|
+
are gratefully accepted, bugfixes even more so. Helping out with bugs is the
|
9
|
+
easiest way to contribute.
|
10
|
+
|
11
|
+
|
12
|
+
## The Quick Version
|
13
|
+
|
14
|
+
* Have a [GitHub Account][].
|
15
|
+
* Search the [GitHub Issues][] and see if your issue already present. If so
|
16
|
+
add your comments, :thumbsup:, etc.
|
17
|
+
* Issue not there? Not a problem, open up a [new issue][].
|
18
|
+
* **Bug reports** please be as detailed as possible. Include:
|
19
|
+
* full ruby engine and version: `ruby -e 'puts RUBY_DESCRIPTION'`
|
20
|
+
* operating system and version
|
21
|
+
* version of torid `ruby -rubygems -e "require 'torid'; puts Torid::VERSION"`
|
22
|
+
* as much detail about the bug as possible so I can replicate it. Feel free
|
23
|
+
to link in a [gist][]
|
24
|
+
* **New Feature**
|
25
|
+
* What the new feature should do.
|
26
|
+
* What benefit the new feature brings to the project.
|
27
|
+
* Fork the [repo][].
|
28
|
+
* Create a new branch for your issue: `git checkout -b issue/my-issue`
|
29
|
+
* Lovingly craft your contribution:
|
30
|
+
* `rake develop` to get started, or if you prefer bundler `rake develop:using_bundler && bundle`.
|
31
|
+
* `rake test` to run tests
|
32
|
+
* Make sure that `rake test` passes. It's important, I said it twice.
|
33
|
+
* Add yourself to the contributors section below.
|
34
|
+
* Submit your [pull request][].
|
35
|
+
|
36
|
+
# Contributors
|
37
|
+
|
38
|
+
* [Jeremy Hinegardner](https://github.com/copiousfreetime)
|
39
|
+
* [Kevin Barnes](https://github.com/vinbarnes)
|
40
|
+
|
41
|
+
[GitHub Account]: https://github.com/signup/free "GitHub Signup"
|
42
|
+
[GitHub Issues]: https://github.com/copiousfreetime/torid/issues "Torid Issues"
|
43
|
+
[new issue]: https://github.com/copiousfreetime/torid/issues/new "New Torid Issue"
|
44
|
+
[gist]: https://gist.github.com/ "New Gist"
|
45
|
+
[repo]: https://github.com/copiousfreetime/torid "Torid Repo"
|
46
|
+
[pull request]: https://help.github.com/articles/using-pull-requests "Using Pull Requests"
|
data/HISTORY.md
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
ISC LICENSE - http://opensource.org/licenses/isc-license.txt
|
2
|
+
|
3
|
+
Copyright (c) 2013 Jeremy Hinegardner
|
4
|
+
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
16
|
+
|
data/Manifest.txt
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
CONTRIBUTING.md
|
2
|
+
HISTORY.md
|
3
|
+
LICENSE
|
4
|
+
Manifest.txt
|
5
|
+
README.md
|
6
|
+
Rakefile
|
7
|
+
lib/torid.rb
|
8
|
+
lib/torid/clock.rb
|
9
|
+
lib/torid/generator.rb
|
10
|
+
lib/torid/uuid.rb
|
11
|
+
tasks/default.rake
|
12
|
+
tasks/this.rb
|
13
|
+
test/test_clock.rb
|
14
|
+
test/test_generator.rb
|
15
|
+
test/test_helper.rb
|
16
|
+
test/test_torid.rb
|
17
|
+
test/test_uuid.rb
|
18
|
+
test/test_version.rb
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
## Torid
|
2
|
+
|
3
|
+
* [Homepage](https://github.com/copiousfreetime/torid/)
|
4
|
+
* [Github Project](https://github.com/copiousfreetime/torid)
|
5
|
+
* email jeremy at copiousfreetime dot org
|
6
|
+
|
7
|
+
## DESCRIPTION
|
8
|
+
|
9
|
+
Temporally Ordered IDs. Generate universally unique identifiers (UUID)
|
10
|
+
that sort lexically in time order.
|
11
|
+
|
12
|
+
Torid exists to solve the problem of generating UUIDs that when ordered
|
13
|
+
lexically, they are also ordered temporally. I needed a way to generate ids for
|
14
|
+
events that are entering a system with the following criteria:
|
15
|
+
|
16
|
+
1. Fast ID generation
|
17
|
+
2. No central coordinating server/system
|
18
|
+
3. No local storage
|
19
|
+
4. Library code, that is multiple apps on the same machine can use the same code
|
20
|
+
and they will not generate duplicate ids
|
21
|
+
5. Eventually stored in a UUID field in a database. So 128bit ids are totally
|
22
|
+
fine.
|
23
|
+
|
24
|
+
The IDs that Torid generates are 128bit IDs made up of 2, 64bit parts.
|
25
|
+
|
26
|
+
* 64bit microsecond level UNIX timestamp
|
27
|
+
* 64bit hash of the system hostname, process id and a random value.
|
28
|
+
|
29
|
+
## EXAMPLES
|
30
|
+
|
31
|
+
#### Using the defaults
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
require 'torid'
|
35
|
+
|
36
|
+
uuid = Torid.uuid
|
37
|
+
uuid.to_s # => "0004fda4-318e-f380-5a45-5321cd065b02"
|
38
|
+
uuid.bytes # => "\x00\x04\xFD\xA41\x8E\xF3\x80ZES!\xCD\x06[\x02"
|
39
|
+
```
|
40
|
+
|
41
|
+
#### Using your own instance of a Generator
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
require 'torid'
|
45
|
+
|
46
|
+
generator = Torid::Generator.new
|
47
|
+
uuid = generator.next
|
48
|
+
|
49
|
+
uuid.to_s # => "0004fda4-3f42-3d01-4731-5a4aa8ddd6c3"
|
50
|
+
uuid.bytes # => "\x00\x04\xFD\xA4?B=\x01G1ZJ\xA8\xDD\xD6\xC3"
|
51
|
+
```
|
52
|
+
|
53
|
+
## CREDITS / RESOURCES
|
54
|
+
|
55
|
+
The vast majority of the credit and research stems from:
|
56
|
+
|
57
|
+
* [jondot's](https://github.com/jondot) blog post on [Fast ID Generation](http://blog.paracode.com/2012/04/16/fast-id-generation-part-1/) served to solidify my thoughts on the criteria I needed in an ID generation system.
|
58
|
+
* This let me to [Boundary's Flake](http://boundary.com/blog/2012/01/12/flake-a-decentralized-k-ordered-unique-id-generator-in-erlang/)
|
59
|
+
* [James Golick's](https://github.com/jamesgolick) [lexical_uuid](https://github.com/jamesgolick/lexical_uuid), which if I had found a day earlier, I might be using instead of creating this.
|
60
|
+
|
61
|
+
You could consider Torid to be a reimplementation of [lexical_uuid](https://github.com/jamesgolick/lexical_uuid). It definately steals some code from it and [simple_uuid](https://github.com/cassandra-rb/simple_uuid)
|
62
|
+
|
63
|
+
Blog posts around ID generation:
|
64
|
+
|
65
|
+
* [Wikipedia UUID](http://en.wikipedia.org/wiki/Universally_unique_identifier)
|
66
|
+
* [RFC 4122](http://tools.ietf.org/html/rfc4122)
|
67
|
+
* [Fast ID Generation Part I](http://blog.paracode.com/2012/04/16/fast-id-generation-part-1/)
|
68
|
+
* [Boundary's Flake Project](http://boundary.com/blog/2012/01/12/flake-a-decentralized-k-ordered-unique-id-generator-in-erlang/)
|
69
|
+
* [Flickr's Ticket Server](http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/)
|
70
|
+
* [Twitter Snowflake](https://blog.twitter.com/2010/announcing-snowflake)
|
71
|
+
* [Sharding & ID's at Instagram](http://instagram-engineering.tumblr.com/post/10853187575/sharding-ids-at-instagram)
|
72
|
+
|
73
|
+
Libraries implementing similar approaches:
|
74
|
+
|
75
|
+
* [simple_uuid](https://github.com/cassandra-rb/simple_uuid)
|
76
|
+
* [uuid](https://github.com/assaf/uuid)
|
77
|
+
* [flake](http://github.com/boundary/flake)
|
78
|
+
* [BSON Object ID](https://github.com/mongodb/bson-ruby/blob/master/lib/bson/object_id.rb) and in [C](https://github.com/mongodb/bson-ruby/blob/master/ext/bson/native.c)
|
79
|
+
* [ffi-uuid](https://github.com/mmullis/ffi-uuid)
|
80
|
+
* [lexical_uuid](https://github.com/jamesgolick/lexical_uuid)
|
81
|
+
|
82
|
+
## ISC LICENSE
|
83
|
+
|
84
|
+
http://opensource.org/licenses/isc-license.txt
|
85
|
+
|
86
|
+
Copyright (c) 2014 Jeremy Hinegardner
|
87
|
+
|
88
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
89
|
+
purpose with or without fee is hereby granted, provided that the above
|
90
|
+
copyright notice
|
91
|
+
and this permission notice appear in all copies.
|
92
|
+
|
93
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
94
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
95
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
96
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
97
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
98
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
99
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
100
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# vim: syntax=ruby
|
2
|
+
load 'tasks/this.rb'
|
3
|
+
|
4
|
+
This.name = "torid"
|
5
|
+
This.author = "Jeremy Hinegardner"
|
6
|
+
This.email = "jeremy@copiousfreetime.org"
|
7
|
+
This.homepage = "http://github.com/copiousfreetime/#{ This.name }"
|
8
|
+
|
9
|
+
This.ruby_gemspec do |spec|
|
10
|
+
spec.add_dependency( 'fnv', '~> 0.2' )
|
11
|
+
|
12
|
+
spec.add_development_dependency( 'rake' , '~> 10.1')
|
13
|
+
spec.add_development_dependency( 'minitest' , '~> 5.0' )
|
14
|
+
spec.add_development_dependency( 'rdoc' , '~> 4.0' )
|
15
|
+
end
|
16
|
+
|
17
|
+
load 'tasks/default.rake'
|
data/lib/torid.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# Torid - Temporally Ordered IDs. Generate universally unique identifiers (UUID)
|
2
|
+
# that sort lexically in time order.
|
3
|
+
module Torid
|
4
|
+
# Public: The Version of the Torid library as a String
|
5
|
+
VERSION = "1.0.0"
|
6
|
+
|
7
|
+
# Public: return the next Torid::UUID from the default Generator
|
8
|
+
#
|
9
|
+
# This is just a shortcut to Torid::Generator.next
|
10
|
+
#
|
11
|
+
# Example:
|
12
|
+
#
|
13
|
+
# Torid.uuid # => Torid::UUID
|
14
|
+
#
|
15
|
+
# Returns a Torid::UUID
|
16
|
+
def self.uuid
|
17
|
+
Torid::Generator.next
|
18
|
+
end
|
19
|
+
end
|
20
|
+
require 'torid/clock'
|
21
|
+
require 'torid/uuid'
|
22
|
+
require 'torid/generator'
|
data/lib/torid/clock.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'thread'
|
2
|
+
module Torid
|
3
|
+
# Internal: A source for non-duplicate microsecond timestamps.
|
4
|
+
#
|
5
|
+
# Clock generates microsecond UNIX timestamps and guarantees that once a Clock
|
6
|
+
# instance is created, `Clock#tick` will never return the same value twice for
|
7
|
+
# that instance.
|
8
|
+
#
|
9
|
+
# This is effectively a reimplementation of
|
10
|
+
# https://github.com/jamesgolick/lexical_uuid/blob/master/lib/increasing_microsecond_clock.rb
|
11
|
+
# combined with
|
12
|
+
# https://github.com/jamesgolick/lexical_uuid/blob/master/lib/time_ext.rb
|
13
|
+
#
|
14
|
+
class Clock
|
15
|
+
|
16
|
+
# Internal: Return the current microsecond UNIX timstamp
|
17
|
+
#
|
18
|
+
# Example:
|
19
|
+
#
|
20
|
+
# Clock.stamp => 1404774462369341
|
21
|
+
#
|
22
|
+
# Returns an Integer
|
23
|
+
def self.stamp
|
24
|
+
now = Time.now
|
25
|
+
(now.to_f * 1_000_000).floor
|
26
|
+
end
|
27
|
+
|
28
|
+
# Internal: Create a new Clock
|
29
|
+
#
|
30
|
+
# prev_stamp - An initial value for the previous timestamp (default:
|
31
|
+
# Clock.stamp)
|
32
|
+
# mutex - The synchronizing object to use
|
33
|
+
def initialize( prev_stamp = Clock.stamp, mutex = Mutex.new )
|
34
|
+
@prev_stamp = prev_stamp
|
35
|
+
@mutex = mutex
|
36
|
+
end
|
37
|
+
|
38
|
+
# Internal: Return the next tick of the clock.
|
39
|
+
#
|
40
|
+
# Return the next tick of the clock, which will be a Clock.stamp value. This
|
41
|
+
# method will continue to return ever increasing values from when it was
|
42
|
+
# created.
|
43
|
+
#
|
44
|
+
# Returns an Integer.
|
45
|
+
def tick
|
46
|
+
@mutex.synchronize do
|
47
|
+
new_stamp = Clock.stamp
|
48
|
+
@prev_stamp = if new_stamp > @prev_stamp then
|
49
|
+
new_stamp
|
50
|
+
else
|
51
|
+
@prev_stamp + 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Internal: The default instance to use for generating ticks
|
57
|
+
@instance = Clock.new
|
58
|
+
|
59
|
+
# Internal: Return the next `#tick` of the default Clock.
|
60
|
+
#
|
61
|
+
def self.tick
|
62
|
+
@instance.tick
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'fnv'
|
2
|
+
require 'socket'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'torid/clock'
|
5
|
+
require 'torid/uuid'
|
6
|
+
|
7
|
+
module Torid
|
8
|
+
# Public: A class that will generate unique identifiers.
|
9
|
+
#
|
10
|
+
# Torid::Generator is the class that is used to generate Torid::UUID
|
11
|
+
# instances.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# Torid::Generator.next # => Torid::UUID
|
16
|
+
#
|
17
|
+
# generator = Torid::Generator.new
|
18
|
+
# generator.next # => Torid::UUID
|
19
|
+
#
|
20
|
+
class Generator
|
21
|
+
# Internal: The Clock instance used to get 64bit timestamps
|
22
|
+
attr_reader :clock
|
23
|
+
|
24
|
+
# Internal: The Node id of this instance
|
25
|
+
attr_reader :node_id
|
26
|
+
|
27
|
+
# Internal: Create a new Torid UUID Generator
|
28
|
+
#
|
29
|
+
# clock - an object that responds to `#tick` and returns a 64bit integer.
|
30
|
+
# (default: Torid::Clock)
|
31
|
+
# node_id - the 64bit node id of this node. (default: Generator.node_id)
|
32
|
+
#
|
33
|
+
def initialize( clock = Torid::Clock, node_id = Generator.node_id )
|
34
|
+
@clock = clock
|
35
|
+
@node_id = node_id
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Return the next UUID from this generator
|
39
|
+
#
|
40
|
+
# Returns Torid::UUID
|
41
|
+
def next
|
42
|
+
Torid::UUID.new( @clock.tick, @node_id )
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Internal: Generate a unique node identifier.
|
47
|
+
#
|
48
|
+
# Uses the first hostname of the system, the process id, some random bytes
|
49
|
+
# and hashes them all together using the non-cryptographic FNV hash.
|
50
|
+
#
|
51
|
+
# http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
52
|
+
#
|
53
|
+
# This method is copeid from
|
54
|
+
# https://github.com/jamesgolick/lexical_uuid/blob/master/lib/lexical_uuid.rb#L14
|
55
|
+
# with the random bytes added by me.
|
56
|
+
#
|
57
|
+
# Returns a 64 bit Integer
|
58
|
+
def self.create_node_id
|
59
|
+
hostname = Socket.gethostbyname( Socket.gethostname ).first
|
60
|
+
pid = Process.pid
|
61
|
+
random = SecureRandom.hex( 16 )
|
62
|
+
FNV.new.fnv1a_64("#{hostname}-#{pid}-#{random}")
|
63
|
+
end
|
64
|
+
|
65
|
+
# Internal: The default generator used by the system.
|
66
|
+
@instance = ::Torid::Generator.new( Torid::Clock, Generator.create_node_id )
|
67
|
+
|
68
|
+
# Public: Return the node id of the default Generator instance
|
69
|
+
#
|
70
|
+
# Returns a 64 bit Integer
|
71
|
+
def self.node_id
|
72
|
+
@instance.node_id
|
73
|
+
end
|
74
|
+
|
75
|
+
# Public: Return the next UUID from the default Generator
|
76
|
+
#
|
77
|
+
# Returns a Torid::UUID
|
78
|
+
def self.next
|
79
|
+
@instance.next
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/torid/uuid.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
module Torid
|
2
|
+
# Public: Represents a UUID generated by Torid
|
3
|
+
#
|
4
|
+
# Torid::UUID wraps 2 64bit Integer values and can convert them back and forth
|
5
|
+
# between raw bytes and the canonical UUID form of 32 lowercase hexadecimal
|
6
|
+
# lowercase hexadecimal digits, displayed in five groups separated by hyphens,
|
7
|
+
# in the form 8-4-4-4-12 for a total of 36 characters (32 alphanumeric
|
8
|
+
# characters and four hyphens)
|
9
|
+
#
|
10
|
+
# Since internally, Torid::UUID's represent a 64bit microsecond timestamp and
|
11
|
+
# a 'node_id', those data fields are also able to be returned as a Time
|
12
|
+
# instance or an Integer respectively.
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# uuid = Torid.uuid
|
17
|
+
# uuid.to_s # => "0004fda3-8c73-5e0f-bae4-e9c86e3684a5"
|
18
|
+
# uuid.bytes # => "\x00\x04\xFD\xA3\x8Cs^\x0F\xBA\xE4\xE9\xC8n6\x84\xA5"
|
19
|
+
#
|
20
|
+
# uuid.timestamp # => Time
|
21
|
+
# uuid.node_id # => Integer
|
22
|
+
class UUID
|
23
|
+
|
24
|
+
# Public: Create a Torid::UUID from an existing String.
|
25
|
+
#
|
26
|
+
# The String can either be a 16 byte binary string, or a 36byte hexadecimal
|
27
|
+
# UUID in the 8-4-4-4-12 format.
|
28
|
+
#
|
29
|
+
# Examples
|
30
|
+
#
|
31
|
+
# Torid::UUID.from( "0004fda3-8c73-5e0f-bae4-e9c86e3684a5" ) # => Torid::UUID
|
32
|
+
# Torid::UUID.from( "\x00\x04\xFD\xA3\x8Cs^\x0F\xBA\xE4\xE9\xC8n6\x84\xA5" ) # => Torid::UUID
|
33
|
+
#
|
34
|
+
# Returns a Torid::UUID
|
35
|
+
# Raises ArgumentError if the String is not convertable to a UUID.
|
36
|
+
def self.from( str )
|
37
|
+
case str.bytesize
|
38
|
+
when 36
|
39
|
+
from_string( str )
|
40
|
+
when 16
|
41
|
+
from_bytes( str )
|
42
|
+
else
|
43
|
+
raise ArgumentError, "UUID can only be loaded from a 16 byte binary string or a 36 byte formatted UUID string."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Internal: Create a new UUID from an existing string in the 8-4-4-4-12 format
|
48
|
+
#
|
49
|
+
# Copied from lexical_uuid
|
50
|
+
#
|
51
|
+
# Returns a Torid::UUID
|
52
|
+
def self.from_string( str )
|
53
|
+
hex = str.split('-').join
|
54
|
+
bytes = Array( hex ).pack("H32")
|
55
|
+
from_bytes( bytes )
|
56
|
+
end
|
57
|
+
|
58
|
+
# Internal: Create a new UUID from an existing 16 byte String
|
59
|
+
#
|
60
|
+
# Copied from lexical_uuid
|
61
|
+
#
|
62
|
+
# Returns a Torid::UUID
|
63
|
+
def self.from_bytes( bytes )
|
64
|
+
time_high, time_low, node_high, node_low = bytes.unpack("NNNN")
|
65
|
+
timestamp = ( time_high << 32 ) | time_low
|
66
|
+
node_id = ( node_high << 32 ) | node_low
|
67
|
+
new( timestamp, node_id )
|
68
|
+
end
|
69
|
+
|
70
|
+
# Public: The 64bit microsecond UNIX timestamp
|
71
|
+
attr_reader :timestamp
|
72
|
+
|
73
|
+
# Public: The 64bit node id
|
74
|
+
attr_reader :node_id
|
75
|
+
|
76
|
+
# Internal: Create a new UUID.
|
77
|
+
#
|
78
|
+
# UUID's should only be created by calling one of the public methods that
|
79
|
+
# generate id's. See `Torid.uuid` or `Torid::Generator.next`. This
|
80
|
+
# constructor should not be called by users of this library.
|
81
|
+
#
|
82
|
+
# timestamp - an Integer value representing UNIX timestamp in microseconds
|
83
|
+
# node_id - an Integer value representing the unique node id where this
|
84
|
+
# UUID is generatoed
|
85
|
+
#
|
86
|
+
def initialize( timestamp = nil, node_id = nil )
|
87
|
+
@timestamp = timestamp
|
88
|
+
@node_id = node_id
|
89
|
+
@bytes = nil
|
90
|
+
@time = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# Public: Return the Time value the internal microsecond timestamp
|
94
|
+
# represents.
|
95
|
+
#
|
96
|
+
# Examples
|
97
|
+
#
|
98
|
+
# uuid.time # => Time
|
99
|
+
#
|
100
|
+
# Returns a Time instance
|
101
|
+
def time
|
102
|
+
@time ||= Time.at( timestamp / 1_000_000.0 )
|
103
|
+
end
|
104
|
+
|
105
|
+
# Public: Return the UUID as 16 bytes of raw data.
|
106
|
+
#
|
107
|
+
# Copied from lexical_uuid
|
108
|
+
#
|
109
|
+
# Examples
|
110
|
+
#
|
111
|
+
# uuid.bytes # => "\x00\x04\xFD\xA3\x8Cs^\x0F\xBA\xE4\xE9\xC8n6\x84\xA5"
|
112
|
+
#
|
113
|
+
# Returns a binary String
|
114
|
+
def bytes
|
115
|
+
@bytes ||= [ @timestamp >> 32,
|
116
|
+
@timestamp & 0xFFFF_FFFF,
|
117
|
+
@node_id >> 32,
|
118
|
+
@node_id & 0xFFFF_FFFF ].pack("NNNN")
|
119
|
+
end
|
120
|
+
|
121
|
+
# Public: Return the hexadecimal UUID string representation. This is the
|
122
|
+
# standard 8-4-4-4-12 UUID string representation.
|
123
|
+
#
|
124
|
+
# Copied from simple_uuid via lexical_uuid.
|
125
|
+
#
|
126
|
+
# Examples
|
127
|
+
#
|
128
|
+
# uuid.to_s # => "0004fda3-8c73-5e0f-bae4-e9c86e3684a5"
|
129
|
+
#
|
130
|
+
# Returns a String
|
131
|
+
def to_s
|
132
|
+
elements = bytes.unpack("NnnCCa6")
|
133
|
+
node = elements[-1].unpack('C*')
|
134
|
+
elements[-1] = '%02x%02x%02x%02x%02x%02x' % node
|
135
|
+
"%08x-%04x-%04x-%02x%02x-%s" % elements
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/tasks/default.rake
ADDED
@@ -0,0 +1,276 @@
|
|
1
|
+
# vim: syntax=ruby
|
2
|
+
require 'rake/clean'
|
3
|
+
require 'digest'
|
4
|
+
#------------------------------------------------------------------------------
|
5
|
+
# If you want to Develop on this project just run 'rake develop' and you'll
|
6
|
+
# have all you need to get going. If you want to use bundler for development,
|
7
|
+
# then run 'rake develop:using_bundler'
|
8
|
+
#------------------------------------------------------------------------------
|
9
|
+
namespace :develop do
|
10
|
+
|
11
|
+
# Install all the development and runtime dependencies of this gem using the
|
12
|
+
# gemspec.
|
13
|
+
task :default do
|
14
|
+
require 'rubygems/dependency_installer'
|
15
|
+
installer = ::Gem::DependencyInstaller.new
|
16
|
+
|
17
|
+
This.set_coverage_gem
|
18
|
+
|
19
|
+
puts "Installing gem depedencies needed for development"
|
20
|
+
This.platform_gemspec.dependencies.each do |dep|
|
21
|
+
if dep.matching_specs.empty? then
|
22
|
+
puts "Installing : #{dep}"
|
23
|
+
installer.install dep
|
24
|
+
else
|
25
|
+
puts "Skipping : #{dep} -> already installed #{dep.matching_specs.first.full_name}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
puts "\n\nNow run 'rake test'"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Create a Gemfile that just references the gemspec
|
32
|
+
file 'Gemfile' => :gemspec do
|
33
|
+
File.open( "Gemfile", "w+" ) do |f|
|
34
|
+
f.puts "# DO NOT EDIT - This file is automatically generated"
|
35
|
+
f.puts "# Make changes to Manifest.txt and/or Rakefile and regenerate"
|
36
|
+
f.puts 'source "https://rubygems.org/"'
|
37
|
+
f.puts 'gemspec'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "Create a bundler Gemfile"
|
42
|
+
task :using_bundler => 'Gemfile' do
|
43
|
+
puts "Now you can 'bundle'"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Gemfiles are build artifacts
|
47
|
+
CLOBBER << FileList['Gemfile*']
|
48
|
+
end
|
49
|
+
desc "Boostrap development"
|
50
|
+
task :develop => "develop:default"
|
51
|
+
|
52
|
+
#------------------------------------------------------------------------------
|
53
|
+
# Minitest - standard TestTask
|
54
|
+
#------------------------------------------------------------------------------
|
55
|
+
begin
|
56
|
+
require 'rake/testtask'
|
57
|
+
Rake::TestTask.new( :test ) do |t|
|
58
|
+
t.ruby_opts = %w[ -w -rubygems ]
|
59
|
+
t.libs = %w[ lib spec test ]
|
60
|
+
t.pattern = "{test,spec}/**/{test_*,*_spec}.rb"
|
61
|
+
end
|
62
|
+
|
63
|
+
task :test_requirements
|
64
|
+
task :test => :test_requirements
|
65
|
+
task :default => :test
|
66
|
+
rescue LoadError
|
67
|
+
This.task_warning( 'test' )
|
68
|
+
end
|
69
|
+
|
70
|
+
#------------------------------------------------------------------------------
|
71
|
+
# RDoc - standard rdoc rake task, although we must make sure to use a more
|
72
|
+
# recent version of rdoc since it is the one that has 'tomdoc' markup
|
73
|
+
#------------------------------------------------------------------------------
|
74
|
+
begin
|
75
|
+
gem 'rdoc' # otherwise we get the wrong task from stdlib
|
76
|
+
require 'rdoc/task'
|
77
|
+
RDoc::Task.new do |t|
|
78
|
+
t.markup = 'tomdoc'
|
79
|
+
t.rdoc_dir = 'doc'
|
80
|
+
t.main = 'README.md'
|
81
|
+
t.title = "#{This.name} #{This.version}"
|
82
|
+
t.rdoc_files.include( FileList['*.{rdoc,md,txt}'], FileList['ext/**/*.c'],
|
83
|
+
FileList['lib/**/*.rb'] )
|
84
|
+
end
|
85
|
+
rescue StandardError, LoadError
|
86
|
+
This.task_warning( 'rdoc' )
|
87
|
+
end
|
88
|
+
|
89
|
+
#------------------------------------------------------------------------------
|
90
|
+
# Coverage - optional code coverage, rcov for 1.8 and simplecov for 1.9, so
|
91
|
+
# for the moment only rcov is listed.
|
92
|
+
#------------------------------------------------------------------------------
|
93
|
+
if RUBY_VERSION < "1.9.0"
|
94
|
+
begin
|
95
|
+
require 'rcov/rcovtask'
|
96
|
+
Rcov::RcovTask.new( 'coverage' ) do |t|
|
97
|
+
t.libs << 'spec'
|
98
|
+
t.pattern = 'spec/**/*_spec.rb'
|
99
|
+
t.verbose = true
|
100
|
+
t.rcov_opts << "-x ^/" # remove all the global files
|
101
|
+
t.rcov_opts << "--sort coverage" # so we see the worst files at the top
|
102
|
+
end
|
103
|
+
rescue LoadError
|
104
|
+
This.task_warning( 'rcov' )
|
105
|
+
end
|
106
|
+
else
|
107
|
+
begin
|
108
|
+
require 'simplecov'
|
109
|
+
desc 'Run tests with code coverage'
|
110
|
+
task :coverage do
|
111
|
+
ENV['COVERAGE'] = 'true'
|
112
|
+
Rake::Task[:test].execute
|
113
|
+
end
|
114
|
+
CLOBBER << FileList["coverage"]
|
115
|
+
rescue LoadError
|
116
|
+
This.task_warning( 'simplecov' )
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#------------------------------------------------------------------------------
|
121
|
+
# Manifest - We want an explicit list of thos files that are to be packaged in
|
122
|
+
# the gem. Most of this is from Hoe.
|
123
|
+
#------------------------------------------------------------------------------
|
124
|
+
namespace 'manifest' do
|
125
|
+
desc "Check the manifest"
|
126
|
+
task :check => :clean do
|
127
|
+
files = FileList["**/*", ".*"].exclude( This.exclude_from_manifest ).to_a.sort
|
128
|
+
files = files.select{ |f| File.file?( f ) }
|
129
|
+
|
130
|
+
tmp = "Manifest.tmp"
|
131
|
+
File.open( tmp, 'w' ) do |f|
|
132
|
+
f.puts files.join("\n")
|
133
|
+
end
|
134
|
+
|
135
|
+
begin
|
136
|
+
sh "diff -du Manifest.txt #{tmp}"
|
137
|
+
ensure
|
138
|
+
rm tmp
|
139
|
+
end
|
140
|
+
puts "Manifest looks good"
|
141
|
+
end
|
142
|
+
|
143
|
+
desc "Generate the manifest"
|
144
|
+
task :generate => :clean do
|
145
|
+
files = %x[ git ls-files ].split("\n").sort
|
146
|
+
files.reject! { |f| f =~ This.exclude_from_manifest }
|
147
|
+
File.open( "Manifest.txt", "w" ) do |f|
|
148
|
+
f.puts files.join("\n")
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
#------------------------------------------------------------------------------
|
154
|
+
# Fixme - look for fixmes and report them
|
155
|
+
#------------------------------------------------------------------------------
|
156
|
+
namespace :fixme do
|
157
|
+
task :default => 'manifest:check' do
|
158
|
+
This.manifest.each do |file|
|
159
|
+
next if file == __FILE__
|
160
|
+
next unless file =~ %r/(txt|rb|md|rdoc|css|html|xml|css)\Z/
|
161
|
+
puts "FIXME: Rename #{file}" if file =~ /fixme/i
|
162
|
+
IO.readlines( file ).each_with_index do |line, idx|
|
163
|
+
prefix = "FIXME: #{file}:#{idx+1}".ljust(42)
|
164
|
+
puts "#{prefix} => #{line.strip}" if line =~ /fixme/i
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def fixme_project_root
|
170
|
+
This.project_path( '../fixme' )
|
171
|
+
end
|
172
|
+
|
173
|
+
def fixme_project_path( subtree )
|
174
|
+
fixme_project_root.join( subtree )
|
175
|
+
end
|
176
|
+
|
177
|
+
def local_fixme_files
|
178
|
+
This.manifest.select { |p| p =~ %r|^tasks/| }
|
179
|
+
end
|
180
|
+
|
181
|
+
def outdated_fixme_files
|
182
|
+
local_fixme_files.reject do |local|
|
183
|
+
upstream = fixme_project_path( local )
|
184
|
+
Digest::SHA256.file( local ) == Digest::SHA256.file( upstream )
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def fixme_up_to_date?
|
189
|
+
outdated_fixme_files.empty?
|
190
|
+
end
|
191
|
+
|
192
|
+
desc "See if the fixme tools are outdated"
|
193
|
+
task :outdated => :release_check do
|
194
|
+
if fixme_up_to_date? then
|
195
|
+
puts "Fixme files are up to date."
|
196
|
+
else
|
197
|
+
outdated_fixme_files.each do |f|
|
198
|
+
puts "#{f} is outdated"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
desc "Update outdated fixme files"
|
204
|
+
task :update => :release_check do
|
205
|
+
if fixme_up_to_date? then
|
206
|
+
puts "Fixme files are already up to date."
|
207
|
+
else
|
208
|
+
puts "Updating fixme files:"
|
209
|
+
outdated_fixme_files.each do |local|
|
210
|
+
upstream = fixme_project_path( local )
|
211
|
+
puts " * #{local}"
|
212
|
+
FileUtils.cp( upstream, local )
|
213
|
+
end
|
214
|
+
puts "Use your git commands as appropriate."
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
desc "Look for fixmes and report them"
|
219
|
+
task :fixme => "fixme:default"
|
220
|
+
|
221
|
+
#------------------------------------------------------------------------------
|
222
|
+
# Gem Specification
|
223
|
+
#------------------------------------------------------------------------------
|
224
|
+
# Really this is only here to support those who use bundler
|
225
|
+
desc "Build the #{This.name}.gemspec file"
|
226
|
+
task :gemspec do
|
227
|
+
File.open( This.gemspec_file, "wb+" ) do |f|
|
228
|
+
f.puts "# DO NOT EDIT - This file is automatically generated"
|
229
|
+
f.puts "# Make changes to Manifest.txt and/or Rakefile and regenerate"
|
230
|
+
f.write This.platform_gemspec.to_ruby
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# the gemspec is also a dev artifact and should not be kept around.
|
235
|
+
CLOBBER << This.gemspec_file.to_s
|
236
|
+
|
237
|
+
# .rbc files from ruby 2.0
|
238
|
+
CLOBBER << FileList["**/*.rbc"]
|
239
|
+
|
240
|
+
# The standard gem packaging task, everyone has it.
|
241
|
+
require 'rubygems/package_task'
|
242
|
+
::Gem::PackageTask.new( This.platform_gemspec ) do
|
243
|
+
# nothing
|
244
|
+
end
|
245
|
+
|
246
|
+
#------------------------------------------------------------------------------
|
247
|
+
# Release - the steps we go through to do a final release, this is pulled from
|
248
|
+
# a compbination of mojombo's rakegem, hoe and hoe-git
|
249
|
+
#
|
250
|
+
# 1) make sure we are on the master branch
|
251
|
+
# 2) make sure there are no uncommitted items
|
252
|
+
# 3) check the manifest and make sure all looks good
|
253
|
+
# 4) build the gem
|
254
|
+
# 5) do an empty commit to have the commit message of the version
|
255
|
+
# 6) tag that commit as the version
|
256
|
+
# 7) push master
|
257
|
+
# 8) push the tag
|
258
|
+
# 7) pus the gem
|
259
|
+
#------------------------------------------------------------------------------
|
260
|
+
task :release_check do
|
261
|
+
unless `git branch` =~ /^\* master$/
|
262
|
+
abort "You must be on the master branch to release!"
|
263
|
+
end
|
264
|
+
unless `git status` =~ /^nothing to commit/m
|
265
|
+
abort "Nope, sorry, you have unfinished business"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
desc "Create tag v#{This.version}, build and push #{This.platform_gemspec.full_name} to rubygems.org"
|
270
|
+
task :release => [ :release_check, 'manifest:check', :gem ] do
|
271
|
+
sh "git commit --allow-empty -a -m 'Release #{This.version}'"
|
272
|
+
sh "git tag -a -m 'v#{This.version}' v#{This.version}"
|
273
|
+
sh "git push origin master"
|
274
|
+
sh "git push origin v#{This.version}"
|
275
|
+
sh "gem push pkg/#{This.platform_gemspec.full_name}.gem"
|
276
|
+
end
|
data/tasks/this.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
# Public: A Class containing all the metadata and utilities needed to manage a
|
4
|
+
# ruby project.
|
5
|
+
class ThisProject
|
6
|
+
# The name of this project
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
# The author's name
|
10
|
+
attr_accessor :author
|
11
|
+
|
12
|
+
# The email address of the author(s)
|
13
|
+
attr_accessor :email
|
14
|
+
|
15
|
+
# The homepage of this project
|
16
|
+
attr_accessor :homepage
|
17
|
+
|
18
|
+
# The regex of files to exclude from the manifest
|
19
|
+
attr_accessor :exclude_from_manifest
|
20
|
+
|
21
|
+
# The hash of Gem::Specifications keyed' by platform
|
22
|
+
attr_accessor :gemspecs
|
23
|
+
|
24
|
+
# Public: Initialize ThisProject
|
25
|
+
#
|
26
|
+
# Yields self
|
27
|
+
def initialize(&block)
|
28
|
+
@exclude_from_manifest = %r/\.(git|DS_Store)|^(doc|coverage|pkg|tmp|Gemfile(\.lock)?)|^[^\/]+\.gemspec|\.(swp|jar|bundle|so|rvmrc)$|~$/
|
29
|
+
@gemspecs = Hash.new
|
30
|
+
yield self if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: return the version of ThisProject
|
34
|
+
#
|
35
|
+
# Search the ruby files in the project looking for the one that has the
|
36
|
+
# version string in it. This does not eval any code in the project, it parses
|
37
|
+
# the source code looking for the string.
|
38
|
+
#
|
39
|
+
# Returns a String version
|
40
|
+
def version
|
41
|
+
[ "lib/#{ name }.rb", "lib/#{ name }/version.rb" ].each do |v|
|
42
|
+
path = project_path( v )
|
43
|
+
line = path.read[/^\s*VERSION\s*=\s*.*/]
|
44
|
+
if line then
|
45
|
+
return line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Internal: Return a section of an RDoc file with the given section name
|
51
|
+
#
|
52
|
+
# path - the relative path in the project of the file to parse
|
53
|
+
# section_name - the section out of the file from which to parse data
|
54
|
+
#
|
55
|
+
# Retuns the text of the section as an array of paragrphs.
|
56
|
+
def section_of( file, section_name )
|
57
|
+
re = /^[=#]+ (.*)$/
|
58
|
+
sectional = project_path( file )
|
59
|
+
parts = sectional.read.split( re )[1..-1]
|
60
|
+
parts.map! { |p| p.strip }
|
61
|
+
|
62
|
+
sections = Hash.new
|
63
|
+
Hash[*parts].each do |k,v|
|
64
|
+
sections[k] = v.split("\n\n")
|
65
|
+
end
|
66
|
+
return sections[section_name]
|
67
|
+
end
|
68
|
+
|
69
|
+
# Internal: print out a warning about the give task
|
70
|
+
def task_warning( task )
|
71
|
+
warn "WARNING: '#{task}' tasks are not defined. Please run 'rake develop'"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Internal: Return the full path to the file that is relative to the project
|
75
|
+
# root.
|
76
|
+
#
|
77
|
+
# path - the relative path of the file from the project root
|
78
|
+
#
|
79
|
+
# Returns the Pathname of the file
|
80
|
+
def project_path( *relative_path )
|
81
|
+
project_root.join( *relative_path )
|
82
|
+
end
|
83
|
+
|
84
|
+
# Internal: The absolute path of this file
|
85
|
+
#
|
86
|
+
# Returns the Pathname of this file.
|
87
|
+
def this_file_path
|
88
|
+
Pathname.new( __FILE__ ).expand_path
|
89
|
+
end
|
90
|
+
|
91
|
+
# Internal: The root directory of this project
|
92
|
+
#
|
93
|
+
# This is defined as being the directory that is in the path of this project
|
94
|
+
# that has the first Rakefile
|
95
|
+
#
|
96
|
+
# Returns the Pathname of the directory
|
97
|
+
def project_root
|
98
|
+
this_file_path.ascend do |p|
|
99
|
+
rakefile = p.join( 'Rakefile' )
|
100
|
+
return p if rakefile.exist?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Internal: Returns the contents of the Manifest.txt file as an array
|
105
|
+
#
|
106
|
+
# Returns an Array of strings
|
107
|
+
def manifest
|
108
|
+
manifest_file = project_path( "Manifest.txt" )
|
109
|
+
abort "You need a Manifest.txt" unless manifest_file.readable?
|
110
|
+
manifest_file.readlines.map { |l| l.strip }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Internal: Return the files that define the extensions
|
114
|
+
#
|
115
|
+
# Returns an Array
|
116
|
+
def extension_conf_files
|
117
|
+
manifest.grep( /extconf.rb\Z/ )
|
118
|
+
end
|
119
|
+
|
120
|
+
# Internal: Returns the gemspace associated with the current ruby platform
|
121
|
+
def platform_gemspec
|
122
|
+
gemspecs[platform]
|
123
|
+
end
|
124
|
+
|
125
|
+
def core_gemspec
|
126
|
+
Gem::Specification.new do |spec|
|
127
|
+
spec.name = name
|
128
|
+
spec.version = version
|
129
|
+
spec.author = author
|
130
|
+
spec.email = email
|
131
|
+
spec.homepage = homepage
|
132
|
+
|
133
|
+
spec.summary = summary
|
134
|
+
spec.description = description
|
135
|
+
spec.license = license
|
136
|
+
|
137
|
+
spec.files = manifest
|
138
|
+
spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
|
139
|
+
spec.test_files = spec.files.grep(/^spec/)
|
140
|
+
|
141
|
+
spec.extra_rdoc_files += spec.files.grep(/(txt|rdoc|md)$/)
|
142
|
+
spec.rdoc_options = [ "--main" , 'README.md',
|
143
|
+
"--markup", "tomdoc" ]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Internal: Return the gemspec for the ruby platform
|
148
|
+
def ruby_gemspec( core = core_gemspec, &block )
|
149
|
+
yielding_gemspec( 'ruby', core, &block )
|
150
|
+
end
|
151
|
+
|
152
|
+
# Internal: Return the gemspec for the jruby platform
|
153
|
+
def java_gemspec( core = core_gemspec, &block )
|
154
|
+
yielding_gemspec( 'java', core, &block )
|
155
|
+
end
|
156
|
+
|
157
|
+
# Internal: give an initial spec and a key, create a new gemspec based off of
|
158
|
+
# it.
|
159
|
+
#
|
160
|
+
# This will force the new gemspecs 'platform' to be that of the key, since the
|
161
|
+
# only reason you would have multiple gemspecs at this point is to deal with
|
162
|
+
# different platforms.
|
163
|
+
def yielding_gemspec( key, core )
|
164
|
+
spec = gemspecs[key] ||= core.dup
|
165
|
+
spec.platform = key
|
166
|
+
yield spec if block_given?
|
167
|
+
return spec
|
168
|
+
end
|
169
|
+
|
170
|
+
# Internal: Set the recovery gem development dependency
|
171
|
+
#
|
172
|
+
# These are dynamically set since they cannot be hard coded as there is
|
173
|
+
# no way to ship them correctly in the gemspec
|
174
|
+
#
|
175
|
+
# Returns nothing.
|
176
|
+
def set_coverage_gem
|
177
|
+
if RUBY_VERSION < "1.9.0"
|
178
|
+
platform_gemspec.add_development_dependency( 'rcov', '~> 1.0.0' )
|
179
|
+
else
|
180
|
+
platform_gemspec.add_development_dependency( 'simplecov', '~> 0.8.2' )
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Internal: Return the platform of ThisProject at the current moment in time.
|
185
|
+
def platform
|
186
|
+
(RUBY_PLATFORM == "java") ? 'java' : Gem::Platform::RUBY
|
187
|
+
end
|
188
|
+
|
189
|
+
# Internal: Return the DESCRIPTION section of the README.rdoc file
|
190
|
+
def description_section
|
191
|
+
section_of( 'README.md', 'DESCRIPTION')
|
192
|
+
end
|
193
|
+
|
194
|
+
# Internal: Return the summary text from the README
|
195
|
+
def summary
|
196
|
+
description_section.first
|
197
|
+
end
|
198
|
+
|
199
|
+
# Internal: Return the full description text from the README
|
200
|
+
def description
|
201
|
+
description_section.join(" ").tr("\n", ' ').gsub(/[{}]/,'').gsub(/\[[^\]]+\]/,'') # strip rdoc
|
202
|
+
end
|
203
|
+
|
204
|
+
def license
|
205
|
+
"ISC"
|
206
|
+
end
|
207
|
+
|
208
|
+
# Internal: The path to the gemspec file
|
209
|
+
def gemspec_file
|
210
|
+
project_path( "#{ name }.gemspec" )
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
This = ThisProject.new
|
data/test/test_clock.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'torid/clock'
|
3
|
+
|
4
|
+
module Torid
|
5
|
+
class ClockTest < ::Minitest::Test
|
6
|
+
def test_tick_is_after_stamp
|
7
|
+
stamp = Clock.stamp
|
8
|
+
tick = Clock.tick
|
9
|
+
assert( tick > stamp )
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_tick_follows_clock_not_time
|
13
|
+
stamp = Clock.stamp
|
14
|
+
future = stamp + 10_000_000
|
15
|
+
clock = Clock.new( future )
|
16
|
+
tick = clock.tick
|
17
|
+
assert_equal( future + 1, tick )
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'torid/generator'
|
3
|
+
|
4
|
+
module Torid
|
5
|
+
class GeneratorTest < ::Minitest::Test
|
6
|
+
|
7
|
+
def test_can_set_clock_on_initialize
|
8
|
+
g = Torid::Generator.new( 'clock' )
|
9
|
+
assert_equal( 'clock', g.clock )
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_can_set_node_id_on_initialize
|
13
|
+
g = Torid::Generator.new( 'clock', 'node-id' )
|
14
|
+
assert_equal( 'clock', g.clock )
|
15
|
+
assert_equal( 'node-id', g.node_id)
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_default_clock_is_set_on_initializes
|
19
|
+
g = Torid::Generator.new
|
20
|
+
assert_equal( Torid::Clock, g.clock )
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_default_node_id_is_set_on_initialize
|
24
|
+
g = Torid::Generator.new
|
25
|
+
assert( g.node_id > 0 )
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_clocks_tick_is_called_on_next
|
29
|
+
clock = ::Minitest::Mock.new
|
30
|
+
clock.expect( :tick, 42 )
|
31
|
+
g = Torid::Generator.new( clock )
|
32
|
+
g.next
|
33
|
+
clock.verify
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_default_instance_creates_uuid
|
37
|
+
uuid = Torid::Generator.next
|
38
|
+
assert_instance_of( Torid::UUID, uuid )
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
data/test/test_helper.rb
ADDED
data/test/test_torid.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'torid'
|
3
|
+
|
4
|
+
module Torid
|
5
|
+
class ToridTest < ::Minitest::Test
|
6
|
+
def test_torid_generates_uuid
|
7
|
+
uuid = Torid.uuid
|
8
|
+
assert_instance_of( Torid::UUID, uuid)
|
9
|
+
assert_equal( Torid::Generator.node_id, uuid.node_id )
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/test/test_uuid.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'torid/uuid'
|
3
|
+
|
4
|
+
module Torid
|
5
|
+
class UUIDTest < ::Minitest::Test
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@timestamp = 1404617330909742
|
9
|
+
@node_id = 42
|
10
|
+
@bytes = [ @timestamp >> 32, @timestamp & 0xFFFF_FFFF,
|
11
|
+
@node_id >> 32, @node_id & 0XFFFF_FFFF ].pack("NNNN")
|
12
|
+
@guid = "0004fd7d-f50d-e22e-0000-00000000002a"
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_round_trips_bytes
|
16
|
+
uuid = ::Torid::UUID.from( @bytes)
|
17
|
+
assert_equal( @bytes, uuid.bytes )
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_extracts_timestamp_from_bytes
|
21
|
+
uuid = ::Torid::UUID.from( @bytes)
|
22
|
+
assert_equal( @timestamp, uuid.timestamp )
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_extracts_node_id_from_bytes
|
26
|
+
uuid = ::Torid::UUID.from( @bytes)
|
27
|
+
assert_equal( @node_id , uuid.node_id )
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_round_trips_uuid_string
|
31
|
+
uuid = ::Torid::UUID.from( @guid )
|
32
|
+
assert_equal( uuid.to_s, uuid.to_s )
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_extracts_timestamp_from_uuid
|
36
|
+
uuid = ::Torid::UUID.from( @guid )
|
37
|
+
assert_equal( @timestamp, uuid.timestamp )
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_extracts_node_id_from_uuid
|
41
|
+
uuid = ::Torid::UUID.from( @guid )
|
42
|
+
assert_equal( @node_id , uuid.node_id )
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_create_from_raises_error
|
46
|
+
assert_raises( ArgumentError ) do
|
47
|
+
::Torid::UUID.from( "abcdef" )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_creates_a_time_from_uuid
|
52
|
+
time = Time.at( @timestamp / 1_000_000.0 )
|
53
|
+
uuid = ::Torid::UUID.from( @guid )
|
54
|
+
assert_equal( time, uuid.time )
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'torid'
|
3
|
+
|
4
|
+
module Torid
|
5
|
+
class VersionTest < ::Minitest::Test
|
6
|
+
def test_has_the_proper_format
|
7
|
+
assert_match( /\A\d+\.\d+\.\d+\Z/, Torid::VERSION)
|
8
|
+
assert_match( /\A\d+\.\d+\.\d+\Z/, Torid::VERSION.to_s)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: torid
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Hinegardner
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fnv
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rdoc
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
69
|
+
description: 'Temporally Ordered IDs. Generate universally unique identifiers (UUID)
|
70
|
+
that sort lexically in time order. Torid exists to solve the problem of generating
|
71
|
+
UUIDs that when ordered lexically, they are also ordered temporally. I needed a
|
72
|
+
way to generate ids for events that are entering a system with the following criteria:
|
73
|
+
1. Fast ID generation 2. No central coordinating server/system 3. No local storage
|
74
|
+
4. Library code, that is multiple apps on the same machine can use the same code and
|
75
|
+
they will not generate duplicate ids 5. Eventually stored in a UUID field in a database.
|
76
|
+
So 128bit ids are totally fine. The IDs that Torid generates are 128bit IDs made
|
77
|
+
up of 2, 64bit parts. * 64bit microsecond level UNIX timestamp * 64bit hash of the
|
78
|
+
system hostname, process id and a random value.'
|
79
|
+
email: jeremy@copiousfreetime.org
|
80
|
+
executables: []
|
81
|
+
extensions: []
|
82
|
+
extra_rdoc_files:
|
83
|
+
- CONTRIBUTING.md
|
84
|
+
- HISTORY.md
|
85
|
+
- Manifest.txt
|
86
|
+
- README.md
|
87
|
+
files:
|
88
|
+
- CONTRIBUTING.md
|
89
|
+
- HISTORY.md
|
90
|
+
- LICENSE
|
91
|
+
- Manifest.txt
|
92
|
+
- README.md
|
93
|
+
- Rakefile
|
94
|
+
- lib/torid.rb
|
95
|
+
- lib/torid/clock.rb
|
96
|
+
- lib/torid/generator.rb
|
97
|
+
- lib/torid/uuid.rb
|
98
|
+
- tasks/default.rake
|
99
|
+
- tasks/this.rb
|
100
|
+
- test/test_clock.rb
|
101
|
+
- test/test_generator.rb
|
102
|
+
- test/test_helper.rb
|
103
|
+
- test/test_torid.rb
|
104
|
+
- test/test_uuid.rb
|
105
|
+
- test/test_version.rb
|
106
|
+
homepage: http://github.com/copiousfreetime/torid
|
107
|
+
licenses:
|
108
|
+
- ISC
|
109
|
+
metadata: {}
|
110
|
+
post_install_message:
|
111
|
+
rdoc_options:
|
112
|
+
- "--main"
|
113
|
+
- README.md
|
114
|
+
- "--markup"
|
115
|
+
- tomdoc
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.2.2
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Temporally Ordered IDs. Generate universally unique identifiers (UUID) that
|
134
|
+
sort lexically in time order.
|
135
|
+
test_files: []
|