id_coder 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.
- data/.document +5 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +32 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +44 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/id_coder.rb +203 -0
- data/test/helper.rb +18 -0
- data/test/test_id_coder.rb +152 -0
- metadata +156 -0
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.2.5)
|
5
|
+
jeweler (1.8.3)
|
6
|
+
bundler (~> 1.0)
|
7
|
+
git (>= 1.2.5)
|
8
|
+
rake
|
9
|
+
rdoc
|
10
|
+
json (1.6.6)
|
11
|
+
modalsettings (1.0.0)
|
12
|
+
modalsupport (>= 0.8.1)
|
13
|
+
modalsupport (0.8.3)
|
14
|
+
rake (0.9.2.2)
|
15
|
+
rdoc (3.12)
|
16
|
+
json (~> 1.4)
|
17
|
+
shoulda (3.0.1)
|
18
|
+
shoulda-context (~> 1.0.0)
|
19
|
+
shoulda-matchers (~> 1.0.0)
|
20
|
+
shoulda-context (1.0.0)
|
21
|
+
shoulda-matchers (1.0.0)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
bundler (~> 1)
|
28
|
+
jeweler (~> 1.8.3)
|
29
|
+
modalsettings
|
30
|
+
modalsupport
|
31
|
+
rdoc (~> 3.12)
|
32
|
+
shoulda
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Javier Goizueta
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
= id_coder
|
2
|
+
|
3
|
+
Id-codes are relatively short codes to represent externally id internal numbers.
|
4
|
+
|
5
|
+
A bijection between ids and codes is used to assure uniqueness of codes and to avoid storing the
|
6
|
+
codes in the database (codes and ids are computed on-the-fly from each other).
|
7
|
+
|
8
|
+
Codes are user-visible; they're designed to be the user's identification of some element.
|
9
|
+
|
10
|
+
Only digits an capital letters are used for the codes, excluding I,O,1,0 to avoid confusion,
|
11
|
+
and a check digit can be used to detect most common transcription errors. This makes codes
|
12
|
+
viable to be transmitted orally, etc.
|
13
|
+
|
14
|
+
The number of digits used is scalable: the minimum number of digits and the increment-size can be
|
15
|
+
parameterized to render good-looking codes.
|
16
|
+
|
17
|
+
An IdCoder can be defined passing to it three optional parameters that define the lenght of the codes:
|
18
|
+
|
19
|
+
IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
|
20
|
+
|
21
|
+
An additional parameter can be passed to define which characters will be used as digits for the codes
|
22
|
+
and its order; this must be a String of IdCoder::RADIX distinct characters:
|
23
|
+
|
24
|
+
IdCoder[:code_digits=>"0123456789ABCDEFGHIJKLMNOPQRSTUV"]
|
25
|
+
|
26
|
+
Alternatively, a seed parameter can be passed to generate a randomized string
|
27
|
+
|
28
|
+
IdCoder[:seed=>8734112]
|
29
|
+
|
30
|
+
Since this might produce different results in different Ruby versions, the code_digits produced can be accessed
|
31
|
+
and kept for portability:
|
32
|
+
|
33
|
+
IdCoder[:seed=>8734112].code_digits # -> "9GTFNJSZA6LQWXV3BEKHYMP75U8R24CD"
|
34
|
+
|
35
|
+
Ids (integers) can be coded and decoded into alphanumeric strings like this:
|
36
|
+
|
37
|
+
id_coder = IdCoder[]
|
38
|
+
id_coder.id_to_code(7) # => "JEQJTE3"
|
39
|
+
id_coder.code_to_id("JEQJTE3") # => 7
|
40
|
+
|
41
|
+
== Copyright
|
42
|
+
|
43
|
+
Copyright (c) 2012 Javier Goizueta. See LICENSE.txt for
|
44
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "id_coder"
|
18
|
+
gem.homepage = "http://github.com/jgoizueta/id_coder"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Alphanumeric code generator}
|
21
|
+
gem.description = %Q{Generates relatively short codes to represent id numbers externally}
|
22
|
+
gem.email = "jgoizueta@gmail.com"
|
23
|
+
gem.authors = ["Javier Goizueta"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new(:test) do |test|
|
30
|
+
test.libs << 'lib' << 'test'
|
31
|
+
test.pattern = 'test/**/test_*.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :test
|
36
|
+
|
37
|
+
require 'rdoc/task'
|
38
|
+
Rake::RDocTask.new do |rdoc|
|
39
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
40
|
+
|
41
|
+
rdoc.rdoc_dir = 'rdoc'
|
42
|
+
rdoc.title = "id_coder #{version}"
|
43
|
+
rdoc.rdoc_files.include('README*')
|
44
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
45
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/lib/id_coder.rb
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'modalsupport'
|
3
|
+
require 'modalsettings'
|
4
|
+
|
5
|
+
# Id-codes are relatively short codes to represent externally id internal numbers.
|
6
|
+
#
|
7
|
+
# A bijection between ids and codes is used to assure uniqueness of codes and to avoid storing the
|
8
|
+
# codes in the database (codes and ids are computed on-the-fly from each other).
|
9
|
+
#
|
10
|
+
# Codes are user-visible; they're designed to be the user's identification of some element.
|
11
|
+
#
|
12
|
+
# Only digits an capital letters are used for the codes, excluding I,O,1,0 to avoid confusion,
|
13
|
+
# and a check digit can be used to detect most common transcription errors. This makes codes
|
14
|
+
# viable to be transmitted orally, etc.
|
15
|
+
#
|
16
|
+
# The number of digits used is scalable: the minimum number of digits and the increment-size can be
|
17
|
+
# parameterized to render good-looking codes.
|
18
|
+
#
|
19
|
+
# An IdCoder can be defined passing to it three optional parameters that define the lenght of the codes:
|
20
|
+
# IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
|
21
|
+
# An additional parameter can be passed to define which characters will be used as digits for the codes
|
22
|
+
# and its order; this must be a String of IdCoder::RADIX distinct characters:
|
23
|
+
# IdCoder[:code_digits=>"0123456789ABCDEFGHIJKLMNOPQRSTUV"]
|
24
|
+
# Alternatively, a seed parameter can be passed to generate a randomized string
|
25
|
+
# IdCoder[:seed=>8734112]
|
26
|
+
# Since this might produce different results in different Ruby versions, the code_digits produced can be accessed
|
27
|
+
# and kept for portability:
|
28
|
+
# IdCoder[:seed=>8734112].code_digits # -> "9GTFNJSZA6LQWXV3BEKHYMP75U8R24CD"
|
29
|
+
#
|
30
|
+
class IdCoder
|
31
|
+
|
32
|
+
# Internal parameters
|
33
|
+
RADIX = 32
|
34
|
+
DIGITS = "0123456789abcdefghijklmnopqrstuv" # RADIX-digits used by Integer#to_s(RADIX), String#to_i(RADIX)
|
35
|
+
CODE_DIGITS = "BAFTQ4EJCNVYZKDPG37H5S8WML692RXU" # RADIX-digits used for the codes (permutation of 2-9,A-Z except I,O)
|
36
|
+
|
37
|
+
class InvalidCode < RuntimeError; end
|
38
|
+
class InvalidNumber < RuntimeError; end
|
39
|
+
class InvalidCodeDigits < RuntimeError; end
|
40
|
+
|
41
|
+
def initialize(config={})
|
42
|
+
config = Settings[config]
|
43
|
+
if config.code_digits
|
44
|
+
@code_digits = config.code_digits
|
45
|
+
raise InvalidCodeDigits, "Invalid digits" if @code_digits.bytes.to_a.uniq.size!=RADIX
|
46
|
+
else
|
47
|
+
@code_digits = CODE_DIGITS
|
48
|
+
if config.seed
|
49
|
+
srand config.seed
|
50
|
+
@code_digits = @code_digits.bytes.to_a.shuffle.map(&:chr)*""
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@num_digits = (config.num_digits || 6).to_i
|
54
|
+
@block_digits = (config.block_digits || 2).to_i
|
55
|
+
@check_digit = config.check_digit
|
56
|
+
@check_digit = true if @check_digit.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
include ModalSupport::BracketConstructor
|
60
|
+
|
61
|
+
include
|
62
|
+
|
63
|
+
# configurable parameters
|
64
|
+
|
65
|
+
# Minumum number of digits used; limits the number of valid ids before scaling up to RADIX**num_digits
|
66
|
+
def num_digits
|
67
|
+
@num_digits
|
68
|
+
end
|
69
|
+
|
70
|
+
# Use a check digit?: this increases the length of codes in one
|
71
|
+
def check_digit?
|
72
|
+
@check_digit
|
73
|
+
end
|
74
|
+
|
75
|
+
# Number of digits for incremental scaling-up blocks
|
76
|
+
def block_digits
|
77
|
+
@block_digits
|
78
|
+
end
|
79
|
+
|
80
|
+
# Properties of the codes
|
81
|
+
|
82
|
+
# mininum code_length
|
83
|
+
def code_length
|
84
|
+
num_digits + (check_digit? ? 1 : 0)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Number of valid codes before scaling-up
|
88
|
+
def num_valid_codes
|
89
|
+
RADIX**num_digits
|
90
|
+
end
|
91
|
+
|
92
|
+
def num_digits_for(id)
|
93
|
+
if id<num_valid_codes
|
94
|
+
num_digits
|
95
|
+
else
|
96
|
+
# ((Math.log(id)/Math.log(RADIX)-code_length)/block_digits).ceil*block_digits + num_digits
|
97
|
+
nd = num_digits
|
98
|
+
loop do
|
99
|
+
nd += block_digits
|
100
|
+
nvc = RADIX**nd
|
101
|
+
break if id<nvc
|
102
|
+
end
|
103
|
+
# check = ((Math.log(id)/Math.log(RADIX)-code_length)/block_digits).ceil*block_digits + num_digits
|
104
|
+
# raise "nd=#{nd}; [#{check}] id=#{id}" unless nd==check
|
105
|
+
nd
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def code_length_for(id)
|
110
|
+
num_digits_for(id) + (check_digit? ? 1 : 0)
|
111
|
+
end
|
112
|
+
|
113
|
+
def code_digits
|
114
|
+
@code_digits
|
115
|
+
end
|
116
|
+
|
117
|
+
def parameters
|
118
|
+
{ :num_digits=>@num_digits, :block_digits=>@block_digits, :check_digit=>@check_digit, :code_digits=>@code_digits }
|
119
|
+
end
|
120
|
+
|
121
|
+
def inspect
|
122
|
+
"IdCoder[#{parameters.inspect.unwrap('[]')}]"
|
123
|
+
end
|
124
|
+
|
125
|
+
# Code generatioon
|
126
|
+
|
127
|
+
# Direct encoding: generate an Id-Code from an integer id.
|
128
|
+
def id_to_code(id)
|
129
|
+
raise InvalidNumber, "Numbers to be coded must be passed as integers" unless id.kind_of?(Integer)
|
130
|
+
raise InvalidNumber, "Negative numbers cannot be encoded: #{id}" if id<0
|
131
|
+
nd = num_digits_for(id)
|
132
|
+
v = id.to_s(RADIX)
|
133
|
+
v = "0"*(nd-v.size) + v
|
134
|
+
i = 0
|
135
|
+
code = ""
|
136
|
+
mask = 0
|
137
|
+
v.reverse.each_byte do |b|
|
138
|
+
d = DIGITS.index(b.chr)
|
139
|
+
code << @code_digits[mask = ((d+i)%RADIX ^ mask),1]
|
140
|
+
i += 1
|
141
|
+
end
|
142
|
+
code << check_digit(code) if check_digit?
|
143
|
+
code
|
144
|
+
end
|
145
|
+
|
146
|
+
# Inverse encoding: compute the integer id for a Id-Code
|
147
|
+
def code_to_id(code)
|
148
|
+
raise InvalidCode, "Codes must be strings" unless code.kind_of?(String)
|
149
|
+
code = code.strip.upcase # tolerate case differences & surrounding whitespace
|
150
|
+
raise InvalidCode, "Invalid code: #{code}" unless code =~ /\A[#{@code_digits}]+\Z/
|
151
|
+
# raise InvalidCode, "Invalid code length: #{code.size} for #{code} (must be #{code_length})" unless code.size==code_length
|
152
|
+
if check_digit?
|
153
|
+
cd = code[-1,1]
|
154
|
+
code = code[0...-1]
|
155
|
+
raise InvalidCode, "Invalid code: #{code+cd}" if cd!=check_digit(code)
|
156
|
+
end
|
157
|
+
nd = code.size
|
158
|
+
sx = nd-num_digits
|
159
|
+
raise InvalidCode, "Invalid code length: #{code.size} for #{code}" unless sx>=0 && (sx%block_digits)==0
|
160
|
+
i = 0
|
161
|
+
v = ""
|
162
|
+
mask = 0
|
163
|
+
code.each_byte do |b|
|
164
|
+
next_mask = @code_digits.index(b.chr)
|
165
|
+
d = ((next_mask ^ mask)-i)%RADIX
|
166
|
+
mask = next_mask
|
167
|
+
d = DIGITS[d,1]
|
168
|
+
v = d + v
|
169
|
+
i += 1
|
170
|
+
end
|
171
|
+
v.to_i(RADIX)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Check code: returns true (valid) or false (invalid)
|
175
|
+
def valid_code?(code)
|
176
|
+
is_valid = true
|
177
|
+
begin
|
178
|
+
code_to_id(code)
|
179
|
+
rescue InvalidCode
|
180
|
+
is_valid = false
|
181
|
+
end
|
182
|
+
is_valid
|
183
|
+
end
|
184
|
+
|
185
|
+
private
|
186
|
+
|
187
|
+
# Check digits are computed using the Luhn Mod N Algorithm:
|
188
|
+
# http://en.wikipedia.org/wiki/Luhn_mod_N_algorithm
|
189
|
+
def check_digit(code)
|
190
|
+
factor = 2
|
191
|
+
sum = 0
|
192
|
+
code.each_byte do |b|
|
193
|
+
addend = factor * @code_digits.index(b.chr)
|
194
|
+
factor = (factor==2) ? 1 : 2
|
195
|
+
addend = (addend / RADIX) + (addend % RADIX)
|
196
|
+
sum += addend
|
197
|
+
end
|
198
|
+
remainder = sum % RADIX
|
199
|
+
@code_digits[(RADIX - remainder) % RADIX, 1]
|
200
|
+
end
|
201
|
+
|
202
|
+
|
203
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rubygems'
|
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 'test/unit'
|
11
|
+
require 'shoulda'
|
12
|
+
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
|
+
require 'id_coder'
|
16
|
+
|
17
|
+
class Test::Unit::TestCase
|
18
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestIdCoder < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@id_coder = IdCoder[
|
7
|
+
:num_digits => 6,
|
8
|
+
:block_digits => 2,
|
9
|
+
:check_digit => true
|
10
|
+
]
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_bijectivity
|
14
|
+
# sanity check
|
15
|
+
assert@id_coder.check_digit?
|
16
|
+
assert_equal 6,@id_coder.num_digits
|
17
|
+
assert_equal 2,@id_coder.block_digits
|
18
|
+
assert_equal 7,@id_coder.code_length
|
19
|
+
assert_equal 32, IdCoder::RADIX
|
20
|
+
assert_equal 32**6,@id_coder.num_valid_codes
|
21
|
+
|
22
|
+
# Test numbers within the basic range (num_digits)
|
23
|
+
test_numbers = (0..100).to_a + (99999900..100000020).to_a + [232211,9898774,1002,343332,1023,1024,1025]
|
24
|
+
test_numbers.each do |number|
|
25
|
+
code =@id_coder.id_to_code(number)
|
26
|
+
assert_equal 7, code.size # test also code length here
|
27
|
+
assert_equal number,@id_coder.code_to_id(code),"#{number} is converted to a code and back"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test larger numbers
|
31
|
+
test_numbers = (1_999_999_990..2_000_000_010).to_a + (1_999_999_999_990..2_000_000_000_010).to_a
|
32
|
+
test_numbers.each do |number|
|
33
|
+
code =@id_coder.id_to_code(number)
|
34
|
+
assert code.size > 7
|
35
|
+
assert(
|
36
|
+
(code.size-(@id_coder.check_digit? ? 1 : 0)-@id_coder.num_digits)%@id_coder.block_digits == 0
|
37
|
+
)
|
38
|
+
assert_equal number,@id_coder.code_to_id(code),"#{number} is converted to a code and back"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_invalid_numbers
|
44
|
+
# sanity check
|
45
|
+
assert@id_coder.check_digit?
|
46
|
+
assert_equal 6,@id_coder.num_digits
|
47
|
+
assert_equal 2,@id_coder.block_digits
|
48
|
+
assert_equal 7,@id_coder.code_length
|
49
|
+
assert_equal 32, IdCoder::RADIX
|
50
|
+
assert_equal 32**6,@id_coder.num_valid_codes
|
51
|
+
# => ~1000000000 codes
|
52
|
+
|
53
|
+
assert_raise(IdCoder::InvalidNumber){@id_coder.id_to_code("1") }
|
54
|
+
assert_raise(IdCoder::InvalidNumber){@id_coder.id_to_code(-1) }
|
55
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(2000000000) }
|
56
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(0) }
|
57
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(1) }
|
58
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(100) }
|
59
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(1000000000) }
|
60
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(2000000000) }
|
61
|
+
assert_nothing_raised(IdCoder::InvalidNumber){@id_coder.id_to_code(100000000000000000000) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_invalid_codes
|
65
|
+
@id_coder = IdCoder[:num_digits=>4, :block_digits=>3, :check_digit=>false]
|
66
|
+
# sanity check
|
67
|
+
assert !@id_coder.check_digit?
|
68
|
+
assert_equal 4,@id_coder.num_digits
|
69
|
+
assert_equal 3,@id_coder.block_digits
|
70
|
+
assert_equal 4,@id_coder.code_length
|
71
|
+
assert_equal 32, IdCoder::RADIX
|
72
|
+
assert_equal 32**4,@id_coder.num_valid_codes
|
73
|
+
|
74
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(1023) }
|
75
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(234) }
|
76
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(2345) }
|
77
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('2345') }
|
78
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('345') }
|
79
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('34567') }
|
80
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3415') }
|
81
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('34I5') }
|
82
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A04') }
|
83
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2AO4') }
|
84
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A-3') }
|
85
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A 3') }
|
86
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2A)3') }
|
87
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('345678') }
|
88
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('3456789') }
|
89
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3456789A') }
|
90
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3456789AB') }
|
91
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('3456789ABC') }
|
92
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3456789ABCD') }
|
93
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('2A45') }
|
94
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id(' 2A43 ') }
|
95
|
+
assert_nothing_raised(IdCoder::InvalidCode){@id_coder.code_to_id('2a43') }
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_check_digits
|
99
|
+
# sanity check
|
100
|
+
assert@id_coder.check_digit?
|
101
|
+
assert_equal 6,@id_coder.num_digits
|
102
|
+
assert_equal 2,@id_coder.block_digits
|
103
|
+
assert_equal 7,@id_coder.code_length
|
104
|
+
assert_equal 32, IdCoder::RADIX
|
105
|
+
assert_equal 32**6,@id_coder.num_valid_codes
|
106
|
+
|
107
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(1023) }
|
108
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(234) }
|
109
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(2345938) }
|
110
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('2345938') }
|
111
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('345432') }
|
112
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id('3454321') }
|
113
|
+
valid_code =@id_coder.id_to_code(234331)
|
114
|
+
assert_equal 7, valid_code.size
|
115
|
+
digits = %w{2 3 4 5 6 7 8 9 A B C D E F G H J K L M N P Q R S T U V W X Y Z}
|
116
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(valid_code[0...-1])}
|
117
|
+
# Check that changing the check digit makes the code invalid
|
118
|
+
digits.each do |digit|
|
119
|
+
next if digit == valid_code[-1,1]
|
120
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(valid_code[0...-1]+digit)}
|
121
|
+
end
|
122
|
+
# Check that any single-digit change makes the code invalid
|
123
|
+
(0..5).each do |i|
|
124
|
+
digits.each do |digit|
|
125
|
+
next if digit == valid_code[i,1]
|
126
|
+
invalid_code = valid_code[0,i] + digit + valid_code[i+1..-1]
|
127
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(invalid_code)}
|
128
|
+
end
|
129
|
+
end
|
130
|
+
# Check that adjacent digit swapping makes the code invalid
|
131
|
+
# (there are some exceptions though, see http://en.wikipedia.org/wiki/Luhn_mod_N_algorithm)
|
132
|
+
# TODO: sistematic check avoiding exceptions
|
133
|
+
invalid_code = valid_code[0,2] + valid_code[3,1] + valid_code[2,1] + valid_code[4..-1]
|
134
|
+
assert_raise(IdCoder::InvalidCode){@id_coder.code_to_id(invalid_code)}
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_internal_sanity
|
138
|
+
# Check the coded digits used: must be exactly IdCoder::RADIX unique uppercase characters
|
139
|
+
chars = []
|
140
|
+
IdCoder::CODE_DIGITS.each_byte{|b| chars << b.chr}
|
141
|
+
assert_equal IdCoder::RADIX, chars.uniq.size
|
142
|
+
assert_equal IdCoder::CODE_DIGITS.upcase, IdCoder::CODE_DIGITS
|
143
|
+
|
144
|
+
# Check the internally-used radix-32 digits to be consistent with Integer#to_s
|
145
|
+
assert_equal IdCoder::RADIX, IdCoder::DIGITS.size
|
146
|
+
(0...IdCoder::RADIX).each do |i|
|
147
|
+
assert_equal i.to_s(IdCoder::RADIX), IdCoder::DIGITS[i,1]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
end
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: id_coder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Javier Goizueta
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: modalsupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: modalsettings
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: shoulda
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rdoc
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '3.12'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '3.12'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: bundler
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ~>
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '1'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ~>
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '1'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: jeweler
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.8.3
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.8.3
|
110
|
+
description: Generates relatively short codes to represent id numbers externally
|
111
|
+
email: jgoizueta@gmail.com
|
112
|
+
executables: []
|
113
|
+
extensions: []
|
114
|
+
extra_rdoc_files:
|
115
|
+
- LICENSE.txt
|
116
|
+
- README.rdoc
|
117
|
+
files:
|
118
|
+
- .document
|
119
|
+
- Gemfile
|
120
|
+
- Gemfile.lock
|
121
|
+
- LICENSE.txt
|
122
|
+
- README.rdoc
|
123
|
+
- Rakefile
|
124
|
+
- VERSION
|
125
|
+
- lib/id_coder.rb
|
126
|
+
- test/helper.rb
|
127
|
+
- test/test_id_coder.rb
|
128
|
+
homepage: http://github.com/jgoizueta/id_coder
|
129
|
+
licenses:
|
130
|
+
- MIT
|
131
|
+
post_install_message:
|
132
|
+
rdoc_options: []
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ! '>='
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
hash: -1112580131298559240
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 1.8.21
|
153
|
+
signing_key:
|
154
|
+
specification_version: 3
|
155
|
+
summary: Alphanumeric code generator
|
156
|
+
test_files: []
|