hash-tree 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in hash_tree.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 De Marque inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ Hash Tree
2
+ ===============
3
+
4
+ [![Build Status](https://secure.travis-ci.org/demarque/hash-tree.png?branch=master)](http://travis-ci.org/demarque/hash-tree)
5
+
6
+ Hash Tree gives you the abilty to manipulate nested hashes. The nodes of the hashes
7
+ can be others hashes or even arrays of hashes.
8
+
9
+ Install
10
+ -------
11
+
12
+ ```
13
+ gem install hash-tree
14
+ ```
15
+
16
+ Rails 3
17
+ -------
18
+
19
+ In your Gemfile:
20
+
21
+ ```ruby
22
+ gem 'hash-tree'
23
+ ```
24
+
25
+ Usage
26
+ -----
27
+
28
+ **FILL THE USAGE**
29
+
30
+ ```ruby
31
+ HashTree.new
32
+ ```
33
+
34
+ Copyright
35
+ ---------
36
+
37
+ Copyright (c) 2012 De Marque inc. See LICENSE for further details.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler"
2
+ require "rspec/core/rake_task"
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ desc "Run all specs in spec directory"
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = "spec/**/*_spec.rb"
9
+ end
10
+
11
+ task :default => :spec
data/lib/hash-tree.rb ADDED
@@ -0,0 +1,450 @@
1
+ require 'json'
2
+ require 'nori'
3
+
4
+ class HashTree
5
+ #*************************************************************************************
6
+ # CONSTRUCTOR
7
+ #*************************************************************************************
8
+ def initialize(hash={})
9
+ hash = {} unless hash.is_a? Hash
10
+
11
+ @hash = hash
12
+ end
13
+
14
+
15
+ #*************************************************************************************
16
+ # PUBLIC CLASS METHODS
17
+ #*************************************************************************************
18
+ def self.from_json(json_data)
19
+ return nil if json_data.to_s.empty?
20
+
21
+ parsed_data = JSON.parse(json_data)
22
+
23
+ tree = self.new(parsed_data)
24
+ tree.replace_values!(nil, '')
25
+
26
+ return tree
27
+ end
28
+
29
+ def self.from_json_path(json_path)
30
+ from_json File.read(json_path)
31
+ end
32
+
33
+ def self.from_xml(xml_data)
34
+ return nil if xml_data.to_s.empty?
35
+
36
+ tree = self.new(Nori.parse(xml_data))
37
+ tree.replace_values!(nil, '')
38
+
39
+ return tree
40
+ end
41
+
42
+ def self.from_xml_path(xml_path)
43
+ from_xml File.read(xml_path)
44
+ end
45
+
46
+ def self.from_yml_path(yml_path)
47
+ yml_data = YAML.load_file(yml_path)
48
+
49
+ tree = self.new(yml_data)
50
+
51
+ return tree
52
+ end
53
+
54
+
55
+ #*************************************************************************************
56
+ # PUBLIC METHODS
57
+ #*************************************************************************************
58
+ def checksum
59
+ Digest::MD5.hexdigest(@hash.to_json.scan(/\S/).sort.join)
60
+ end
61
+
62
+ def children(name)
63
+ if @hash[name] and @hash[name][name.chop]
64
+ return [@hash[name][name.chop]].flatten
65
+ else
66
+ return []
67
+ end
68
+ end
69
+
70
+ def clone_tree
71
+ HashTree.new(Marshal.load(Marshal.dump(@hash)))
72
+ end
73
+
74
+ # Remove all key with a nil value
75
+ def compact!
76
+ @hash = compact
77
+ end
78
+
79
+ def each(options={}, &block)
80
+ options = { :hash => @hash, :key_path => [], :scope => nil }.merge(options)
81
+
82
+ options[:hash].each do |key, value|
83
+ key_path = [options[:key_path], key].flatten
84
+ key_path_string = key_path.join('.')
85
+
86
+ if in_scope?(key_path_string, options[:scope])
87
+ if (options[:scope] and options[:scope] == key_path_string)
88
+ yield options[:hash], key, value, key_path_string
89
+ else
90
+ cast(value, Array).each do |item|
91
+ if item.is_a? Hash
92
+ each(:hash => item, :key_path => key_path, :scope => options[:scope], &block)
93
+ else
94
+ yield options[:hash], key, item, key_path_string
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def empty?
103
+ @hash.empty?
104
+ end
105
+
106
+ def exists?(path='', hash=nil)
107
+ hash = @hash unless hash
108
+
109
+ path_parts = path.split('.')
110
+
111
+ hash.each do |key, value|
112
+ value_for_loop = (value.is_a? Array) ? value : [value]
113
+
114
+ if path_parts[0] == key
115
+ return true if path_parts.length == 1
116
+
117
+ value_for_loop.each do |item|
118
+ if item.is_a?(Hash)
119
+ return true if exists?(path_parts[1..-1].join('.'), item)
120
+ end
121
+ end
122
+
123
+ end
124
+ end
125
+
126
+ return false
127
+ end
128
+
129
+ def get(path='', options={})
130
+ options = { :default => '', :force => nil }.merge(options)
131
+
132
+ if not path.empty?
133
+ data = []
134
+ self.each(:scope => path) { |parent, k, v| data << cast(v, options[:force]) }
135
+ data = (data.length <= 1 ? data.first : data)
136
+ else
137
+ data = @hash
138
+ end
139
+
140
+ return (data == nil ? options[:default] : data)
141
+ end
142
+
143
+ def keys_to_s!(options={})
144
+ options = { :hash => nil }.merge(options)
145
+
146
+ options[:hash] = @hash unless options[:hash]
147
+
148
+ options[:hash].keys_to_s!
149
+
150
+ options[:hash].each do |key, value|
151
+ value_for_loop = (value.is_a? Array) ? value : [value]
152
+ value_for_loop.each { |item| keys_to_s!(:hash => item) if item.is_a? Hash }
153
+ end
154
+ end
155
+
156
+ def id
157
+ #override the id method of all object
158
+ get('id')
159
+ end
160
+
161
+ def insert(path, content)
162
+ current_value = get(path)
163
+
164
+ if current_value.is_a? Array
165
+ if content.is_a? Array
166
+ current_value = current_value.concat(content)
167
+ else
168
+ current_value << content
169
+ end
170
+ end
171
+
172
+ set(path, current_value)
173
+ end
174
+
175
+ def inspect(options={})
176
+ options = { :hash => nil, :position => 0, :raw => true }.merge(options)
177
+
178
+ options[:hash] = @hash unless options[:hash]
179
+
180
+ return options[:hash].inspect if options[:raw]
181
+
182
+ content = ""
183
+
184
+ options[:hash].each do |key, value|
185
+ convert_to_array(value).each do |item|
186
+ content << "\n" + (' ' * options[:position]) + "#{key} : "
187
+ content << (item.is_a?(Hash) ? inspect(:hash => item, :position => options[:position]+1) : value.inspect)
188
+ end
189
+ end
190
+
191
+ return content
192
+ end
193
+
194
+ def merge(other_hash)
195
+ @hash = merge_children(@hash, other_hash)
196
+ end
197
+
198
+ def remove(path, options={})
199
+ options = { :if => nil, :remove_leaf => true, :unless => nil }.merge(options)
200
+
201
+ set(path, nil, options) if exists?(path)
202
+ end
203
+
204
+ def rename_key!(path, new_name, hash=nil)
205
+ hash = @hash unless hash
206
+
207
+ path_parts = path.split('.')
208
+
209
+ renamed_keys = {}
210
+
211
+ hash.each do |key, value|
212
+ if path_parts[0] == key
213
+ if path_parts.length == 1
214
+ renamed_keys[new_name] = hash.delete path_parts[0]
215
+ else
216
+ convert_to_array(value).each { |i| rename_key!(path_parts[1..-1].join('.'), new_name, i) if i.is_a? Hash }
217
+ end
218
+ end
219
+ end
220
+
221
+ hash.merge! renamed_keys if not renamed_keys.empty?
222
+ end
223
+
224
+ def replace_values!(old_value, new_value, hash=nil)
225
+ hash = @hash unless hash
226
+
227
+ hash.each do |key, value|
228
+ if value.is_a? Array
229
+ value.each do |item|
230
+ if item.is_a?(Hash)
231
+ item = replace_values!(old_value, new_value, item)
232
+ else
233
+ item = new_value if item == old_value
234
+ end
235
+ end
236
+ elsif value.is_a? Hash
237
+ value = replace_values!(old_value, new_value, value)
238
+ elsif value == old_value
239
+ value = new_value
240
+ end
241
+
242
+ hash[key] = value
243
+ end
244
+
245
+ return hash
246
+ end
247
+
248
+ def set(path, value, options={})
249
+ options = { :accept_nil => true, :if => nil, :remove_leaf => false, :unless => nil }.merge(options)
250
+
251
+ set_children(@hash, path, value, options) if options[:accept_nil] or value
252
+ end
253
+
254
+ def slash(path)
255
+ if exists?(path)
256
+ slashed_tree = get(path)
257
+ slashed_tree = slashed_tree.first if slashed_tree.is_a? Array and slashed_tree.length == 1 and slashed_tree.first.is_a? Hash
258
+ else
259
+ slashed_tree = @hash
260
+ end
261
+
262
+ return HashTree.new(slashed_tree)
263
+ end
264
+
265
+ def slash!(path)
266
+ @hash = slash(path).get
267
+ end
268
+
269
+ def to_json
270
+ @hash.to_json
271
+ end
272
+
273
+ def to_yaml
274
+ self.keys_to_s!
275
+
276
+ return @hash.ya2yaml
277
+ end
278
+
279
+ #*************************************************************************************
280
+ # PRIVATE METHODS
281
+ #*************************************************************************************
282
+ private
283
+
284
+ def compact(hash=nil)
285
+ hash = @hash unless hash
286
+
287
+ hash.each do |key, value|
288
+ if value.is_a? Array
289
+ hash[key] = compact_array value
290
+ elsif value.is_a? Hash
291
+ hash[key] = compact value
292
+ end
293
+ end
294
+
295
+ hash = compact_simple_hash(hash)
296
+
297
+ return (hash.empty? ? nil : hash)
298
+ end
299
+
300
+ def compact_array(array)
301
+ array.each { |item| item = compact(item) if item.is_a? Hash }
302
+ array.compact!
303
+
304
+ return (array.empty? ? nil : array)
305
+ end
306
+
307
+ def compact_simple_hash(hash)
308
+ hash.delete_if { |k,v| not v }
309
+ end
310
+
311
+ def cast(value, type)
312
+ case type.to_s
313
+ when 'Array' then convert_to_array value
314
+ when 'HashTree' then convert_to_hash_tree value
315
+ when 'String' then value.to_s
316
+ else value
317
+ end
318
+ end
319
+
320
+ def check_conditions(hash, conditions, comparaison = :if)
321
+ if conditions
322
+ conditions.each do |value, equal|
323
+ compare_values = hash
324
+ equal = [equal] unless equal.is_a? Array
325
+
326
+ path_value = value.split('.')
327
+ path_value.delete_at(0)
328
+
329
+ count = 0
330
+ path_value.each do |p|
331
+ if compare_values.is_a? Array
332
+ values = []
333
+ compare_values.each { |v| values << v[p] }
334
+ compare_values = values
335
+ elsif compare_values[p] != nil
336
+ compare_values = compare_values[p]
337
+ else
338
+ compare_values = nil
339
+ break
340
+ end
341
+ count += 1
342
+ end
343
+
344
+ if compare_values.is_a? Array
345
+ found = 0
346
+
347
+ compare_values.each { |v| found += 1 if equal.include?(v) }
348
+
349
+ if comparaison == :if and found == 0
350
+ return false
351
+ elsif comparaison == :unless and found > 0
352
+ return false
353
+ end
354
+ else
355
+ return false if comparaison == :if and not equal.include?(compare_values)
356
+ return false if comparaison == :unless and equal.include?(compare_values)
357
+ end
358
+ end
359
+ end
360
+
361
+ return true
362
+ end
363
+
364
+ def check_mixed_conditions(hash, if_conditions, unless_conditions)
365
+ valid = true
366
+
367
+ valid = check_conditions(hash, if_conditions, :if) if if_conditions
368
+ valid = check_conditions(hash, unless_conditions, :unless) if valid and unless_conditions
369
+
370
+ return valid
371
+ end
372
+
373
+ def convert_to_array(value)
374
+ value.is_a?(Array) ? value : [value]
375
+ end
376
+
377
+ def convert_to_hash_tree(value)
378
+ if value.is_a? HashTree
379
+ value
380
+ elsif value.is_a? Array
381
+ value.map{ |v| HashTree.new(v) }
382
+ else
383
+ HashTree.new(value)
384
+ end
385
+ end
386
+
387
+ def in_scope?(target, scope)
388
+ not scope or "^#{scope}".include? "^#{target}"
389
+ end
390
+
391
+ def merge_children(hash, other_hash)
392
+ other_hash = other_hash.get if other_hash.is_a? HashTree
393
+
394
+ if other_hash
395
+ other_hash.each do |key, value|
396
+ value = merge_children(hash[key], other_hash[key]) if hash[key] and value.is_a? Hash
397
+
398
+ hash[key] = value
399
+ end
400
+ end
401
+
402
+ return hash
403
+ end
404
+
405
+ def set_children(root, path, value, options={})
406
+ path = path.split('.')
407
+
408
+ selection = path[0]
409
+
410
+ setted = false
411
+
412
+ if path.length > 1
413
+ children = path.drop(1).join('.')
414
+
415
+ root[selection] = {} if not root[selection] or (not root[selection].is_a? Hash and not root[selection].is_a? Array)
416
+
417
+ if root[selection].is_a? Array
418
+ root[selection].each_with_index do |item, index|
419
+ set_children_node(root[selection][index], path, children, value, options) if item.is_a? Hash
420
+ end
421
+ else
422
+ set_children_node(root[selection], path, children, value, options)
423
+ end
424
+ else
425
+ root[selection] = value if check_mixed_conditions(root, options[:if], options[:unless])
426
+ end
427
+ end
428
+
429
+ def set_children_node(hash, path, children, value, options)
430
+ if options[:remove_leaf] and path.length == 2
431
+ if hash[children].is_a? Array
432
+ hash[children].delete_if{ |i| check_mixed_conditions(i, options[:if], options[:unless]) }
433
+ hash.delete(children) if hash[children].empty?
434
+ else
435
+ hash.delete(children) if check_mixed_conditions(hash[children], options[:if], options[:unless])
436
+ end
437
+ else
438
+ set_children(hash, children, value, options)
439
+ end
440
+ end
441
+
442
+ def method_missing(m, *args, &block)
443
+ if exists?(m.to_s)
444
+ return get(m.to_s)
445
+ else
446
+ return nil
447
+ #raise "DIG : The method #{m} doesn't exist in HashTree"
448
+ end
449
+ end
450
+ end
@@ -0,0 +1,14 @@
1
+ {
2
+ "book":
3
+ {
4
+ "id": 2,
5
+ "title": "Steppenwolf",
6
+ "author": ["Hermann Hesse"],
7
+ "formats": [
8
+ { "nature": "pdf", "price": [{ "amount": 799, "currency": "cad" }]},
9
+ { "nature": "epub", "price": [{ "amount": 799, "currency": "cad" }, { "amount": 759, "currency": "usd" }]}
10
+ ],
11
+ "tags": ["conflict", "isolation", "reality", "animalistic"],
12
+ "year": 1927
13
+ }
14
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "books":[
3
+ {
4
+ "id": 1,
5
+ "title": "Don Quixote",
6
+ "summary": "The novel follows the adventures of Alonso Quijano, who reads too many chivalric novels, and sets out to revive chivalry under the name of Don Quixote.",
7
+ "author": ["Miguel de Cervantes"],
8
+ "formats": [
9
+ { "nature": "pdf", "prices": [{ "amount": 999, "currency": "cad" }]},
10
+ { "nature": "epub", "prices": [{ "amount": 1099, "currency": "cad" }, { "amount": 999, "currency": "usd" }]}
11
+ ],
12
+ "tags": ["realism", "chivalry", "satire", "deception"],
13
+ "year": 1615
14
+ },
15
+
16
+ {
17
+ "id": 2,
18
+ "title": "Steppenwolf",
19
+ "summary": null,
20
+ "author": ["Hermann Hesse"],
21
+ "formats": [
22
+ { "nature": "pdf", "prices": [{ "amount": 799, "currency": "cad" }]},
23
+ { "nature": "epub", "prices": [{ "amount": 799, "currency": "cad" }, { "amount": 759, "currency": "usd" }]}
24
+ ],
25
+ "tags": ["conflict", "isolation", "reality", "animalistic"],
26
+ "year": 1927
27
+ },
28
+
29
+ {
30
+ "id": 3,
31
+ "title": "The Idiot",
32
+ "summary": "",
33
+ "author": ["Fyodor Dostoyevsky"],
34
+ "formats": [
35
+ { "nature": "epub", "prices": [{ "amount": 1599, "currency": "cad" }] }
36
+ ],
37
+ "tags": [],
38
+ "year": 1869
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,101 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <books>
3
+ <book>
4
+ <id>1</id>
5
+ <title>Don Quixote</title>
6
+ <authors>
7
+ <author>Miguel de Cervantes</author>
8
+ </authors>
9
+ <formats>
10
+ <format>
11
+ <nature>pdf</nature>
12
+ <prices>
13
+ <price>
14
+ <amount>999</amount>
15
+ <currency>cad</currency>
16
+ </price>
17
+ </prices>
18
+ </format>
19
+ <format>
20
+ <nature>epub</nature>
21
+ <prices>
22
+ <price>
23
+ <amount>1099</amount>
24
+ <currency>cad</currency>
25
+ </price>
26
+ <price>
27
+ <amount>999</amount>
28
+ <currency>usd</currency>
29
+ </price>
30
+ </prices>
31
+ </format>
32
+ </formats>
33
+ <tags>
34
+ <tag>realism</tag>
35
+ <tag>chivalry</tag>
36
+ <tag>satire</tag>
37
+ <tag>deception</tag>
38
+ </tags>
39
+ <year>1615</year>
40
+ </book>
41
+
42
+ <book>
43
+ <id>2</id>
44
+ <title>Steppenwolf</title>
45
+ <authors>
46
+ <author>Hermann Hesse</author>
47
+ </authors>
48
+ <formats>
49
+ <format>
50
+ <nature>pdf</nature>
51
+ <prices>
52
+ <price>
53
+ <amount>799</amount>
54
+ <currency>cad</currency>
55
+ </price>
56
+ </prices>
57
+ </format>
58
+ <format>
59
+ <nature>epub</nature>
60
+ <prices>
61
+ <price>
62
+ <amount>799</amount>
63
+ <currency>cad</currency>
64
+ </price>
65
+ <price>
66
+ <amount>759</amount>
67
+ <currency>usd</currency>
68
+ </price>
69
+ </prices>
70
+ </format>
71
+ </formats>
72
+ <tags>
73
+ <tag>conflict</tag>
74
+ <tag>isolation</tag>
75
+ <tag>reality</tag>
76
+ <tag>animalistic</tag>
77
+ </tags>
78
+ <year>1927</year>
79
+ </book>
80
+
81
+ <book>
82
+ <id>3</id>
83
+ <title>The Idiot</title>
84
+ <authors>
85
+ <author>Fyodor Dostoyevsky</author>
86
+ </authors>
87
+ <formats>
88
+ <format>
89
+ <nature>epub</nature>
90
+ <prices>
91
+ <price>
92
+ <amount>1599</amount>
93
+ <currency>cad</currency>
94
+ </price>
95
+ </prices>
96
+ </format>
97
+ </formats>
98
+ <tags></tags>
99
+ <year>1869</year>
100
+ </book>
101
+ </books>
@@ -0,0 +1,73 @@
1
+ ---
2
+ "books":
3
+ -
4
+ "id": 1
5
+ "title": "Don Quixote"
6
+ "author":
7
+ - "Miguel de Cervantes"
8
+ "formats":
9
+ -
10
+ "nature": "pdf"
11
+ "price":
12
+ -
13
+ "amount": 999
14
+ "currency": "cad"
15
+ -
16
+ "nature": "epub"
17
+ "price":
18
+ -
19
+ "amount": 1099
20
+ "currency": "cad"
21
+ -
22
+ "amount": 999
23
+ "currency": "usd"
24
+ "tags":
25
+ - "realism"
26
+ - "chivalry"
27
+ - "satire"
28
+ - "deception"
29
+ "year": 1615
30
+
31
+ -
32
+ "id": 2
33
+ "title": "Steppenwolf"
34
+ "author":
35
+ - "Hermann Hesse"
36
+ "formats":
37
+ -
38
+ "nature": "pdf"
39
+ "price":
40
+ -
41
+ "amount": 799
42
+ "currency": "cad"
43
+
44
+ -
45
+ "nature": "epub"
46
+ "price":
47
+ -
48
+ "amount": 799
49
+ "currency": "cad"
50
+ -
51
+ "amount": 759
52
+ "currency": "usd"
53
+ "tags":
54
+ - "conflict"
55
+ - "isolation"
56
+ - "reality"
57
+ - "animalistic"
58
+ "year": 1927
59
+
60
+ -
61
+ "id": 3
62
+ "title": "The Idiot"
63
+ "author":
64
+ - "Fyodor Dostoyevsky"
65
+ "formats":
66
+ -
67
+ "nature": "epub"
68
+ "price":
69
+ -
70
+ "amount": 1599
71
+ "currency": "cad"
72
+ "tags": ""
73
+ "year": 1869
@@ -0,0 +1,239 @@
1
+ require 'spec_helper'
2
+
3
+ include ApplicationHelpers
4
+
5
+ describe HashTree do
6
+ describe "::from_json" do
7
+ context "when using the books.json fixture" do
8
+ let(:hashtree) { HashTree.from_json(File.read('spec/fixtures/books.json')) }
9
+
10
+ subject { hashtree }
11
+
12
+ it { should_not be_empty }
13
+ the("hashtree.get('books').length") { should eql 3 }
14
+ end
15
+
16
+ context "when using an empty fixture" do
17
+ subject { HashTree.from_json('') }
18
+
19
+ it { should be_nil }
20
+ end
21
+ end
22
+
23
+ describe "::from_json_path" do
24
+ context "when using the books.json fixture" do
25
+ subject { HashTree.from_json_path('spec/fixtures/books.json') }
26
+ it { should_not be_empty }
27
+ end
28
+ end
29
+
30
+ describe "::from_xml" do
31
+ context "when using the books.xml fixture" do
32
+ let(:hashtree) { HashTree.from_xml(File.read('spec/fixtures/books.xml')) }
33
+
34
+ subject { hashtree }
35
+
36
+ it { should_not be_empty }
37
+ the("hashtree.get('books.book').length") { should eql 3 }
38
+ end
39
+
40
+ context "when using an empty fixture" do
41
+ subject { HashTree.from_xml('') }
42
+
43
+ it { should be_nil }
44
+ end
45
+ end
46
+
47
+ describe "::from_xml_path" do
48
+ context "when using the books.xml fixture" do
49
+ subject { HashTree.from_xml_path('spec/fixtures/books.xml') }
50
+ it { should_not be_empty }
51
+ end
52
+ end
53
+
54
+ describe "::from_yml_path" do
55
+ context "when using the books.yml fixture" do
56
+ let(:hashtree) { HashTree.from_yml_path('spec/fixtures/books.yml') }
57
+
58
+ subject { hashtree }
59
+
60
+ it { should_not be_empty }
61
+ the("hashtree.get('books').length") { should eql 3 }
62
+ end
63
+ end
64
+
65
+ describe "#checksum" do
66
+ context "with an empty hash" do
67
+ specify { HashTree.new.checksum.should eql '99914b932bd37a50b983c5e7c90ae93b' }
68
+ end
69
+
70
+ with_books_fixture do
71
+ specify { hashtree.checksum.should eql 'c15fddfa0bea3610663d019b8b5b4a4d' }
72
+ end
73
+ end
74
+
75
+ describe "#children" do
76
+ pending 'TOTEST'
77
+ end
78
+
79
+ describe "#clone_tree" do
80
+ with_books_fixture do
81
+ context "and cloning it" do
82
+ subject { hashtree.clone_tree }
83
+
84
+ its(:object_id) { should_not eql hashtree.object_id }
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#compact!" do
90
+ fixture :hashtree, HashTree.new({ 'books' => [ { 'title' => 'Don Quixote' }, { 'title' => nil }, { 'title' => 'Steppenwolf', 'formats' => [nil, 'pdf', 'epub'] } ]}) do
91
+ before { hashtree.compact! }
92
+
93
+ the("hashtree.get('books.title')") { should eql ['Don Quixote', 'Steppenwolf'] }
94
+ the("hashtree.get('books.formats')") { should eql ['pdf', 'epub'] }
95
+ end
96
+ end
97
+
98
+ describe "#each" do
99
+ pending 'TOTEST'
100
+ end
101
+
102
+ describe "#empty?" do
103
+ fixture :ht, HashTree.new do
104
+ it { should be_empty }
105
+ end
106
+
107
+ fixture :ht, HashTree.new({ 'book' => 'Steppenwolf' }) do
108
+ it { should_not be_empty }
109
+ end
110
+ end
111
+
112
+ describe "#exists?" do
113
+ fixture :hashtree, HashTree.new({ 'books' => [ { 'title' => 'Don Quixote' }, { 'formats' => [{ 'price' => 999 }] } ]}) do
114
+ the("hashtree.exists?('books.title')") { should be_true }
115
+ the("hashtree.exists?('books.formats.price')") { should be_true }
116
+ the("hashtree.exists?('books.unknown')") { should be_false }
117
+ end
118
+ end
119
+
120
+ describe "#get" do
121
+ fixture :books, HashTree.new({ 'books' => [ { 'title' => 'Don Quixote' }, { 'formats' => [{ 'nature' => 'pdf' }, {'nature' => 'epub' }] } ]}) do
122
+ the("books.get('books.title')") { should eql "Don Quixote" }
123
+ the("books.get('books.title', :force => Array)") { should eql ["Don Quixote"] }
124
+ the("books.get('books.formats')") { should eql [{ 'nature' => 'pdf' }, {'nature' => 'epub' }] }
125
+ the("books.get('books.formats.nature')") { should eql ['pdf', 'epub'] }
126
+ the("books.get('books.name')") { should eql "" }
127
+ the("books.get('books.name', :default => nil)") { should be_nil }
128
+ end
129
+ end
130
+
131
+ describe "#keys_to_s!" do
132
+ pending 'TOTEST'
133
+ end
134
+
135
+ describe "#id" do
136
+ context "with an hash tree having the attribute id with the value 123" do
137
+ subject { HashTree.new(:id => 123, :name => 'test') }
138
+
139
+ its(:id) { should eql 123 }
140
+ end
141
+ end
142
+
143
+ describe "#insert" do
144
+ pending 'TOTEST'
145
+ end
146
+
147
+ describe "#inspect" do
148
+ pending 'TOTEST'
149
+ end
150
+
151
+ describe "#merge" do
152
+ pending 'TOTEST'
153
+ end
154
+
155
+ describe "#remove" do
156
+ pending 'TOTEST'
157
+ end
158
+
159
+ describe "#rename_key!" do
160
+ with_books_fixture do
161
+ context "and renaming the key books.title for name" do
162
+ before { hashtree.rename_key! 'books.title', 'name' }
163
+
164
+ the("hashtree.exists?('books.title')") { should be_false }
165
+ the("hashtree.exists?('books.name')") { should be_true }
166
+ end
167
+
168
+ context "and renaming the key books.formats.prices.currency for specie" do
169
+ before { hashtree.rename_key! 'books.formats.prices.currency', 'specie' }
170
+
171
+ the("hashtree.exists?('books.formats.prices.currency')") { should be_false }
172
+ the("hashtree.exists?('books.formats.prices.specie')") { should be_true }
173
+ end
174
+ end
175
+ end
176
+
177
+ describe "#replace_values!" do
178
+ with_books_fixture do
179
+ the("hashtree.get('books.formats.nature')") { should include 'pdf' }
180
+
181
+ context "and replacing value pdf for paper" do
182
+ before { hashtree.replace_values!('pdf', 'paper') }
183
+ the("hashtree.get('books.formats.nature')") { should_not include 'pdf' }
184
+ end
185
+
186
+ context "and replacing value unknown for paper" do
187
+ before { hashtree.replace_values!('unknown', 'paper') }
188
+ the("hashtree.get('books.formats.nature')") { should include 'pdf' }
189
+ end
190
+ end
191
+ end
192
+
193
+ describe "#set" do
194
+ pending 'TOTEST'
195
+ end
196
+
197
+ describe "#slash" do
198
+ with_book_fixture do
199
+ context "and slashing book" do
200
+ subject { hashtree.slash 'book' }
201
+
202
+ its(:title) { should eql 'Steppenwolf' }
203
+ the("hashtree.book['title']") { should eql 'Steppenwolf' }
204
+ end
205
+
206
+ context "and not slashing book" do
207
+ its(:title) { should be_nil }
208
+ end
209
+ end
210
+ end
211
+
212
+ describe "#slash!" do
213
+ with_book_fixture do
214
+ context "and slashing book" do
215
+ before { hashtree.slash! 'book' }
216
+
217
+ its(:title) { should eql 'Steppenwolf' }
218
+ the("hashtree.book") { should be_nil }
219
+ end
220
+ end
221
+ end
222
+
223
+ describe "#to_json" do
224
+ with_books_fixture do
225
+ context "and converting it to json" do
226
+ subject { hashtree.to_json }
227
+
228
+ it { should_not be_empty }
229
+ it { should include '{"books":[{' }
230
+ it { should include '"tags":["conflict","isolation","reality","animalistic"]' }
231
+ it { should include '"author":["Fyodor Dostoyevsky"]' }
232
+ end
233
+ end
234
+ end
235
+
236
+ describe "#to_yaml" do
237
+ pending 'TOTEST'
238
+ end
239
+ end
@@ -0,0 +1,14 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require 'rspec'
5
+ require 'rspec-aspic'
6
+
7
+ require File.expand_path('../../lib/hash-tree', __FILE__)
8
+
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
10
+
11
+ RSpec.configure do |config|
12
+ config.mock_with :rspec
13
+ config.include RSpecAspic
14
+ end
@@ -0,0 +1,15 @@
1
+ module ApplicationHelpers
2
+ def with_book_fixture(&block)
3
+ with_books_fixture('spec/fixtures/book.json', &block)
4
+ end
5
+
6
+ def with_books_fixture(file_path='spec/fixtures/books.json', &block)
7
+ context "when using the " + file_path.split('/').last + " fixture" do
8
+ let(:hashtree) { HashTree.from_json(File.read(file_path)) }
9
+
10
+ subject { hashtree }
11
+
12
+ self.instance_exec &block
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hash-tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sebastien Rosa
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: &2157134360 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2157134360
25
+ - !ruby/object:Gem::Dependency
26
+ name: ya2yaml
27
+ requirement: &2157133800 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0.30'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2157133800
36
+ - !ruby/object:Gem::Dependency
37
+ name: nori
38
+ requirement: &2157133320 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.1.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2157133320
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &2157132840 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.7
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *2157132840
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &2157132320 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '2.0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *2157132320
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-aspic
71
+ requirement: &2157131820 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 0.0.2
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *2157131820
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec-compact-doc-formatter
82
+ requirement: &2157131240 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: 0.0.3
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *2157131240
91
+ description: HashTree help you to work with nested hashes and arrays.
92
+ email:
93
+ - sebastien@demarque.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files:
97
+ - LICENSE
98
+ - README.md
99
+ files:
100
+ - lib/hash-tree.rb
101
+ - spec/fixtures/book.json
102
+ - spec/fixtures/books.json
103
+ - spec/fixtures/books.xml
104
+ - spec/fixtures/books.yml
105
+ - spec/hash-tree_spec.rb
106
+ - spec/spec_helper.rb
107
+ - spec/support/application_helpers.rb
108
+ - LICENSE
109
+ - README.md
110
+ - Rakefile
111
+ - Gemfile
112
+ homepage: https://github.com/demarque/hash-tree
113
+ licenses:
114
+ - MIT
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ none: false
127
+ requirements:
128
+ - - ! '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project: hash-tree
133
+ rubygems_version: 1.8.17
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Manage nested hash
137
+ test_files: []