order_tree 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ report/*
4
+ Gemfile.lock
5
+ pkg/*
data/.yardoc/checksums ADDED
@@ -0,0 +1,5 @@
1
+ lib/order_tree.rb 1587f76284fef215167a4b0970a1411503f6543f
2
+ lib/order_tree/version.rb 12d621b5c0101d185a48bac5d5ad897d7b83053b
3
+ lib/order_tree/unique_proxy.rb 632e468875c365d94b069f5c7e4d3d10030ccb55
4
+ lib/order_tree/order_tree.rb 3dc2a2a576334be3fe9cd19fe999b310fbfd7ea6
5
+ lib/order_tree/order_store.rb cfef2da2707e61ed0637a48e9a0c1a55d9578b1c
Binary file
Binary file
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in order_tree.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ #Ordered Tree
2
+
3
+ This is a nested hash / unbalance tree structure which iterates over all of
4
+ its available values in insertion order.
5
+
6
+ ##Like a Normal Hash?
7
+
8
+ Exactly like a normal hash. Althought with a standard Ruby Hash it's impossible to
9
+ know whether or not `hash["a"]["b"]` was inserted before or after `hash["b"]["c"]`
10
+ because each individual Hash maintains it's own insertion order.
11
+
12
+ Now you can know. If you need to. The main thing you gain with this over an
13
+ Array is the ability to prune or transplant multiple values at the same
14
+ time by cutting on one of the branches.
15
+
16
+ ##Caveat
17
+
18
+ Each value is actually stored in a proxy object which maintains a unique id.
19
+ This is necessary so that if you insert three `4`s into the tree you can tell
20
+ which one came first. This wouldn't actually be necessary in a C implementation,
21
+ or in one based on WeakRefs, but it works okay here.
22
+
23
+ You can generally treat the OrderTree exactly like a nested hash, but be aware
24
+ that the first and last methods (as well as the #each iterator) actually return
25
+ an OrderTreeNode and not the actual object stored at that location. (You can
26
+ get at it by using #orig on the returned value.
data/Rakefile ADDED
@@ -0,0 +1,39 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core'
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec) do |spec|
8
+ spec.pattern = FileList['spec/**/*_spec.rb']
9
+ spec.rspec_opts = "-d"
10
+ end
11
+
12
+ RSpec::Core::RakeTask.new(:spec_with_report) do |spec|
13
+ spec.fail_on_error = false
14
+ spec.pattern = FileList['spec/**/*_spec.rb']
15
+ spec.rspec_opts = "--format html --out report/test_report.html"
16
+ end
17
+
18
+ task :report do
19
+ Dir.mkdir "report" unless File.exists? "report"
20
+ Dir.mkdir "report/profile" unless File.exists? "report/profile"
21
+ File.open "report/index.html","w" do |f|
22
+ f.write <<-HTML
23
+ <html>
24
+ <body>
25
+ <h1> Status Report </h1>
26
+ <a href="coverage/index.html"> Coverage </a>
27
+ <a href="profile/profile.html"> Speed Profile </a>
28
+ <a href="test_report.html"> Test Report </a>
29
+ </body>
30
+ </html>
31
+ HTML
32
+ end
33
+ ENV["REPORT"] = "1"
34
+ Rake::Task[:spec_with_report].invoke
35
+ ENV["REPORT"] = ""
36
+ end
37
+
38
+ task :default => :spec
39
+
data/lib/order_tree.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'order_tree/unique_proxy'
2
+ require 'order_tree/order_tree_node'
3
+ require 'order_tree/order_tree'
4
+
5
+ module OrderTree
6
+ end
@@ -0,0 +1,319 @@
1
+ require 'delegate'
2
+
3
+ module OrderTree
4
+
5
+ # A unbalance tree / nested hash type structure that implements #each and returns
6
+ # the values in the order in which they were inserted, regardless of depth
7
+ # It can mostly be treated as a nested hash - but #each will return a #path
8
+ # to the values it iterates
9
+ class OrderTree
10
+ include Enumerable
11
+ include ProxyOperator
12
+
13
+ class PathNotFound < StandardError; end
14
+
15
+ attr_accessor :first, :root
16
+ attr_reader :last
17
+
18
+ # Create a new OrderTree
19
+ # @param [Hash] constructor - a hash literal for the initial values
20
+ # @param [OrderTree] OrderTree - the root tree object.
21
+ # @note The order of insertion might not be what you would expect for multi-
22
+ # level hash literals. The most deeply nested values will be inserted FIRST.
23
+ def initialize(constructor = {}, root = nil)
24
+ @_delegate_hash = {}
25
+ self.root = root || self
26
+ constructor.each_with_object(self) do |(k,v),memo|
27
+ memo[k] = v
28
+ end
29
+ self.default = OrderTreeNode.new(nil,self) if self.root
30
+ _delegate_hash.default = self.default
31
+ end
32
+
33
+ def last= obj
34
+ if @last.nil?
35
+ @first = obj
36
+ @last = obj
37
+ else
38
+ @last = obj
39
+ end
40
+ end
41
+ protected :last=
42
+
43
+ # Set the default value for the tree
44
+ # This place the default object behind a UniqueProxy. The default
45
+ # is not remembered within the order
46
+ # @param [Object] obj
47
+ def default= obj
48
+ unless proxy? obj
49
+ obj = OrderTreeNode.new(obj,self)
50
+ end
51
+ @default = obj
52
+ _delegate_hash.default = @default
53
+ _delegate_hash.each_value do |v|
54
+ if v.is_a? OrderTree
55
+ v.default= obj
56
+ end
57
+ end
58
+ end
59
+
60
+ attr_reader :default
61
+
62
+ # Yields each path, value pair of the OrderTree in the order in which it was
63
+ # inserted
64
+ # @return [Enumerator]
65
+ # @yield [path, value] yields the path (as an array) to a value
66
+ # @yieldparam [Array] path the path as an array
67
+ # @yieldparam [Object] value the original object stored in the OrderTree
68
+ def each_pair
69
+ return enum_for(:each_pair) unless block_given?
70
+ self.each do |c|
71
+ yield c.path, c.orig
72
+ end
73
+ end
74
+
75
+ # @return [Array] the results of calling {#each}.to_a
76
+ def order
77
+ each_pair.to_a
78
+ end
79
+
80
+ # @return [Enumerator] collection of OrderTreeNode objects in insertion order
81
+ def each
82
+ return enum_for(:each) unless block_given?
83
+ c = root.first
84
+ while c
85
+ yield c
86
+ c = c.next
87
+ end
88
+ end
89
+
90
+ # @return [Enumerator] collection of paths in insertion order
91
+ def each_path
92
+ return enum_for(:each_path) unless block_given?
93
+ self.each do |v|
94
+ yield v.path
95
+ end
96
+ end
97
+
98
+ # @return [Enumerator] collection of leaf values in insertion order
99
+ def each_leaf
100
+ return enum_for(:each_leaf) unless block_given?
101
+ self.each do |v|
102
+ unless v.is_a? OrderTree
103
+ yield v.orig
104
+ end
105
+ end
106
+ end
107
+
108
+ # @return [Enumerator] collection of values in insertion order, including subnodes
109
+ def each_value
110
+ return enum_for(:each_value) unless block_given?
111
+ self.each do |v|
112
+ yield v.orig
113
+ end
114
+ end
115
+
116
+ # Finds the first occurence of an object in the tree
117
+ # @param [Object] val the value to search for this is a O(n) operation
118
+ # @param [Block] block if both a block and val are passed, the
119
+ # block will be evalueated first, then the value
120
+ # @return [Array, false, true] path to value, false if not found, or true if value == self
121
+ # @yield [value] use the block to perform more complicated tests
122
+ # @yieldreturn [Boolean]
123
+ # @raises [ArgumentError] if neither a val nor block is given.
124
+ # @note You cannot search for a nil value by passing nil as the value. You must
125
+ # pass a block that compares for nil
126
+ # @note This methods does NOT guarantee that you will recieve the result
127
+ # in inserted order
128
+ def path val = nil, &block
129
+ begin
130
+ path! val, &block
131
+ rescue PathNotFound
132
+ nil
133
+ end
134
+ end
135
+
136
+ # Finds the first occurence of a specific insertion in the tree
137
+ # @param [OrderTreeNode] val the proxy object to find
138
+ def strict_path val = nil
139
+ begin
140
+ strict_path! val
141
+ rescue PathNotFound
142
+ nil
143
+ end
144
+ end
145
+
146
+ # Raises an exception if it can't determine the path
147
+ # @see {#path}
148
+ def path! val = nil, &block
149
+ raise ArgumentError, "requires search value or block" if val.nil? and block.nil?
150
+ __path(val, false, [], &block)
151
+ end
152
+
153
+ # Raises an exception if it can't find the object
154
+ # @see {#strict_path}
155
+ def strict_path! val
156
+ unless ProxyOperator.proxy? val
157
+ raise ArgumentError, "OrderTreeNode expected for strict_path"
158
+ end
159
+ __path(val, true)
160
+ end
161
+
162
+ # @private
163
+ def __path val = nil, strict = false, key_path = [], &block
164
+ op = strict ? :equal? : :==
165
+ return true if (yield(val) unless block.nil?) or self.__send__ op, val
166
+ _delegate_hash.each do |k,v|
167
+ if (yield v unless block.nil?) or v.__send__ op, val
168
+ key_path << k
169
+ break
170
+ elsif v.respond_to? :__path, true
171
+ begin
172
+ v.__path(val, strict, key_path, &block)
173
+ key_path.unshift(k)
174
+ break
175
+ rescue PathNotFound
176
+ # try the next one
177
+ end
178
+ end
179
+ end
180
+ raise PathNotFound, "Couldn't find path to #{val} in #{self.to_s}" if key_path.empty?
181
+ key_path
182
+ end
183
+ private :__path
184
+
185
+ # @private
186
+ # @api Delegate
187
+ def _delegate_hash
188
+ @_delegate_hash
189
+ end
190
+ private :_delegate_hash
191
+
192
+ def to_s
193
+ "#<#{self.class}:#{'0x%x' % self.__id__ << 1}>"
194
+ end
195
+
196
+ def inspect
197
+ _delegate_hash.inspect
198
+ end
199
+
200
+ # @param [OrderTree] other
201
+ # @return [true] if self and other do not share all leaves or were inserted in a different order
202
+ def != other
203
+ !(self == other)
204
+ end
205
+
206
+ # @param [OrderTree] other
207
+ # @return [true] if self and other share all of their leaves and were inserted in the same order
208
+ def == other
209
+ return false if other.class != self.class
210
+ ov,sv = other.each_value.to_a, self.each_value.to_a
211
+ t_arr = ov.size > sv.size ? (ov.zip(sv)) : (sv.zip(ov))
212
+ t_arr.each do |sv, ov|
213
+ return false if sv.nil? ^ ov.nil?
214
+ if [ov,sv].map { |v| v.respond_to? :_delegate_hash, true}.all?
215
+ return false unless ov.send(:_delegate_hash) == sv.send(:_delegate_hash)
216
+ elsif ov != sv
217
+ return false
218
+ end
219
+ end
220
+ end
221
+
222
+ # @param [OrderTree] other
223
+ # @return [true] if self and other contains the same key/path pairs as leaf nodes, regardless of their
224
+ # order of insertion
225
+ def contents_equal? other
226
+ return false if other.class != self.class
227
+ ov,sv = other.each_leaf.to_a.sort, self.each_leaf.to_a.sort
228
+ t_arr = ov.size > sv.size ? (ov.zip(sv)) : (sv.zip(ov))
229
+ t_arr.each do |sv,ov|
230
+ return false unless sv == ov
231
+ end
232
+ end
233
+
234
+ # Returns the UniqueProxy at path.
235
+ # @param [Array] path the path to return
236
+ # @return [OrdeTreeNode] either OrderTreeNode default or the OrderTreeNode at path
237
+ def at *paths
238
+ t = self
239
+ paths.each do |p|
240
+ if t.respond_to? :at # if it's a branch nodejkeep looking
241
+ t = (t.__send__ :_delegate_hash)[p]
242
+ else
243
+ #other wise resturn the default
244
+ return self.root.default
245
+ end
246
+ end
247
+ t
248
+ end
249
+
250
+ # Return the object stored at path
251
+ # @param [Array] path you may specify the path as either an array, or
252
+ # by using the nested hash syntax
253
+ # @example
254
+ # obj = OrderTree.new( :first => { :second => { :third => 3 }})
255
+ # obj[:first, :second, :third] #=> 3
256
+ # obj[:first][:second][:third] #=> 3
257
+ def [] *paths
258
+ t = self.at *paths
259
+ t.orig
260
+ end
261
+
262
+
263
+ # @private
264
+ def _find_delegate_hash *paths
265
+ under = self
266
+ paths.each do |k|
267
+ under = under.instance_eval do
268
+ unless self.respond_to? :_delegate_hash, true
269
+ raise NoMethodError, "Can't reifiy tree leaf on access to #{paths}"
270
+ end
271
+ h = self.send :_delegate_hash
272
+ if h.has_key? k and k != paths.last
273
+ h[k]
274
+ else
275
+ break h
276
+ end
277
+ end
278
+ end
279
+ under
280
+ end
281
+ private :_find_delegate_hash
282
+
283
+ # Prune branch from the tree
284
+ # @param [Array] path
285
+ def delete *paths
286
+ under = _find_delegate_hash *paths
287
+ if under.has_key? paths.last
288
+ under[paths.last].remove
289
+ end
290
+ end
291
+
292
+ # Stores the value at path
293
+ # @param [Array] path
294
+ # @param [Object] value
295
+ def []= *paths, value
296
+ under = _find_delegate_hash *paths
297
+
298
+ if value.kind_of? Hash or value.kind_of? OrderTree
299
+ value = OrderTree.new(value, @root)
300
+ value.default= self.root.default
301
+ end
302
+
303
+ if under.has_key? paths.last # i am overwriting a path
304
+ under[paths.last].remove
305
+ end
306
+
307
+ under[paths.last] = OrderTreeNode.new(value, self)
308
+ under[paths.last].prev = root.last if root.last
309
+ root.last.next = under[paths.last] if root.last
310
+ root.last = under[paths.last]
311
+
312
+ #@order.push under[paths.last]
313
+
314
+ #puts "insertion of '#{value}' in #{self.to_s} -> #{@order.to_s} (id #{under[paths.last].unique_id})"
315
+ value
316
+ end
317
+ alias :store :[]=
318
+ end
319
+ end
@@ -0,0 +1,66 @@
1
+ module OrderTree
2
+ class OrderTreeNode < UniqueProxy
3
+ attr_accessor :next, :prev
4
+ attr_accessor :tree
5
+ attr_reader :path
6
+
7
+ def initialize obj, tree
8
+ super(obj)
9
+ @tree = tree
10
+ end
11
+
12
+ def remove
13
+ prev_node = self.prev
14
+ next_node = self.next
15
+ self.next.prev = prev_node if self.next
16
+ self.prev.next = next_node if self.prev
17
+
18
+ if self.tree.root.first.equal? self
19
+ self.tree.root.first = self.next
20
+ end
21
+
22
+ # try this so that the node can remove
23
+ # itself fromt he tree
24
+ my_path = self.path
25
+ self.tree.instance_eval do
26
+ _delegate_hash.delete my_path.last
27
+ end
28
+ @path = nil
29
+ @tree = nil
30
+ @next = nil
31
+ @prev = nil
32
+ self
33
+ end
34
+
35
+ def before other
36
+ (self <=> other) == -1 ? true : false
37
+ end
38
+
39
+ def after other
40
+ (self <=> other) == 1 ? true : false
41
+ end
42
+
43
+ def <=> other
44
+ if self.equal? other
45
+ return 0
46
+ else
47
+ p, n = self.prev, self.next
48
+ while p or n
49
+ return 1 if p.equal? other
50
+ return -1 if n.equal? other
51
+ p = p.prev if p
52
+ n = n.next if n
53
+ end
54
+ end
55
+ raise ::ArgumentError, "Cannot compare #{self} and #{other} because they are not in the same tree"
56
+ end
57
+
58
+ def path
59
+ @path || self.path!
60
+ end
61
+
62
+ def path!
63
+ @path = self.tree.root.strict_path! self
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,96 @@
1
+ require 'securerandom'
2
+
3
+ module OrderTree
4
+
5
+ # Mixin module that provides a proxy convience functions
6
+ module ProxyOperator
7
+ #@param [Object] obj object to test
8
+ #@return [Boolean] is object in fact a proxy?
9
+ def proxy? obj
10
+ !!(obj.instance_eval { @is_proxy })
11
+ rescue false
12
+ end
13
+ module_function :proxy?
14
+
15
+ #@param [Object] obj object to proxy
16
+ #@return [UniqueProxy] create a unique proxy over obj
17
+ def proxy obj
18
+ UniqueProxy.new obj
19
+ rescue false
20
+ end
21
+ module_function :proxy
22
+ end
23
+
24
+ # Simple Proxy for distinguishing between the insertions of two identical
25
+ # objects in an order tree. Assign a unique ID to any object passed through
26
+ # the proxy, so you can always find the same object, even if you move it
27
+ # around in the tree. It also enables you to tell the differen between two
28
+ # different insertions of the same singleton object.
29
+ class UniqueProxy < BasicObject
30
+ class << self
31
+ attr_accessor :verbose_inspect
32
+ end
33
+
34
+ # @param [Object] obj - the proxy target
35
+ def initialize obj
36
+ @is_proxy = true
37
+ @obj = obj
38
+ @uuid ||= ::SecureRandom.uuid
39
+ end
40
+
41
+ # @return [String] the unique ID of the proxy
42
+ def unique_id
43
+ @uuid
44
+ end
45
+
46
+ # @return [String] a string describing the proxy if UniqueProxy.verbose_inspect is not false
47
+ # otherwise calls #inspect on the proxied object
48
+ def inspect
49
+ if UniqueProxy.verbose_inspect
50
+ "#<#{UniqueProxy}::#{@uuid} => #{@obj.inspect}>"
51
+ else
52
+ @obj.inspect
53
+ end
54
+ end
55
+
56
+ # @return [String] a eval-able string to create a new proxy over this proxied object
57
+ def to_s
58
+ if UniqueProxy.verbose_inspect
59
+ "proxy(#{@obj.to_s})"
60
+ else
61
+ @obj.to_s
62
+ end
63
+ end
64
+
65
+ # Is true only if the other object has the same unique_id as self
66
+ def equal? other
67
+ (@uuid == other.unique_id) rescue false
68
+ end
69
+
70
+ # @return [Object] the unproxied target
71
+ def orig
72
+ @obj
73
+ end
74
+
75
+ # Dispatches methods calls to proxy target
76
+ def method_missing(method, *args, &block)
77
+ @obj.__send__ method, *args, &block
78
+ end
79
+
80
+ # @private
81
+ def !
82
+ !@obj
83
+ end
84
+
85
+ # @private
86
+ def == arg
87
+ @obj == arg
88
+ end
89
+
90
+ # @private
91
+ def != arg
92
+ @obj != arg
93
+ end
94
+ end
95
+ end
96
+
@@ -0,0 +1,3 @@
1
+ module OrderTree
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "order_tree/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "order_tree"
7
+ s.version = OrderTree::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Stephen Prater"]
10
+ s.email = ["stephenp@agrussell.com"]
11
+ s.homepage = "http://github.com/stephenprater/order_tree"
12
+ s.summary = %q{An unbalanced tree / nested hash which remember insertion order}
13
+ s.description = %q{Use OrderTree when you need both insertion order access and nested hash path style access}
14
+
15
+ s.rubyforge_project = "order_tree"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency('rspec')
23
+ s.add_development_dependency('simplecov')
24
+ s.add_development_dependency('ruby-prof')
25
+ s.add_development_dependency('ruby-debug19')
26
+ end
@@ -0,0 +1,338 @@
1
+ require 'spec_helper'
2
+
3
+ require 'pp'
4
+
5
+ require 'order_tree'
6
+
7
+ describe OrderTree::UniqueProxy do
8
+ before :all do
9
+ Object.send :include, OrderTree::ProxyOperator
10
+ end
11
+
12
+ it "can tell apart things that are the same" do
13
+ (4 == 4).should eq true
14
+ (4.equal? 4).should eq true #because fixnums are really the same object
15
+ a = OrderTree::UniqueProxy.new(4)
16
+ b = OrderTree::UniqueProxy.new(4)
17
+ c = OrderTree::UniqueProxy.new(5)
18
+ (a == b).should eq true
19
+ (!c).should eq false
20
+ (c != a).should eq true
21
+ (a.equal? b).should eq false
22
+ (a.orig.equal? b.orig).should eq true
23
+ end
24
+
25
+ it "can retrieve the unique id" do
26
+ a = OrderTree::UniqueProxy.new(4)
27
+ b = OrderTree::UniqueProxy.new(4)
28
+ a.unique_id.should_not eq b.unique_id
29
+ end
30
+
31
+ it "can identify a proxy" do
32
+ a = OrderTree::UniqueProxy.new(5)
33
+ proxy(5).should eq proxy(5)
34
+ (proxy(5).equal? proxy(5)).should be_false
35
+ (proxy? a).should be_true
36
+ (proxy? 5).should be_false
37
+ end
38
+
39
+ it "can help you inspect and to_s proxies" do
40
+ proxy(5).to_s.should eq "5"
41
+ proxy(5).inspect.should eq "5"
42
+ OrderTree::UniqueProxy.verbose_inspect = true
43
+ p = proxy(5)
44
+ p.inspect.to_s.should match(/#<UniqueProxy:(.*?)\s=>\s5>/)
45
+ p.to_s.should == "proxy(5)"
46
+ OrderTree::UniqueProxy.verbose_inspect = false
47
+ p2 = eval(p.to_s)
48
+ p2.should eq p
49
+ (p2.equal? p).should be_false
50
+ end
51
+ end
52
+
53
+ describe OrderTree::OrderTree do
54
+ before :all do
55
+ @testhash = {
56
+ :from => {
57
+ :a => {
58
+ :b => 4,
59
+ :c => 4,
60
+ }
61
+ },
62
+ :to => {
63
+ :d => 4,
64
+ :e => 4,
65
+ :to_to => {
66
+ :f => 5,
67
+ :g => 6,
68
+ :h => 7,
69
+ }
70
+ }
71
+ }
72
+
73
+ @testhash_insertion = {
74
+ :to => {
75
+ :d => 4,
76
+ :e => 4,
77
+ :to_to => {
78
+ :f => 5,
79
+ :g => 6,
80
+ :h => 7,
81
+ }
82
+ },
83
+ :from => {
84
+ :a => {
85
+ :b => 4,
86
+ :c => 4,
87
+ }
88
+ }
89
+ }
90
+
91
+ @order = [[:from, :a, :b],
92
+ [:from, :a, :c],
93
+ [:from, :a],
94
+ [:from],
95
+ [:to, :d],
96
+ [:to, :e],
97
+ [:to, :to_to, :f],
98
+ [:to, :to_to, :g],
99
+ [:to, :to_to, :h],
100
+ [:to, :to_to],
101
+ [:to]]
102
+ end
103
+
104
+ it "initializes with a hash" do
105
+ ot = OrderTree::OrderTree.new(@testhash)
106
+ ot2 = OrderTree::OrderTree.new(@testhash_insertion)
107
+ end
108
+
109
+ it "can retrieve based on path or nest" do
110
+ ot = OrderTree::OrderTree.new(@testhash)
111
+ ot2 = OrderTree::OrderTree.new(@testhash_insertion)
112
+ [ot, ot2].map do |t|
113
+ t[:from][:a][:c].should eq 4
114
+ t[:from, :a, :c].should eq 4
115
+ end
116
+ end
117
+
118
+ it "can set based on path or nest" do
119
+ ot = OrderTree::OrderTree.new(@testhash)
120
+ ot2 = OrderTree::OrderTree.new(@testhash_insertion)
121
+ [ot, ot2].map do |t|
122
+ t[:from][:a][:d] = 4
123
+ t[:from, :a, :d].should eq 4
124
+ t[:from, :a, :e] = 6
125
+ t[:from][:a][:e].should eq 6
126
+ end
127
+ end
128
+
129
+ it "remember the order" do
130
+ ot = OrderTree::OrderTree.new(@testhash)
131
+ ot2 = OrderTree::OrderTree.new(@testhash_insertion)
132
+ ot.each_path.to_a.should eq @order
133
+ ot2.each_path.to_a.should_not eq @order
134
+ end
135
+
136
+ it "does not reify the hash on access" do
137
+ ot = OrderTree::OrderTree.new
138
+ lambda do
139
+ ot[:a, :b, :c] = 4
140
+ end.should raise_error NoMethodError
141
+ end
142
+
143
+ it "remembers the order after initialize" do
144
+ ot = OrderTree::OrderTree.new
145
+ order_paths = [[:a],
146
+ [:a, :a1],
147
+ [:a, :a2],
148
+ [:b],
149
+ [:b, :c],
150
+ [:b, :c, :d]]
151
+ order_paths.map do |v|
152
+ if [[:a], [:b], [:b, :c]].include? v
153
+ ot[*v] = {}
154
+ else
155
+ ot[*v] = 4
156
+ end
157
+ end
158
+ ot.each_path.to_a.should eq order_paths
159
+ end
160
+
161
+ it "can retrieve each pair" do
162
+ ot = OrderTree::OrderTree.new @testhash
163
+
164
+ ot.each_pair.with_index do |(p,v),i|
165
+ p.should eq @order[i]
166
+ ot[*p].should eq v
167
+ end
168
+ end
169
+
170
+ it "can overwrite nodes" do
171
+ ot = OrderTree::OrderTree.new @testhash
172
+ ot[:from, :a, :c] = 'overwritten'
173
+
174
+ new_pairs = ot.each_pair.to_a
175
+ p,v = new_pairs.last
176
+ p.should eq [:from, :a, :c]
177
+ v.should eq 'overwritten'
178
+
179
+ ot[:from, :a, :c] = 'overwritten again'
180
+
181
+ p.should eq [:from, :a, :c]
182
+ ot[:from, :a, :c].should eq 'overwritten again'
183
+
184
+ new_pairs = ot.each_pair.to_a
185
+ p,v = new_pairs.first
186
+ p.should eq [:from, :a, :b]
187
+ v.should eq 4
188
+
189
+ p,v = new_pairs[3]
190
+ p.should eq [:to, :d]
191
+ v.should eq 4
192
+ end
193
+
194
+ it "does == comparison" do
195
+ ot = OrderTree::OrderTree.new @testhash
196
+ ot2 = OrderTree::OrderTree.new @testhash
197
+
198
+ ot.first.should eq ot2.first #because underlying objects are compared
199
+ (ot == ot2).should be_true #each order and == on the object
200
+ ot.equal?(ot2).should be_false #we're comparing the proxies here
201
+
202
+ (ot.first.equal? ot2.first).should be_false
203
+ end
204
+
205
+ it "does != comparison" do
206
+ ot = OrderTree::OrderTree.new @testhash
207
+ ot2 = OrderTree::OrderTree.new @testhash_insertion
208
+
209
+ (ot != ot2).should be_true
210
+ end
211
+
212
+ it "does leaf/node equality with contents_equal?" do
213
+ ot = OrderTree::OrderTree.new @testhash
214
+ ot2 = OrderTree::OrderTree.new @testhash_insertion
215
+ (ot.contents_equal? ot2).should be_true
216
+ end
217
+
218
+ it "overwriting a key moves it to the end of the order" do
219
+ ot = OrderTree::OrderTree.new
220
+ ot[:a] = 4
221
+ ot[:b] = 4
222
+ ot.each_path.to_a.should eq [[:a], [:b]]
223
+ ot[:a] = 5
224
+ ot.each_path.to_a.should eq [[:b], [:a]]
225
+ end
226
+
227
+ it "overwriting a nested keys moves to the end of the order" do
228
+ ot = OrderTree::OrderTree.new( {:a => { :b => 4}, :c => 5})
229
+ ot.each_path.to_a.should eq [[:a, :b], [:a], [:c]]
230
+ ot[:a,:b] = 5
231
+ ot.each_path.to_a.should eq [[:a], [:c], [:a, :b]]
232
+ end
233
+
234
+ it "does not double proxy the default" do
235
+ ot = OrderTree::OrderTree.new @testhash
236
+ (proxy? ot.default).should be_true
237
+ (proxy? ot[:foobar]).should be_false
238
+ (proxy? ot[:to, :to_to, :no_key]).should be_false
239
+ end
240
+
241
+ it "returns a default when the key doesn't exist" do
242
+ ot = OrderTree::OrderTree.new @testhash
243
+ ot.default = "foo"
244
+ ot[:to, :foo].should eq "foo"
245
+
246
+ #copies it to nested levels
247
+ ot.default = "bar"
248
+ ot[:to, :foo].should eq "bar"
249
+
250
+ ot[:to, :to_to, :no_key].should eq "bar"
251
+
252
+ # does't matter how deep i look
253
+ ot[:foo, :bar, :foo, :monkey].should eq "bar"
254
+ end
255
+
256
+ it "can find the path for value" do
257
+ ot = OrderTree::OrderTree.new @testhash
258
+ ot.path(7).should eq [:to, :to_to, :h]
259
+ ot.path(8).should be_nil
260
+
261
+ lambda do
262
+ ot.path!(7).should eq [:to, :to_to, :h]
263
+ ot.path!(8)
264
+ end.should raise_error OrderTree::OrderTree::PathNotFound
265
+ end
266
+
267
+ it "can prune the tree" do
268
+ ot = OrderTree::OrderTree.new @testhash
269
+ ot.default = "bob"
270
+ ot.delete :from, :a, :b
271
+ ot[:from, :a, :b].should eq "bob"
272
+
273
+ to_to = ot.at :to, :to_to
274
+ to_to.remove
275
+
276
+ ot[:to, :to_to].should eq "bob"
277
+ end
278
+
279
+ it "can find the path for a node object" do
280
+ ot = OrderTree::OrderTree.new @testhash
281
+ lambda do
282
+ ot.strict_path(7)
283
+ end.should raise_error ArgumentError
284
+
285
+ seven_node = ot.at *ot.path(7)
286
+ ot.strict_path(seven_node).should eq [:to, :to_to, :h]
287
+
288
+ seven_node.remove
289
+ ot.strict_path(seven_node).should be_nil
290
+ #this is the internal call that it uses - it's just here for completeness
291
+ lambda do
292
+ ot.strict_path!(seven_node)
293
+ end.should raise_error OrderTree::OrderTree::PathNotFound
294
+ end
295
+
296
+ it "can run enumerable methods which depend on <=>" do
297
+ ot = OrderTree::OrderTree.new @testhash
298
+ ot.max.should eq ot.last
299
+ ot.min.should eq ot.first
300
+ ot.sort.should eq ot.each_value.to_a
301
+
302
+ # roundabout
303
+ ot.max.should eq ot[*ot.strict_path!(ot.last)]
304
+ ot.min.should eq ot[*ot.strict_path!(ot.first)]
305
+ end
306
+
307
+ it "can tell you about insertion order, natch" do
308
+ ot = OrderTree::OrderTree.new @testhash
309
+ ot2 = OrderTree::OrderTree.new @testhash_insertion
310
+
311
+ (ot.at(:from, :a, :c).before(ot.at(:from, :a, :b))).should be_false
312
+ (ot.at(:from, :a, :b).before(ot.at(:to, :d))).should be_true
313
+
314
+ (ot.at(:from, :a, :c).after(ot.at(:from, :a, :b))).should be_true
315
+ (ot.at(:to, :e).after(ot.at(:from, :a, :b))).should be_true
316
+
317
+ #this probably is only possible if you're doing this.
318
+ (ot.at(:from, :a, :b) <=> ot.at(:from, :a, :b)).should eq 0
319
+ end
320
+
321
+ it "can't compare nodes across trees" do
322
+ ot = OrderTree::OrderTree.new @testhash
323
+ ot2 = OrderTree::OrderTree.new @testhash_insertion
324
+
325
+ lambda do
326
+ ot.at(:from, :a, :c).before(ot2.at(:from, :a, :b))
327
+ end.should raise_error ArgumentError
328
+ end
329
+
330
+ it "can run regular enumerable methods" do
331
+ # i'm not going to try all of these, just the one i know
332
+ # i didn't define.
333
+ ot = OrderTree::OrderTree.new @testhash
334
+ ot.each_cons(3).with_index do |v,idx|
335
+ v.collect { |e| e.path }.should eq @order[idx..idx+2]
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,35 @@
1
+ if ENV["REPORT"] == "1" then
2
+ require 'simplecov'
3
+ require 'ruby-prof'
4
+ require 'ruby-debug'
5
+
6
+ SimpleCov.start do
7
+ add_filter "spec.rb"
8
+ coverage_dir "report/coverage"
9
+ end
10
+
11
+ RSpec.configure do |config|
12
+ config.before :suite do |example|
13
+ STDOUT << '|'
14
+ RubyProf.start
15
+ end
16
+
17
+ config.around :each do |example|
18
+ STDOUT << '.'
19
+ RubyProf.resume do
20
+ example.run
21
+ end
22
+ end
23
+
24
+ config.after :suite do
25
+ result = RubyProf.stop
26
+ result.eliminate_methods!([/RSpec::Matchers#.*?/])
27
+ printer = RubyProf::MultiPrinter.new(result)
28
+ printer.print(:path => 'report/profile', :profile => "profile")
29
+ end
30
+ end
31
+ end
32
+
33
+ # Requires supporting files with custom matchers and macros, etc,
34
+ # in ./support/ and its subdirectories.
35
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: order_tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stephen Prater
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-07-25 00:00:00.000000000 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &3691310 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *3691310
26
+ - !ruby/object:Gem::Dependency
27
+ name: simplecov
28
+ requirement: &3691090 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *3691090
37
+ - !ruby/object:Gem::Dependency
38
+ name: ruby-prof
39
+ requirement: &3690870 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *3690870
48
+ - !ruby/object:Gem::Dependency
49
+ name: ruby-debug19
50
+ requirement: &3690650 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *3690650
59
+ description: Use OrderTree when you need both insertion order access and nested hash
60
+ path style access
61
+ email:
62
+ - stephenp@agrussell.com
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - .DS_Store
68
+ - .gitignore
69
+ - .yardoc/checksums
70
+ - .yardoc/objects/root.dat
71
+ - .yardoc/proxy_types
72
+ - Gemfile
73
+ - README.md
74
+ - Rakefile
75
+ - lib/order_tree.rb
76
+ - lib/order_tree/order_tree.rb
77
+ - lib/order_tree/order_tree_node.rb
78
+ - lib/order_tree/unique_proxy.rb
79
+ - lib/order_tree/version.rb
80
+ - order_tree.gemspec
81
+ - spec/order_tree_spec.rb
82
+ - spec/spec_helper.rb
83
+ has_rdoc: true
84
+ homepage: http://github.com/stephenprater/order_tree
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project: order_tree
104
+ rubygems_version: 1.5.2
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: An unbalanced tree / nested hash which remember insertion order
108
+ test_files:
109
+ - spec/order_tree_spec.rb
110
+ - spec/spec_helper.rb