torid 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/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: []
|