hipsterhash 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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.2"
8
- gem.authors = ["Bryan JJ Buckley"]
9
- gem.email = ["jjbuckley@gmail.com"]
10
- gem.description = %q{A hash which is all, like, whatever.}
11
- gem.summary = %q{A HipsterHash is just like a regular ruby Hash, except that it doesn't distinguish between symbols or strings, and the keys are case insensitive.}
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.add_dependency "activesupport"
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
- def initialize(initial = {}, &block)
9
- case initial
10
- when Hash
11
- super(&block)
12
- initial.each { |k, v| self[k] = v }
13
- else
14
- super
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
- convert_value(super(convert_key(key)))
29
+ super || super(convert_key(key)).tap { |v| store!(key, v) }
20
30
  end
21
31
 
22
- def []=(key, value)
23
- super(convert_key(key), value)
32
+ def key?(sym)
33
+ super || super(convert_key(sym))
24
34
  end
25
35
 
26
- def method_missing(sym, *args, &block)
27
- if args.empty? and !block_given?
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
- def convert_key(k)
36
- self.class.convert_key(k)
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 self.convert_key(k)
40
- k.to_s.underscore.to_sym
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 Hash then new(arg)
46
- when Array then arg.map { |a| convert_value(a) }
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
- require 'hipster_hash'
2
+ require_relative './hipster_hash'
@@ -1,51 +1,139 @@
1
1
  # -*- encoding : utf-8 -*-
2
- require 'test/unit'
3
- require File.expand_path('../../lib/hipster_hash', __FILE__)
2
+ require 'test_helper'
3
+ require 'hipster_hash'
4
4
 
5
- class HipsterHashTest < Test::Unit::TestCase
6
- def test_normal_behaviour
5
+ describe HipsterHash do
6
+ it "acts like a hash" do
7
7
  hh = HipsterHash.new
8
- hh["foo"] = "bar"
9
- assert_kind_of Hash, hh
10
- assert_equal "bar", hh[:foo]
11
- assert_nil hh[:pigs]
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
- def test_indifference_to_input_type
15
- hh = HipsterHash.new(:foo => "bar")
16
- assert_equal "bar", hh["foo"]
17
- hh['foo'] = "baz"
18
- assert_equal "baz", hh[:foo]
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
- def test_nesting
22
- hh = HipsterHash.new(:foo => { :bar => :baz })
23
- assert_kind_of HipsterHash, hh[:foo]
24
- assert_equal :baz, hh["Foo"][:Bar]
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
- def test_case_indifference
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
- assert_equal "bar", hh["FooBar"]
63
+ hh["FooBar"].must_equal "bar"
31
64
  hh['FooBar'] = "baz"
32
- assert_equal "baz", hh[:foo_bar]
65
+ hh[:foo_bar].must_equal "baz"
33
66
  end
34
67
 
35
- def test_creation_with_a_hash
36
- hh = HipsterHash.new(:foo_bar => "Bar")
37
- assert_equal "Bar", hh["FooBar"]
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
- def test_subclassing
41
- hc = Class.new(HipsterHash)
42
- hh = hc.new(:foo_bar => "Baz")
43
- assert_equal "Baz", hh["FooBar"]
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
- def test_method_missing
47
- hh = HipsterHash.new(:Foo => :bar)
48
- assert_equal :bar, hh.foo
49
- assert_equal :bar, hh.FOO
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
@@ -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
@@ -0,0 +1,12 @@
1
+ #! /usr/bin/env ruby -w
2
+
3
+ require_relative '../lib/hipsterhash'
4
+
5
+
6
+ require 'profile'
7
+ hh = HipsterHash[foo: { bar: { x: "123" } }]
8
+
9
+ n = 1000
10
+ n.times do
11
+ hh.foo["Bar"].x
12
+ end
@@ -0,0 +1,2 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest/focus'
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.2
5
- prerelease:
4
+ version: 0.0.3
6
5
  platform: ruby
7
6
  authors:
8
- - Bryan JJ Buckley
7
+ - bjjb
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-10-22 00:00:00.000000000 Z
11
+ date: 2014-03-29 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: activesupport
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: :runtime
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
- description: A hash which is all, like, whatever.
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
- - jjbuckley@gmail.com
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: 1.8.24
81
+ rubygems_version: 2.2.0
67
82
  signing_key:
68
- specification_version: 3
69
- summary: A HipsterHash is just like a regular ruby Hash, except that it doesn't distinguish
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