mash 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.
Files changed (5) hide show
  1. data/History.txt +9 -0
  2. data/README.txt +1 -1
  3. data/lib/mash.rb +134 -37
  4. data/spec/mash_spec.rb +34 -0
  5. metadata +3 -3
@@ -1,3 +1,12 @@
1
+ === 0.0.3 / 2008-04-19
2
+
3
+ * [] no longer defaults to a new Mash, will return nil if
4
+ * Attribute-esque method names will yield the default value if not set
5
+ * Hash extended with #to_mash and #stringify_keys
6
+ * Added #dup and #deep_merge
7
+ * Aliased the default Hash methods so they are still accessible
8
+ * Cleaned up the recursive conversion process
9
+
1
10
  === 0.0.2 / 2008-04-12
2
11
 
3
12
  * Added bang(!) method support
data/README.txt CHANGED
@@ -32,7 +32,7 @@ RubyGem:
32
32
 
33
33
  Git:
34
34
 
35
- git clone git://github.com/mbleigh/mash
35
+ git clone git://github.com/mbleigh/mash.git
36
36
 
37
37
  == LICENSE:
38
38
 
@@ -40,14 +40,14 @@
40
40
  # mash.author # => <Mash name="Michael Bleigh">
41
41
  #
42
42
  class Mash < Hash
43
- VERSION = '0.0.2'
43
+ VERSION = '0.0.3'
44
44
 
45
45
  # If you pass in an existing hash, it will
46
46
  # convert it to a Mash including recursively
47
47
  # descending into arrays and hashes, converting
48
48
  # them as well.
49
49
  def initialize(source_hash = nil)
50
- mash_a_hash(source_hash) if source_hash
50
+ deep_update(source_hash) if source_hash
51
51
  super(nil)
52
52
  end
53
53
 
@@ -55,15 +55,56 @@ class Mash < Hash
55
55
  self["id"] ? self["id"] : super
56
56
  end
57
57
 
58
- def [](key) #:nodoc:
59
- key = key.to_s
60
- return Mash.new unless key?(key)
61
- super
58
+ # Borrowed from Merb's Mash object.
59
+ #
60
+ # ==== Parameters
61
+ # key<Object>:: The default value for the mash. Defaults to nil.
62
+ #
63
+ # ==== Alternatives
64
+ # If key is a Symbol and it is a key in the mash, then the default value will
65
+ # be set to the value matching the key.
66
+ def default(key = nil)
67
+ if key.is_a?(Symbol) && key?(key)
68
+ self[key]
69
+ else
70
+ super
71
+ end
62
72
  end
63
73
 
74
+ alias_method :regular_reader, :[]
75
+ alias_method :regular_writer, :[]=
76
+
77
+ # Retrieves an attribute set in the Mash. Will convert
78
+ # any key passed in to a string before retrieving.
79
+ def [](key)
80
+ key = convert_key(key)
81
+ regular_reader(key)
82
+ end
83
+
84
+ # Sets an attribute in the Mash. Key will be converted to
85
+ # a string before it is set.
64
86
  def []=(key,value) #:nodoc:
65
- key = key.to_s
66
- super
87
+ key = convert_key(key)
88
+ regular_writer(key,convert_value(value))
89
+ end
90
+
91
+ # This is the bang method reader, it will return a new Mash
92
+ # if there isn't a value already assigned to the key requested.
93
+ def initializing_reader(key)
94
+ return self[key] if key?(key)
95
+ self[key] = Mash.new
96
+ end
97
+
98
+ # Duplicates the current mash as a new mash.
99
+ def dup
100
+ Mash.new(self)
101
+ end
102
+
103
+ alias_method :regular_inspect, :inspect
104
+
105
+ alias_method :picky_key?, :key?
106
+ def key?(key)
107
+ picky_key?(convert_key(key))
67
108
  end
68
109
 
69
110
  # Prints out a pretty object-like string of the
@@ -76,8 +117,53 @@ class Mash < Hash
76
117
  ret << ">"
77
118
  ret
78
119
  end
120
+ alias_method :to_s, :inspect
121
+
122
+ # Performs a deep_update on a duplicate of the
123
+ # current mash.
124
+ def deep_merge(other_hash)
125
+ dup.deep_merge!(other_hash)
126
+ end
79
127
 
80
- alias :to_s :inspect
128
+ # Recursively merges this mash with the passed
129
+ # in hash, merging each hash in the hierarchy.
130
+ def deep_update(other_hash)
131
+ stringified_hash = other_hash.stringify_keys
132
+ stringified_hash.each_pair do |k,v|
133
+ k = convert_key(k)
134
+ self[k] = self[k].to_mash if self[k].is_a?(Hash) unless self[k].is_a?(Mash)
135
+ if self[k].is_a?(Hash) && stringified_hash[k].is_a?(Hash)
136
+ self[k].deep_merge!(stringified_hash[k])
137
+ else
138
+ self.send(k + "=", convert_value(stringified_hash[k]))
139
+ end
140
+ end
141
+ end
142
+ alias_method :deep_merge!, :deep_update
143
+
144
+ # ==== Parameters
145
+ # other_hash<Hash>::
146
+ # A hash to update values in the mash with. Keys will be
147
+ # stringified and Hashes will be converted to Mashes.
148
+ #
149
+ # ==== Returns
150
+ # Mash:: The updated mash.
151
+ def update(other_hash)
152
+ other_hash.each_pair do |key, value|
153
+ if respond_to?(convert_key(key) + "=")
154
+ self.send(convert_key(key) + "=", convert_value(value))
155
+ else
156
+ regular_writer(convert_key(key), convert_value(value))
157
+ end
158
+ end
159
+ self
160
+ end
161
+ alias_method :merge!, :update
162
+
163
+ # Converts a mash back to a hash (with stringified keys)
164
+ def to_hash
165
+ Hash.new(default).merge(self)
166
+ end
81
167
 
82
168
  def method_missing(method_name, *args) #:nodoc:
83
169
  if (match = method_name.to_s.match(/(.*)=$/)) && args.size == 1
@@ -85,12 +171,11 @@ class Mash < Hash
85
171
  elsif (match = method_name.to_s.match(/(.*)\?$/)) && args.size == 0
86
172
  key?(match[1])
87
173
  elsif (match = method_name.to_s.match(/(.*)!$/)) && args.size == 0
88
- return self[match[1]] if key?(match[1])
89
- self[match[1]] = Mash.new
90
- elsif keys.include?(method_name.to_s)
174
+ initializing_reader(match[1])
175
+ elsif key?(method_name)
91
176
  self[method_name]
92
177
  elsif match = method_name.to_s.match(/^([a-z][a-z0-9A-Z_]+)$/)
93
- nil
178
+ default
94
179
  else
95
180
  super
96
181
  end
@@ -98,32 +183,44 @@ class Mash < Hash
98
183
 
99
184
  protected
100
185
 
101
- def mash_a_hash(hash) #:nodoc:
102
- hash.each do |k,v|
103
- case v
104
- when Hash
105
- v = Mash.new(v) if v.is_a?(Hash)
106
- when Array
107
- v = collect_mashed_hashes_in(v) if v.is_a?(Array)
108
- end
109
-
110
- # we use the method call instead of []= here so that
111
- # it can be easily overridden for custom behavior in
112
- # inheriting objects
113
- self.send "#{k.to_s}=", v
114
- end
186
+ def convert_key(key) #:nodoc:
187
+ key.to_s
115
188
  end
116
189
 
117
- def collect_mashed_hashes_in(array) #:nodoc:
118
- array.collect do |value|
119
- case value
120
- when Hash
121
- Mash.new(value)
122
- when Array
123
- collect_mashed_hashes_in(value)
124
- else
125
- value
126
- end
190
+ def convert_value(value) #:nodoc:
191
+ case value
192
+ when Hash
193
+ value.is_a?(Mash) ? value : value.to_mash
194
+ when Array
195
+ value.collect{ |e| convert_value(e) }
196
+ else
197
+ value
127
198
  end
128
199
  end
200
+ end
201
+
202
+ class Hash
203
+ # Returns a new Mash initialized from this Hash.
204
+ def to_mash
205
+ mash = Mash.new(self)
206
+ mash.default = default
207
+ mash
208
+ end
209
+
210
+ # Returns a duplicate of the current hash with
211
+ # all of the keys converted to strings.
212
+ def stringify_keys
213
+ dup.stringify_keys!
214
+ end
215
+
216
+ # Converts all of the keys to strings
217
+ def stringify_keys!
218
+ keys.each{|k|
219
+ v = delete(k)
220
+ self[k.to_s] = v
221
+ v.stringify_keys! if v.is_a?(Hash)
222
+ v.each{|p| p.stringify_keys! if p.is_a?(Hash)} if v.is_a?(Array)
223
+ }
224
+ self
225
+ end
129
226
  end
@@ -50,6 +50,10 @@ describe Mash do
50
50
  @mash.name!.should == "Bob"
51
51
  end
52
52
 
53
+ it "#initializing_reader should return a Mash when passed a non-existent key" do
54
+ @mash.initializing_reader(:abc).is_a?(Mash).should be_true
55
+ end
56
+
53
57
  it "should allow for multi-level assignment through bang methods" do
54
58
  @mash.author!.name = "Michael Bleigh"
55
59
  @mash.author.should == Mash.new(:name => "Michael Bleigh")
@@ -57,6 +61,14 @@ describe Mash do
57
61
  @mash.author.website.should == Mash.new(:url => "http://www.mbleigh.com/")
58
62
  end
59
63
 
64
+ it "#deep_update should recursively mash mashes and hashes together" do
65
+ @mash.first_name = "Michael"
66
+ @mash.last_name = "Bleigh"
67
+ @mash.details = {:email => "michael@asf.com"}.to_mash
68
+ @mash.deep_update({:details => {:email => "michael@intridea.com"}})
69
+ @mash.details.email.should == "michael@intridea.com"
70
+ end
71
+
60
72
  context "#initialize" do
61
73
  it "should convert an existing hash to a Mash" do
62
74
  converted = Mash.new({:abc => 123, :name => "Bob"})
@@ -77,4 +89,26 @@ describe Mash do
77
89
  converted.a.last.should == 23
78
90
  end
79
91
  end
92
+ end
93
+
94
+ describe Hash do
95
+ it "should be convertible to a Mash" do
96
+ mash = {:some => "hash"}.to_mash
97
+ mash.is_a?(Mash).should be_true
98
+ mash.some.should == "hash"
99
+ end
100
+
101
+ it "#stringify_keys! should turn all keys into strings" do
102
+ hash = {:a => "hey", 123 => "bob"}
103
+ hash.stringify_keys!
104
+ hash.should == {"a" => "hey", "123" => "bob"}
105
+ end
106
+
107
+ it "#stringify_keys should return a hash with stringified keys" do
108
+ hash = {:a => "hey", 123 => "bob"}
109
+ stringified_hash = hash.stringify_keys
110
+ hash.should == {:a => "hey", 123 => "bob"}
111
+ stringified_hash.should == {"a" => "hey", "123" => "bob"}
112
+ end
113
+
80
114
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mash
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-04-12 00:00:00 -04:00
12
+ date: 2008-04-19 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -63,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  requirements: []
64
64
 
65
65
  rubyforge_project: mash-hash
66
- rubygems_version: 1.1.0
66
+ rubygems_version: 1.1.1
67
67
  signing_key:
68
68
  specification_version: 2
69
69
  summary: Mash is an extended Hash that gives simple pseudo-object functionality that can be built from hashes and easily extended