hashids 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +5 -0
- data/README.md +5 -0
- data/hashids.gemspec +2 -0
- data/lib/hashids.rb +33 -41
- data/spec/hashids_profile.rb +21 -0
- data/spec/hashids_spec.rb +4 -4
- metadata +8 -3
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -5,10 +5,15 @@ Use hashids when you do not want to expose your database ids to the user.
|
|
5
5
|
|
6
6
|
[http://www.hashids.org/ruby/](http://www.hashids.org/ruby/)
|
7
7
|
|
8
|
+
[![Build Status](https://secure.travis-ci.org/peterhellberg/hashids.rb.png)](http://travis-ci.org/peterhellberg/hashids.rb)
|
9
|
+
(1.9.2, 1.9.3, jruby-19mode, rbx-19mode and ruby-head)
|
10
|
+
|
8
11
|
## What is it?
|
9
12
|
|
10
13
|
hashids (Hash ID's) creates short, unique, decryptable hashes from unsigned integers.
|
11
14
|
|
15
|
+
_(NOTE: This is **NOT** a true cryptographic hash, since it is reversible)_
|
16
|
+
|
12
17
|
It was designed for websites to use in URL shortening, tracking stuff, or
|
13
18
|
making pages private (or at least unguessable).
|
14
19
|
|
data/hashids.gemspec
CHANGED
@@ -12,6 +12,8 @@ Gem::Specification.new do |gem|
|
|
12
12
|
gem.description = %q{Use hashids when you do not want to expose your database ids to the user.}
|
13
13
|
gem.homepage = "https://github.com/peterhellberg/hashids.rb"
|
14
14
|
|
15
|
+
gem.required_ruby_version = '>= 1.9.2'
|
16
|
+
|
15
17
|
gem.files = `git ls-files`.split($/)
|
16
18
|
gem.test_files = gem.files.grep(%r{^(spec)/})
|
17
19
|
gem.require_paths = ["lib"]
|
data/lib/hashids.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
class Hashids
|
4
|
-
VERSION = "0.0.
|
4
|
+
VERSION = "0.0.2"
|
5
5
|
DEFAULT_ALPHABET = "xcS4F6h89aUbideAI7tkynuopqrXCgTE5GBKHLMjfRsz"
|
6
6
|
PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43]
|
7
7
|
|
@@ -10,22 +10,22 @@ class Hashids
|
|
10
10
|
class AlphabetError < ArgumentError; end
|
11
11
|
|
12
12
|
def initialize(salt = "", min_length = 0, alphabet = DEFAULT_ALPHABET)
|
13
|
-
@salt
|
14
|
-
@min_length
|
15
|
-
@alphabet
|
13
|
+
@salt = salt
|
14
|
+
@min_length = min_length
|
15
|
+
@alphabet = alphabet
|
16
|
+
|
17
|
+
@chars_regex = /./
|
16
18
|
|
17
19
|
validate_attributes
|
18
20
|
setup_alphabet
|
19
21
|
end
|
20
22
|
|
21
23
|
def encrypt(*numbers)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
if numbers.empty? || numbers.reject { |n| Integer(n) && n > 0 }.any?
|
25
|
+
""
|
26
|
+
else
|
27
|
+
encode(numbers, @alphabet, @salt, @min_length)
|
26
28
|
end
|
27
|
-
|
28
|
-
encode(numbers, @alphabet, @salt, @min_length)
|
29
29
|
end
|
30
30
|
|
31
31
|
def decrypt(hash)
|
@@ -52,7 +52,7 @@ class Hashids
|
|
52
52
|
@seps = []
|
53
53
|
@guards = []
|
54
54
|
|
55
|
-
@alphabet = @alphabet.
|
55
|
+
@alphabet = @alphabet.scan(@chars_regex).uniq.join('')
|
56
56
|
|
57
57
|
if @alphabet.length < 4
|
58
58
|
raise AlphabetError, "Alphabet must contain at least 4 unique characters."
|
@@ -63,7 +63,7 @@ class Hashids
|
|
63
63
|
|
64
64
|
break if char.nil?
|
65
65
|
|
66
|
-
@seps
|
66
|
+
@seps << char
|
67
67
|
@alphabet.gsub!(char, ' ')
|
68
68
|
end
|
69
69
|
|
@@ -71,7 +71,7 @@ class Hashids
|
|
71
71
|
separator = @seps[index]
|
72
72
|
|
73
73
|
unless separator.nil?
|
74
|
-
@guards
|
74
|
+
@guards << separator
|
75
75
|
@seps.delete_at(index)
|
76
76
|
end
|
77
77
|
end
|
@@ -83,13 +83,13 @@ class Hashids
|
|
83
83
|
def encode(numbers, alphabet, salt, min_length = 0)
|
84
84
|
ret = ""
|
85
85
|
|
86
|
-
seps = consistent_shuffle(@seps, numbers).
|
86
|
+
seps = consistent_shuffle(@seps, numbers).scan(@chars_regex)
|
87
87
|
lottery_char = ""
|
88
88
|
|
89
89
|
numbers.each_with_index do |number, i|
|
90
90
|
if i == 0
|
91
91
|
lottery_salt = numbers.join('-')
|
92
|
-
numbers.each { |n| lottery_salt += "
|
92
|
+
numbers.each { |n| lottery_salt += "-#{(n + 1) * 2}" }
|
93
93
|
|
94
94
|
lottery = consistent_shuffle(alphabet, lottery_salt)
|
95
95
|
|
@@ -172,8 +172,8 @@ class Hashids
|
|
172
172
|
end
|
173
173
|
|
174
174
|
if alphabet.length > 0 && lottery_char.length > 0
|
175
|
-
alphabet = consistent_shuffle(alphabet, (lottery_char.ord & 12345).to_s +
|
176
|
-
ret
|
175
|
+
alphabet = consistent_shuffle(alphabet, (lottery_char.ord & 12345).to_s + @salt)
|
176
|
+
ret << unhash(sub_hash, alphabet)
|
177
177
|
end
|
178
178
|
end
|
179
179
|
end
|
@@ -187,24 +187,21 @@ class Hashids
|
|
187
187
|
def consistent_shuffle(alphabet, salt)
|
188
188
|
ret = ""
|
189
189
|
|
190
|
-
alphabet = alphabet.join
|
191
|
-
salt = salt.join
|
190
|
+
alphabet = alphabet.join("") if alphabet.respond_to? :join
|
191
|
+
salt = salt.join("") if salt.respond_to? :join
|
192
192
|
|
193
|
-
alphabet_array = alphabet.
|
194
|
-
salt_array
|
195
|
-
sorting_array
|
193
|
+
alphabet_array = alphabet.scan(@chars_regex)
|
194
|
+
salt_array = salt.scan(@chars_regex)
|
195
|
+
sorting_array = []
|
196
196
|
|
197
|
-
salt_array
|
198
|
-
|
199
|
-
salt_array.each do |salt_char|
|
200
|
-
sorting_array.push salt_char.ord || 0
|
201
|
-
end
|
197
|
+
salt_array << "" if salt_array.empty?
|
198
|
+
salt_array.each { |char| sorting_array << char.ord }
|
202
199
|
|
203
200
|
sorting_array.each_with_index do |int,i|
|
204
201
|
add = true
|
205
202
|
k = i
|
206
203
|
|
207
|
-
while k !=
|
204
|
+
while k != sorting_array.length + i - 1
|
208
205
|
next_index = (k + 1) % sorting_array.length
|
209
206
|
|
210
207
|
if add
|
@@ -223,9 +220,8 @@ class Hashids
|
|
223
220
|
i = 0
|
224
221
|
|
225
222
|
while alphabet_array.length > 0
|
226
|
-
alphabet_size = alphabet_array.length
|
227
223
|
pos = sorting_array[i]
|
228
|
-
pos %=
|
224
|
+
pos %= alphabet_array.length if pos >= alphabet_array.length
|
229
225
|
ret += alphabet_array[pos]
|
230
226
|
|
231
227
|
alphabet_array.delete_at(pos)
|
@@ -237,11 +233,10 @@ class Hashids
|
|
237
233
|
|
238
234
|
def hash(number, alphabet)
|
239
235
|
hash = ""
|
240
|
-
alphabet_length = alphabet.length
|
241
236
|
|
242
237
|
while number > 0
|
243
|
-
hash = alphabet[number %
|
244
|
-
number = number /
|
238
|
+
hash = alphabet[number % alphabet.length] + hash
|
239
|
+
number = number / alphabet.length
|
245
240
|
end
|
246
241
|
|
247
242
|
hash
|
@@ -250,15 +245,12 @@ class Hashids
|
|
250
245
|
def unhash(hash, alphabet)
|
251
246
|
number = 0
|
252
247
|
|
253
|
-
hash.split('').each_with_index
|
254
|
-
pos = alphabet.index
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
number += pos * alphabet.length ** (hash.length - i - 1)
|
259
|
-
}
|
248
|
+
hash.split('').each_with_index do |char, i|
|
249
|
+
if pos = alphabet.index(char)
|
250
|
+
number += pos * alphabet.length ** (hash.length - i - 1)
|
251
|
+
end
|
252
|
+
end
|
260
253
|
|
261
254
|
number
|
262
255
|
end
|
263
|
-
|
264
256
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Profile with perftools.rb
|
4
|
+
#
|
5
|
+
# CPUPROFILE=/tmp/hashids_profile \
|
6
|
+
# RUBYOPT="-r`gem which 'perftools.rb' | tail -1`" \
|
7
|
+
# ruby spec/hashids_profile.rb
|
8
|
+
#
|
9
|
+
# Generate diagram
|
10
|
+
#
|
11
|
+
# pprof.rb --gif /tmp/hashids_profile > /tmp/hashids_profile.gif && \
|
12
|
+
# open /tmp/hashids_profile.gif
|
13
|
+
#
|
14
|
+
|
15
|
+
require_relative "../lib/hashids"
|
16
|
+
|
17
|
+
h = Hashids.new('this is my salt')
|
18
|
+
|
19
|
+
10_000.times do |n|
|
20
|
+
h.decrypt(h.encrypt(n))
|
21
|
+
end
|
data/spec/hashids_spec.rb
CHANGED
@@ -89,15 +89,15 @@ describe Hashids do
|
|
89
89
|
h.encrypt(1,2,3,4,5).must_equal 'adcdacddcdaacdad'
|
90
90
|
end
|
91
91
|
|
92
|
-
it "
|
92
|
+
it "does not produce repeating patterns for identical numbers" do
|
93
93
|
hashids.encrypt(5,5,5,5).must_equal 'GLh5SMs9'
|
94
94
|
end
|
95
95
|
|
96
|
-
it "
|
96
|
+
it "does not produce repeating patterns for incremented numbers" do
|
97
97
|
hashids.encrypt(*(1..10).to_a).must_equal 'zEUzfySGIpuyhpF6HaC7'
|
98
98
|
end
|
99
99
|
|
100
|
-
it "
|
100
|
+
it "does not produce similarities between incrementing number hashes" do
|
101
101
|
hashids.encrypt(1).must_equal 'LX'
|
102
102
|
hashids.encrypt(2).must_equal 'ed'
|
103
103
|
hashids.encrypt(3).must_equal 'o9'
|
@@ -126,7 +126,7 @@ describe Hashids do
|
|
126
126
|
}
|
127
127
|
end
|
128
128
|
|
129
|
-
it "
|
129
|
+
it "does not decrypt with a different salt" do
|
130
130
|
peppers = Hashids.new('this is my pepper')
|
131
131
|
hashids.decrypt('ryBo').must_equal [12345]
|
132
132
|
peppers.decrypt('ryBo').must_equal []
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hashids
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -19,12 +19,14 @@ extensions: []
|
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
21
|
- .gitignore
|
22
|
+
- .travis.yml
|
22
23
|
- Gemfile
|
23
24
|
- LICENSE.txt
|
24
25
|
- README.md
|
25
26
|
- Rakefile
|
26
27
|
- hashids.gemspec
|
27
28
|
- lib/hashids.rb
|
29
|
+
- spec/hashids_profile.rb
|
28
30
|
- spec/hashids_spec.rb
|
29
31
|
homepage: https://github.com/peterhellberg/hashids.rb
|
30
32
|
licenses: []
|
@@ -37,13 +39,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
37
39
|
requirements:
|
38
40
|
- - ! '>='
|
39
41
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
42
|
+
version: 1.9.2
|
41
43
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
44
|
none: false
|
43
45
|
requirements:
|
44
46
|
- - ! '>='
|
45
47
|
- !ruby/object:Gem::Version
|
46
48
|
version: '0'
|
49
|
+
segments:
|
50
|
+
- 0
|
51
|
+
hash: 2124593182474944467
|
47
52
|
requirements: []
|
48
53
|
rubyforge_project:
|
49
54
|
rubygems_version: 1.8.24
|
@@ -51,5 +56,5 @@ signing_key:
|
|
51
56
|
specification_version: 3
|
52
57
|
summary: Generate YouTube-like hashes from one or many numbers.
|
53
58
|
test_files:
|
59
|
+
- spec/hashids_profile.rb
|
54
60
|
- spec/hashids_spec.rb
|
55
|
-
has_rdoc:
|