hwia 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|