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 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
- = HashHash
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
- == Example
9
+ ## Example
10
10
 
11
- === Indifferent to key type
11
+ ### Indifferent to key type
12
12
 
13
13
  HashDigest.hexdigest(:a => 1) #=> '3872c9ae3f427af0be0ead09d07ae2cf'
14
14
  HashDigest.hexdigest('a' => 1) #=> '3872c9ae3f427af0be0ead09d07ae2cf'
15
15
 
16
- === Indifferent to key order
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
- == Algorithm
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
- == Potential issues
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
- == Copyright
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
- # specify any dependencies here; for example:
22
- # s.add_development_dependency "rspec"
23
- # s.add_runtime_dependency "rest-client"
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
@@ -1,3 +1,3 @@
1
1
  module HashDigest
2
- VERSION = "1.0.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/hash_digest.rb CHANGED
@@ -1,27 +1,76 @@
1
1
  require 'digest/md5'
2
- require 'active_support'
3
- require 'active_support/version'
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
- def self.hexdigest(hsh)
15
- ordered_list = if hsh.is_a?(::Hash)
16
- hsh = hsh.stringify_keys
17
- hsh.keys.sort.map { |k| hsh[k].to_query k }
18
- elsif hsh.is_a?(::Array)
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
- hsh.each_with_index { |v, i| ary.push v.to_query(i.to_s) }
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 #{hsh.class}"
33
+ raise ::ArgumentError, "[hash_digest gem] Can only digest Hashes and Arrays, not #{obj.class}"
24
34
  end
25
- ::Digest::MD5.hexdigest ordered_list.join('&')
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
- class MiniTest::Unit::TestCase
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
@@ -1,24 +1,82 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  require 'helper'
3
+ require 'stringio'
3
4
 
4
5
  describe HashDigest do
5
- it 'generates a hash of a hash' do
6
- HashDigest.hexdigest(:a => 1).must_equal '3872c9ae3f427af0be0ead09d07ae2cf'
7
- end
8
-
9
- it 'is indifferent to key type' do
10
- HashDigest.hexdigest(:a => 1, 'b' => 2).must_equal(HashDigest.hexdigest('a' => 1, :b => 2))
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
- it 'is indifferent to key order' do
14
- HashDigest.hexdigest(:a => 1, 'b' => 2).must_equal(HashDigest.hexdigest(:b => 2, 'a' => 1))
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
- it 'just as a bonus, works on arrays' do
18
- HashDigest.hexdigest([:a, 1]).must_equal '8ce19b95077ec34a4fd06b089f368678'
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 something it doesn't handle" do
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.0.0
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: 2011-09-23 00:00:00.000000000Z
11
+ date: 2014-01-01 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: activesupport
16
- requirement: &2154254960 !ruby/object:Gem::Requirement
17
- none: false
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: *2154254960
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: &2154254540 !ruby/object:Gem::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: *2154254540
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: &2154254120 !ruby/object:Gem::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: *2154254120
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.8.6
134
+ rubygems_version: 2.1.11
85
135
  signing_key:
86
- specification_version: 3
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: