hash_digest 1.0.0 → 1.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 +15 -0
- data/CHANGELOG +12 -0
- data/README.markdown +7 -7
- data/hash_digest.gemspec +5 -5
- data/lib/hash_digest/version.rb +1 -1
- data/lib/hash_digest.rb +65 -16
- data/test/helper.rb +23 -2
- data/test/test_hash_digest.rb +71 -13
- metadata +68 -17
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ODI5OGYwMmEyMTQ1ZjMyNmNiMjcyMmIyMTU3NTBmMWFmNjgzMDVmNA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
ZmVlMDRkN2MxOTY4MDRiMWJjNjMyOGNmMmUwMDdlMWQyYzAyM2VlZg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MjllMmZlNjc3Zjg4M2Q2YzA5OTFlOTA4MWUzYjA0MDQ5YzA3MjRhNmU4ZWZk
|
10
|
+
NWM0Yjg4MTM3NzdjNTYwMjNmMTA0ODFiMmJjMTY5N2MyZWI0MDE2MDdkYjVm
|
11
|
+
MTcwNmY1Y2Y5MmUyYzAxNjIzOWM2MzY5ZDQ3ODY1MzZhZTgyOGE=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MTk0MDNkMzk5ZTMzOTNkMGY5OTJhOWMzZDhjNGFhY2ZiY2VkYTNkZTc0OTE2
|
14
|
+
MWJiZTFiMzdmNTgwMGE1ZjVkNGQ1YWQ4MTdlZmRhOWNiMGYxYTM0ZTgzMjUw
|
15
|
+
YzlkZDNhYzFjNWFkMzIxMDQzNGIzNTFhNmMzNjVmMDg5ZDYwMzg=
|
data/CHANGELOG
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
1.1.0 / 2014-01-01
|
2
|
+
|
3
|
+
* Breaking changes
|
4
|
+
|
5
|
+
* UTF-8 strings only because of EscapeUtils "limitations"
|
6
|
+
|
7
|
+
* Enhancements
|
8
|
+
|
9
|
+
* a changelog!
|
10
|
+
* replace CGI.escape with EscapeUtils.escape_url, which is 2x faster or so
|
11
|
+
* add HashDigest.digest2, which uses murmurhash3 instead of md5
|
12
|
+
* remove runtime dependency on activesupport
|
data/README.markdown
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# HashDigest
|
2
2
|
|
3
3
|
Generates MD5 digests of Hashes (and Arrays) indifferent to key type and ordering.
|
4
4
|
|
@@ -6,19 +6,19 @@ Useful for hashing rows in a 2-dimensional table.
|
|
6
6
|
|
7
7
|
Used by the [remote_table](https://github.com/seamusabshere/remote_table) gem.
|
8
8
|
|
9
|
-
|
9
|
+
## Example
|
10
10
|
|
11
|
-
|
11
|
+
### Indifferent to key type
|
12
12
|
|
13
13
|
HashDigest.hexdigest(:a => 1) #=> '3872c9ae3f427af0be0ead09d07ae2cf'
|
14
14
|
HashDigest.hexdigest('a' => 1) #=> '3872c9ae3f427af0be0ead09d07ae2cf'
|
15
15
|
|
16
|
-
|
16
|
+
### Indifferent to key order
|
17
17
|
|
18
18
|
HashDigest.hexdigest(:a => 1, 'b' => 2) == HashDigest.hexdigest('a' => 1, :b => 2) # true
|
19
19
|
HashDigest.hexdigest(:a => 1, 'b' => 2) == HashDigest.hexdigest(:b => 2, 'a' => 1) # true
|
20
20
|
|
21
|
-
|
21
|
+
## Algorithm
|
22
22
|
|
23
23
|
Basically represent the hash as a URL querystring, ordered by key, and MD5 that.
|
24
24
|
|
@@ -29,12 +29,12 @@ Basically represent the hash as a URL querystring, ordered by key, and MD5 that.
|
|
29
29
|
|
30
30
|
To digest an array, just pretend it's a hash with keys like 1, 2, 3, etc.
|
31
31
|
|
32
|
-
|
32
|
+
## Potential issues
|
33
33
|
|
34
34
|
* Uses MD5 (not cryptographically awesome)
|
35
35
|
* Uses ActiveSupport's <tt>#to_query</tt> method to create a digestible string like "foo=bar&baz=bam" (slow)
|
36
36
|
* Meant for flat hashes, e.g. { :a => 1, :b => 2 } and not { :x => { :y => :z } }
|
37
37
|
|
38
|
-
|
38
|
+
## Copyright
|
39
39
|
|
40
40
|
Copyright 2011 Seamus Abshere
|
data/hash_digest.gemspec
CHANGED
@@ -18,11 +18,11 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
s.add_runtime_dependency 'activesupport'
|
25
|
-
|
21
|
+
s.add_runtime_dependency 'murmurhash3'
|
22
|
+
s.add_runtime_dependency 'escape_utils'
|
23
|
+
|
26
24
|
s.add_development_dependency 'minitest'
|
27
25
|
s.add_development_dependency 'rake'
|
26
|
+
s.add_development_dependency 'activesupport'
|
27
|
+
s.add_development_dependency 'benchmark-ips'
|
28
28
|
end
|
data/lib/hash_digest/version.rb
CHANGED
data/lib/hash_digest.rb
CHANGED
@@ -1,27 +1,76 @@
|
|
1
1
|
require 'digest/md5'
|
2
|
-
require '
|
3
|
-
require '
|
4
|
-
%w{
|
5
|
-
active_support/core_ext/hash/keys
|
6
|
-
active_support/core_ext/object/to_query
|
7
|
-
}.each do |active_support_3_requirement|
|
8
|
-
require active_support_3_requirement
|
9
|
-
end if ::ActiveSupport::VERSION::MAJOR >= 3
|
2
|
+
require 'murmurhash3'
|
3
|
+
require 'escape_utils'
|
10
4
|
|
11
5
|
require "hash_digest/version"
|
12
6
|
|
13
7
|
module HashDigest
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
# CURRENT
|
9
|
+
|
10
|
+
def self.as_digest2(obj)
|
11
|
+
obj.to_hash_digest_query
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.digest2(obj)
|
15
|
+
::MurmurHash3::V32.str_hash(as_digest2(obj)).to_s 36
|
16
|
+
end
|
17
|
+
|
18
|
+
# LEGACY
|
19
|
+
|
20
|
+
def self.as_digest1(obj)
|
21
|
+
ordered_list = case obj
|
22
|
+
when ::Hash
|
23
|
+
obj.map do |k, v|
|
24
|
+
obj[k].to_hash_digest_query k
|
25
|
+
end.sort
|
26
|
+
when ::Array
|
19
27
|
ary = []
|
20
|
-
|
28
|
+
obj.each_with_index do |v, i|
|
29
|
+
ary << v.to_hash_digest_query(i)
|
30
|
+
end
|
21
31
|
ary
|
22
32
|
else
|
23
|
-
raise ::ArgumentError, "[hash_digest gem] Can only digest Hashes and Arrays, not #{
|
33
|
+
raise ::ArgumentError, "[hash_digest gem] Can only digest Hashes and Arrays, not #{obj.class}"
|
24
34
|
end
|
25
|
-
|
35
|
+
ordered_list.join '&'
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.hexdigest(obj)
|
39
|
+
::Digest::MD5.hexdigest as_digest1(obj)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# EVERYTHING BELOW IS COPIED FROM ACTIVESUPPORT 4.0.2
|
44
|
+
|
45
|
+
class Object
|
46
|
+
def to_hash_digest_param
|
47
|
+
to_s
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Array
|
52
|
+
def to_hash_digest_param
|
53
|
+
map { |e| e.to_hash_digest_param }.join '/'
|
54
|
+
end
|
55
|
+
|
56
|
+
def to_hash_digest_query(key)
|
57
|
+
prefix = "#{key}[]"
|
58
|
+
map { |value| value.to_hash_digest_query(prefix) }.join '&'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Hash
|
63
|
+
def to_hash_digest_param(namespace = nil)
|
64
|
+
map do |key, value|
|
65
|
+
value.to_hash_digest_query(namespace ? "#{namespace}[#{key}]" : key)
|
66
|
+
end.sort.join '&'
|
67
|
+
end
|
68
|
+
alias_method :to_hash_digest_query, :to_hash_digest_param
|
69
|
+
end
|
70
|
+
|
71
|
+
class Object
|
72
|
+
def to_hash_digest_query(key)
|
73
|
+
# "#{::CGI.escape(key.to_hash_digest_param)}=#{::CGI.escape(to_hash_digest_param)}"
|
74
|
+
"#{::EscapeUtils.escape_url(key.to_hash_digest_param)}=#{::EscapeUtils.escape_url(to_hash_digest_param)}"
|
26
75
|
end
|
27
76
|
end
|
data/test/helper.rb
CHANGED
@@ -1,10 +1,31 @@
|
|
1
1
|
require 'bundler/setup'
|
2
|
-
require 'minitest/spec'
|
3
2
|
require 'minitest/autorun'
|
4
3
|
|
5
4
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
6
5
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
6
|
require 'hash_digest'
|
8
7
|
|
9
|
-
|
8
|
+
require 'benchmark/ips'
|
9
|
+
require 'active_support/core_ext'
|
10
|
+
|
11
|
+
module TestHelper
|
12
|
+
def as_digest1(hsh)
|
13
|
+
ordered_list = if hsh.is_a?(::Hash)
|
14
|
+
hsh = hsh.stringify_keys
|
15
|
+
hsh.keys.sort.map { |k| hsh[k].to_query k }
|
16
|
+
elsif hsh.is_a?(::Array)
|
17
|
+
ary = []
|
18
|
+
hsh.each_with_index { |v, i| ary.push v.to_query(i.to_s) }
|
19
|
+
ary
|
20
|
+
else
|
21
|
+
raise ::ArgumentError, "[hash_digest gem] Can only digest Hashes and Arrays, not #{hsh.class}"
|
22
|
+
end
|
23
|
+
ordered_list.join('&')
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert_same_as_old(a)
|
27
|
+
expected = as_digest1(a)
|
28
|
+
got = HashDigest.as_digest1(a)
|
29
|
+
got.must_equal expected
|
30
|
+
end
|
10
31
|
end
|
data/test/test_hash_digest.rb
CHANGED
@@ -1,24 +1,82 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
require 'helper'
|
3
|
+
require 'stringio'
|
3
4
|
|
4
5
|
describe HashDigest do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
include TestHelper
|
7
|
+
|
8
|
+
describe '.digest2' do
|
9
|
+
it "is much faster than .hexdigest" do
|
10
|
+
hsh = { 'a' => 1, :b => { foo: :bar, zoo: 'animal' }, ";b\n`&" => { foo: :bar, zoo: "==&ani!!.;&mal\n" } }
|
11
|
+
begin
|
12
|
+
old_stdout = $stdout
|
13
|
+
$stdout = StringIO.new
|
14
|
+
Benchmark.ips do |x|
|
15
|
+
x.report("HashDigest.hexdigest") { HashDigest.hexdigest(hsh) }
|
16
|
+
x.report("HashDigest.digest2") { HashDigest.digest2(hsh) }
|
17
|
+
end
|
18
|
+
$stdout.rewind
|
19
|
+
result = $stdout.read
|
20
|
+
ensure
|
21
|
+
$stdout = old_stdout
|
22
|
+
end
|
23
|
+
result.must_equal ''
|
24
|
+
end
|
11
25
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
26
|
+
|
27
|
+
describe 'backwards compatibility' do
|
28
|
+
it 'for hashes' do
|
29
|
+
HashDigest.hexdigest(:a => 1).must_equal '3872c9ae3f427af0be0ead09d07ae2cf'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'for arrays' do
|
33
|
+
HashDigest.hexdigest([:a, 1]).must_equal '8ce19b95077ec34a4fd06b089f368678'
|
34
|
+
end
|
35
|
+
|
36
|
+
[
|
37
|
+
{ :a => 1 },
|
38
|
+
{ 'b' => 99.9 },
|
39
|
+
{ 'b`&' => 99.9 },
|
40
|
+
{ ';b`&' => '99.9!!.;&' },
|
41
|
+
{ ";b\n`&" => "99.9!\n!.;&" },
|
42
|
+
{ :b => { foo: :bar, zoo: 'animal' } },
|
43
|
+
{ :b => { foo: :bar, zoo: '==&ani!!.;&mal' } },
|
44
|
+
{ :b => { foo: :bar, zoo: "==&ani!!.;&mal\n" } },
|
45
|
+
{ :a => 1, 'b' => 2 },
|
46
|
+
{ 'a' => 1, :b => 2 },
|
47
|
+
{ 'a' => 1, :b => [1, 2] },
|
48
|
+
{ 'a' => 1, :b => { foo: :bar, zoo: 'animal' } },
|
49
|
+
].each do |hsh|
|
50
|
+
it "treats #{hsh} same as old" do
|
51
|
+
assert_same_as_old hsh
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
15
55
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
56
|
+
|
57
|
+
describe 'indifference to' do
|
58
|
+
it 'key type' do
|
59
|
+
HashDigest.as_digest2(:a => 1, 'b' => 2).must_equal(HashDigest.as_digest2('a' => 1, :b => 2))
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'key order' do
|
63
|
+
HashDigest.as_digest2(:a => 1, 'b' => 2).must_equal(HashDigest.as_digest2(:b => 2, 'a' => 1))
|
64
|
+
end
|
65
|
+
|
66
|
+
[
|
67
|
+
[{:a => 1, 'b' => 2}, {'a' => 1, :b => 2}],
|
68
|
+
[{:a => 1, 'b' => [1, 2]}, {'a' => 1, :b => ['1', 2]}],
|
69
|
+
[{:a => 1, 'b' => { foo: :bar }}, {'a' => 1, :b => { 'foo' => 'bar' }}],
|
70
|
+
].each do |a, b|
|
71
|
+
it "trivial difference between #{a} and #{b}" do
|
72
|
+
assert_same_as_old a
|
73
|
+
assert_same_as_old b
|
74
|
+
HashDigest.as_digest2(b).must_equal(HashDigest.as_digest2(b))
|
75
|
+
end
|
76
|
+
end
|
19
77
|
end
|
20
78
|
|
21
|
-
it "raises an exception if you try to digest
|
79
|
+
it "raises an exception if you try to digest anything other than a Hash or Array" do
|
22
80
|
lambda { HashDigest.hexdigest('foobar') }.must_raise(::ArgumentError)
|
23
81
|
end
|
24
82
|
end
|
metadata
CHANGED
@@ -1,49 +1,99 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_digest
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Seamus Abshere
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-01-01 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
requirement:
|
17
|
-
|
14
|
+
name: murmurhash3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: escape_utils
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
18
30
|
requirements:
|
19
31
|
- - ! '>='
|
20
32
|
- !ruby/object:Gem::Version
|
21
33
|
version: '0'
|
22
34
|
type: :runtime
|
23
35
|
prerelease: false
|
24
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
25
41
|
- !ruby/object:Gem::Dependency
|
26
42
|
name: minitest
|
27
|
-
requirement:
|
28
|
-
none: false
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
29
44
|
requirements:
|
30
45
|
- - ! '>='
|
31
46
|
- !ruby/object:Gem::Version
|
32
47
|
version: '0'
|
33
48
|
type: :development
|
34
49
|
prerelease: false
|
35
|
-
version_requirements:
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
36
55
|
- !ruby/object:Gem::Dependency
|
37
56
|
name: rake
|
38
|
-
requirement:
|
39
|
-
none: false
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
40
58
|
requirements:
|
41
59
|
- - ! '>='
|
42
60
|
- !ruby/object:Gem::Version
|
43
61
|
version: '0'
|
44
62
|
type: :development
|
45
63
|
prerelease: false
|
46
|
-
version_requirements:
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activesupport
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: benchmark-ips
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
47
97
|
description: Make consistent hashcodes from flat Hashes, regardless of key ordering.
|
48
98
|
Useful for hashing rows in a table.
|
49
99
|
email:
|
@@ -53,6 +103,7 @@ extensions: []
|
|
53
103
|
extra_rdoc_files: []
|
54
104
|
files:
|
55
105
|
- .gitignore
|
106
|
+
- CHANGELOG
|
56
107
|
- Gemfile
|
57
108
|
- README.markdown
|
58
109
|
- Rakefile
|
@@ -63,28 +114,28 @@ files:
|
|
63
114
|
- test/test_hash_digest.rb
|
64
115
|
homepage: https://github.com/seamusabshere/hash_digest
|
65
116
|
licenses: []
|
117
|
+
metadata: {}
|
66
118
|
post_install_message:
|
67
119
|
rdoc_options: []
|
68
120
|
require_paths:
|
69
121
|
- lib
|
70
122
|
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
-
none: false
|
72
123
|
requirements:
|
73
124
|
- - ! '>='
|
74
125
|
- !ruby/object:Gem::Version
|
75
126
|
version: '0'
|
76
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
-
none: false
|
78
128
|
requirements:
|
79
129
|
- - ! '>='
|
80
130
|
- !ruby/object:Gem::Version
|
81
131
|
version: '0'
|
82
132
|
requirements: []
|
83
133
|
rubyforge_project: hash_digest
|
84
|
-
rubygems_version: 1.
|
134
|
+
rubygems_version: 2.1.11
|
85
135
|
signing_key:
|
86
|
-
specification_version:
|
136
|
+
specification_version: 4
|
87
137
|
summary: Make consistent hashcodes from flat Hashes, regardless of key ordering
|
88
138
|
test_files:
|
89
139
|
- test/helper.rb
|
90
140
|
- test/test_hash_digest.rb
|
141
|
+
has_rdoc:
|