order_tree 0.0.2

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.
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