noid 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +2 -14
- data/Rakefile +5 -50
- data/VERSION +1 -1
- data/lib/noid.rb +6 -11
- data/lib/noid/minter.rb +113 -1
- data/lib/noid/template.rb +144 -0
- data/lib/noid/version.rb +9 -0
- data/noid.gemspec +18 -80
- data/spec/lib/minter_spec.rb +153 -0
- data/spec/spec_helper.rb +7 -0
- metadata +65 -153
- data/.document +0 -5
- data/doc/active_record_sample.rb +0 -6
- data/lib/noid/active_record.rb +0 -5
- data/lib/noid/active_record/provider.rb +0 -36
- data/lib/noid/base.rb +0 -154
- data/lib/noid/identifier.rb +0 -24
- data/lib/noid/identifier/anvl.rb +0 -32
- data/lib/noid/identifier/singleton.rb +0 -14
- data/lib/noid/persistence.rb +0 -4
- data/lib/noid/persistence/base.rb +0 -11
- data/lib/noid/persistence/json.rb +0 -38
- data/test/helper.rb +0 -18
- data/test/test_binding.rb +0 -42
- data/test/test_noid.rb +0 -134
- data/test/test_persistence.rb +0 -47
data/.gitignore
ADDED
data/Gemfile
CHANGED
@@ -1,16 +1,4 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
5
|
-
gem "anvl"
|
6
|
-
gem "json"
|
7
|
-
gem "backports"
|
8
2
|
|
9
|
-
#
|
10
|
-
|
11
|
-
group :development do
|
12
|
-
gem "shoulda", ">= 0"
|
13
|
-
gem "bundler", "~> 1.0.0"
|
14
|
-
gem "jeweler", "~> 1.5.1"
|
15
|
-
gem "rcov", ">= 0"
|
16
|
-
end
|
3
|
+
# Specify your gem's dependencies in noid.gemspec
|
4
|
+
gemspec
|
data/Rakefile
CHANGED
@@ -1,53 +1,8 @@
|
|
1
|
-
require '
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
require 'rake'
|
11
|
-
|
12
|
-
require 'jeweler'
|
13
|
-
Jeweler::Tasks.new do |gem|
|
14
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
-
gem.name = "noid"
|
16
|
-
gem.homepage = "http://github.com/cbeer/noid"
|
17
|
-
gem.license = "MIT"
|
18
|
-
gem.summary = %Q{Nice Opaque Identifier}
|
19
|
-
gem.description = %Q{}
|
20
|
-
gem.email = "chris@cbeer.info"
|
21
|
-
gem.authors = ["Chris Beer"]
|
22
|
-
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
-
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
-
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
-
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
-
end
|
27
|
-
Jeweler::RubygemsDotOrgTasks.new
|
28
|
-
|
29
|
-
require 'rake/testtask'
|
30
|
-
Rake::TestTask.new(:test) do |test|
|
31
|
-
test.libs << 'lib' << 'test'
|
32
|
-
test.pattern = 'test/**/test_*.rb'
|
33
|
-
test.verbose = true
|
34
|
-
end
|
35
|
-
|
36
|
-
require 'rcov/rcovtask'
|
37
|
-
Rcov::RcovTask.new do |test|
|
38
|
-
test.libs << 'test'
|
39
|
-
test.pattern = 'test/**/test_*.rb'
|
40
|
-
test.verbose = true
|
41
|
-
end
|
42
|
-
|
43
|
-
task :default => :test
|
1
|
+
require 'bundler/gem_tasks'
|
44
2
|
|
45
|
-
require '
|
46
|
-
Rake::RDocTask.new do |rdoc|
|
47
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
3
|
+
require 'rspec/core/rake_task'
|
48
4
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
5
|
+
RSpec::Core::RakeTask.new do |t|
|
6
|
+
t.rspec_opts = ["-c", "-f progress", "-r ./spec/spec_helper.rb"]
|
7
|
+
t.pattern = 'spec/**/*_spec.rb'
|
53
8
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.5.0
|
data/lib/noid.rb
CHANGED
@@ -1,13 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require 'noid/persistence/base'
|
5
|
-
require 'noid/persistence/json'
|
6
|
-
require 'noid/identifier'
|
7
|
-
require 'noid/identifier/singleton'
|
8
|
-
require 'noid/identifier/anvl'
|
9
|
-
require 'noid/active_record'
|
10
|
-
require 'noid/active_record/provider'
|
11
|
-
module Noid
|
1
|
+
require "noid/version"
|
2
|
+
require "noid/minter"
|
3
|
+
require "noid/template"
|
12
4
|
|
5
|
+
module Noid
|
6
|
+
XDIGIT = ['0','1','2','3','4','5','6','7','8','9','b','c','d','f','g','h','j','k','m','n','p','q','r','s','t','v','w','x','z']
|
7
|
+
MAX_COUNTERS = 293
|
13
8
|
end
|
data/lib/noid/minter.rb
CHANGED
@@ -1,5 +1,117 @@
|
|
1
|
+
require 'backports'
|
2
|
+
require 'backports'
|
3
|
+
|
1
4
|
module Noid
|
2
5
|
class Minter
|
3
|
-
|
6
|
+
attr_reader :seed, :seq
|
7
|
+
attr_writer :counters
|
8
|
+
|
9
|
+
def initialize args = {}
|
10
|
+
@seq = 0
|
11
|
+
seed(args[:seed], args[:seq])
|
12
|
+
@template_string = args[:template]
|
13
|
+
@max_counters = args[:max_counters]
|
14
|
+
@counters = args[:counters]
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Mint a new identifier
|
19
|
+
def mint
|
20
|
+
n = nil
|
21
|
+
|
22
|
+
|
23
|
+
case template.generator
|
24
|
+
when 's'
|
25
|
+
n = next_in_sequence
|
26
|
+
when 'z'
|
27
|
+
n = next_in_sequence
|
28
|
+
when 'r'
|
29
|
+
raise Exception if counters.size == 0
|
30
|
+
i = @rand.rand(counters.size)
|
31
|
+
next_in_sequence
|
32
|
+
n = counters[i][:value]
|
33
|
+
counters[i][:value] += 1
|
34
|
+
counters.delete_at(i) if counters[i][:value] == counters[i][:max]
|
35
|
+
end
|
36
|
+
|
37
|
+
template.mint(n)
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Noid identifier template
|
42
|
+
#
|
43
|
+
# @return Noid::Template
|
44
|
+
def template
|
45
|
+
@template ||= Noid::Template.new(@template_string)
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Is the identifier valid under the template string and checksum?
|
50
|
+
# @param [String] id
|
51
|
+
# @return bool
|
52
|
+
def valid? id
|
53
|
+
prefix = id[0..@prefix.length-1]
|
54
|
+
ch = id[@prefix.length..-1].split('')
|
55
|
+
check = ch.pop if @check
|
56
|
+
return false unless prefix == @prefix
|
57
|
+
|
58
|
+
return false unless @characters.length == ch.length
|
59
|
+
@characters.each_with_index do |c, i|
|
60
|
+
return false unless Noid::XDIGIT.include? ch[i]
|
61
|
+
return false if c == 'd' and ch[i] =~ /[^\d]/
|
62
|
+
end
|
63
|
+
|
64
|
+
return false unless check.nil? or check == checkdigit(id[0..-2])
|
65
|
+
|
66
|
+
true
|
67
|
+
end
|
68
|
+
|
69
|
+
##
|
70
|
+
# Seed the random number generator with a seed and sequence offset
|
71
|
+
# @param [Integer] seed
|
72
|
+
# @param [Integer] seq
|
73
|
+
# @return [Random]
|
74
|
+
def seed seed = nil, seq = 0
|
75
|
+
@rand = Random.new(seed) if seed
|
76
|
+
@rand ||= Random.new
|
77
|
+
@seed = @rand.seed
|
78
|
+
|
79
|
+
seq.times { @rand.rand } if seq
|
80
|
+
|
81
|
+
@rand
|
82
|
+
end
|
83
|
+
|
84
|
+
def next_in_sequence
|
85
|
+
n = @seq
|
86
|
+
@seq += 1
|
87
|
+
n
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Counters to use for quasi-random NOID sequences
|
92
|
+
def counters
|
93
|
+
return @counters if @counters
|
94
|
+
return [] unless template.generator == "r"
|
95
|
+
|
96
|
+
percounter = template.max / (@max_counters || Noid::MAX_COUNTERS) + 1
|
97
|
+
t = 0
|
98
|
+
@counters = []
|
99
|
+
|
100
|
+
while t < template.max
|
101
|
+
counter = {}
|
102
|
+
counter[:value] = t
|
103
|
+
counter[:max] = [t + percounter, template.max].min
|
104
|
+
|
105
|
+
t += percounter
|
106
|
+
|
107
|
+
@counters << counter
|
108
|
+
end
|
109
|
+
|
110
|
+
@counters
|
111
|
+
end
|
112
|
+
|
113
|
+
def dump
|
114
|
+
{ :seq => @seq, :seed => @seed, :template => template.template, :counters => Marshal.load(Marshal.dump(counters)) }
|
115
|
+
end
|
4
116
|
end
|
5
117
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Noid
|
2
|
+
class Template
|
3
|
+
attr_reader :template
|
4
|
+
|
5
|
+
# @param [String] template A Template is a coded string of the form Prefix.Mask that governs how identifiers will be minted.
|
6
|
+
def initialize template
|
7
|
+
@template = template
|
8
|
+
end
|
9
|
+
|
10
|
+
def mint n
|
11
|
+
str = prefix
|
12
|
+
str += n2xdig(n)
|
13
|
+
str += checkdigit(str) if checkdigit?
|
14
|
+
|
15
|
+
str
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid? str
|
19
|
+
return false unless str[0..prefix.length] == prefix
|
20
|
+
|
21
|
+
if generator == 'z'
|
22
|
+
str[prefix.length..-1].length > 2
|
23
|
+
else
|
24
|
+
str[prefix.length..-1].length == characters.length
|
25
|
+
end
|
26
|
+
|
27
|
+
characters.each_with_index do |c, i|
|
28
|
+
case c
|
29
|
+
when 'e'
|
30
|
+
return false unless Noid::XDIGIT.include? str[prefix.length + i]
|
31
|
+
when 'd'
|
32
|
+
return false unless str[prefix.length + i] =~ /\d/
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return false unless checkdigit(str[0..-2]) == str.split('').last if checkdigit?
|
37
|
+
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# identifier prefix string
|
43
|
+
def prefix
|
44
|
+
@prefix ||= @template.split('.').first
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# identifier mask string
|
49
|
+
def mask
|
50
|
+
@mask ||= @template.split('.').last
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# generator type to use: r, s, z
|
55
|
+
def generator
|
56
|
+
@generator ||= mask[0..0]
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# sequence pattern: e (extended), d (digit)
|
61
|
+
def characters
|
62
|
+
@characters ||= begin
|
63
|
+
if checkdigit?
|
64
|
+
mask[1..-2]
|
65
|
+
else
|
66
|
+
mask[1..-1]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# should generated identifiers have a checkdigit?
|
73
|
+
def checkdigit?
|
74
|
+
mask.split('').last == 'k'
|
75
|
+
end
|
76
|
+
|
77
|
+
##
|
78
|
+
# calculate a checkdigit for the str
|
79
|
+
# @param [String] str
|
80
|
+
# @return [String] checkdigit
|
81
|
+
def checkdigit str
|
82
|
+
Noid::XDIGIT[str.split('').map { |x| Noid::XDIGIT.index(x).to_i }.each_with_index.map { |n, idx| n*(idx+1) }.inject { |sum, n| sum += n } % Noid::XDIGIT.length ]
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# minimum sequence value
|
87
|
+
def min
|
88
|
+
@min ||= 0
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# maximum sequence value for the template
|
93
|
+
def max
|
94
|
+
@max ||= begin
|
95
|
+
case generator
|
96
|
+
when 'z'
|
97
|
+
nil
|
98
|
+
else
|
99
|
+
characters.split('').map { |x| character_space(x) }.compact.inject(1) { |total, x| total *= x }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
protected
|
106
|
+
##
|
107
|
+
# total size of a given template character value
|
108
|
+
# @param [String] c
|
109
|
+
def character_space c
|
110
|
+
case c
|
111
|
+
when 'e'
|
112
|
+
Noid::XDIGIT.length
|
113
|
+
when 'd'
|
114
|
+
10
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# convert a minter position to a noid string under this template
|
120
|
+
# @param [Integer] n
|
121
|
+
# @return [String]
|
122
|
+
def n2xdig n
|
123
|
+
xdig = characters.reverse.split('').map do |c|
|
124
|
+
value = n % character_space(c)
|
125
|
+
n = n / character_space(c)
|
126
|
+
Noid::XDIGIT[value]
|
127
|
+
end.compact.join('')
|
128
|
+
|
129
|
+
if generator == 'z'
|
130
|
+
c = characters.split('').last
|
131
|
+
while n > 0
|
132
|
+
value = n % character_space(c)
|
133
|
+
n = n / character_space(c)
|
134
|
+
xdig += Noid::XDIGIT[value]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
raise Exception if n > 0
|
139
|
+
|
140
|
+
xdig.reverse
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
data/lib/noid/version.rb
ADDED
data/noid.gemspec
CHANGED
@@ -1,87 +1,25 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "noid/version"
|
5
4
|
|
6
5
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
9
|
-
|
10
|
-
s.
|
11
|
-
s.
|
12
|
-
s.
|
6
|
+
s.name = "noid"
|
7
|
+
s.version = Noid::VERSION
|
8
|
+
s.authors = ["Chris Beer"]
|
9
|
+
s.email = ["chris@cbeer.info"]
|
10
|
+
s.homepage = "http://github.com/microservices/noid"
|
11
|
+
s.summary = %q{Nice Opaque Identifier}
|
13
12
|
s.description = %q{}
|
14
|
-
s.email = %q{chris@cbeer.info}
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE.txt",
|
17
|
-
"README.rdoc"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
"Gemfile",
|
22
|
-
"LICENSE.txt",
|
23
|
-
"README.rdoc",
|
24
|
-
"Rakefile",
|
25
|
-
"VERSION",
|
26
|
-
"doc/active_record_sample.rb",
|
27
|
-
"lib/noid.rb",
|
28
|
-
"lib/noid/active_record.rb",
|
29
|
-
"lib/noid/active_record/provider.rb",
|
30
|
-
"lib/noid/base.rb",
|
31
|
-
"lib/noid/identifier.rb",
|
32
|
-
"lib/noid/identifier/anvl.rb",
|
33
|
-
"lib/noid/identifier/singleton.rb",
|
34
|
-
"lib/noid/minter.rb",
|
35
|
-
"lib/noid/persistence.rb",
|
36
|
-
"lib/noid/persistence/base.rb",
|
37
|
-
"lib/noid/persistence/json.rb",
|
38
|
-
"noid.gemspec",
|
39
|
-
"test/helper.rb",
|
40
|
-
"test/test_binding.rb",
|
41
|
-
"test/test_noid.rb",
|
42
|
-
"test/test_persistence.rb"
|
43
|
-
]
|
44
|
-
s.homepage = %q{http://github.com/cbeer/noid}
|
45
|
-
s.licenses = ["MIT"]
|
46
|
-
s.require_paths = ["lib"]
|
47
|
-
s.rubygems_version = %q{1.3.7}
|
48
|
-
s.summary = %q{Nice Opaque Identifier}
|
49
|
-
s.test_files = [
|
50
|
-
"test/helper.rb",
|
51
|
-
"test/test_binding.rb",
|
52
|
-
"test/test_noid.rb",
|
53
|
-
"test/test_persistence.rb"
|
54
|
-
]
|
55
13
|
|
56
|
-
|
57
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
58
|
-
s.specification_version = 3
|
14
|
+
s.rubyforge_project = "noid"
|
59
15
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
65
|
-
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
66
|
-
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
67
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
68
|
-
else
|
69
|
-
s.add_dependency(%q<anvl>, [">= 0"])
|
70
|
-
s.add_dependency(%q<json>, [">= 0"])
|
71
|
-
s.add_dependency(%q<backports>, [">= 0"])
|
72
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
73
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
74
|
-
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
75
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
76
|
-
end
|
77
|
-
else
|
78
|
-
s.add_dependency(%q<anvl>, [">= 0"])
|
79
|
-
s.add_dependency(%q<json>, [">= 0"])
|
80
|
-
s.add_dependency(%q<backports>, [">= 0"])
|
81
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
82
|
-
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
83
|
-
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
84
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
85
|
-
end
|
86
|
-
end
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
87
20
|
|
21
|
+
s.add_dependency "backports"
|
22
|
+
s.add_development_dependency "bundler", "~> 1.0.0"
|
23
|
+
s.add_development_dependency "rspec", ">= 2.0"
|
24
|
+
s.add_development_dependency "rcov", ">= 0"
|
25
|
+
end
|