hipsterhash 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +0 -6
- data/Rakefile +10 -0
- data/hipsterhash.gemspec +10 -6
- data/lib/hipster_hash.rb +56 -23
- data/lib/hipsterhash.rb +1 -1
- data/test/hipster_hash_test.rb +119 -31
- data/test/performance.rb +16 -0
- data/test/profile.rb +12 -0
- data/test/test_helper.rb +2 -0
- metadata +38 -21
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bdd5f38b32f9a671fb09d65b478f7f7ef334e286
|
4
|
+
data.tar.gz: 09a15c9f8c71ab5e9688fe8fae12e72ab86c9980
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8e837e6299ccc72a60bcd331630c4962d5d5783e6a1461cadb12af9c10a20f285dc23bda9df89d58ca490e1b525b3380874e8287600435deec31cad293086821
|
7
|
+
data.tar.gz: fb2bf26f0fcfddc9e96ebc0a3b4ea226887e2285130ea2381e9d4148fec909a6a19816fb5339e65df80213ba9509dcae571a30b808f39ce398a6b88f7c23440f
|
data/README.md
CHANGED
@@ -24,9 +24,6 @@ hh[:foo_bar] = "Pigs"
|
|
24
24
|
hh["FooBar"] # => "Pigs"
|
25
25
|
```
|
26
26
|
|
27
|
-
It uses [ActiveSupport][1] [Inflectors][2] internally, so you can probably muck
|
28
|
-
around with them to modify the behaviour.
|
29
|
-
|
30
27
|
## Contributing
|
31
28
|
|
32
29
|
1. Fork it
|
@@ -34,6 +31,3 @@ around with them to modify the behaviour.
|
|
34
31
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
35
32
|
4. Push to the branch (`git push origin my-new-feature`)
|
36
33
|
5. Create new Pull Request
|
37
|
-
|
38
|
-
[1]: https://github.com/rails/rails/tree/master/activesupport/lib/active_support
|
39
|
-
[2]: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/inflections.rb
|
data/Rakefile
CHANGED
@@ -9,4 +9,14 @@ Rake::TestTask.new do |t|
|
|
9
9
|
t.test_files = FileList["test/**/*_test.rb"]
|
10
10
|
end
|
11
11
|
|
12
|
+
desc "Run profiler to see what's slow"
|
13
|
+
task :profile do
|
14
|
+
require_relative './test/profile'
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run benchmarks against Hash"
|
18
|
+
task :performance do
|
19
|
+
require_relative './test/performance'
|
20
|
+
end
|
21
|
+
|
12
22
|
task :default => :test
|
data/hipsterhash.gemspec
CHANGED
@@ -4,11 +4,14 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "hipsterhash"
|
7
|
-
gem.version = "0.0.
|
8
|
-
gem.authors = ["
|
9
|
-
gem.email = ["
|
10
|
-
gem.
|
11
|
-
gem.
|
7
|
+
gem.version = "0.0.3"
|
8
|
+
gem.authors = ["bjjb"]
|
9
|
+
gem.email = ["jj@bjjb.org"]
|
10
|
+
gem.summary = %q{A Hash which is all, like, whatever.}
|
11
|
+
gem.description = <<-DESC
|
12
|
+
A HipsterHash is just like a regular ruby Hash, except that it doesn't
|
13
|
+
distinguish between symbols or strings, and the keys are case insensitive.
|
14
|
+
DESC
|
12
15
|
gem.homepage = "http://jjbuckley.github.com/hipsterhash"
|
13
16
|
|
14
17
|
gem.files = `git ls-files`.split($/)
|
@@ -16,5 +19,6 @@ Gem::Specification.new do |gem|
|
|
16
19
|
gem.test_files = gem.files.grep(%r{^(test)/})
|
17
20
|
gem.require_paths = ["lib"]
|
18
21
|
|
19
|
-
gem.
|
22
|
+
gem.add_development_dependency "rake"
|
23
|
+
gem.add_development_dependency "minitest-focus"
|
20
24
|
end
|
data/lib/hipster_hash.rb
CHANGED
@@ -1,49 +1,66 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
-
require 'active_support/core_ext/string/inflections'
|
3
2
|
|
4
3
|
# A HipsterHash is like a regular Hash, except the keys are semi case
|
5
4
|
# insensitive, and symbols and strings are equivalent. Therefore, :foo_bar,
|
6
5
|
# :FooBar, and "foo_bar" are considered to be the same key.
|
7
6
|
class HipsterHash < Hash
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
7
|
+
|
8
|
+
alias store! store
|
9
|
+
|
10
|
+
def store(key, value)
|
11
|
+
memoize(key) do
|
12
|
+
k, v = convert_key(key), convert_value(value)
|
13
|
+
store!(k, v)
|
15
14
|
end
|
16
15
|
end
|
17
16
|
|
17
|
+
def []=(key, value)
|
18
|
+
memoize(key) do
|
19
|
+
k, v = convert_key(key), convert_value(value)
|
20
|
+
store!(k, v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(key)
|
25
|
+
super rescue super(convert_key(key)).tap { |v| store!(key, v) }
|
26
|
+
end
|
27
|
+
|
18
28
|
def [](key)
|
19
|
-
|
29
|
+
super || super(convert_key(key)).tap { |v| store!(key, v) }
|
20
30
|
end
|
21
31
|
|
22
|
-
def
|
23
|
-
super(convert_key(
|
32
|
+
def key?(sym)
|
33
|
+
super || super(convert_key(sym))
|
24
34
|
end
|
25
35
|
|
26
|
-
def
|
27
|
-
|
28
|
-
self[sym] || super
|
29
|
-
else
|
30
|
-
super
|
31
|
-
end
|
36
|
+
def self.[](*args)
|
37
|
+
new.tap { |hh| Hash[*args].each { |k, v| hh.store(k, v) } }
|
32
38
|
end
|
33
39
|
|
34
40
|
protected
|
35
|
-
|
36
|
-
|
41
|
+
|
42
|
+
def method_missing(sym, *args, &block)
|
43
|
+
return super if block_given?
|
44
|
+
return store(sym[0...-1], args.first) if sym[-1] == '=' and args.length == 1
|
45
|
+
return fetch(sym) if args.empty? and key?(sym)
|
46
|
+
super
|
37
47
|
end
|
38
48
|
|
39
|
-
def
|
40
|
-
k
|
49
|
+
def converted_keys
|
50
|
+
@converted_keys ||= Hash.new do |h, k|
|
51
|
+
h[k] = underscore(k.to_s).to_sym
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def convert_key(k)
|
56
|
+
converted_keys[k]
|
41
57
|
end
|
42
58
|
|
43
59
|
def self.convert_value(arg)
|
44
60
|
case arg
|
45
|
-
when
|
46
|
-
when
|
61
|
+
when self then arg
|
62
|
+
when Hash then self[arg]
|
63
|
+
when Array then arg.map { |x| convert_value(x) }
|
47
64
|
else arg
|
48
65
|
end
|
49
66
|
end
|
@@ -51,4 +68,20 @@ protected
|
|
51
68
|
def convert_value(arg)
|
52
69
|
self.class.convert_value(arg)
|
53
70
|
end
|
71
|
+
|
72
|
+
def underscore(s)
|
73
|
+
s.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
74
|
+
end
|
75
|
+
|
76
|
+
def memoize(sym, &block)
|
77
|
+
yield.tap do |x|
|
78
|
+
eigenklass.class_eval { define_method(:"#{sym}") { x } } unless @@unmemoizable.key?(convert_key(sym))
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def eigenklass
|
83
|
+
@eigenklass ||= (class << self; self; end)
|
84
|
+
end
|
85
|
+
|
86
|
+
@@unmemoizable = Hash[[methods + instance_methods].flatten.uniq.zip([])]
|
54
87
|
end
|
data/lib/hipsterhash.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
-
|
2
|
+
require_relative './hipster_hash'
|
data/test/hipster_hash_test.rb
CHANGED
@@ -1,51 +1,139 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
|
-
require '
|
3
|
-
require
|
2
|
+
require 'test_helper'
|
3
|
+
require 'hipster_hash'
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
describe HipsterHash do
|
6
|
+
it "acts like a hash" do
|
7
7
|
hh = HipsterHash.new
|
8
|
-
hh
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
hh.must_be_kind_of Hash
|
9
|
+
hh.store(:foo, :bar)
|
10
|
+
hh.fetch(:foo).must_equal :bar
|
11
|
+
hh[:bar] = 123
|
12
|
+
hh[:bar].must_equal 123
|
13
|
+
hh.keys.must_equal [:foo, :bar]
|
14
|
+
hh.delete(:foo).must_equal :bar
|
15
|
+
hh.keys.must_equal [:bar]
|
16
|
+
lambda { hh.fetch(:foo) }.must_raise KeyError
|
17
|
+
lambda { hh.fetch('foo') }.must_raise KeyError
|
18
|
+
lambda { hh.fetch('Foo') }.must_raise KeyError
|
19
|
+
hh[:foo].must_be_nil
|
20
|
+
hh = HipsterHash[:foo, 123, :bar, 234]
|
21
|
+
hh[:foo].must_equal 123
|
22
|
+
hh[:bar].must_equal 234
|
23
|
+
hh.fetch(:foo).must_equal 123
|
24
|
+
hh.fetch('foo').must_equal 123
|
25
|
+
hh.fetch('Foo').must_equal 123
|
26
|
+
hh = HipsterHash[[[:foo, 123], [:bar, 234]]]
|
27
|
+
hh[:foo].must_equal 123
|
28
|
+
hh[:bar].must_equal 234
|
29
|
+
hh = HipsterHash[{foo: 123, bar: 234}]
|
30
|
+
hh[:foo].must_equal 123
|
31
|
+
hh[:bar].must_equal 234
|
32
|
+
hh[:bar] = 77
|
33
|
+
hh[:bar].must_equal 77
|
12
34
|
end
|
13
35
|
|
14
|
-
|
15
|
-
hh = HipsterHash
|
16
|
-
|
17
|
-
hh
|
18
|
-
|
36
|
+
it "treats symbols and strings equally" do
|
37
|
+
hh = HipsterHash[foo: "bar"]
|
38
|
+
hh.keys.must_equal [:foo]
|
39
|
+
hh.key?(:foo).must_equal true
|
40
|
+
hh.foo.must_equal "bar"
|
41
|
+
hh['foo'] = 'baz'
|
42
|
+
hh.keys.must_equal [:foo]
|
43
|
+
hh[:foo].must_equal "baz"
|
19
44
|
end
|
20
45
|
|
21
|
-
|
22
|
-
hh = HipsterHash
|
23
|
-
|
24
|
-
|
46
|
+
it "handles nested hashes" do
|
47
|
+
hh = HipsterHash[foo: { bar: :baz }]
|
48
|
+
hh.foo.must_be_kind_of HipsterHash
|
49
|
+
hh["Foo"][:Bar].must_equal :baz
|
25
50
|
end
|
26
51
|
|
27
|
-
|
52
|
+
it "handles nested arrays" do
|
53
|
+
hh = HipsterHash[{foo: { bar: [ { baz: 1 } ] }}]
|
54
|
+
hh.foo.must_be_kind_of HipsterHash
|
55
|
+
hh.foo.bar[0].baz.must_equal 1
|
56
|
+
hh = HipsterHash[{"foo" => { "bar" => [ { "baz" => 1 } ] }}]
|
57
|
+
hh.foo.bar[0].baz.must_equal 1
|
58
|
+
end
|
59
|
+
|
60
|
+
it "is indifferent to case" do
|
28
61
|
hh = HipsterHash.new
|
29
62
|
hh[:foo_bar] = "bar"
|
30
|
-
|
63
|
+
hh["FooBar"].must_equal "bar"
|
31
64
|
hh['FooBar'] = "baz"
|
32
|
-
|
65
|
+
hh[:foo_bar].must_equal "baz"
|
33
66
|
end
|
34
67
|
|
35
|
-
|
36
|
-
|
37
|
-
|
68
|
+
it "can be inherited" do
|
69
|
+
hc = Class.new(HipsterHash)
|
70
|
+
hh = hc[foo_bar: "Baz"]
|
71
|
+
hh[:foo_bar].must_equal "Baz"
|
72
|
+
hh[:FooBar].must_equal "Baz"
|
73
|
+
hh["FooBar"].must_equal "Baz"
|
74
|
+
hh["foo_bar"].must_equal "Baz"
|
75
|
+
hh.foo_bar.must_equal "Baz"
|
38
76
|
end
|
39
77
|
|
40
|
-
|
41
|
-
|
42
|
-
hh =
|
43
|
-
|
78
|
+
it "acts like an openstruct" do
|
79
|
+
hh = HipsterHash[:foo, 123]
|
80
|
+
hh.bar = 234
|
81
|
+
hh.foo.must_equal 123
|
82
|
+
hh.bar.must_equal 234
|
83
|
+
lambda { hh.baz }.must_raise NoMethodError
|
84
|
+
hh[:foo] = "bar"
|
85
|
+
hh.foo.must_equal "bar"
|
86
|
+
hh.foo = "baz"
|
87
|
+
hh.foo.must_equal "baz"
|
88
|
+
hh.store("foo", "bpb")
|
89
|
+
hh.foo.must_equal "bpb"
|
90
|
+
hh2 = HipsterHash[:foo, 999]
|
91
|
+
hh2.foo.must_equal 999
|
92
|
+
hh.foo.must_equal "bpb"
|
93
|
+
end
|
94
|
+
|
95
|
+
it "doesn't break if you use a special method name" do
|
96
|
+
hh = HipsterHash.new
|
97
|
+
hh[:foo] = 1
|
98
|
+
hh.foo.must_equal 1
|
99
|
+
m = hh.method(:[])
|
100
|
+
hh['[]'] = "blah"
|
101
|
+
hh.method(:[]).must_equal m
|
102
|
+
hh.foo.must_equal 1
|
103
|
+
hh['[]'].must_equal "blah"
|
104
|
+
hh.eigenklass = "blah"
|
105
|
+
hh.foo.must_equal 1
|
106
|
+
hh.foo = 2
|
107
|
+
hh.foo.must_equal 2
|
108
|
+
end
|
109
|
+
|
110
|
+
it "doesn't break with funny keys" do
|
111
|
+
hh = HipsterHash.new
|
112
|
+
hh[0] = 1
|
113
|
+
hh[0].must_equal 1
|
114
|
+
hh[":blah"] = 99
|
115
|
+
hh.send(":blah").must_equal 99
|
116
|
+
hh[nil] = "klm"
|
117
|
+
hh[nil].must_equal "klm"
|
118
|
+
hh[:'→'] = "arrow"
|
119
|
+
hh.send('→').must_equal 'arrow'
|
44
120
|
end
|
45
121
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
122
|
+
describe "key conversion" do
|
123
|
+
it "leaves underscored symbols alone" do
|
124
|
+
convert(:foo).must_equal :foo
|
125
|
+
end
|
126
|
+
it "converts underscored strings to symbols" do
|
127
|
+
convert("foo").must_equal :foo
|
128
|
+
end
|
129
|
+
it "underscores camelcase symbols" do
|
130
|
+
convert(:FooBar).must_equal :foo_bar
|
131
|
+
end
|
132
|
+
it "underscores and symbolizes camelcased strings" do
|
133
|
+
convert("FooBar").must_equal :foo_bar
|
134
|
+
end
|
135
|
+
def convert(k)
|
136
|
+
HipsterHash.new.send(:convert_key, k)
|
137
|
+
end
|
50
138
|
end
|
51
139
|
end
|
data/test/performance.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#! /usr/bin/env ruby -w
|
2
|
+
require 'benchmark'
|
3
|
+
require_relative '../lib/hipsterhash'
|
4
|
+
|
5
|
+
h = { foo: { bar: { x: "123", y: [ 1, 2, 3 ] } }, baz: "123" }
|
6
|
+
hh = HipsterHash[h.dup]
|
7
|
+
|
8
|
+
n = 1000000
|
9
|
+
Benchmark.bmbm do |x|
|
10
|
+
x.report("Hash#[] ") { n.times { h[:foo][:bar][:x] } }
|
11
|
+
x.report("HipsterHash#[] ") { n.times { hh[:foo][:bar][:x] } }
|
12
|
+
x.report("Hash#fetch ") { n.times { h.fetch(:foo).fetch(:bar).fetch(:x) } }
|
13
|
+
x.report("HipsterHash#fetch ") { n.times { hh.fetch(:foo).fetch(:bar).fetch(:x) } }
|
14
|
+
x.report("HipsterHash#fetch2 ") { n.times { hh.fetch(:"Foo").fetch(:"Bar").fetch(:"X") } }
|
15
|
+
x.report("HipsterHash#method_missing") { n.times { hh.foo.bar.x } }
|
16
|
+
end
|
data/test/profile.rb
ADDED
data/test/test_helper.rb
ADDED
metadata
CHANGED
@@ -1,40 +1,53 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hipsterhash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.0.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
|
-
-
|
7
|
+
- bjjb
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-03-29 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
14
|
+
name: rake
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: '0'
|
22
|
-
type: :
|
20
|
+
type: :development
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
|
-
- -
|
24
|
+
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
26
|
version: '0'
|
30
|
-
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest-focus
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: |
|
42
|
+
A HipsterHash is just like a regular ruby Hash, except that it doesn't
|
43
|
+
distinguish between symbols or strings, and the keys are case insensitive.
|
31
44
|
email:
|
32
|
-
-
|
45
|
+
- jj@bjjb.org
|
33
46
|
executables: []
|
34
47
|
extensions: []
|
35
48
|
extra_rdoc_files: []
|
36
49
|
files:
|
37
|
-
- .gitignore
|
50
|
+
- ".gitignore"
|
38
51
|
- Gemfile
|
39
52
|
- LICENSE.txt
|
40
53
|
- README.md
|
@@ -43,30 +56,34 @@ files:
|
|
43
56
|
- lib/hipster_hash.rb
|
44
57
|
- lib/hipsterhash.rb
|
45
58
|
- test/hipster_hash_test.rb
|
59
|
+
- test/performance.rb
|
60
|
+
- test/profile.rb
|
61
|
+
- test/test_helper.rb
|
46
62
|
homepage: http://jjbuckley.github.com/hipsterhash
|
47
63
|
licenses: []
|
64
|
+
metadata: {}
|
48
65
|
post_install_message:
|
49
66
|
rdoc_options: []
|
50
67
|
require_paths:
|
51
68
|
- lib
|
52
69
|
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
-
none: false
|
54
70
|
requirements:
|
55
|
-
- -
|
71
|
+
- - ">="
|
56
72
|
- !ruby/object:Gem::Version
|
57
73
|
version: '0'
|
58
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
-
none: false
|
60
75
|
requirements:
|
61
|
-
- -
|
76
|
+
- - ">="
|
62
77
|
- !ruby/object:Gem::Version
|
63
78
|
version: '0'
|
64
79
|
requirements: []
|
65
80
|
rubyforge_project:
|
66
|
-
rubygems_version:
|
81
|
+
rubygems_version: 2.2.0
|
67
82
|
signing_key:
|
68
|
-
specification_version:
|
69
|
-
summary: A
|
70
|
-
between symbols or strings, and the keys are case insensitive.
|
83
|
+
specification_version: 4
|
84
|
+
summary: A Hash which is all, like, whatever.
|
71
85
|
test_files:
|
72
86
|
- test/hipster_hash_test.rb
|
87
|
+
- test/performance.rb
|
88
|
+
- test/profile.rb
|
89
|
+
- test/test_helper.rb
|