hashman 0.1.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/.document +3 -0
- data/.gitignore +7 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +119 -0
- data/Rakefile +28 -0
- data/gemspec.yml +19 -0
- data/hashman.gemspec +60 -0
- data/lib/.DS_Store +0 -0
- data/lib/hashman.rb +7 -0
- data/lib/hashman/hasher.rb +297 -0
- data/lib/hashman/inclusion.rb +62 -0
- data/lib/hashman/version.rb +4 -0
- data/spec/hashman_spec.rb +8 -0
- data/spec/spec_helper.rb +5 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f1c21f334159a16a1c366703d22c2459332b8074
|
4
|
+
data.tar.gz: d1b72f68482fb9911dea923e8037ea2a0b04f291
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a63aada8af51e93873e377ed4ab1246ddc20a8f6f29c781ccb15db9c8f2d3b3ee86990d066d39207e62cd95aa0e20d9f04f9ac13e102f1e212bb5389ec5d4155
|
7
|
+
data.tar.gz: 66be8d0cbcf7cd11c65129ca24e56dbaf4def4f5a6241a3aee6e17f28bda3c3b0956be51d0e22f0d6020b6dd9296ce6e27e76242709affca6d51b193fd480a82
|
data/.document
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--markup markdown --title "hashman Documentation" --protected
|
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 Evan Surdam
|
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.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
# hashman
|
2
|
+
|
3
|
+
* [Homepage](https://github.com/esurdam/hashman#readme)
|
4
|
+
* [Issues](https://github.com/esurdam/hashman/issues)
|
5
|
+
* [Documentation](http://rubydoc.info/gems/hashman/frames)
|
6
|
+
* [Email](mailto:es at cosi.io)
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
Add some hash magic to your ruby classes.
|
11
|
+
|
12
|
+
Use this gem to conceal ids or any integer based array.
|
13
|
+
|
14
|
+
Ideally, user a serializer to exlucde ids and include your hash.
|
15
|
+
|
16
|
+
Credits on hasher to [Peter Hellberg's Hashid](https://github.com/peterhellberg/hashids.rb)
|
17
|
+
|
18
|
+
## Features
|
19
|
+
- Hash integer and UUID based ids
|
20
|
+
- Reverse hash for lookup
|
21
|
+
- Create a hashprint based on an array of integers.
|
22
|
+
- Index your hashprint for uniqueness (Useful for payment data!) [EXAMPLE]()
|
23
|
+
|
24
|
+
## Examples
|
25
|
+
|
26
|
+
### Rails Usage
|
27
|
+
|
28
|
+
Include ```HashMan``` in your model
|
29
|
+
```
|
30
|
+
class User < ActiveRecord::Base
|
31
|
+
include HashMan
|
32
|
+
# include activuuid only if thats your style
|
33
|
+
include activeuuid
|
34
|
+
|
35
|
+
# optionally set a hash length minimum
|
36
|
+
# useful for integer based ids
|
37
|
+
hash_length 8
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
A typical user will look like:
|
42
|
+
```
|
43
|
+
user = User.create({ :name => "awesome" })
|
44
|
+
user.id
|
45
|
+
=> #<UUID:0x3fe2f1dda1d0 UUID:5604ee6e-c934-44cb-89ff-d2934a708e55>
|
46
|
+
```
|
47
|
+
|
48
|
+
Generate a hash for an id (integer or uuid):
|
49
|
+
```
|
50
|
+
user.hash_id
|
51
|
+
=> "rkALRLO3rJyz3vRjx9XJrzlq6"
|
52
|
+
```
|
53
|
+
|
54
|
+
Reverse the hash for lookup:
|
55
|
+
```
|
56
|
+
User.reverse_hash("rkALRLO3rJyz3vRjx9XJrzlq6")
|
57
|
+
=> #<UUID:0x3fe2f1d00f70 UUID:5604ee6e-c934-44cb-89ff-d2934a708e55>
|
58
|
+
```
|
59
|
+
|
60
|
+
Hashprint sensitive data:
|
61
|
+
```
|
62
|
+
# secrets are created in userspace
|
63
|
+
secrets = {:number => 1234123412341234, :cvv => 701, :zip => 90025 }
|
64
|
+
|
65
|
+
# hashprint is created before saving to model
|
66
|
+
secret_hash = User.create_hashprint = [ secrets[:number], secrets[:cvv], secrets[:zip ]
|
67
|
+
=> "evALO3rJyzJrzlq6"
|
68
|
+
|
69
|
+
# persist the secret
|
70
|
+
user.hashprint = secret_hash
|
71
|
+
user.save!
|
72
|
+
```
|
73
|
+
```
|
74
|
+
# Do some cool stuff in your model
|
75
|
+
def verify_cvv(cvv)
|
76
|
+
# where [1] is the index of the value
|
77
|
+
# `decoded_hashprint` is a private method form hashman
|
78
|
+
cvv == self.decoded_hashprint[1]
|
79
|
+
end
|
80
|
+
|
81
|
+
user.verify_cvv(700)
|
82
|
+
=> false
|
83
|
+
|
84
|
+
user.verify_cvv(701)
|
85
|
+
=> true
|
86
|
+
```
|
87
|
+
Secret data is then hidden from prying eyes!
|
88
|
+
|
89
|
+
|
90
|
+
Index hashprint for uniqueness validation! ;)
|
91
|
+
|
92
|
+
## Standalone
|
93
|
+
|
94
|
+
require 'hashman'
|
95
|
+
|
96
|
+
magic
|
97
|
+
|
98
|
+
```
|
99
|
+
HashMan.encode(integer)
|
100
|
+
HashMan.create_hashprint(integer_array)
|
101
|
+
Hashman.decode(hash)
|
102
|
+
|
103
|
+
```
|
104
|
+
## Requirements
|
105
|
+
|
106
|
+
## Todo
|
107
|
+
|
108
|
+
- Auto intercept `find` method on model to allow hash_id
|
109
|
+
- I forget the others
|
110
|
+
|
111
|
+
## Install
|
112
|
+
|
113
|
+
$ gem install hashman
|
114
|
+
|
115
|
+
## Copyright
|
116
|
+
|
117
|
+
Copyright (c) 2016 Evan Surdam
|
118
|
+
|
119
|
+
See {file:LICENSE.txt} for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'bundler/setup'
|
7
|
+
rescue LoadError => e
|
8
|
+
abort e.message
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'rake'
|
12
|
+
|
13
|
+
require 'rubygems/tasks'
|
14
|
+
Gem::Tasks.new
|
15
|
+
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
RSpec::Core::RakeTask.new
|
18
|
+
|
19
|
+
task :test => :spec
|
20
|
+
task :default => :spec
|
21
|
+
|
22
|
+
require 'yard'
|
23
|
+
YARD::Rake::YardocTask.new
|
24
|
+
task :doc => :yard
|
25
|
+
|
26
|
+
task :console do
|
27
|
+
exec "irb -r hashman -I ./lib"
|
28
|
+
end
|
data/gemspec.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
name: hashman
|
2
|
+
summary: "add some hash magic to your classes"
|
3
|
+
description: "HashMan takes any number (or uuid) and converts it to a unique hash. Includes methods on your class
|
4
|
+
to access and reverse hash"
|
5
|
+
license: MIT
|
6
|
+
authors: Evan Surdam
|
7
|
+
email: es@cosi.io
|
8
|
+
homepage: https://github.com/esurdam/hashman#readme
|
9
|
+
|
10
|
+
dependencies:
|
11
|
+
activeuuid: ~> 0.6
|
12
|
+
activesupport: ~> 4.0
|
13
|
+
|
14
|
+
development_dependencies:
|
15
|
+
bundler: ~> 1.10
|
16
|
+
rake: ~> 10.0
|
17
|
+
rspec: ~> 3.0
|
18
|
+
rubygems-tasks: ~> 0.2
|
19
|
+
yard: ~> 0.8
|
data/hashman.gemspec
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gemspec = YAML.load_file('gemspec.yml')
|
7
|
+
|
8
|
+
gem.name = gemspec.fetch('name')
|
9
|
+
gem.version = gemspec.fetch('version') do
|
10
|
+
lib_dir = File.join(File.dirname(__FILE__),'lib')
|
11
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
12
|
+
|
13
|
+
require 'hashman/version'
|
14
|
+
HashMan::VERSION
|
15
|
+
end
|
16
|
+
|
17
|
+
gem.summary = gemspec['summary']
|
18
|
+
gem.description = gemspec['description']
|
19
|
+
gem.licenses = Array(gemspec['license'])
|
20
|
+
gem.authors = Array(gemspec['authors'])
|
21
|
+
gem.email = gemspec['email']
|
22
|
+
gem.homepage = gemspec['homepage']
|
23
|
+
|
24
|
+
glob = lambda { |patterns| gem.files & Dir[*patterns] }
|
25
|
+
|
26
|
+
gem.files = `git ls-files`.split($/)
|
27
|
+
gem.files = glob[gemspec['files']] if gemspec['files']
|
28
|
+
|
29
|
+
gem.executables = gemspec.fetch('executables') do
|
30
|
+
glob['bin/*'].map { |path| File.basename(path) }
|
31
|
+
end
|
32
|
+
gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
|
33
|
+
|
34
|
+
gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
|
35
|
+
gem.test_files = glob[gemspec['test_files'] || '{test/{**/}*_test.rb']
|
36
|
+
gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
|
37
|
+
|
38
|
+
gem.require_paths = Array(gemspec.fetch('require_paths') {
|
39
|
+
%w[ext lib].select { |dir| File.directory?(dir) }
|
40
|
+
})
|
41
|
+
|
42
|
+
gem.requirements = Array(gemspec['requirements'])
|
43
|
+
gem.required_ruby_version = gemspec['required_ruby_version']
|
44
|
+
gem.required_rubygems_version = gemspec['required_rubygems_version']
|
45
|
+
gem.post_install_message = gemspec['post_install_message']
|
46
|
+
|
47
|
+
split = lambda { |string| string.split(/,\s*/) }
|
48
|
+
|
49
|
+
if gemspec['dependencies']
|
50
|
+
gemspec['dependencies'].each do |name,versions|
|
51
|
+
gem.add_dependency(name,split[versions])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if gemspec['development_dependencies']
|
56
|
+
gemspec['development_dependencies'].each do |name,versions|
|
57
|
+
gem.add_development_dependency(name,split[versions])
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/.DS_Store
ADDED
Binary file
|
data/lib/hashman.rb
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
module HashMan
|
2
|
+
class Hasher
|
3
|
+
# VERSION = "1.0.2"
|
4
|
+
|
5
|
+
MIN_ALPHABET_LENGTH = 16
|
6
|
+
SEP_DIV = 3.5
|
7
|
+
GUARD_DIV = 12.0
|
8
|
+
|
9
|
+
DEFAULT_SEPS = "cfhistuCFHISTU"
|
10
|
+
|
11
|
+
DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyz" +
|
12
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
13
|
+
"1234567890"
|
14
|
+
|
15
|
+
attr_reader :salt, :min_hash_length, :alphabet, :seps, :guards
|
16
|
+
|
17
|
+
def initialize(salt = "", min_hash_length = 0, alphabet = DEFAULT_ALPHABET)
|
18
|
+
@salt = salt
|
19
|
+
@min_hash_length = min_hash_length
|
20
|
+
@alphabet = alphabet
|
21
|
+
|
22
|
+
setup_alphabet
|
23
|
+
end
|
24
|
+
|
25
|
+
def encode(*numbers)
|
26
|
+
numbers.flatten! if numbers.length == 1
|
27
|
+
|
28
|
+
if numbers.empty? || numbers.reject { |n| Integer(n) && n >= 0 }.any?
|
29
|
+
""
|
30
|
+
else
|
31
|
+
internal_encode(numbers)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def encode_hex(str)
|
36
|
+
return "" unless hex_string?(str)
|
37
|
+
|
38
|
+
numbers = str.scan(/[\w\W]{1,12}/).map do |num|
|
39
|
+
"1#{num}".to_i(16)
|
40
|
+
end
|
41
|
+
|
42
|
+
encode(numbers)
|
43
|
+
end
|
44
|
+
|
45
|
+
def decode(hash)
|
46
|
+
return [] if hash.nil? || hash.empty?
|
47
|
+
|
48
|
+
internal_decode(hash, @alphabet)
|
49
|
+
end
|
50
|
+
|
51
|
+
def decode_hex(hash)
|
52
|
+
ret = ""
|
53
|
+
numbers = decode(hash)
|
54
|
+
|
55
|
+
numbers.length.times do |i|
|
56
|
+
ret += numbers[i].to_s(16)[1 .. -1]
|
57
|
+
end
|
58
|
+
|
59
|
+
ret.upcase
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def internal_encode(numbers)
|
65
|
+
ret = ""
|
66
|
+
|
67
|
+
alphabet = @alphabet
|
68
|
+
length = numbers.length
|
69
|
+
hash_int = 0
|
70
|
+
|
71
|
+
length.times do |i|
|
72
|
+
hash_int += (numbers[i] % (i + 100))
|
73
|
+
end
|
74
|
+
|
75
|
+
lottery = ret = alphabet[hash_int % alphabet.length]
|
76
|
+
|
77
|
+
length.times do |i|
|
78
|
+
num = numbers[i]
|
79
|
+
buf = lottery + salt + alphabet
|
80
|
+
|
81
|
+
alphabet = consistent_shuffle(alphabet, buf[0, alphabet.length])
|
82
|
+
last = hash(num, alphabet)
|
83
|
+
|
84
|
+
ret += last
|
85
|
+
|
86
|
+
if (i + 1) < length
|
87
|
+
num %= (last.ord + i)
|
88
|
+
ret += seps[num % seps.length]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if ret.length < min_hash_length
|
93
|
+
ret = guards[(hash_int + ret[0].ord) % guards.length] + ret
|
94
|
+
|
95
|
+
if ret.length < min_hash_length
|
96
|
+
ret += guards[(hash_int + ret[2].ord) % guards.length]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
half_length = alphabet.length.div(2)
|
101
|
+
|
102
|
+
while(ret.length < min_hash_length)
|
103
|
+
alphabet = consistent_shuffle(alphabet, alphabet)
|
104
|
+
ret = alphabet[half_length .. -1] + ret + alphabet[0, half_length]
|
105
|
+
|
106
|
+
excess = ret.length - min_hash_length
|
107
|
+
ret = ret[excess / 2, min_hash_length] if excess > 0
|
108
|
+
end
|
109
|
+
|
110
|
+
ret
|
111
|
+
end
|
112
|
+
|
113
|
+
def internal_decode(hash, alphabet)
|
114
|
+
ret = []
|
115
|
+
|
116
|
+
breakdown = hash.gsub(/[#{@guards}]/, " ")
|
117
|
+
array = breakdown.split(" ")
|
118
|
+
|
119
|
+
i = [3,2].include?(array.length) ? 1 : 0
|
120
|
+
|
121
|
+
if breakdown = array[i]
|
122
|
+
lottery = breakdown[0]
|
123
|
+
breakdown = breakdown[1 .. -1].gsub(/[#{@seps}]/, " ")
|
124
|
+
array = breakdown.split(" ")
|
125
|
+
|
126
|
+
array.length.times do |i|
|
127
|
+
sub_hash = array[i]
|
128
|
+
buffer = lottery + salt + alphabet
|
129
|
+
alphabet = consistent_shuffle(alphabet, buffer[0, alphabet.length])
|
130
|
+
|
131
|
+
ret.push unhash(sub_hash, alphabet)
|
132
|
+
end
|
133
|
+
|
134
|
+
if encode(ret) != hash
|
135
|
+
ret = []
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
ret
|
140
|
+
end
|
141
|
+
|
142
|
+
def consistent_shuffle(alphabet, salt)
|
143
|
+
return alphabet if salt.nil? || salt.empty?
|
144
|
+
|
145
|
+
v = 0
|
146
|
+
p = 0
|
147
|
+
|
148
|
+
(alphabet.length-1).downto(1) do |i|
|
149
|
+
v = v % salt.length
|
150
|
+
p += n = salt[v].ord
|
151
|
+
j = (n + v + p) % i
|
152
|
+
|
153
|
+
tmp_char = alphabet[j]
|
154
|
+
|
155
|
+
alphabet = alphabet[0, j] + alphabet[i] + alphabet[j + 1..-1]
|
156
|
+
alphabet = alphabet[0, i] + tmp_char + alphabet[i + 1..-1]
|
157
|
+
|
158
|
+
v += 1
|
159
|
+
end
|
160
|
+
|
161
|
+
alphabet
|
162
|
+
end
|
163
|
+
|
164
|
+
def hash(input, alphabet)
|
165
|
+
num = input.to_i
|
166
|
+
len = alphabet.length
|
167
|
+
res = ""
|
168
|
+
|
169
|
+
begin
|
170
|
+
res = "#{alphabet[num % len]}#{res}"
|
171
|
+
num = num.div(alphabet.length)
|
172
|
+
end while num > 0
|
173
|
+
|
174
|
+
res
|
175
|
+
end
|
176
|
+
|
177
|
+
def unhash(input, alphabet)
|
178
|
+
num = 0
|
179
|
+
|
180
|
+
input.length.times do |i|
|
181
|
+
pos = alphabet.index(input[i])
|
182
|
+
|
183
|
+
raise InputError, "unable to unhash" unless pos
|
184
|
+
|
185
|
+
num += pos * alphabet.length ** (input.length - i - 1)
|
186
|
+
end
|
187
|
+
|
188
|
+
num
|
189
|
+
end
|
190
|
+
|
191
|
+
private
|
192
|
+
|
193
|
+
def setup_alphabet
|
194
|
+
validate_attributes
|
195
|
+
|
196
|
+
@alphabet = uniq_characters(alphabet)
|
197
|
+
|
198
|
+
validate_alphabet
|
199
|
+
|
200
|
+
setup_seps
|
201
|
+
setup_guards
|
202
|
+
end
|
203
|
+
|
204
|
+
def setup_seps
|
205
|
+
@seps = DEFAULT_SEPS
|
206
|
+
|
207
|
+
seps.length.times do |i|
|
208
|
+
# Seps should only contain characters present in alphabet,
|
209
|
+
# and alphabet should not contains seps
|
210
|
+
if j = alphabet.index(seps[i])
|
211
|
+
@alphabet = pick_characters(alphabet, j)
|
212
|
+
else
|
213
|
+
@seps = pick_characters(seps, i)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
alphabet.delete!(' ')
|
218
|
+
seps.delete!(' ')
|
219
|
+
|
220
|
+
@seps = consistent_shuffle(seps, salt)
|
221
|
+
|
222
|
+
if seps.length == 0 || (alphabet.length / seps.length.to_f) > SEP_DIV
|
223
|
+
seps_length = (alphabet.length / SEP_DIV).ceil
|
224
|
+
seps_length = 2 if seps_length == 1
|
225
|
+
|
226
|
+
if seps_length > seps.length
|
227
|
+
diff = seps_length - seps.length;
|
228
|
+
|
229
|
+
@seps += alphabet[0, diff]
|
230
|
+
@alphabet = alphabet[diff .. -1]
|
231
|
+
else
|
232
|
+
@seps = seps[0, seps_length]
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
@alphabet = consistent_shuffle(alphabet, salt)
|
237
|
+
end
|
238
|
+
|
239
|
+
def setup_guards
|
240
|
+
gc = (alphabet.length / GUARD_DIV).ceil
|
241
|
+
|
242
|
+
if alphabet.length < 3
|
243
|
+
@guards = seps[0, gc]
|
244
|
+
@seps = seps[gc .. -1]
|
245
|
+
else
|
246
|
+
@guards = alphabet[0, gc]
|
247
|
+
@alphabet = alphabet[gc .. -1]
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
SaltError = Class.new(ArgumentError)
|
252
|
+
MinLengthError = Class.new(ArgumentError)
|
253
|
+
AlphabetError = Class.new(ArgumentError)
|
254
|
+
InputError = Class.new(ArgumentError)
|
255
|
+
|
256
|
+
def validate_attributes
|
257
|
+
unless salt.kind_of?(String)
|
258
|
+
raise SaltError, "The salt must be a String"
|
259
|
+
end
|
260
|
+
|
261
|
+
unless min_hash_length.kind_of?(Fixnum)
|
262
|
+
raise MinLengthError, "The min length must be a Fixnum"
|
263
|
+
end
|
264
|
+
|
265
|
+
unless min_hash_length >= 0
|
266
|
+
raise MinLengthError, "The min length must be 0 or more"
|
267
|
+
end
|
268
|
+
|
269
|
+
unless alphabet.kind_of?(String)
|
270
|
+
raise AlphabetError, "The alphabet must be a String"
|
271
|
+
end
|
272
|
+
|
273
|
+
if alphabet.include?(' ')
|
274
|
+
raise AlphabetError, "The alphabet can’t include spaces"
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def validate_alphabet
|
279
|
+
unless alphabet.length >= MIN_ALPHABET_LENGTH
|
280
|
+
raise AlphabetError, "Alphabet must contain at least " +
|
281
|
+
"#{MIN_ALPHABET_LENGTH} unique characters."
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
def hex_string?(string)
|
286
|
+
string.to_s.match(/\A[0-9a-fA-F]+\Z/)
|
287
|
+
end
|
288
|
+
|
289
|
+
def pick_characters(array, index)
|
290
|
+
array[0, index] + " " + array[index + 1 .. -1]
|
291
|
+
end
|
292
|
+
|
293
|
+
def uniq_characters(string)
|
294
|
+
string.split('').uniq.join('')
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'hashman/version'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
module HashMan
|
5
|
+
module Inclusion
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def hash_id
|
9
|
+
self.class.generate_hash_id(id)
|
10
|
+
end
|
11
|
+
|
12
|
+
# extend methods for class
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
|
19
|
+
# generate the hash_id using the @magic_number declared in class
|
20
|
+
# id class === String || UUIDTools::UUID || Fixnum
|
21
|
+
|
22
|
+
def hash_length(number)
|
23
|
+
@magic_number = number
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_hash_id(id)
|
27
|
+
hasher.encode(rinse_id(id))
|
28
|
+
end
|
29
|
+
|
30
|
+
# reverse the has to get the original id
|
31
|
+
# if UUID, parse it and return the UUID object
|
32
|
+
|
33
|
+
def reverse_hash(hash)
|
34
|
+
id = hasher.decode(hash).try(:first)
|
35
|
+
(self.include? Dynamoid::Document) || (self.columns_hash["id"].type == :uuid) ? UUIDTools::UUID.parse_int(id) : id
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def hasher
|
41
|
+
Hashman::Hasher.new("#{Rails.application.secrets.magic_number}", @magic_number || 4)
|
42
|
+
end
|
43
|
+
|
44
|
+
# standardize the id integer
|
45
|
+
# we do not specify UUIDTools since comparisons break!
|
46
|
+
# String (activeUUID tables), FixNum (normal ids), UUID (dynamoid/VideoClips)
|
47
|
+
|
48
|
+
def rinse_id(id)
|
49
|
+
case
|
50
|
+
when id.class == String
|
51
|
+
return UUIDTools::UUID.parse(id).to_i
|
52
|
+
when id.class == Fixnum
|
53
|
+
return id
|
54
|
+
else
|
55
|
+
return id.to_i
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hashman
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Evan Surdam
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-07-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activeuuid
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubygems-tasks
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: yard
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.8'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.8'
|
111
|
+
description: HashMan takes any number (or uuid) and converts it to a unique hash.
|
112
|
+
Includes methods on your class to access and reverse hash
|
113
|
+
email: es@cosi.io
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files:
|
117
|
+
- ChangeLog.md
|
118
|
+
- LICENSE.txt
|
119
|
+
- README.md
|
120
|
+
files:
|
121
|
+
- ".document"
|
122
|
+
- ".gitignore"
|
123
|
+
- ".rspec"
|
124
|
+
- ".yardopts"
|
125
|
+
- ChangeLog.md
|
126
|
+
- Gemfile
|
127
|
+
- LICENSE.txt
|
128
|
+
- README.md
|
129
|
+
- Rakefile
|
130
|
+
- gemspec.yml
|
131
|
+
- hashman.gemspec
|
132
|
+
- lib/.DS_Store
|
133
|
+
- lib/hashman.rb
|
134
|
+
- lib/hashman/hasher.rb
|
135
|
+
- lib/hashman/inclusion.rb
|
136
|
+
- lib/hashman/version.rb
|
137
|
+
- spec/hashman_spec.rb
|
138
|
+
- spec/spec_helper.rb
|
139
|
+
homepage: https://github.com/esurdam/hashman#readme
|
140
|
+
licenses:
|
141
|
+
- MIT
|
142
|
+
metadata: {}
|
143
|
+
post_install_message:
|
144
|
+
rdoc_options: []
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
requirements: []
|
158
|
+
rubyforge_project:
|
159
|
+
rubygems_version: 2.5.1
|
160
|
+
signing_key:
|
161
|
+
specification_version: 4
|
162
|
+
summary: add some hash magic to your classes
|
163
|
+
test_files: []
|