hwia 1.0.2
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/README +93 -0
- data/Rakefile +73 -0
- data/bench/as_hwia.rb +137 -0
- data/bench/bench.rb +52 -0
- data/ext/hwia/extconf.rb +12 -0
- data/ext/hwia/hwia.c +497 -0
- data/hwia.gemspec +25 -0
- data/lib/hwia_rails.rb +26 -0
- data/test/helper.rb +2 -0
- data/test/test_hwia.rb +284 -0
- metadata +65 -0
data/README
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
Attempt at a zippier HashWithIndifferentAccess for Ruby MRI
|
2
|
+
(c) 2009 Lourens Naudé (methodmissing), James Tucker (raggi)
|
3
|
+
|
4
|
+
http://github.com/methodmissing/hwia
|
5
|
+
|
6
|
+
This library works with Ruby MRI > 1.8.6 && 1.9.2 and is a more efficient implementation of
|
7
|
+
ActiveSupport::HashWithIndifferentAccess, which allows interchangeable use of string and symbol
|
8
|
+
hash keys.
|
9
|
+
|
10
|
+
The numbers ?
|
11
|
+
|
12
|
+
methodmissing:hwia lourens$ rake bench
|
13
|
+
(in /Users/lourens/projects/hwia)
|
14
|
+
/opt/local/bin/ruby extconf.rb
|
15
|
+
checking for rb_thread_blocking_region()... no
|
16
|
+
checking for rb_trap_immediate in ruby.h,rubysig.h... yes
|
17
|
+
creating Makefile
|
18
|
+
make
|
19
|
+
gcc -I. -I. -I/opt/local/lib/ruby/1.8/i686-darwin9.5.1 -I. -DHAVE_RB_TRAP_IMMEDIATE -DRUBY18 -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -fno-common -O2 -pipe -fno-common -c hwia.c
|
20
|
+
cc -dynamic -bundle -undefined suppress -flat_namespace -o hwia.bundle hwia.o -L. -L/opt/local/lib -L. -ldl -lobjc
|
21
|
+
/opt/local/bin/ruby bench/bench.rb
|
22
|
+
Rehearsal ------------------------------------------------------------------------------
|
23
|
+
StrHash#[:sym] 0.000000 0.000000 0.000000 ( 0.002999)
|
24
|
+
HashWithIndifferentAccess#[:sym] 0.020000 0.000000 0.020000 ( 0.021942)
|
25
|
+
StrHash#['str'] 0.010000 0.000000 0.010000 ( 0.004590)
|
26
|
+
HashWithIndifferentAccess#['str] 0.000000 0.000000 0.000000 ( 0.003842)
|
27
|
+
StrHash#key?(:sym) 0.010000 0.000000 0.010000 ( 0.002902)
|
28
|
+
HashWithIndifferentAccess#key?(:sym) 0.010000 0.000000 0.010000 ( 0.013906)
|
29
|
+
StrHash#key?('str') 0.000000 0.000000 0.000000 ( 0.004245)
|
30
|
+
HashWithIndifferentAccess#key?('str') 0.010000 0.000000 0.010000 ( 0.010769)
|
31
|
+
StrHash#fetch(:sym) 0.010000 0.000000 0.010000 ( 0.003285)
|
32
|
+
HashWithIndifferentAccess#fetch(:sym) 0.020000 0.000000 0.020000 ( 0.019375)
|
33
|
+
StrHash#fetch('str') 0.000000 0.000000 0.000000 ( 0.003811)
|
34
|
+
HashWithIndifferentAccess#fetch('str') 0.010000 0.000000 0.010000 ( 0.014550)
|
35
|
+
StrHash#values_at(:sym) 0.010000 0.000000 0.010000 ( 0.006830)
|
36
|
+
HashWithIndifferentAccess#values_at(:sym) 0.020000 0.000000 0.020000 ( 0.020391)
|
37
|
+
StrHash#values_at('str') 0.010000 0.000000 0.010000 ( 0.007286)
|
38
|
+
HashWithIndifferentAccess#values_at('str') 0.020000 0.000000 0.020000 ( 0.019495)
|
39
|
+
StrHash#['str']= 0.000000 0.000000 0.000000 ( 0.004249)
|
40
|
+
HashWithIndifferentAccess#['str]= 0.020000 0.000000 0.020000 ( 0.017406)
|
41
|
+
StrHash#[:sym]= 0.000000 0.000000 0.000000 ( 0.003898)
|
42
|
+
HashWithIndifferentAccess#[:sym]= 0.020000 0.000000 0.020000 ( 0.020366)
|
43
|
+
StrHash#update 0.010000 0.000000 0.010000 ( 0.005549)
|
44
|
+
HashWithIndifferentAccess#update 0.050000 0.000000 0.050000 ( 0.058935)
|
45
|
+
StrHash#dup 0.030000 0.000000 0.030000 ( 0.029752)
|
46
|
+
HashWithIndifferentAccess#dup 0.120000 0.000000 0.120000 ( 0.128600)
|
47
|
+
StrHash#merge 0.030000 0.000000 0.030000 ( 0.027572)
|
48
|
+
HashWithIndifferentAccess#merge 0.180000 0.000000 0.180000 ( 0.179814)
|
49
|
+
StrHash#to_hash 0.030000 0.000000 0.030000 ( 0.034352)
|
50
|
+
HashWithIndifferentAccess#to_hash 0.040000 0.000000 0.040000 ( 0.038520)
|
51
|
+
--------------------------------------------------------------------- total: 0.690000sec
|
52
|
+
|
53
|
+
user system total real
|
54
|
+
StrHash#[:sym] 0.010000 0.000000 0.010000 ( 0.003005)
|
55
|
+
HashWithIndifferentAccess#[:sym] 0.020000 0.000000 0.020000 ( 0.020551)
|
56
|
+
StrHash#['str'] 0.000000 0.000000 0.000000 ( 0.003312)
|
57
|
+
HashWithIndifferentAccess#['str] 0.000000 0.000000 0.000000 ( 0.003213)
|
58
|
+
StrHash#key?(:sym) 0.000000 0.000000 0.000000 ( 0.002851)
|
59
|
+
HashWithIndifferentAccess#key?(:sym) 0.010000 0.000000 0.010000 ( 0.012139)
|
60
|
+
StrHash#key?('str') 0.010000 0.000000 0.010000 ( 0.003496)
|
61
|
+
HashWithIndifferentAccess#key?('str') 0.010000 0.000000 0.010000 ( 0.009912)
|
62
|
+
StrHash#fetch(:sym) 0.000000 0.000000 0.000000 ( 0.003172)
|
63
|
+
HashWithIndifferentAccess#fetch(:sym) 0.020000 0.000000 0.020000 ( 0.017662)
|
64
|
+
StrHash#fetch('str') 0.000000 0.000000 0.000000 ( 0.003850)
|
65
|
+
HashWithIndifferentAccess#fetch('str') 0.020000 0.000000 0.020000 ( 0.014771)
|
66
|
+
StrHash#values_at(:sym) 0.000000 0.000000 0.000000 ( 0.004340)
|
67
|
+
HashWithIndifferentAccess#values_at(:sym) 0.030000 0.000000 0.030000 ( 0.020821)
|
68
|
+
StrHash#values_at('str') 0.000000 0.000000 0.000000 ( 0.004842)
|
69
|
+
HashWithIndifferentAccess#values_at('str') 0.020000 0.000000 0.020000 ( 0.017479)
|
70
|
+
StrHash#['str']= 0.010000 0.000000 0.010000 ( 0.004225)
|
71
|
+
HashWithIndifferentAccess#['str]= 0.010000 0.000000 0.010000 ( 0.018206)
|
72
|
+
StrHash#[:sym]= 0.010000 0.000000 0.010000 ( 0.003825)
|
73
|
+
HashWithIndifferentAccess#[:sym]= 0.020000 0.000000 0.020000 ( 0.019323)
|
74
|
+
StrHash#update 0.000000 0.000000 0.000000 ( 0.005478)
|
75
|
+
HashWithIndifferentAccess#update 0.050000 0.000000 0.050000 ( 0.048082)
|
76
|
+
StrHash#dup 0.030000 0.000000 0.030000 ( 0.023855)
|
77
|
+
HashWithIndifferentAccess#dup 0.110000 0.000000 0.110000 ( 0.120992)
|
78
|
+
StrHash#merge 0.030000 0.000000 0.030000 ( 0.027412)
|
79
|
+
HashWithIndifferentAccess#merge 0.180000 0.000000 0.180000 ( 0.173209)
|
80
|
+
StrHash#to_hash 0.030000 0.000000 0.030000 ( 0.034561)
|
81
|
+
HashWithIndifferentAccess#to_hash 0.030000 0.000000 0.030000 ( 0.025392)
|
82
|
+
|
83
|
+
Installation
|
84
|
+
|
85
|
+
1. sudo gem install methodmissing-hwia
|
86
|
+
2. 'require "hwia_rails"' from within an initializer
|
87
|
+
|
88
|
+
There's no step three, however, if the gem hasn't built due to github queue delays today (22/08/2009), clone, 'rake gem' and install.
|
89
|
+
|
90
|
+
It's unlikely you'd see these improvements for most apps as at the framework level, especially for Rails 3.0,
|
91
|
+
this mostly negates query params access overheads.Any pointers and refactoring suggestions much appreciated.
|
92
|
+
|
93
|
+
Have fun, happy hacking!
|
data/Rakefile
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
4
|
+
|
5
|
+
HWIA_ROOT = 'ext/hwia'
|
6
|
+
|
7
|
+
desc 'Default: test'
|
8
|
+
task :default => :test
|
9
|
+
|
10
|
+
desc 'Run hwia tests.'
|
11
|
+
Rake::TestTask.new(:test) do |t|
|
12
|
+
t.libs = [HWIA_ROOT]
|
13
|
+
t.pattern = 'test/test_*.rb'
|
14
|
+
t.verbose = true
|
15
|
+
end
|
16
|
+
task :test => :build
|
17
|
+
|
18
|
+
namespace :build do
|
19
|
+
file "#{HWIA_ROOT}/hwia.c"
|
20
|
+
file "#{HWIA_ROOT}/extconf.rb"
|
21
|
+
file "#{HWIA_ROOT}/Makefile" => %W(#{HWIA_ROOT}/hwia.c #{HWIA_ROOT}/extconf.rb) do
|
22
|
+
Dir.chdir(HWIA_ROOT) do
|
23
|
+
ruby 'extconf.rb'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "generate makefile"
|
28
|
+
task :makefile => %W(#{HWIA_ROOT}/Makefile #{HWIA_ROOT}/hwia.c)
|
29
|
+
|
30
|
+
dlext = Config::CONFIG['DLEXT']
|
31
|
+
file "#{HWIA_ROOT}/hwia.#{dlext}" => %W(#{HWIA_ROOT}/Makefile #{HWIA_ROOT}/hwia.c) do
|
32
|
+
Dir.chdir(HWIA_ROOT) do
|
33
|
+
sh 'make' # TODO - is there a config for which make somewhere?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "compile hwia extension"
|
38
|
+
task :compile => "#{HWIA_ROOT}/hwia.#{dlext}"
|
39
|
+
|
40
|
+
task :clean do
|
41
|
+
Dir.chdir(HWIA_ROOT) do
|
42
|
+
sh 'make clean'
|
43
|
+
end if File.exists?("#{HWIA_ROOT}/Makefile")
|
44
|
+
end
|
45
|
+
|
46
|
+
CLEAN.include("#{HWIA_ROOT}/Makefile")
|
47
|
+
CLEAN.include("#{HWIA_ROOT}/hwia.#{dlext}")
|
48
|
+
end
|
49
|
+
|
50
|
+
task :clean => %w(build:clean)
|
51
|
+
|
52
|
+
desc "compile"
|
53
|
+
task :build => %w(build:compile)
|
54
|
+
|
55
|
+
task :install do |t|
|
56
|
+
Dir.chdir(HWIA_ROOT) do
|
57
|
+
sh 'sudo make install'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
desc "clean build install"
|
62
|
+
task :setup => %w(clean build install)
|
63
|
+
|
64
|
+
desc "run benchmarks"
|
65
|
+
task :bench do
|
66
|
+
ruby "bench/bench.rb"
|
67
|
+
end
|
68
|
+
task :bench => :build
|
69
|
+
|
70
|
+
desc "build gem"
|
71
|
+
task :gem do
|
72
|
+
sh "gem build hwia.gemspec"
|
73
|
+
end
|
data/bench/as_hwia.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# This class has dubious semantics and we only have it so that
|
2
|
+
# people can write params[:key] instead of params['key']
|
3
|
+
# and they get the same value for both keys.
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
class HashWithIndifferentAccess < Hash
|
7
|
+
def initialize(constructor = {})
|
8
|
+
if constructor.is_a?(Hash)
|
9
|
+
super()
|
10
|
+
update(constructor)
|
11
|
+
else
|
12
|
+
super(constructor)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def default(key = nil)
|
17
|
+
if key.is_a?(Symbol) && include?(key = key.to_s)
|
18
|
+
self[key]
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
|
25
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
26
|
+
|
27
|
+
# Assigns a new value to the hash:
|
28
|
+
#
|
29
|
+
# hash = HashWithIndifferentAccess.new
|
30
|
+
# hash[:key] = "value"
|
31
|
+
#
|
32
|
+
def []=(key, value)
|
33
|
+
regular_writer(convert_key(key), convert_value(value))
|
34
|
+
end
|
35
|
+
|
36
|
+
# Updates the instantized hash with values from the second:
|
37
|
+
#
|
38
|
+
# hash_1 = HashWithIndifferentAccess.new
|
39
|
+
# hash_1[:key] = "value"
|
40
|
+
#
|
41
|
+
# hash_2 = HashWithIndifferentAccess.new
|
42
|
+
# hash_2[:key] = "New Value!"
|
43
|
+
#
|
44
|
+
# hash_1.update(hash_2) # => {"key"=>"New Value!"}
|
45
|
+
#
|
46
|
+
def update(other_hash)
|
47
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
alias_method :merge!, :update
|
52
|
+
|
53
|
+
# Checks the hash for a key matching the argument passed in:
|
54
|
+
#
|
55
|
+
# hash = HashWithIndifferentAccess.new
|
56
|
+
# hash["key"] = "value"
|
57
|
+
# hash.key? :key # => true
|
58
|
+
# hash.key? "key" # => true
|
59
|
+
#
|
60
|
+
def key?(key)
|
61
|
+
super(convert_key(key))
|
62
|
+
end
|
63
|
+
|
64
|
+
alias_method :include?, :key?
|
65
|
+
alias_method :has_key?, :key?
|
66
|
+
alias_method :member?, :key?
|
67
|
+
|
68
|
+
# Fetches the value for the specified key, same as doing hash[key]
|
69
|
+
def fetch(key, *extras)
|
70
|
+
super(convert_key(key), *extras)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns an array of the values at the specified indices:
|
74
|
+
#
|
75
|
+
# hash = HashWithIndifferentAccess.new
|
76
|
+
# hash[:a] = "x"
|
77
|
+
# hash[:b] = "y"
|
78
|
+
# hash.values_at("a", "b") # => ["x", "y"]
|
79
|
+
#
|
80
|
+
def values_at(*indices)
|
81
|
+
indices.collect {|key| self[convert_key(key)]}
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns an exact copy of the hash.
|
85
|
+
def dup
|
86
|
+
HashWithIndifferentAccess.new(self)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
|
90
|
+
# Does not overwrite the existing hash.
|
91
|
+
def merge(hash)
|
92
|
+
self.dup.update(hash)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
|
96
|
+
# This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
|
97
|
+
def reverse_merge(other_hash)
|
98
|
+
super other_hash.with_indifferent_access
|
99
|
+
end
|
100
|
+
|
101
|
+
def reverse_merge!(other_hash)
|
102
|
+
replace(reverse_merge( other_hash ))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Removes a specified key from the hash.
|
106
|
+
def delete(key)
|
107
|
+
super(convert_key(key))
|
108
|
+
end
|
109
|
+
|
110
|
+
def stringify_keys!; self end
|
111
|
+
def symbolize_keys!; self end
|
112
|
+
def to_options!; self end
|
113
|
+
|
114
|
+
# Convert to a Hash with String keys.
|
115
|
+
def to_hash
|
116
|
+
Hash.new(default).merge!(self)
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
def convert_key(key)
|
121
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
122
|
+
end
|
123
|
+
|
124
|
+
def convert_value(value)
|
125
|
+
case value
|
126
|
+
when Hash
|
127
|
+
value.with_indifferent_access
|
128
|
+
when Array
|
129
|
+
value.collect { |e| e.is_a?(Hash) ? e.with_indifferent_access : e }
|
130
|
+
else
|
131
|
+
value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
|
data/bench/bench.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require "benchmark"
|
2
|
+
$:.unshift "." #ruby 1.9.2
|
3
|
+
require File.dirname(__FILE__) + "/../ext/hwia/hwia"
|
4
|
+
require File.dirname(__FILE__) + "/as_hwia"
|
5
|
+
|
6
|
+
STR_HASH = { :a => 1, 'b' => 2, 1 => 1, [1] => 1 }.strhash
|
7
|
+
HWIA_HASH = HashWithIndifferentAccess.new({ :a => 1, 'b' => 2, 1 => 1, [1] => 1 })
|
8
|
+
HASH = { :d => :d, 'e' => :e }
|
9
|
+
|
10
|
+
TESTS = 100_000
|
11
|
+
Benchmark.bmbm do |results|
|
12
|
+
results.report("StrHash#[:sym]") { TESTS.times { STR_HASH[:sym] } }
|
13
|
+
results.report("HashWithIndifferentAccess#[:sym]") { TESTS.times { HWIA_HASH[:a] } }
|
14
|
+
results.report("StrHash#['str']") { TESTS.times { STR_HASH['b'] } }
|
15
|
+
results.report("HashWithIndifferentAccess#['str]") { TESTS.times { HWIA_HASH['b'] } }
|
16
|
+
results.report("StrHash#[1]") { TESTS.times { STR_HASH[1] } }
|
17
|
+
results.report("HashWithIndifferentAccess#[1]") { TESTS.times { HWIA_HASH[1] } }
|
18
|
+
results.report("StrHash#[[1]]") { TESTS.times { STR_HASH[[1]] } }
|
19
|
+
results.report("HashWithIndifferentAccess#[[1]]") { TESTS.times { HWIA_HASH[[1]] } }
|
20
|
+
results.report("StrHash#key?(:sym)") { TESTS.times { STR_HASH.key?(:a) } }
|
21
|
+
results.report("HashWithIndifferentAccess#key?(:sym)") { TESTS.times { HWIA_HASH.key?(:a) } }
|
22
|
+
results.report("StrHash#key?('str')") { TESTS.times { STR_HASH.key?('a') } }
|
23
|
+
results.report("HashWithIndifferentAccess#key?('str')") { TESTS.times { HWIA_HASH.key?('a') } }
|
24
|
+
results.report("StrHash#fetch(:sym)") { TESTS.times { STR_HASH.fetch(:a) } }
|
25
|
+
results.report("HashWithIndifferentAccess#fetch(:sym)") { TESTS.times { HWIA_HASH.fetch(:a) } }
|
26
|
+
results.report("StrHash#fetch('str')") { TESTS.times { STR_HASH.fetch('a') } }
|
27
|
+
results.report("HashWithIndifferentAccess#fetch('str')") { TESTS.times { HWIA_HASH.fetch('a') } }
|
28
|
+
results.report("StrHash#values_at(:sym)") { TESTS.times { STR_HASH.values_at(:a) } }
|
29
|
+
results.report("HashWithIndifferentAccess#values_at(:sym)") { TESTS.times { HWIA_HASH.values_at(:a) } }
|
30
|
+
results.report("StrHash#values_at('str')") { TESTS.times { STR_HASH.values_at('a') } }
|
31
|
+
results.report("HashWithIndifferentAccess#values_at('str')") { TESTS.times { HWIA_HASH.values_at('a') } }
|
32
|
+
results.report("StrHash#['str']=") { TESTS.times { STR_HASH['c'] = :c } }
|
33
|
+
results.report("HashWithIndifferentAccess#['str]=") { TESTS.times { HWIA_HASH['c'] = :c } }
|
34
|
+
results.report("StrHash#[:sym]=") { TESTS.times { STR_HASH[:c] = :c } }
|
35
|
+
results.report("HashWithIndifferentAccess#[:sym]=") { TESTS.times { HWIA_HASH[:c] = :c } }
|
36
|
+
results.report("StrHash#[2]=") { TESTS.times { STR_HASH[2] = 2 } }
|
37
|
+
results.report("HashWithIndifferentAccess#[2]=") { TESTS.times { HWIA_HASH[2] = 2 } }
|
38
|
+
results.report("StrHash#[[2]]=") { TESTS.times { STR_HASH[[2]] = 2 } }
|
39
|
+
results.report("HashWithIndifferentAccess#[[2]]=") { TESTS.times { HWIA_HASH[[2]] = 2 } }
|
40
|
+
results.report("StrHash#update") { TESTS.times { STR_HASH.update(HASH) } }
|
41
|
+
results.report("HashWithIndifferentAccess#update") { TESTS.times { HWIA_HASH.update(HASH) } }
|
42
|
+
results.report("StrHash#dup") { TESTS.times { STR_HASH.dup } }
|
43
|
+
results.report("HashWithIndifferentAccess#dup") { TESTS.times { HWIA_HASH.dup } }
|
44
|
+
results.report("StrHash#merge") { TESTS.times { STR_HASH.merge(HASH) } }
|
45
|
+
results.report("HashWithIndifferentAccess#merge") { TESTS.times { HWIA_HASH.merge(HASH) } }
|
46
|
+
results.report("StrHash#to_hash") { TESTS.times { STR_HASH.to_hash } }
|
47
|
+
results.report("HashWithIndifferentAccess#to_hash") { TESTS.times { HWIA_HASH.to_hash } }
|
48
|
+
results.report("StrHash#keys") { TESTS.times { STR_HASH.keys } }
|
49
|
+
results.report("HashWithIndifferentAccess#keys") { TESTS.times { HWIA_HASH.keys } }
|
50
|
+
results.report("StrHash#values") { TESTS.times { STR_HASH.values } }
|
51
|
+
results.report("HashWithIndifferentAccess#values") { TESTS.times { HWIA_HASH.values } }
|
52
|
+
end
|
data/ext/hwia/extconf.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
def add_define(name)
|
4
|
+
$defs.push("-D#{name}")
|
5
|
+
end
|
6
|
+
|
7
|
+
dir_config('hwia')
|
8
|
+
|
9
|
+
add_define 'RUBY19' if have_func('rb_thread_blocking_region') and have_macro('RUBY_UBF_IO', 'ruby.h')
|
10
|
+
add_define 'RUBY18' if have_var('rb_trap_immediate', ['ruby.h', 'rubysig.h'])
|
11
|
+
|
12
|
+
create_makefile('hwia')
|
data/ext/hwia/hwia.c
ADDED
@@ -0,0 +1,497 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
#ifdef RUBY19
|
3
|
+
#include "ruby/st.h"
|
4
|
+
#else
|
5
|
+
#include "st.h"
|
6
|
+
#endif
|
7
|
+
|
8
|
+
VALUE rb_cStrHash;
|
9
|
+
|
10
|
+
static ID id_strhash, id_hash;
|
11
|
+
static VALUE hash_format;
|
12
|
+
|
13
|
+
#ifndef RSTRING_PTR
|
14
|
+
#define RSTRING_PTR(obj) RSTRING(obj)->ptr
|
15
|
+
#endif
|
16
|
+
|
17
|
+
#ifndef RSTRING_LEN
|
18
|
+
#define RSTRING_LEN(obj) RSTRING(obj)->len
|
19
|
+
#endif
|
20
|
+
|
21
|
+
#ifndef RARRAY_PTR
|
22
|
+
#define RARRAY_PTR(obj) RARRAY(obj)->heap.ptr
|
23
|
+
#endif
|
24
|
+
|
25
|
+
#ifndef RARRAY_LEN
|
26
|
+
#define RARRAY_LEN(obj) RARRAY(obj)->heap.len
|
27
|
+
#endif
|
28
|
+
|
29
|
+
#ifdef RUBY19
|
30
|
+
#define HASH_TBL(obj) RHASH(obj)->ntbl
|
31
|
+
#else
|
32
|
+
#define HASH_TBL(obj) RHASH(obj)->tbl
|
33
|
+
#endif
|
34
|
+
|
35
|
+
static int
|
36
|
+
strhash(register const char *string)
|
37
|
+
{
|
38
|
+
register int c;
|
39
|
+
register int val = 0;
|
40
|
+
while ((c = *string++) != '\0') {
|
41
|
+
val = val*997 + c;
|
42
|
+
}
|
43
|
+
return val + (val>>5);
|
44
|
+
}
|
45
|
+
|
46
|
+
int
|
47
|
+
rb_sym_strhash(VALUE *sym)
|
48
|
+
{
|
49
|
+
ID id = SYM2ID(*sym);
|
50
|
+
return strhash((char*)rb_id2name(id));
|
51
|
+
}
|
52
|
+
|
53
|
+
static VALUE
|
54
|
+
rb_sym_strhash_m(VALUE sym)
|
55
|
+
{
|
56
|
+
return INT2FIX(rb_sym_strhash(&sym));
|
57
|
+
}
|
58
|
+
|
59
|
+
int
|
60
|
+
rb_str_strhash(VALUE *str)
|
61
|
+
{
|
62
|
+
return strhash((char*)RSTRING_PTR(*str));
|
63
|
+
}
|
64
|
+
|
65
|
+
static VALUE
|
66
|
+
rb_str_strhash_m(VALUE str)
|
67
|
+
{
|
68
|
+
return INT2FIX(rb_str_strhash(&str));
|
69
|
+
}
|
70
|
+
|
71
|
+
int
|
72
|
+
rb_strhash_cmp(VALUE *s1,VALUE *s2)
|
73
|
+
{
|
74
|
+
int s1_hash = SYMBOL_P(*s1) ? rb_sym_strhash(s1) : rb_str_strhash(s1);
|
75
|
+
int s2_hash = SYMBOL_P(*s2) ? rb_sym_strhash(s2) : rb_str_strhash(s2);
|
76
|
+
if (s1_hash == s2_hash) return 0;
|
77
|
+
if (s1_hash > s2_hash) return 1;
|
78
|
+
return -1;
|
79
|
+
}
|
80
|
+
|
81
|
+
/* hash.c */
|
82
|
+
static VALUE
|
83
|
+
rb_hash_has_key(hash, key)
|
84
|
+
VALUE hash;
|
85
|
+
VALUE key;
|
86
|
+
{
|
87
|
+
#ifdef RUBY19
|
88
|
+
if (!HASH_TBL(hash))
|
89
|
+
return Qfalse;
|
90
|
+
#endif
|
91
|
+
if (st_lookup(HASH_TBL(hash), key, 0)) {
|
92
|
+
return Qtrue;
|
93
|
+
}
|
94
|
+
return Qfalse;
|
95
|
+
}
|
96
|
+
|
97
|
+
/* hash.c */
|
98
|
+
#ifdef RUBY18
|
99
|
+
static VALUE
|
100
|
+
eql(VALUE *args)
|
101
|
+
{
|
102
|
+
return (VALUE)rb_eql(args[0], args[1]);
|
103
|
+
}
|
104
|
+
#endif
|
105
|
+
|
106
|
+
/* hash.c */
|
107
|
+
static int
|
108
|
+
rb_strhash_hash_cmp(VALUE a, VALUE b)
|
109
|
+
{
|
110
|
+
#ifdef RUBY18
|
111
|
+
VALUE args[2];
|
112
|
+
#endif
|
113
|
+
if (a == b) return 0;
|
114
|
+
if (FIXNUM_P(a) && FIXNUM_P(b)) return a != b;
|
115
|
+
if (a == Qundef || b == Qundef) return -1;
|
116
|
+
if (SYMBOL_P(a) && SYMBOL_P(b)) return a != b;
|
117
|
+
if ((TYPE(a) == T_STRING && RBASIC(a)->klass == rb_cString && SYMBOL_P(b)) || (TYPE(b) == T_STRING && RBASIC(b)->klass == rb_cString && SYMBOL_P(a))) {
|
118
|
+
return rb_strhash_cmp(&a, &b);
|
119
|
+
}
|
120
|
+
if (TYPE(a) == T_STRING && RBASIC(a)->klass == rb_cString &&
|
121
|
+
TYPE(b) == T_STRING && RBASIC(b)->klass == rb_cString) {
|
122
|
+
return rb_str_cmp(a, b);
|
123
|
+
}
|
124
|
+
#ifdef RUBY18
|
125
|
+
args[0] = a;
|
126
|
+
args[1] = b;
|
127
|
+
return !rb_with_disable_interrupt(eql, (VALUE)args);
|
128
|
+
#else
|
129
|
+
return !rb_eql(a, b);
|
130
|
+
#endif
|
131
|
+
}
|
132
|
+
|
133
|
+
/* hash.c */
|
134
|
+
static int
|
135
|
+
rb_strhash_hash(VALUE a)
|
136
|
+
{
|
137
|
+
VALUE hval;
|
138
|
+
int hnum;
|
139
|
+
|
140
|
+
switch (TYPE(a)) {
|
141
|
+
case T_FIXNUM:
|
142
|
+
#ifdef RUBY18
|
143
|
+
return (int)a;
|
144
|
+
#else
|
145
|
+
hnum = (int)a;
|
146
|
+
break;
|
147
|
+
#endif
|
148
|
+
case T_SYMBOL:
|
149
|
+
hnum = rb_sym_strhash(&a);
|
150
|
+
break;
|
151
|
+
case T_STRING:
|
152
|
+
hnum = rb_str_strhash(&a);
|
153
|
+
break;
|
154
|
+
|
155
|
+
default:
|
156
|
+
hval = rb_hash(a);
|
157
|
+
#ifdef RUBY18
|
158
|
+
if (!FIXNUM_P(hval)) {
|
159
|
+
hval = rb_funcall(hval, '%', 1, hash_format);
|
160
|
+
}
|
161
|
+
#endif
|
162
|
+
hnum = (int)hval;
|
163
|
+
}
|
164
|
+
#ifdef RUBY18
|
165
|
+
return hnum;
|
166
|
+
#else
|
167
|
+
hnum <<= 1;
|
168
|
+
return RSHIFT(hnum, 1);
|
169
|
+
#endif
|
170
|
+
}
|
171
|
+
|
172
|
+
static struct st_hash_type objstrhash = {
|
173
|
+
rb_strhash_hash_cmp,
|
174
|
+
rb_strhash_hash,
|
175
|
+
};
|
176
|
+
|
177
|
+
static void
|
178
|
+
rb_hash_modify_check(VALUE *hash){
|
179
|
+
if (OBJ_FROZEN(*hash)) rb_error_frozen("hash");
|
180
|
+
if (!OBJ_TAINTED(*hash) && rb_safe_level() >= 4)
|
181
|
+
rb_raise(rb_eSecurityError, "Insecure: can't modify hash");
|
182
|
+
}
|
183
|
+
|
184
|
+
/* hash.c */
|
185
|
+
static void
|
186
|
+
rb_hash_modify(VALUE hash)
|
187
|
+
{
|
188
|
+
#ifdef RUBY18
|
189
|
+
if (!HASH_TBL(hash)) rb_raise(rb_eTypeError, "uninitialized Hash");
|
190
|
+
#endif
|
191
|
+
rb_hash_modify_check(&hash);
|
192
|
+
#ifdef RUBY19
|
193
|
+
if (!HASH_TBL(hash)) HASH_TBL(hash) = st_init_table(&objstrhash);
|
194
|
+
#endif
|
195
|
+
}
|
196
|
+
|
197
|
+
static VALUE strhash_alloc0 _((VALUE*));
|
198
|
+
static VALUE strhash_alloc _((VALUE));
|
199
|
+
/* hash.c */
|
200
|
+
static VALUE
|
201
|
+
strhash_alloc0(VALUE *klass)
|
202
|
+
{
|
203
|
+
NEWOBJ(hash, struct RHash);
|
204
|
+
OBJSETUP(hash, *klass, T_HASH);
|
205
|
+
|
206
|
+
hash->ifnone = Qnil;
|
207
|
+
|
208
|
+
return (VALUE)hash;
|
209
|
+
}
|
210
|
+
|
211
|
+
static VALUE
|
212
|
+
strhash_alloc(VALUE klass)
|
213
|
+
{
|
214
|
+
VALUE hash = strhash_alloc0(&klass);
|
215
|
+
|
216
|
+
HASH_TBL(hash) = st_init_table(&objstrhash);
|
217
|
+
|
218
|
+
return hash;
|
219
|
+
}
|
220
|
+
|
221
|
+
static VALUE rb_hash_strhash(VALUE hash);
|
222
|
+
static void rb_strhash_convert(VALUE *value);
|
223
|
+
|
224
|
+
/* hash.c */
|
225
|
+
static int
|
226
|
+
rb_hash_rehash_i(VALUE key, VALUE value, st_table *tbl)
|
227
|
+
{
|
228
|
+
if (key != Qundef){
|
229
|
+
rb_strhash_convert(&value);
|
230
|
+
st_insert(tbl, key, value);
|
231
|
+
}
|
232
|
+
return ST_CONTINUE;
|
233
|
+
}
|
234
|
+
|
235
|
+
/* hash.c */
|
236
|
+
static VALUE
|
237
|
+
rb_strhash_rehash(VALUE hash)
|
238
|
+
{
|
239
|
+
st_table *tbl;
|
240
|
+
#ifdef RUBY19
|
241
|
+
if (RHASH(hash)->iter_lev > 0) {
|
242
|
+
rb_raise(rb_eRuntimeError, "rehash during iteration");
|
243
|
+
}
|
244
|
+
rb_hash_modify_check(&hash);
|
245
|
+
if (!RHASH(hash)->ntbl)
|
246
|
+
return hash;
|
247
|
+
#endif
|
248
|
+
rb_hash_modify(hash);
|
249
|
+
tbl = st_init_table_with_size(&objstrhash, HASH_TBL(hash)->num_entries);
|
250
|
+
rb_hash_foreach(hash, rb_hash_rehash_i, (st_data_t)tbl);
|
251
|
+
st_free_table(HASH_TBL(hash));
|
252
|
+
HASH_TBL(hash) = tbl;
|
253
|
+
|
254
|
+
return hash;
|
255
|
+
}
|
256
|
+
|
257
|
+
static void
|
258
|
+
rb_strhash_convert(VALUE *val)
|
259
|
+
{
|
260
|
+
int i;
|
261
|
+
VALUE values;
|
262
|
+
|
263
|
+
switch (TYPE(*val)) {
|
264
|
+
case T_HASH:
|
265
|
+
*val = rb_hash_strhash(*val);
|
266
|
+
break;
|
267
|
+
case T_ARRAY:
|
268
|
+
values = rb_ary_new2(RARRAY_LEN(*val));
|
269
|
+
for (i = 0; i < RARRAY_LEN(*val); i++) {
|
270
|
+
VALUE el = RARRAY_PTR(*val)[i];
|
271
|
+
rb_ary_push(values, (TYPE(el) == T_HASH) ? rb_hash_strhash(el) : el);
|
272
|
+
}
|
273
|
+
*val = values;
|
274
|
+
break;
|
275
|
+
}
|
276
|
+
}
|
277
|
+
|
278
|
+
static VALUE
|
279
|
+
rb_strhash_aset(VALUE hash, VALUE key, VALUE val){
|
280
|
+
rb_strhash_convert(&val);
|
281
|
+
rb_hash_aset(hash, key, val);
|
282
|
+
return val;
|
283
|
+
}
|
284
|
+
|
285
|
+
/* hash.c */
|
286
|
+
static VALUE
|
287
|
+
rb_strhash_s_create(int argc, VALUE *argv, VALUE klass)
|
288
|
+
{
|
289
|
+
VALUE hash;
|
290
|
+
int i;
|
291
|
+
|
292
|
+
if (argc == 1 && TYPE(argv[0]) == T_HASH) {
|
293
|
+
hash = strhash_alloc0(&klass);
|
294
|
+
HASH_TBL(hash) = st_copy(HASH_TBL(argv[0]));
|
295
|
+
HASH_TBL(hash)->type = &objstrhash;
|
296
|
+
RHASH(hash)->ifnone = RHASH(argv[0])->ifnone;
|
297
|
+
return rb_strhash_rehash(hash);
|
298
|
+
}
|
299
|
+
|
300
|
+
if (argc % 2 != 0) {
|
301
|
+
rb_raise(rb_eArgError, "odd number of arguments for Hash");
|
302
|
+
}
|
303
|
+
|
304
|
+
hash = strhash_alloc(klass);
|
305
|
+
for (i=0; i<argc; i+=2) {
|
306
|
+
rb_strhash_aset(hash, argv[i], argv[i + 1]);
|
307
|
+
}
|
308
|
+
|
309
|
+
return hash;
|
310
|
+
}
|
311
|
+
|
312
|
+
static VALUE
|
313
|
+
rb_strhash_strhash(VALUE hash)
|
314
|
+
{
|
315
|
+
return hash;
|
316
|
+
}
|
317
|
+
|
318
|
+
static VALUE
|
319
|
+
rb_hash_strhash(VALUE hash)
|
320
|
+
{
|
321
|
+
VALUE args[1];
|
322
|
+
args[0] = hash;
|
323
|
+
return rb_strhash_s_create(1, (VALUE *)args, rb_cStrHash);
|
324
|
+
}
|
325
|
+
|
326
|
+
/* hash.c */
|
327
|
+
static VALUE
|
328
|
+
to_strhash(hash)
|
329
|
+
VALUE hash;
|
330
|
+
{
|
331
|
+
return rb_convert_type(hash, T_HASH, "StrHash", "to_hash");
|
332
|
+
}
|
333
|
+
|
334
|
+
/* hash.c */
|
335
|
+
static int
|
336
|
+
rb_strhash_update_i(VALUE key, VALUE value, VALUE *hash)
|
337
|
+
{
|
338
|
+
if (key == Qundef) return ST_CONTINUE;
|
339
|
+
rb_strhash_convert(&value);
|
340
|
+
st_insert(HASH_TBL(*hash), key, value);
|
341
|
+
return ST_CONTINUE;
|
342
|
+
}
|
343
|
+
|
344
|
+
/* hash.c */
|
345
|
+
static int
|
346
|
+
rb_strhash_update_block_i(VALUE key, VALUE value, VALUE *hash)
|
347
|
+
{
|
348
|
+
if (key == Qundef) return ST_CONTINUE;
|
349
|
+
if (rb_hash_has_key(*hash, key)) {
|
350
|
+
value = rb_yield_values(3, key, rb_hash_aref(*hash, key), value);
|
351
|
+
}
|
352
|
+
rb_strhash_convert(&value);
|
353
|
+
st_insert(HASH_TBL(*hash), key, value);
|
354
|
+
return ST_CONTINUE;
|
355
|
+
}
|
356
|
+
|
357
|
+
/* hash.c */
|
358
|
+
static VALUE
|
359
|
+
rb_strhash_update(VALUE hash1, VALUE hash2)
|
360
|
+
{
|
361
|
+
#ifdef RUBY19
|
362
|
+
rb_hash_modify(hash1);
|
363
|
+
#endif
|
364
|
+
hash2 = to_strhash(hash2);
|
365
|
+
if (rb_block_given_p()) {
|
366
|
+
rb_hash_foreach(hash2, rb_strhash_update_block_i, &hash1);
|
367
|
+
}
|
368
|
+
else {
|
369
|
+
rb_hash_foreach(hash2, rb_strhash_update_i, &hash1);
|
370
|
+
}
|
371
|
+
return hash1;
|
372
|
+
}
|
373
|
+
|
374
|
+
static VALUE
|
375
|
+
rb_strhash_initialize(int argc, VALUE *argv, VALUE hash){
|
376
|
+
VALUE constructor;
|
377
|
+
rb_scan_args(argc, argv, "01", &constructor);
|
378
|
+
if(TYPE(constructor) == T_HASH){
|
379
|
+
return rb_strhash_update(hash,constructor);
|
380
|
+
}else{
|
381
|
+
return rb_call_super(argc,argv);
|
382
|
+
}
|
383
|
+
}
|
384
|
+
|
385
|
+
static VALUE
|
386
|
+
rb_strhash_merge(VALUE hash1, VALUE hash2){
|
387
|
+
/* see note in Init */
|
388
|
+
return rb_strhash_update(hash1,hash2);
|
389
|
+
}
|
390
|
+
|
391
|
+
/*hash.c, see rb_strhash_to_hash*/
|
392
|
+
static VALUE
|
393
|
+
to_hash(hash)
|
394
|
+
VALUE hash;
|
395
|
+
{
|
396
|
+
return rb_convert_type(hash, T_HASH, "Hash", "to_hash");
|
397
|
+
}
|
398
|
+
|
399
|
+
/*hash.c, see rb_strhash_to_hash*/
|
400
|
+
static int
|
401
|
+
rb_hash_update_i(key, value, hash)
|
402
|
+
VALUE key, value;
|
403
|
+
VALUE *hash;
|
404
|
+
{
|
405
|
+
if (key == Qundef) return ST_CONTINUE;
|
406
|
+
#ifdef RUBY19
|
407
|
+
st_insert(RHASH(*hash)->ntbl, key, value);
|
408
|
+
#else
|
409
|
+
rb_hash_aset(*hash, key, value);
|
410
|
+
#endif
|
411
|
+
return ST_CONTINUE;
|
412
|
+
}
|
413
|
+
|
414
|
+
/*hash.c, see rb_strhash_to_hash*/
|
415
|
+
static int
|
416
|
+
rb_hash_update_block_i(key, value, hash)
|
417
|
+
VALUE key, value;
|
418
|
+
VALUE *hash;
|
419
|
+
{
|
420
|
+
if (key == Qundef) return ST_CONTINUE;
|
421
|
+
if (rb_hash_has_key(*hash, key)) {
|
422
|
+
value = rb_yield_values(3, key, rb_hash_aref(*hash, key), value);
|
423
|
+
}
|
424
|
+
#ifdef RUBY19
|
425
|
+
st_insert(RHASH(*hash)->ntbl, key, value);
|
426
|
+
#else
|
427
|
+
rb_hash_aset(*hash, key, value);
|
428
|
+
#endif
|
429
|
+
return ST_CONTINUE;
|
430
|
+
}
|
431
|
+
|
432
|
+
/*hash.c, see rb_strhash_to_hash*/
|
433
|
+
static VALUE
|
434
|
+
rb_hash_update(hash1, hash2)
|
435
|
+
VALUE hash1, hash2;
|
436
|
+
{
|
437
|
+
#ifdef RUBY19
|
438
|
+
rb_hash_modify(hash1);
|
439
|
+
#endif
|
440
|
+
hash2 = to_hash(hash2);
|
441
|
+
if (rb_block_given_p()) {
|
442
|
+
rb_hash_foreach(hash2, rb_hash_update_block_i, &hash1);
|
443
|
+
}
|
444
|
+
else {
|
445
|
+
rb_hash_foreach(hash2, rb_hash_update_i, &hash1);
|
446
|
+
}
|
447
|
+
return hash1;
|
448
|
+
}
|
449
|
+
|
450
|
+
static int
|
451
|
+
rb_strhash_to_hash_i(VALUE key, VALUE value, VALUE *hash){
|
452
|
+
if (SYMBOL_P(key)){
|
453
|
+
rb_hash_delete(*hash,key);
|
454
|
+
VALUE str = rb_str_new2(rb_id2name(SYM2ID(key)));
|
455
|
+
OBJ_FREEZE(str);
|
456
|
+
rb_hash_aset(*hash, str, value);
|
457
|
+
}
|
458
|
+
return ST_CONTINUE;
|
459
|
+
}
|
460
|
+
|
461
|
+
static VALUE
|
462
|
+
rb_strhash_to_hash(VALUE hash){
|
463
|
+
VALUE hsh = rb_hash_update(rb_hash_new(), hash);
|
464
|
+
RHASH(hsh)->ifnone = RHASH(hash)->ifnone;
|
465
|
+
rb_hash_foreach(hsh, rb_strhash_to_hash_i, &hsh);
|
466
|
+
return hsh;
|
467
|
+
}
|
468
|
+
|
469
|
+
void
|
470
|
+
Init_hwia()
|
471
|
+
{
|
472
|
+
id_hash = rb_intern("hash");
|
473
|
+
id_strhash = rb_intern("strhash");
|
474
|
+
hash_format = INT2FIX(536870923);
|
475
|
+
|
476
|
+
rb_cStrHash = rb_define_class("StrHash", rb_cHash);
|
477
|
+
|
478
|
+
rb_undef_alloc_func(rb_cStrHash);
|
479
|
+
rb_define_alloc_func(rb_cStrHash, strhash_alloc);
|
480
|
+
rb_define_singleton_method(rb_cStrHash, "[]", rb_strhash_s_create, -1);
|
481
|
+
|
482
|
+
rb_define_method(rb_cString, "strhash", rb_str_strhash_m, 0);
|
483
|
+
rb_define_method(rb_cSymbol, "strhash", rb_sym_strhash_m, 0);
|
484
|
+
|
485
|
+
rb_define_method(rb_cStrHash,"initialize", rb_strhash_initialize, -1);
|
486
|
+
rb_define_method(rb_cStrHash, "rehash", rb_strhash_rehash, 0);
|
487
|
+
/* revist, same API, but may be clobbered */
|
488
|
+
rb_define_method(rb_cStrHash, "dup", rb_hash_strhash, 0);
|
489
|
+
rb_define_method(rb_cStrHash, "strhash", rb_strhash_strhash, 0);
|
490
|
+
rb_define_method(rb_cStrHash, "[]=", rb_strhash_aset, 2);
|
491
|
+
rb_define_method(rb_cStrHash, "store", rb_strhash_aset, 2);
|
492
|
+
rb_define_method(rb_cStrHash, "update", rb_strhash_update, 1);
|
493
|
+
rb_define_method(rb_cStrHash, "merge!", rb_strhash_update, 1);
|
494
|
+
rb_define_method(rb_cStrHash, "merge", rb_strhash_merge, 1);
|
495
|
+
rb_define_method(rb_cStrHash, "to_hash", rb_strhash_to_hash, 0);
|
496
|
+
rb_define_method(rb_cHash, "strhash", rb_hash_strhash, 0);
|
497
|
+
}
|
data/hwia.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "hwia"
|
3
|
+
s.version = "1.0.2"
|
4
|
+
s.date = "2009-08-23"
|
5
|
+
s.summary = "A faster HashWithIndifferentAccess (hwia) for MRI"
|
6
|
+
s.email = "lourens@methodmissing.com"
|
7
|
+
s.homepage = "http://github.com/methodmissing/hwia"
|
8
|
+
s.description = "A faster HashWithIndifferentAccess (hwia) for MRI (1.8.{6,7} and 1.9.2)"
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Lourens Naudé (methodmissing)"]
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
s.files = %w[
|
13
|
+
README
|
14
|
+
Rakefile
|
15
|
+
bench/bench.rb
|
16
|
+
bench/as_hwia.rb
|
17
|
+
ext/hwia/extconf.rb
|
18
|
+
ext/hwia/hwia.c
|
19
|
+
lib/hwia_rails.rb
|
20
|
+
hwia.gemspec
|
21
|
+
] + Dir.glob('test/*')
|
22
|
+
s.rdoc_options = ["--main", "README"]
|
23
|
+
s.extra_rdoc_files = ["README"]
|
24
|
+
s.extensions << "ext/hwia/extconf.rb"
|
25
|
+
end
|
data/lib/hwia_rails.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'hwia'
|
2
|
+
raise LoadError.new("Rails environment required!") unless Object.const_defined?(:ActiveSupport)
|
3
|
+
|
4
|
+
class Hash
|
5
|
+
alias hash_with_indifferent_access strhash
|
6
|
+
end
|
7
|
+
|
8
|
+
class StrHash
|
9
|
+
def stringify_keys!; self end
|
10
|
+
def symbolize_keys!; self end
|
11
|
+
def to_options!; self end
|
12
|
+
|
13
|
+
protected
|
14
|
+
# AS test suite compat only
|
15
|
+
def convert_key(key)
|
16
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
old, $VERBOSE = $VERBOSE, nil
|
22
|
+
ActiveSupport::HashWithIndifferentAccess = StrHash
|
23
|
+
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
|
24
|
+
ensure
|
25
|
+
$VERBOSE = old
|
26
|
+
end
|
data/test/helper.rb
ADDED
data/test/test_hwia.rb
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
$:.unshift "." # 1.9.2
|
2
|
+
require File.dirname(__FILE__) + '/helper'
|
3
|
+
|
4
|
+
class TestSymbolAndStringHash < Test::Unit::TestCase
|
5
|
+
def test_hwia_hash
|
6
|
+
assert_hash_keys 'key', :key
|
7
|
+
assert_hash_keys 'under_scored_key', :under_scored_key
|
8
|
+
assert_hash_keys '@ivar', :@ivar
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def assert_hash_keys(str,sym)
|
13
|
+
assert_equal str.strhash, sym.strhash
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class HashToStrHash < Test::Unit::TestCase
|
18
|
+
def test_strhash
|
19
|
+
hash = { 'a' => 1, 'b' => 2 }
|
20
|
+
assert_instance_of StrHash, hash.strhash
|
21
|
+
assert_equal %w(a b), hash.keys
|
22
|
+
assert_equal [1,2], hash.values
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class TestStrHash < Test::Unit::TestCase
|
27
|
+
def setup
|
28
|
+
@strings = { 'a' => 1, 'b' => 2 }.strhash
|
29
|
+
@symbols = { :a => 1, :b => 2 }.strhash
|
30
|
+
@mixed = { :a => 1, 'b' => 2 }.strhash
|
31
|
+
@fixnums = { 0 => 1, 1 => 2 }.strhash
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_inherits_hash
|
35
|
+
assert_equal Hash, StrHash.superclass
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_strhash
|
39
|
+
assert_equal @strings.object_id, @strings.strhash.object_id
|
40
|
+
assert_instance_of StrHash, { 'a' => 1, 'b' => 2 }.strhash
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_initialize
|
44
|
+
strhash = StrHash.new({ 'a' => 1, 'b' => 2 })
|
45
|
+
assert_equal 1, strhash[:a]
|
46
|
+
strhash = StrHash.new
|
47
|
+
strhash[:a] = 'a'
|
48
|
+
assert_equal 'a', strhash[:a]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_convert
|
52
|
+
assert_equal 'a', @strings['a'] = 'a'
|
53
|
+
hash = { 'a' => 1, 'b' => 2 }
|
54
|
+
@strings[:str_hash] = hash
|
55
|
+
assert_instance_of StrHash, @strings[:str_hash]
|
56
|
+
assert_equal %w(a b), @strings[:str_hash].keys
|
57
|
+
assert_equal [:a,:b,:c], @strings[:array] = [:a,:b,:c]
|
58
|
+
array_with_hash = [{ 'a' => 1, 'b' => 2 }, [:a,:b,:c]]
|
59
|
+
@strings[:array_with_hash] = array_with_hash
|
60
|
+
assert_instance_of StrHash, @strings[:array_with_hash].shift
|
61
|
+
assert_equal [:a,:b,:c], @strings[:array_with_hash].pop
|
62
|
+
@strings[:other_hash] = { 'a' => 1, 'b' => 2 }
|
63
|
+
assert_instance_of StrHash, @strings[:other_hash]
|
64
|
+
nested_hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}
|
65
|
+
@strings[:nested_hash] = nested_hash
|
66
|
+
assert_instance_of Array, @strings[:nested_hash][:urls][:url]
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_set
|
70
|
+
array = [{ 'a' => 1, 'b' => 2 }, [:a,:b,:c]]
|
71
|
+
@strings[:array] = array
|
72
|
+
assert_instance_of StrHash, @strings[:array].shift
|
73
|
+
assert_instance_of Array, @strings[:array] = array
|
74
|
+
assert_instance_of StrHash, @strings[:hash] = { 'a' => 1, 'b' => 2 }
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_dup
|
78
|
+
assert_equal @strings, @strings.dup
|
79
|
+
assert_equal @mixed, @mixed.dup
|
80
|
+
assert_not_equal @mixed.object_id, @mixed.dup.object_id
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_keys
|
84
|
+
assert_equal ["a", "b"], @strings.keys
|
85
|
+
assert_equal [:a, :b], @symbols.keys
|
86
|
+
assert_equal [:a, "b"], @mixed.keys
|
87
|
+
assert_equal [0, 1], @fixnums.keys
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_values
|
91
|
+
assert_equal [1, 2], @strings.values
|
92
|
+
assert_equal [1, 2], @symbols.values
|
93
|
+
assert_equal [1, 2], @mixed.values
|
94
|
+
assert_equal [1, 2], @fixnums.values
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_fetch
|
98
|
+
assert_equal 1, @strings.fetch('a')
|
99
|
+
assert_equal 1, @strings.fetch(:a.to_s)
|
100
|
+
assert_equal 1, @strings.fetch(:a)
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_key?
|
104
|
+
assert @strings.key?(:a)
|
105
|
+
assert @strings.include?('a')
|
106
|
+
assert @mixed.has_key?('b')
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_delete
|
110
|
+
@strings.delete('a')
|
111
|
+
assert !@strings.key?(:a)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_assorted
|
115
|
+
hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
|
116
|
+
method_map = { :'[]' => 1, :fetch => 1, :values_at => [1],
|
117
|
+
:has_key? => true, :include? => true, :key? => true,
|
118
|
+
:member? => true }
|
119
|
+
|
120
|
+
hashes.each do |name, hash|
|
121
|
+
method_map.sort_by { |m| m.to_s }.each do |meth, expected|
|
122
|
+
assert_equal(expected, hash.__send__(meth, 'a'),
|
123
|
+
"Calling #{name}.#{meth} 'a'")
|
124
|
+
assert_equal(expected, hash.__send__(meth, :a),
|
125
|
+
"Calling #{name}.#{meth} :a")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
assert_equal [1, 2], @strings.values_at('a', 'b')
|
130
|
+
assert_equal [1, 2], @strings.values_at(:a, :b)
|
131
|
+
assert_equal [1, 2], @symbols.values_at('a', 'b')
|
132
|
+
assert_equal [1, 2], @symbols.values_at(:a, :b)
|
133
|
+
assert_equal [1, 2], @mixed.values_at('a', 'b')
|
134
|
+
assert_equal [1, 2], @mixed.values_at(:a, :b)
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_reading
|
138
|
+
hash = StrHash.new
|
139
|
+
hash["a"] = 1
|
140
|
+
hash["b"] = true
|
141
|
+
hash["c"] = false
|
142
|
+
hash["d"] = nil
|
143
|
+
|
144
|
+
assert_equal 1, hash[:a]
|
145
|
+
assert_equal true, hash[:b]
|
146
|
+
assert_equal false, hash[:c]
|
147
|
+
assert_equal nil, hash[:d]
|
148
|
+
assert_equal nil, hash[:e]
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_reading_with_nonnil_default
|
152
|
+
hash = StrHash.new(1)
|
153
|
+
hash["a"] = 1
|
154
|
+
hash["b"] = true
|
155
|
+
hash["c"] = false
|
156
|
+
hash["d"] = nil
|
157
|
+
|
158
|
+
assert_equal 1, hash[:a]
|
159
|
+
assert_equal true, hash[:b]
|
160
|
+
assert_equal false, hash[:c]
|
161
|
+
assert_equal nil, hash[:d]
|
162
|
+
assert_equal 1, hash[:e]
|
163
|
+
end
|
164
|
+
|
165
|
+
def test_writing
|
166
|
+
hash = StrHash.new
|
167
|
+
hash[:a] = 1
|
168
|
+
hash['b'] = 2
|
169
|
+
hash[3] = 3
|
170
|
+
|
171
|
+
assert_equal hash['a'], 1
|
172
|
+
assert_equal hash['b'], 2
|
173
|
+
assert_equal hash[:a], 1
|
174
|
+
assert_equal hash[:b], 2
|
175
|
+
assert_equal hash[3], 3
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_update
|
179
|
+
hash = StrHash.new
|
180
|
+
hash[:a] = 'a'
|
181
|
+
hash['b'] = 'b'
|
182
|
+
|
183
|
+
updated_with_strings = hash.update(@strings)
|
184
|
+
updated_with_symbols = hash.update(@symbols)
|
185
|
+
updated_with_mixed = hash.update(@mixed)
|
186
|
+
|
187
|
+
assert_equal updated_with_strings[:a], 1
|
188
|
+
assert_equal updated_with_strings['a'], 1
|
189
|
+
assert_equal updated_with_strings['b'], 2
|
190
|
+
|
191
|
+
assert_equal updated_with_symbols[:a], 1
|
192
|
+
assert_equal updated_with_symbols['b'], 2
|
193
|
+
assert_equal updated_with_symbols[:b], 2
|
194
|
+
|
195
|
+
assert_equal updated_with_mixed[:a], 1
|
196
|
+
assert_equal updated_with_mixed['b'], 2
|
197
|
+
|
198
|
+
assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
|
199
|
+
end
|
200
|
+
|
201
|
+
def test_merging
|
202
|
+
hash = StrHash.new
|
203
|
+
hash[:a] = 'failure'
|
204
|
+
hash['b'] = 'failure'
|
205
|
+
|
206
|
+
other = { 'a' => 1, :b => 2 }
|
207
|
+
|
208
|
+
merged = hash.merge(other)
|
209
|
+
|
210
|
+
assert_equal StrHash, merged.class
|
211
|
+
assert_equal 1, merged[:a]
|
212
|
+
assert_equal 2, merged['b']
|
213
|
+
|
214
|
+
hash.update(other)
|
215
|
+
|
216
|
+
assert_equal 1, hash[:a]
|
217
|
+
assert_equal 2, hash['b']
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_deleting
|
221
|
+
get_hash = proc{ StrHash[ :a => 'foo' ] }
|
222
|
+
hash = get_hash.call
|
223
|
+
assert_equal hash.delete(:a), 'foo'
|
224
|
+
assert_equal hash.delete(:a), nil
|
225
|
+
hash = get_hash.call
|
226
|
+
assert_equal hash.delete('a'), 'foo'
|
227
|
+
assert_equal hash.delete('a'), nil
|
228
|
+
end
|
229
|
+
|
230
|
+
def test_to_hash
|
231
|
+
assert_instance_of Hash, @strings.to_hash
|
232
|
+
assert_equal %w(a b), @strings.to_hash.keys
|
233
|
+
# Should convert to a Hash with String keys.
|
234
|
+
assert_equal @strings, @mixed.strhash.to_hash
|
235
|
+
|
236
|
+
# Should preserve the default value.
|
237
|
+
mixed_with_default = @mixed.dup
|
238
|
+
mixed_with_default.default = '1234'
|
239
|
+
roundtrip = mixed_with_default.strhash.to_hash
|
240
|
+
assert_equal @strings, roundtrip
|
241
|
+
assert_equal '1234', roundtrip.default
|
242
|
+
end
|
243
|
+
|
244
|
+
def test_hash_with_array_of_hashes
|
245
|
+
hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] }}
|
246
|
+
hwia = StrHash[hash]
|
247
|
+
assert_equal "1", hwia[:urls][:url].first[:address]
|
248
|
+
end
|
249
|
+
|
250
|
+
def test_indifferent_subhashes
|
251
|
+
h = {'user' => {'id' => 5}}.strhash
|
252
|
+
['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
|
253
|
+
|
254
|
+
h = {:user => {:id => 5}}.strhash
|
255
|
+
['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}}
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_assorted_keys_not_stringified
|
259
|
+
original = {Object.new => 2, 1 => 2, [] => true}
|
260
|
+
indiff = original.strhash
|
261
|
+
assert(!indiff.keys.any? {|k| k.kind_of? String}, "A key was converted to a string!")
|
262
|
+
end
|
263
|
+
|
264
|
+
def test_should_use_default_value_for_unknown_key
|
265
|
+
hash_wia = StrHash.new(3)
|
266
|
+
assert_equal 3, hash_wia[:new_key]
|
267
|
+
end
|
268
|
+
|
269
|
+
def test_should_use_default_value_if_no_key_is_supplied
|
270
|
+
hash_wia = StrHash.new(3)
|
271
|
+
assert_equal 3, hash_wia.default
|
272
|
+
end
|
273
|
+
|
274
|
+
def test_should_nil_if_no_default_value_is_supplied
|
275
|
+
hash_wia = StrHash.new
|
276
|
+
assert_nil hash_wia.default
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
|
280
|
+
hash = Hash.new(3)
|
281
|
+
hash_wia = hash.strhash
|
282
|
+
assert_equal 3, hash_wia.default
|
283
|
+
end
|
284
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hwia
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Lourens Naud\xC3\xA9 (methodmissing)"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-08-23 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: A faster HashWithIndifferentAccess (hwia) for MRI (1.8.{6,7} and 1.9.2)
|
17
|
+
email: lourens@methodmissing.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions:
|
21
|
+
- ext/hwia/extconf.rb
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README
|
24
|
+
files:
|
25
|
+
- README
|
26
|
+
- Rakefile
|
27
|
+
- bench/bench.rb
|
28
|
+
- bench/as_hwia.rb
|
29
|
+
- ext/hwia/extconf.rb
|
30
|
+
- ext/hwia/hwia.c
|
31
|
+
- lib/hwia_rails.rb
|
32
|
+
- hwia.gemspec
|
33
|
+
- test/helper.rb
|
34
|
+
- test/test_hwia.rb
|
35
|
+
has_rdoc: true
|
36
|
+
homepage: http://github.com/methodmissing/hwia
|
37
|
+
licenses: []
|
38
|
+
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- --main
|
42
|
+
- README
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: "0"
|
50
|
+
version:
|
51
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
requirements: []
|
58
|
+
|
59
|
+
rubyforge_project:
|
60
|
+
rubygems_version: 1.3.5
|
61
|
+
signing_key:
|
62
|
+
specification_version: 3
|
63
|
+
summary: A faster HashWithIndifferentAccess (hwia) for MRI
|
64
|
+
test_files: []
|
65
|
+
|