reloc 0.0.1
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 +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +9 -0
- data/lib/reloc.rb +5 -0
- data/lib/reloc/core.rb +95 -0
- data/lib/reloc/version.rb +3 -0
- data/reloc.gemspec +21 -0
- data/specs/core_spec.rb +97 -0
- metadata +73 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Dave Kinkead
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# Reloc
|
2
|
+
|
3
|
+
A super simple way to generate easy to read, hard to guess, domain specific unique IDs.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install the gem:
|
8
|
+
|
9
|
+
$ gem install reloc
|
10
|
+
|
11
|
+
Then require it
|
12
|
+
|
13
|
+
require 'reloc'
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Throw Reloc an (auto-incrementing) integer to generate easy to read, hard to guess unique IDs.
|
18
|
+
|
19
|
+
Reloc.generate( 12345 )
|
20
|
+
# => "7bmgk"
|
21
|
+
Reloc.generate( 12346 )
|
22
|
+
# => "2wrdt"
|
23
|
+
|
24
|
+
You can ensure your IDs are domain specific
|
25
|
+
|
26
|
+
Reloc.generate( 12345, 'www.somedomain.com' )
|
27
|
+
# => "8fgqb"
|
28
|
+
Reloc.generate( 12345, 'www.anotherdomain.com' )
|
29
|
+
# => "oswn1"
|
30
|
+
|
31
|
+
And configure the minimum ID length (default is 5 characters)
|
32
|
+
|
33
|
+
Reloc.config( {:min_chars => 3} )
|
34
|
+
Reloc.generate( 12345 )
|
35
|
+
# => "8z7"
|
36
|
+
|
37
|
+
Hook it up to [Redis][redis] and you've got your own URL shortener in just a few lines of code
|
38
|
+
|
39
|
+
require 'redis'
|
40
|
+
require 'reloc'
|
41
|
+
|
42
|
+
redis = Redis.new
|
43
|
+
redis.set( "counter", 0 )
|
44
|
+
|
45
|
+
counter = Reloc.generate( redis.incr "counter" )
|
46
|
+
# => "68hy1"
|
47
|
+
|
48
|
+
redis.set( counter, "http://some-url.com" )
|
49
|
+
# => OK
|
50
|
+
|
51
|
+
redis.get( "68hy1" )
|
52
|
+
# => "http://some-url.com"
|
53
|
+
|
54
|
+
## Contributing
|
55
|
+
|
56
|
+
1. Fork it
|
57
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
58
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
59
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
60
|
+
5. Create new Pull Request
|
61
|
+
|
62
|
+
[redis]:http://redis.io/
|
data/Rakefile
ADDED
data/lib/reloc.rb
ADDED
data/lib/reloc/core.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Reloc
|
4
|
+
|
5
|
+
@@env = {
|
6
|
+
:min_chars => 5,
|
7
|
+
:domain => nil,
|
8
|
+
:salts => {
|
9
|
+
nil => '5it68qhmyrc13jzxelgfdasuv04npo97b2wk', # Generate a new code with (('0'..'9').to_a + ('a'..'z').to_a).shuffle.join
|
10
|
+
}
|
11
|
+
}
|
12
|
+
ALPH = (('0'..'9').to_a + ('a'..'z').to_a).join.split( // )
|
13
|
+
|
14
|
+
|
15
|
+
# Public: Generate a random ordering of an alphanumeric-bet
|
16
|
+
#
|
17
|
+
# domain - A domain to create unique IDs for
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# Reloc.salt
|
22
|
+
# # => 'z2y54c8pnwrl63o17hxmbtauqegd0vfi9ksj'
|
23
|
+
#
|
24
|
+
# Reloc.salt( 'some-domain' )
|
25
|
+
# # => 'dfs7k0eqt6j2mvir3wogn81p9yczuhx5b4al'
|
26
|
+
#
|
27
|
+
# Returns a random ordering of an alpha-num-bet
|
28
|
+
def self.salt( domain=nil )
|
29
|
+
# just suffle the alpha-num-bet
|
30
|
+
return ( ('0'..'9').to_a + ('a'..'z').to_a ).shuffle.join if domain.nil?
|
31
|
+
# unless a domain is provided
|
32
|
+
alph = ALPH.clone
|
33
|
+
hash = ( Digest::MD5.hexdigest( domain ) ).split( // ).map! { |n| n.to_i(16) }
|
34
|
+
salt = []
|
35
|
+
pos = 0
|
36
|
+
36.times do |n|
|
37
|
+
n %= 32
|
38
|
+
pos = ( pos + hash[n] ) % alph.length
|
39
|
+
salt.push( alph[pos] )
|
40
|
+
alph.delete_at pos
|
41
|
+
end
|
42
|
+
salt.join
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Public: Sets configuration variables
|
47
|
+
#
|
48
|
+
# min_chars - The minimum characters to generate in a reloc
|
49
|
+
#
|
50
|
+
# Example
|
51
|
+
#
|
52
|
+
# Reloc.config( {:min_chars => 3} )
|
53
|
+
# # => { :min_chars => 3,
|
54
|
+
# :domain => nil,
|
55
|
+
# :salts => { nil => '5it68qhmyrc13jzxelgfdasuv04npo97b2wk' }
|
56
|
+
# }
|
57
|
+
#
|
58
|
+
# Returns the current config variables
|
59
|
+
def self.config( env={} )
|
60
|
+
env.each { |k,v| @@env[k] = v }
|
61
|
+
@@env
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
# Public: Generate a unique reloc for some integer + domain pair
|
66
|
+
#
|
67
|
+
# num - The integer to be encoded
|
68
|
+
# domain - The domain constraint on uniqueness
|
69
|
+
#
|
70
|
+
# Examples
|
71
|
+
#
|
72
|
+
# Reloc.generate( 123456 )
|
73
|
+
# # => '7bmgk'
|
74
|
+
#
|
75
|
+
# Reloc.generate( 123456, 'some domain' )
|
76
|
+
# # => '8fgqb'
|
77
|
+
#
|
78
|
+
# Returns the encoded integer or nil on error
|
79
|
+
def self.generate( num, domain=nil )
|
80
|
+
return nil unless num.instance_of? Fixnum
|
81
|
+
unless @@env[:salts].has_key?( domain )
|
82
|
+
@@env[:salts][domain] = self.salt( domain )
|
83
|
+
end
|
84
|
+
salt = @@env[:salts][domain].split( // )
|
85
|
+
incr = num % 36
|
86
|
+
num += ( 36**(@@env[:min_chars] - 1) )
|
87
|
+
b36 = num.to_s(36).split( // )
|
88
|
+
res = []
|
89
|
+
b36.each do |s|
|
90
|
+
res << salt[( ALPH.index( s ) + incr * 2 ) % 36]
|
91
|
+
incr += 1
|
92
|
+
end
|
93
|
+
res.join
|
94
|
+
end
|
95
|
+
end
|
data/reloc.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'reloc/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "reloc"
|
8
|
+
gem.version = Reloc::VERSION
|
9
|
+
gem.authors = ["Dave Kinkead"]
|
10
|
+
gem.email = ["dave@kinkead.com.au"]
|
11
|
+
gem.description = %q{Create easy to read, hard to guess, domain specific unique IDs}
|
12
|
+
gem.summary = %q{A super simple way to generate easy to read, hard to guess, domain specific unique IDs.}
|
13
|
+
gem.homepage = "https://github.com/davekinkead/Reloc"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|specs|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'rake'
|
21
|
+
end
|
data/specs/core_spec.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'reloc/core'
|
4
|
+
|
5
|
+
describe 'Reloc' do
|
6
|
+
|
7
|
+
describe 'salt' do
|
8
|
+
it 'should generate a random order of 0-9a-z from no arg' do
|
9
|
+
salt = Reloc.salt
|
10
|
+
salt.must_be_kind_of String
|
11
|
+
salt.length.must_equal 36
|
12
|
+
salt.split( // ).sort.must_equal (('0'..'9').to_a + ('a'..'z').to_a).join.split( // )
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should generate a domain salt if a domain is provided' do
|
16
|
+
salt = Reloc.salt( "I don't always salt my hashes...but when I do" )
|
17
|
+
salt.length.must_equal 36
|
18
|
+
salt[/[a-z0-9]{36}/].must_equal salt
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should generate a different salt for a different domain' do
|
22
|
+
results = []
|
23
|
+
100.times do |n|
|
24
|
+
results.push( Reloc.salt( "Mumbo number #{n}") )
|
25
|
+
end
|
26
|
+
results.length.must_equal results.uniq.length
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'config' do
|
31
|
+
it 'should return a hash' do
|
32
|
+
Reloc.config.must_be_kind_of Hash
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should set minimum characters to return' do
|
36
|
+
env = Reloc.config( { :min_chars => 3 } )
|
37
|
+
env[:min_chars].must_equal 3
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should set the default domain' do
|
41
|
+
domain = 'wiggle**5, yeah you know it!'
|
42
|
+
env = Reloc.config( { :domain => domain } )
|
43
|
+
env[:domain].must_equal domain
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'encode' do
|
48
|
+
before :each do
|
49
|
+
Reloc.config( { :min_chars => 5, :domain => nil } )
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should return nil on error' do
|
53
|
+
Reloc.generate( 'string' ).must_be_nil
|
54
|
+
Reloc.generate( 12.7 ).must_be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should return alphanum of default length 5 for some int' do
|
58
|
+
Reloc.generate( 123456 ).length.must_equal 5
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should return the same ID for the same num+domain pair' do
|
62
|
+
Reloc.generate( 123 ).must_equal Reloc.generate( 123 )
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should fail duplicate test check when duplicate is added' do
|
66
|
+
results = []
|
67
|
+
100.times { |n| results << Reloc.generate( n ) }
|
68
|
+
results.length.must_equal results.uniq.length
|
69
|
+
# now generate a duplicate
|
70
|
+
results << Reloc.generate( 1 )
|
71
|
+
results.length.must_equal ( results.uniq.length + 1 )
|
72
|
+
end
|
73
|
+
|
74
|
+
# I've tested this to 10 Million times with no collision
|
75
|
+
# which theoretically shouldn't occur as extra digits just
|
76
|
+
# get added when num gets larger
|
77
|
+
it 'should pass duplicate test for x million tries' do
|
78
|
+
results = []
|
79
|
+
100.times { |n| results << Reloc.generate( n ) }
|
80
|
+
results.length.must_equal results.uniq.length
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should return the correct number of characters' do
|
84
|
+
Reloc.config( { :min_chars => 3 } )
|
85
|
+
Reloc.generate( 27 ).length.must_equal 3
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should automatically create a domain specific salt if a domain is provided' do
|
89
|
+
config = Reloc.config
|
90
|
+
one = Reloc.generate( 12345 )
|
91
|
+
config[:salts].has_key?( 'some-domain' ).must_equal false
|
92
|
+
two = Reloc.generate( 12345, 'some-domain' )
|
93
|
+
config[:salts].has_key?( 'some-domain' ).must_equal true
|
94
|
+
one.wont_equal two
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reloc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dave Kinkead
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-09 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
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
|
+
description: Create easy to read, hard to guess, domain specific unique IDs
|
31
|
+
email:
|
32
|
+
- dave@kinkead.com.au
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- lib/reloc.rb
|
43
|
+
- lib/reloc/core.rb
|
44
|
+
- lib/reloc/version.rb
|
45
|
+
- reloc.gemspec
|
46
|
+
- specs/core_spec.rb
|
47
|
+
homepage: https://github.com/davekinkead/Reloc
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.23
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: A super simple way to generate easy to read, hard to guess, domain specific
|
71
|
+
unique IDs.
|
72
|
+
test_files:
|
73
|
+
- specs/core_spec.rb
|