sycamore 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ require 'singleton'
2
+
3
+ module Sycamore
4
+ class Path
5
+ ##
6
+ # @api private
7
+ #
8
+ class Root < Path
9
+ include Singleton
10
+
11
+ def initialize
12
+ @parent, @node = nil, nil
13
+ end
14
+
15
+ def up(distance = 1)
16
+ super unless distance.is_a? Integer
17
+ self
18
+ end
19
+
20
+ def root?
21
+ true
22
+ end
23
+
24
+ def length
25
+ 0
26
+ end
27
+
28
+ def join(delimiter = '/')
29
+ ''
30
+ end
31
+
32
+ def to_s
33
+ '#<Path:Root>'
34
+ end
35
+
36
+ def inspect
37
+ '#<Sycamore::Path::Root>'
38
+ end
39
+ end
40
+
41
+ ROOT = Root.instance # @api private
42
+ end
43
+ end
@@ -0,0 +1,4 @@
1
+ require 'sycamore'
2
+
3
+ # optional global shortcut constant for Sycamore::Tree
4
+ STree = Sycamore::Tree
@@ -0,0 +1,1272 @@
1
+ module Sycamore
2
+
3
+ ##
4
+ # A tree data structure as a recursively nested set of {#nodes nodes} of immutable values.
5
+ #
6
+ # See {file:README.md} for a general introduction.
7
+ #
8
+ class Tree
9
+
10
+ include Enumerable
11
+
12
+ # the internal hash representation of this tree
13
+ attr_reader :data
14
+ protected :data
15
+
16
+ ########################################################################
17
+ # @group CQS reflection
18
+ ########################################################################
19
+
20
+ # the names of all command methods, which add elements to a Tree
21
+ ADDITIVE_COMMAND_METHODS = %i[add << replace add_node_with_empty_child] << :[]=
22
+
23
+ # the names of all command methods, which delete elements from a Tree
24
+ DESTRUCTIVE_COMMAND_METHODS = %i[delete >> clear compact replace] << :[]=
25
+
26
+ # the names of all additive command methods, which only add elements from a Tree
27
+ PURE_ADDITIVE_COMMAND_METHODS = ADDITIVE_COMMAND_METHODS - DESTRUCTIVE_COMMAND_METHODS
28
+
29
+ # the names of all destructive command methods, which only delete elements from a Tree
30
+ PURE_DESTRUCTIVE_COMMAND_METHODS = DESTRUCTIVE_COMMAND_METHODS - ADDITIVE_COMMAND_METHODS
31
+
32
+ # the names of all methods, which change the state of a Tree
33
+ COMMAND_METHODS = ADDITIVE_COMMAND_METHODS + DESTRUCTIVE_COMMAND_METHODS +
34
+ %i[freeze]
35
+
36
+ # the names of all query methods, which return a boolean
37
+ PREDICATE_METHODS =
38
+ %i[nothing? absent? existent? present? blank? empty?
39
+ include? include_node? member? key? has_key? include_path? path? >= > < <=
40
+ leaf? leaves? internal? external? flat? nested?
41
+ sleaf? sleaves? strict_leaf? strict_leaves?
42
+ eql? matches? === ==]
43
+
44
+ # the names of all methods, which side-effect-freeze return only a value
45
+ QUERY_METHODS = PREDICATE_METHODS +
46
+ %i[new_child dup hash to_native_object to_h to_s inspect
47
+ node nodes keys child_of child_at dig fetch
48
+ size total_size tsize height
49
+ each each_path paths each_node each_key each_pair] << :[]
50
+
51
+ %i[COMMAND_METHODS QUERY_METHODS PREDICATE_METHODS
52
+ ADDITIVE_COMMAND_METHODS DESTRUCTIVE_COMMAND_METHODS
53
+ PURE_ADDITIVE_COMMAND_METHODS PURE_DESTRUCTIVE_COMMAND_METHODS]
54
+ .each do |method_set|
55
+ define_singleton_method(method_set.downcase) { const_get method_set }
56
+ end
57
+
58
+ ########################################################################
59
+ # @group Construction
60
+ ########################################################################
61
+
62
+ ##
63
+ # Creates a new empty Tree.
64
+ #
65
+ def initialize
66
+ @data = Hash.new
67
+ end
68
+
69
+ ##
70
+ # Creates a new Tree and initializes it with the given data.
71
+ #
72
+ # @param (see #add)
73
+ # @return [Tree]
74
+ #
75
+ # @example
76
+ # Tree[1]
77
+ # Tree[1, 2, 3]
78
+ # Tree[1, 2, 2, 3] # duplicates are ignored, so this results in the same tree as the previous
79
+ # Tree[x: 1, y: 2]
80
+ #
81
+ def self.with(*args)
82
+ tree = new
83
+ tree.add( args.size == 1 ? args.first : args ) unless args.empty?
84
+ tree
85
+ end
86
+
87
+ class << self
88
+ alias from with
89
+ alias [] with
90
+ end
91
+
92
+ ##
93
+ # Creates a new tree meant to be used as a child.
94
+ #
95
+ # This method is used for instantiation of child trees. When you want to a
96
+ # tree with different types child trees, maybe depending on the parent node,
97
+ # you can subclass {Sycamore::Tree} and override this method to your needs.
98
+ # By default it creates trees of the same type as this tree.
99
+ #
100
+ # @param parent_node [Object] of the child tree to be created
101
+ # @return [Tree]
102
+ #
103
+ # @api private
104
+ #
105
+ def new_child(parent_node, *args)
106
+ self.class.new(*args)
107
+ end
108
+
109
+
110
+ ########################################################################
111
+ # @group Absence and Nothing predicates
112
+ ########################################################################
113
+
114
+ ##
115
+ # Checks if this is the {Nothing} tree.
116
+ #
117
+ # @return [Boolean]
118
+ #
119
+ def nothing?
120
+ false
121
+ end
122
+
123
+ ##
124
+ # Checks if this is an unresolved {Absence} or {Nothing}.
125
+ #
126
+ # @return [Boolean]
127
+ #
128
+ def absent?
129
+ false
130
+ end
131
+
132
+ ##
133
+ # Checks if this is not an {Absence} or {Nothing}.
134
+ #
135
+ # @return [Boolean]
136
+ #
137
+ def existent?
138
+ not absent?
139
+ end
140
+
141
+ ##
142
+ # Checks if this is not {#blank? blank}, i.e. {#empty? empty}.
143
+ #
144
+ # @note This is not the negation of {#absent?}, since this would result in a
145
+ # different behaviour than {http://api.rubyonrails.org/classes/Object.html#method-i-present-3F ActiveSupports present?}
146
+ # method. For the negation of {#absent?}, see {#existent?}.
147
+ #
148
+ # @return [Boolean]
149
+ #
150
+ def present?
151
+ not blank?
152
+ end
153
+
154
+
155
+ ########################################################################
156
+ # @group Element access
157
+ ########################################################################
158
+
159
+ #####################
160
+ # command methods #
161
+ #####################
162
+
163
+ ##
164
+ # Adds nodes or a tree structure to this tree.
165
+ #
166
+ # @overload add(node)
167
+ # adds a single node
168
+ # @param node [Object]
169
+ #
170
+ # @overload add(node_collection)
171
+ # adds multiple nodes
172
+ # @param node_collection [Enumerable]
173
+ #
174
+ # @overload add(tree_structure)
175
+ # adds a tree structure of nodes
176
+ # @param tree_structure [Hash, Tree]
177
+ #
178
+ # @return +self+ as a proper command method
179
+ #
180
+ # @raise [InvalidNode] when given a nested node set
181
+ #
182
+ # @example
183
+ # tree = Tree.new
184
+ # tree.add :foo
185
+ # tree.add [:bar, :baz]
186
+ # tree.add [:node, [:nested, :values]] # => raise Sycamore::InvalidNode, "[:nested, :values] is not a valid tree node"
187
+ # tree.add foo: 1, bar: {baz: 2}
188
+ # tree.add foo: [:node, [:nested, :values]] # => raise Sycamore::InvalidNode, "[:nested, :values] is not a valid tree node"
189
+ #
190
+ # tree = Tree.new
191
+ # tree[:foo][:bar] << :baz
192
+ # tree[:foo] << { bar: 1, qux: 2 }
193
+ # tree.to_h # => {:foo=>{:bar=>[:baz, 1], :qux=>2}}
194
+ #
195
+ # @todo support Paths
196
+ #
197
+ def add(nodes_or_tree)
198
+ case
199
+ when Tree.like?(nodes_or_tree) then add_tree(nodes_or_tree)
200
+ when nodes_or_tree.is_a?(Enumerable) then add_nodes(nodes_or_tree)
201
+ else add_node(nodes_or_tree)
202
+ end
203
+
204
+ self
205
+ end
206
+
207
+ alias << add
208
+
209
+ private def add_node(node)
210
+ return self if node.equal? Nothing
211
+ return add_tree(node) if Tree.like? node
212
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
213
+
214
+ @data[node] ||= Nothing
215
+
216
+ self
217
+ end
218
+
219
+ private def add_nodes(nodes)
220
+ nodes.each { |node| add_node(node) }
221
+
222
+ self
223
+ end
224
+
225
+ ##
226
+ # Adds a node with an empty child to this tree.
227
+ #
228
+ # @return +self+ as a proper command method
229
+ #
230
+ # @raise [InvalidNode]
231
+ #
232
+ # @api private
233
+ #
234
+ def add_node_with_empty_child(node)
235
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
236
+
237
+ if @data.fetch(node, Nothing).nothing?
238
+ @data[node] = new_child(node)
239
+ end
240
+
241
+ self
242
+ end
243
+
244
+ private def add_child(node, children)
245
+ return add_node(node) if Nothing.like?(children)
246
+
247
+ add_node_with_empty_child(node)
248
+ @data[node] << children
249
+
250
+ self
251
+ end
252
+
253
+ private def add_tree(tree)
254
+ tree.each { |node, child| add_child(node, child) }
255
+
256
+ self
257
+ end
258
+
259
+ ##
260
+ # Remove nodes or a tree structure from this tree.
261
+ #
262
+ # If a given node is in the {#nodes} set, it gets deleted, otherwise it is
263
+ # silently ignored.
264
+ #
265
+ # @overload delete(node)
266
+ # deletes a single node
267
+ # @param node [Object]
268
+ #
269
+ # @overload delete(node_collection)
270
+ # deletes multiple nodes
271
+ # @param node_collection [Enumerable]
272
+ #
273
+ # @overload delete(tree_structure)
274
+ # deletes a tree structure of nodes
275
+ # @param tree_structure [Hash, Tree]
276
+ #
277
+ # @return +self+ as a proper command method
278
+ #
279
+ # @raise [InvalidNode] when given a nested node set
280
+ #
281
+ # @example
282
+ # tree = Tree[ "a" => 100, "b" => 200, "c" => 300, "d" => {foo: [:bar, :baz]} ]
283
+ # tree.delete "a"
284
+ # tree.to_h # => {"b" => 200, "c" => 300, "d" => {foo: [:bar, :baz]}}
285
+ # tree.delete ["a", "b", "c"]
286
+ # tree.to_h # => {"d" => {foo: [:bar, :baz]}}
287
+ # tree.delete "d" => {foo: :bar}
288
+ # tree.to_h # => {"d" => {foo: :baz}}
289
+ # tree.delete "d" => {foo: :baz}
290
+ # tree.to_h # => {}
291
+ #
292
+ # @todo differentiate a greedy and a non-greedy variant
293
+ # @todo support Paths
294
+ #
295
+ def delete(nodes_or_tree)
296
+ case
297
+ when Tree.like?(nodes_or_tree) then delete_tree(nodes_or_tree)
298
+ when nodes_or_tree.is_a?(Enumerable) then delete_nodes(nodes_or_tree)
299
+ else delete_node(nodes_or_tree)
300
+ end
301
+
302
+ self
303
+ end
304
+
305
+ alias >> delete
306
+
307
+ private def delete_node(node)
308
+ return delete_tree(node) if Tree.like? node
309
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
310
+
311
+ @data.delete(node)
312
+
313
+ self
314
+ end
315
+
316
+ private def delete_nodes(nodes)
317
+ nodes.each { |node| delete_node(node) }
318
+
319
+ self
320
+ end
321
+
322
+ private def delete_tree(tree)
323
+ tree.each do |node, child|
324
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
325
+ next unless include? node
326
+ if Nothing.like?(child) or (child.respond_to?(:empty?) and child.empty?)
327
+ delete_node node
328
+ else
329
+ child_of(node).tap do |this_child|
330
+ this_child.delete child
331
+ delete_node(node) if this_child.empty?
332
+ end
333
+ end
334
+ end
335
+
336
+ self
337
+ end
338
+
339
+ ##
340
+ # Replaces the contents of this tree.
341
+ #
342
+ # @overload replace(node)
343
+ # Replaces the contents of this tree with a single node.
344
+ # @param node [Object]
345
+ #
346
+ # @overload replace(node_collection)
347
+ # Replaces the contents of this tree with multiple nodes.
348
+ # @param node_collection [Enumerable]
349
+ #
350
+ # @overload replace(tree_structure)
351
+ # Replaces the contents of this tree with a tree structure of nodes.
352
+ # @param tree_structure [Hash, Tree]
353
+ #
354
+ # @return +self+ as a proper command method
355
+ #
356
+ # @raise [InvalidNode] when given a nested node set
357
+ #
358
+ # @example
359
+ # tree = Tree[ "a" => 100, "b" => 200, "d" => {foo: [:bar, :baz]} ]
360
+ # tree.replace(new: :content)
361
+ # tree.to_h # => {new: :content}
362
+ #
363
+ def replace(nodes_or_tree)
364
+ clear.add(nodes_or_tree)
365
+ end
366
+
367
+ ##
368
+ # Replaces the contents of a child tree.
369
+ #
370
+ # As this is just a call of {#replace} on the child tree, you can assign
371
+ # content to not existing child trees. Just like {#child_at} you can
372
+ # reference a deeper node with a path of nodes.
373
+ #
374
+ # Note that even if you assign a {Sycamore::Tree} directly the given tree
375
+ # will not become part of this tree by reference.
376
+ #
377
+ # @overload []=(*path, node)
378
+ # Replaces the contents of the child at the given path with a single node.
379
+ # @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object
380
+ # @param node [Object]
381
+ #
382
+ # @overload []=(*path, node_collection)
383
+ # Replaces the contents of the child at the given path with multiple nodes.
384
+ # @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object
385
+ # @param node_collection [Enumerable]
386
+ #
387
+ # @overload []=(*path, tree_structure)
388
+ # Replaces the contents of the child at the given path with a tree structure of nodes.
389
+ # @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object
390
+ # @param tree_structure [Hash, Tree]
391
+ #
392
+ # @return the rvalue as for any Ruby assignment
393
+ #
394
+ # @raise [InvalidNode] when given a nested node set
395
+ #
396
+ # @example
397
+ # tree = Tree[:foo]
398
+ # tree[:foo] = :bar
399
+ # tree.to_h # => {:foo => :bar}
400
+ # tree[:foo] = :baz
401
+ # tree.to_h # => {:foo => :baz}
402
+ # tree[1][2][3] = 4
403
+ # tree[1, 2, 3] = 4
404
+ # tree.to_h # => {:foo => :baz, 1 => {2 => {3 => 4}}}
405
+ # tree[1] = tree[:foo]
406
+ # tree.to_h # => {:foo => :baz, 1 => :baz}
407
+ # tree[:foo] << :bar
408
+ # tree.to_h # => {:foo => [:baz, :bar], 1 => :baz}
409
+ #
410
+ def []=(*args)
411
+ path, nodes_or_tree = args[0..-2], args[-1]
412
+ raise ArgumentError, 'wrong number of arguments (given 1, expected 2)' if path.empty?
413
+
414
+ child_at(*path).replace(nodes_or_tree)
415
+ end
416
+
417
+ ##
418
+ # Deletes all nodes and their children.
419
+ #
420
+ # @return +self+ as a proper command method
421
+ #
422
+ # @example
423
+ # tree = Tree[1, 2, 3]
424
+ # tree.size # => 3
425
+ # tree.clear
426
+ # tree.size # => 0
427
+ #
428
+ def clear
429
+ @data.clear
430
+
431
+ self
432
+ end
433
+
434
+ ##
435
+ # Deletes all empty child trees recursively.
436
+ #
437
+ # @return +self+ as a proper command method
438
+ #
439
+ # @example
440
+ # tree = Tree[foo: {bar: :baz}]
441
+ # tree[:foo, :bar].clear
442
+ # tree.to_h # => {foo: {bar: []}}
443
+ # tree.compact
444
+ # tree.to_h # => {foo: :bar}
445
+ #
446
+ def compact
447
+ @data.each do |node, child| case
448
+ when child.nothing? then next
449
+ when child.empty? then @data[node] = Nothing
450
+ else child.compact
451
+ end
452
+ end
453
+
454
+ self
455
+ end
456
+
457
+
458
+ #####################
459
+ # query methods #
460
+ #####################
461
+
462
+ ##
463
+ # The nodes of this tree (without their children).
464
+ #
465
+ # @return [Array<Object>]
466
+ #
467
+ # @example
468
+ # tree = Tree[foo: [:bar, :baz]]
469
+ # tree.nodes # => [:foo]
470
+ # tree[:foo].nodes # => [:bar, :baz]
471
+ #
472
+ def nodes
473
+ @data.keys
474
+ end
475
+
476
+ alias keys nodes # Hash compatibility
477
+
478
+
479
+ ##
480
+ # The only node of this tree or an exception, if more {#nodes nodes} present.
481
+ #
482
+ # @return [Object, nil] the single present node or +nil+, if no nodes present
483
+ #
484
+ # @raise [NonUniqueNodeSet] if more than one node present
485
+ #
486
+ # @example
487
+ # tree = Tree[foo: 1, bar: [2,3]]
488
+ # tree[:foo].node # => 1
489
+ # tree[:baz].node # => nil
490
+ # tree[:bar].node # => raise Sycamore::NonUniqueNodeSet, "multiple nodes present: [2, 3]"
491
+ #
492
+ def node
493
+ nodes = self.nodes
494
+ raise NonUniqueNodeSet, "multiple nodes present: #{nodes}" if nodes.size > 1
495
+
496
+ nodes.first
497
+ end
498
+
499
+ ##
500
+ # The child tree of a node.
501
+ #
502
+ # When a child to the given node is not a present, an {Absence} object
503
+ # representing the missing tree is returned.
504
+ #
505
+ # @param node [Object]
506
+ # @return [Tree, Absence] the child tree of a node if present, otherwise an {Absence}
507
+ #
508
+ # @raise [InvalidNode] when given an +Enumerable+
509
+ #
510
+ # @example
511
+ # tree = Tree[foo: 1]
512
+ # tree.child_of(:foo).inspect # "#<Sycamore::Tree:0x3fea48dd0e74 {1=>nil}>"
513
+ # tree.child_of(:bar).inspect # "absent child of node :bar in #<Sycamore::Tree:0x3fea48dd0f3c {:foo=>1}>"
514
+ #
515
+ # @todo Should we differentiate the case of a leaf and a not present node? How?
516
+ #
517
+ def child_of(node)
518
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
519
+
520
+ Nothing.like?(child = @data[node]) ? Absence.at(self, node) : child
521
+ end
522
+
523
+ ##
524
+ # The child tree of a node at a path.
525
+ #
526
+ # When a child at the given node path is not a present, an {Absence} object
527
+ # representing the missing tree is returned.
528
+ #
529
+ # @overload child_at(*nodes)
530
+ # @param nodes [Array<Object>] a path as a sequence of nodes
531
+ #
532
+ # @overload child_at(path)
533
+ # @param path [Path] a path as a {Sycamore::Path} object
534
+ #
535
+ # @return [Tree, Absence] the child tree at the given path if present, otherwise an {Absence}
536
+ #
537
+ # @example
538
+ # tree = Tree[foo: {bar: 1}]
539
+ # tree[:foo].inspect # "#<Sycamore::Tree:0x3fea48e24f10 {:bar=>1}>"
540
+ # tree[:foo, :bar].inspect # "#<Sycamore::Tree:0x3fea48e24ed4 {1=>nil}>"
541
+ # tree[:foo, :baz].inspect # "absent child of node :baz in #<Sycamore::Tree:0x3fea48e24f10 {:bar=>1}>"
542
+ #
543
+ # @todo Should we differentiate the case of a leaf and a not present node? How?
544
+ #
545
+ def child_at(*path)
546
+ first = path.first
547
+ case path.length
548
+ when 0
549
+ raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)'
550
+ when 1
551
+ if first.is_a? Enumerable
552
+ child_at(*first)
553
+ else
554
+ child_of(*path)
555
+ end
556
+ else
557
+ child_of(first).child_at(*path[1..-1])
558
+ end
559
+ end
560
+
561
+ alias [] child_at
562
+ alias dig child_at # Hash compatibility
563
+
564
+ ##
565
+ # The child tree of a node.
566
+ #
567
+ # If the node can’t be found or has no child tree, there are several options:
568
+ # - With no other arguments, it will raise a +KeyError+ exception when the
569
+ # node can’t be found or a {ChildError} exception (which is a subclass of
570
+ # +KeyError+) when the node has no child tree
571
+ # - if +default+ is given, then that will be returned;
572
+ # - if the optional code block is specified, then that will be run and its result returned.
573
+ #
574
+ # @param node [Object]
575
+ # @param default [Object] optional
576
+ # @return [Tree, default]
577
+ #
578
+ # @raise [InvalidNode] when given an +Enumerable+ as node
579
+ # @raise [KeyError] when the given +node+ can't be found
580
+ # @raise [ChildError] when no child for the given +node+ present
581
+ #
582
+ # @example
583
+ # tree = Tree[x: 1, y: nil]
584
+ # tree.fetch(:x) # #<Sycamore::Tree:0x3fc798a63854(1)>
585
+ # tree.fetch(:y) # => raise Sycamore::ChildError, "node y has no child tree"
586
+ # tree.fetch(:z) # => raise KeyError, "key not found: :z"
587
+ # tree.fetch(:z, :default) # => :default
588
+ # tree.fetch(:y, :default) # => :default
589
+ # tree.fetch(:z) { :default } # => :default
590
+ #
591
+ # @todo Should we differentiate the case of a leaf and a not present node? How?
592
+ #
593
+ def fetch(node, *default, &block)
594
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
595
+
596
+ child = @data.fetch(node, *default, &block)
597
+ if child.equal? Nothing
598
+ child = case
599
+ when block_given? then yield
600
+ when !default.empty? then default.first
601
+ else raise ChildError, "node #{node} has no child tree"
602
+ end
603
+ end
604
+
605
+ child
606
+ end
607
+
608
+ ##
609
+ # Iterates over all {#nodes nodes} of this tree.
610
+ #
611
+ # Note that does not include the nodes of the child trees.
612
+ #
613
+ # @overload each_node
614
+ # Iterates over all {#nodes nodes} of this tree.
615
+ # @yield [Object] each node
616
+ # @return [Tree]
617
+ #
618
+ # @overload each_node
619
+ # Returns an enumerator over all {#nodes nodes} of this tree.
620
+ # @return [Enumerator<Object>]
621
+ #
622
+ # @example
623
+ # tree = Tree[ "a" => 100, "b" => 200 ]
624
+ # tree.each_node {|node| puts node }
625
+ #
626
+ # > a
627
+ # > b
628
+ #
629
+ def each_node(&block)
630
+ return enum_for(__callee__) unless block_given?
631
+
632
+ @data.each_key(&block)
633
+
634
+ self
635
+ end
636
+
637
+ alias each_key each_node # Hash compatibility
638
+
639
+ ##
640
+ # Iterates over all {#nodes nodes} and their child trees.
641
+ #
642
+ # @overload each_pair
643
+ # Iterates over all {#nodes nodes} and their child trees.
644
+ # @yield [Object, Tree] each node-child pair
645
+ # @return [Tree] +self+
646
+ #
647
+ # @overload each_pair
648
+ # Returns an enumerator over all {#nodes nodes} and their child trees.
649
+ # @return [Enumerator<Array(Object, Tree)>]
650
+ #
651
+ # @example
652
+ # tree = Tree[ "a" => 100, "b" => nil ]
653
+ # tree.each_pair {|node, child| puts "#{node} => #{child}" }
654
+ #
655
+ # > a => #<Tree[ 100 ]>
656
+ # > b => #<Tree: Nothing>
657
+ #
658
+ def each_pair(&block)
659
+ return enum_for(__callee__) unless block_given?
660
+
661
+ @data.each_pair(&block)
662
+
663
+ self
664
+ end
665
+
666
+ alias each each_pair
667
+
668
+ ##
669
+ # Iterates over the {Path paths} to all leaves of this tree.
670
+ #
671
+ # @overload each_path
672
+ # Iterates over the {Path paths} to all leaves of this tree.
673
+ # @yieldparam [Path] path
674
+ # @return [Tree] +self+
675
+ #
676
+ # @overload each_path
677
+ # Returns an enumerator over the {Path paths} to all leaves of this tree.
678
+ # @return [Enumerator<Path>]
679
+ #
680
+ # @example
681
+ # tree = Tree[ "a" => 100, "b" => { foo: [:bar, :baz] } ]
682
+ # tree.each_path { |path| puts path }
683
+ #
684
+ # > #<Path: /a/100>
685
+ # > #<Path: /b/foo/bar>
686
+ # > #<Path: /b/foo/baz>
687
+ #
688
+ def each_path(with_ancestor: Path::ROOT, &block)
689
+ return enum_for(__callee__) unless block_given?
690
+
691
+ each do |node, child|
692
+ if child.empty?
693
+ yield Path[with_ancestor, node]
694
+ else
695
+ child.each_path(with_ancestor: with_ancestor.branch(node), &block)
696
+ end
697
+ end
698
+
699
+ self
700
+ end
701
+
702
+ alias paths each_path
703
+
704
+ ##
705
+ # Checks if a path of nodes exists in this tree.
706
+ #
707
+ # @param args [Array<Object>, Path] a splat of nodes, an array of nodes or a {Path} object
708
+ # @return [Boolean]
709
+ #
710
+ # @example
711
+ # tree = Tree[ "a" => 100, "b" => 200, "c" => { foo: [:bar, :baz] } ]
712
+ # tree.include_path? "a", 200 # => false
713
+ # tree.include_path? "c", :foo, :bar # => true
714
+ # tree.include_path? ["c", :foo, :bar] # => true
715
+ # tree.include_path? Sycamore::Path["c", :foo, :bar] # => true
716
+ #
717
+ def include_path?(*args)
718
+ raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)' if args.count == 0
719
+ first = args.first
720
+ if first.is_a? Enumerable
721
+ return include_path?(*first) if args.count == 1
722
+ raise InvalidNode, "#{first} is not a valid tree node"
723
+ end
724
+
725
+ if args.count == 1
726
+ include? first
727
+ else
728
+ include?(first) and child_of(first).include_path?(args[1..-1])
729
+ end
730
+ end
731
+
732
+ alias path? include_path?
733
+
734
+ ##
735
+ # Checks if a node exists in the {#nodes nodes} set of this tree.
736
+ #
737
+ # @param node [Object]
738
+ # @return [Boolean]
739
+ #
740
+ # @example
741
+ # Tree[1,2,3].include_node? 3 # => true
742
+ # Tree[1 => 2].include_node? 2 # => false
743
+ #
744
+ def include_node?(node)
745
+ @data.include?(node)
746
+ end
747
+
748
+ alias member? include_node? # Hash compatibility
749
+ alias has_key? include_node? # Hash compatibility
750
+ alias key? include_node? # Hash compatibility
751
+
752
+ ##
753
+ # Checks if some nodes or a full tree-like structure is included in this tree.
754
+ #
755
+ # @param elements [Object, Array, Tree, Hash]
756
+ # @return [Boolean]
757
+ #
758
+ # @example
759
+ # tree = Tree[ "a" => 100, "b" => 200, "c" => { foo: [:bar, :baz] } ]
760
+ # tree.include?("a") # => true
761
+ # tree.include?(:foo) # => false
762
+ # tree.include?(["a", "b"]) # => true
763
+ # tree.include?("c" => {foo: :bar}) # => true
764
+ # tree.include?("a", "b" => 200) # => true
765
+ #
766
+ def include?(*elements)
767
+ raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)' if
768
+ elements.size == 0
769
+ return elements.all? { |element| include? element } if
770
+ elements.size > 1
771
+
772
+ elements = elements.first
773
+ case
774
+ when Tree.like?(elements)
775
+ elements.all? do |node, child|
776
+ include_node?(node) and ( child.nil? or child.equal?(Nothing) or
777
+ self.child_of(node).include?(child) )
778
+ end
779
+ when elements.is_a?(Path)
780
+ include_path? elements
781
+ when elements.is_a?(Enumerable)
782
+ elements.all? { |element| include_node? element }
783
+ else
784
+ include_node? elements
785
+ end
786
+ end
787
+
788
+ ##
789
+ # The number of {#nodes nodes} in this tree.
790
+ #
791
+ # Note, this does not count the nodes in the child trees.
792
+ #
793
+ # @return [Fixnum]
794
+ #
795
+ # @example
796
+ # tree = Tree[ "d" => 100, "a" => 200, "v" => 300, "e" => [400, 500] ]
797
+ # tree.size # => 4
798
+ # tree.delete("a")
799
+ # tree.size # => 3
800
+ # tree["e"].size # => 2
801
+ #
802
+ def size
803
+ @data.size
804
+ end
805
+
806
+ ##
807
+ # The number of {#nodes nodes} in this tree and all of their children.
808
+ #
809
+ # @return [Fixnum]
810
+ #
811
+ # @example
812
+ # tree = Tree[ "d" => 100, "a" => 200, "v" => 300, "e" => [400, 500] ]
813
+ # tree.total_size # => 9
814
+ # tree.delete("a")
815
+ # tree.total_size # => 7
816
+ # tree["e"].total_size # => 2
817
+ #
818
+ def total_size
819
+ total = size
820
+ @data.each { |_, child| total += child.total_size }
821
+ total
822
+ end
823
+
824
+ alias tsize total_size
825
+
826
+ ##
827
+ # The length of the longest path of this tree.
828
+ #
829
+ # @return [Fixnum]
830
+ #
831
+ # @example
832
+ # tree = Tree[a: 1, b: {2 => 3}]
833
+ # tree.height # => 3
834
+ #
835
+ def height
836
+ return 0 if empty?
837
+ paths.map(&:length).max
838
+ end
839
+
840
+ ##
841
+ # Checks if this tree is empty.
842
+ #
843
+ # @return [Boolean]
844
+ #
845
+ # @example
846
+ # Tree.new.empty? # => true
847
+ # Tree[a: 1].empty? # => false
848
+ #
849
+ def empty?
850
+ @data.empty?
851
+ end
852
+
853
+ alias blank? empty?
854
+
855
+ ##
856
+ # Checks if the given node has no children.
857
+ #
858
+ # @param node [Object]
859
+ # @return [Boolean]
860
+ #
861
+ # @example
862
+ # tree = Tree[x: 1, y: [], z: nil]
863
+ # tree.leaf?(:x) # => false
864
+ # tree.leaf?(:y) # => true
865
+ # tree.leaf?(:z) # => true
866
+ #
867
+ def leaf?(node)
868
+ include_node?(node) && child_of(node).empty?
869
+ end
870
+
871
+ ##
872
+ # Checks if the given node has no children, even not an empty child tree.
873
+ #
874
+ # @param node [Object]
875
+ # @return [Boolean]
876
+ #
877
+ # @example
878
+ # tree = Tree[x: 1, y: [], z: nil]
879
+ # tree.strict_leaf?(:x) # => false
880
+ # tree.strict_leaf?(:y) # => false
881
+ # tree.strict_leaf?(:z) # => true
882
+ #
883
+ def strict_leaf?(node)
884
+ include_node?(node) && child_of(node).absent?
885
+ end
886
+
887
+ alias sleaf? strict_leaf?
888
+
889
+ ##
890
+ # Checks if all given nodes or that of the tree have no children, even not an empty child tree.
891
+ #
892
+ # @overload strict_leaves?()
893
+ # Returns if all {#nodes} of this tree have no children, even not an empty child tree.
894
+ # @return [Boolean]
895
+ #
896
+ # @overload strict_leaves?(*nodes)
897
+ # Checks if all of the given nodes have no children, even not an empty child tree.
898
+ # @param nodes [Array<Object>] splat of nodes
899
+ # @return [Boolean]
900
+ #
901
+ # @example
902
+ # Tree[1,2,3].strict_leaves? # => true
903
+ # tree = Tree[a: :foo, b: :bar, c: []]
904
+ # tree.strict_leaves? # => false
905
+ # tree.strict_leaves?(:x, :y) # => false
906
+ # tree.strict_leaves?(:y, :z) # => false
907
+ #
908
+ def strict_leaves?(*nodes)
909
+ nodes = self.nodes if nodes.empty?
910
+
911
+ nodes.all? { |node| strict_leaf?(node) }
912
+ end
913
+
914
+ alias sleaves? strict_leaves?
915
+
916
+ ##
917
+ # Checks if all given nodes or that of the tree have no children.
918
+ #
919
+ # @overload external?
920
+ # Checks if all {#nodes} of this tree have no children.
921
+ # @return [Boolean]
922
+ #
923
+ # @overload external?(*nodes)
924
+ # Checks if all of the given nodes have no children.
925
+ # @param nodes [Array<Object>] splat of nodes
926
+ # @return [Boolean]
927
+ #
928
+ # @example
929
+ # Tree[1,2,3].leaves? # => true
930
+ # tree = Tree[x: 1, y: [], z: nil]
931
+ # tree.leaves? # => false
932
+ # tree.leaves?(:x, :y) # => false
933
+ # tree.leaves?(:y, :z) # => true
934
+ #
935
+ def external?(*nodes)
936
+ nodes = self.nodes if nodes.empty?
937
+
938
+ nodes.all? { |node| leaf?(node) }
939
+ end
940
+
941
+ alias leaves? external?
942
+ alias flat? external?
943
+
944
+ ##
945
+ # Checks if all given nodes or that of the tree have children.
946
+ #
947
+ # @overload internal?
948
+ # Checks if all {#nodes} of this tree have children.
949
+ # @return [Boolean]
950
+ #
951
+ # @overload internal?(*nodes)
952
+ # Checks if all of the given nodes have children.
953
+ # @param nodes [Array<Object>] splat of nodes
954
+ # @return [Boolean]
955
+ #
956
+ # @example
957
+ # Tree[x: 1, y: 2].internal? # => true
958
+ # tree = Tree[x: 1, y: [], z: nil]
959
+ # tree.internal? # => false
960
+ # tree.internal?(:x, :y) # => false
961
+ # tree.internal?(:y, :z) # => false
962
+ # tree.internal?(:x) # => true
963
+ #
964
+ # @todo Does it make sense to support the no arguments variant here and with this semantics?
965
+ # One would expect it to be the negation of #external? without arguments.
966
+ #
967
+ def internal?(*nodes)
968
+ return false if self.empty?
969
+ nodes = self.nodes if nodes.empty?
970
+
971
+ nodes.all? { |node| not leaf?(node) and include_node?(node) }
972
+ end
973
+
974
+ alias nested? internal?
975
+
976
+
977
+ ########################################################################
978
+ # @group Comparison
979
+ ########################################################################
980
+
981
+ ##
982
+ # A hash code of this tree.
983
+ #
984
+ # @return [Fixnum]
985
+ #
986
+ def hash
987
+ @data.hash ^ self.class.hash
988
+ end
989
+
990
+ ##
991
+ # Checks if this tree has the same content as another tree.
992
+ #
993
+ # @param other [Object]
994
+ # @return [Boolean]
995
+ #
996
+ # @example
997
+ # tree1 = Tree[a: 1, b: 2]
998
+ # tree2 = Tree[a: 1, b: 2, c: 3]
999
+ # tree3 = Tree[b: 2, a: 1]
1000
+ # tree4 = Tree[a: 1, b: {2 => []}]
1001
+ # tree1.eql? tree2 # => false
1002
+ # tree1.eql? tree3 # => true
1003
+ # tree1.eql? tree4 # => false
1004
+ #
1005
+ def eql?(other)
1006
+ (other.instance_of?(self.class) and @data.eql?(other.data)) or
1007
+ (other.instance_of?(Absence) and other.eql?(self))
1008
+ end
1009
+
1010
+ ##
1011
+ # Checks if this tree has the same content as another tree, but ignores empty child trees.
1012
+ #
1013
+ # @param other [Object]
1014
+ # @return [Boolean]
1015
+ #
1016
+ # @example
1017
+ # tree1 = Tree[a: 1, b: 2]
1018
+ # tree2 = Tree[a: 1, b: 2, c: 3]
1019
+ # tree3 = Tree[b: 2, a: 1]
1020
+ # tree4 = Tree[a: 1, b: {2 => []}]
1021
+ # tree1 == tree2 # => false
1022
+ # tree1 == tree3 # => true
1023
+ # tree1 == tree4 # => true
1024
+ #
1025
+ def ==(other)
1026
+ (other.instance_of?(self.class) and size == other.size and
1027
+ all? { |node, child| other.include?(node) and other[node] == child }) or
1028
+ ((other.equal?(Nothing) or other.instance_of?(Absence)) and
1029
+ other == self)
1030
+ end
1031
+
1032
+ ##
1033
+ # Checks if this tree is a subtree of another tree.
1034
+ #
1035
+ # @param other [Object]
1036
+ # @return [Boolean]
1037
+ #
1038
+ # @example
1039
+ # tree1 = Tree[a: 1, b: 2]
1040
+ # tree2 = Tree[a: 1, b: 2, c: 3]
1041
+ # tree1 < tree2 # => true
1042
+ # tree2 < tree1 # => false
1043
+ # tree1 < tree1 # => false
1044
+ #
1045
+ def <(other)
1046
+ (other.is_a?(Tree) or other.is_a?(Absence)) and other.include?(self) and self != other
1047
+ end
1048
+
1049
+ ##
1050
+ # Checks if this tree is a subtree or equal to another tree.
1051
+ #
1052
+ # @param other [Object]
1053
+ # @return [Boolean]
1054
+ #
1055
+ # @example
1056
+ # tree1 = Tree[a: 1, b: 2]
1057
+ # tree2 = Tree[a: 1, b: 2, c: 3]
1058
+ # tree1 <= tree2 # => true
1059
+ # tree2 <= tree1 # => false
1060
+ # tree1 <= tree1 # => true
1061
+ #
1062
+ def <=(other)
1063
+ (other.is_a?(Tree) or other.is_a?(Absence)) and other.include?(self)
1064
+ end
1065
+
1066
+ ##
1067
+ # Checks if another tree is a subtree or equal to this tree.
1068
+ #
1069
+ # @param other [Object]
1070
+ # @return [Boolean]
1071
+ #
1072
+ # @example
1073
+ # tree1 = Tree[a: 1, b: 2]
1074
+ # tree2 = Tree[a: 1, b: 2, c: 3]
1075
+ # tree1 >= tree2 # => false
1076
+ # tree2 >= tree1 # => true
1077
+ # tree1 >= tree1 # => true
1078
+ #
1079
+ def >=(other)
1080
+ (other.is_a?(Tree) or other.is_a?(Absence)) and self.include?(other)
1081
+ end
1082
+
1083
+ ##
1084
+ # Checks if another tree is a subtree or equal to this tree.
1085
+ #
1086
+ # @param other [Object]
1087
+ # @return [Boolean]
1088
+ #
1089
+ # @example
1090
+ # tree1 = Tree[a: 1, b: 2]
1091
+ # tree2 = Tree[a: 1, b: 2, c: 3]
1092
+ # tree1 > tree2 # => false
1093
+ # tree2 > tree1 # => true
1094
+ # tree1 > tree1 # => false
1095
+ #
1096
+ def >(other)
1097
+ (other.is_a?(Tree) or other.is_a?(Absence)) and self.include?(other) and self != other
1098
+ end
1099
+
1100
+ ##
1101
+ # Checks if another object matches this tree structurally and by content.
1102
+ #
1103
+ # @param other [Object]
1104
+ # @return [Boolean]
1105
+ #
1106
+ # @example
1107
+ # Tree[foo: :bar] === Hash[foo: :bar] # => true
1108
+ # Tree[1, 2, 3] === Array[1, 2, 3] # => true
1109
+ # Tree[42] === 42 # => true
1110
+ #
1111
+ # @todo This should probably apply a less strict equivalence comparison on the nodes.
1112
+ # Problem: Requires a solution which doesn't use +Hash#include?+.
1113
+ #
1114
+ def matches?(other)
1115
+ case
1116
+ when Tree.like?(other) then matches_tree?(other)
1117
+ when other.is_a?(Enumerable) then matches_enumerable?(other)
1118
+ else matches_atom?(other)
1119
+ end
1120
+ end
1121
+
1122
+ alias === matches?
1123
+
1124
+ private def matches_atom?(other)
1125
+ not other.nil? and (size == 1 and nodes.first == other and leaf? other)
1126
+ end
1127
+
1128
+ private def matches_enumerable?(other)
1129
+ size == other.size and
1130
+ all? { |node, child| child.empty? and other.include?(node) }
1131
+ end
1132
+
1133
+ private def matches_tree?(other)
1134
+ size == other.size and
1135
+ all? { |node, child|
1136
+ if child.nothing?
1137
+ other.include?(node) and begin other_child = other.fetch(node, nil)
1138
+ not other_child or
1139
+ (other_child.respond_to?(:empty?) and other_child.empty?)
1140
+ end
1141
+ else
1142
+ child.matches? other[node]
1143
+ end }
1144
+ end
1145
+
1146
+
1147
+ ########################################################################
1148
+ # @group Conversion
1149
+ ########################################################################
1150
+
1151
+ ##
1152
+ # A native Ruby object representing the content of the tree.
1153
+ #
1154
+ # It is used by {#to_h} to produce flattened representations of child trees.
1155
+ #
1156
+ # @api private
1157
+ #
1158
+ def to_native_object(sleaf_child_as: nil, special_nil: false)
1159
+ case
1160
+ when empty?
1161
+ []
1162
+ when strict_leaves?
1163
+ size == 1 && (!special_nil || !nodes.first.nil?) ? nodes.first : nodes
1164
+ else
1165
+ to_h(sleaf_child_as: sleaf_child_as, special_nil: special_nil)
1166
+ end
1167
+ end
1168
+
1169
+ ##
1170
+ # A hash representation of this tree.
1171
+ #
1172
+ # @return [Hash]
1173
+ #
1174
+ def to_h(*args)
1175
+ return {} if empty?
1176
+
1177
+ # not the nicest, but fastest way to inject on hashes, as noted here:
1178
+ # http://stackoverflow.com/questions/3230863/ruby-rails-inject-on-hashes-good-style
1179
+ hash = {}
1180
+ @data.each do |node, child|
1181
+ hash[node] = child.to_native_object(*args)
1182
+ end
1183
+
1184
+ hash
1185
+ end
1186
+
1187
+ ##
1188
+ # A string representation of this tree.
1189
+ #
1190
+ # @return [String]
1191
+ #
1192
+ def to_s
1193
+ if (content = to_native_object(special_nil: true)).is_a? Enumerable
1194
+ "Tree[#{content.inspect[1..-2]}]"
1195
+ else
1196
+ "Tree[#{content.inspect}]"
1197
+ end
1198
+ end
1199
+
1200
+ ##
1201
+ # A developer-friendly string representation of this tree in the usual Ruby +Object#inspect+ style.
1202
+ #
1203
+ # @return [String]
1204
+ #
1205
+ def inspect
1206
+ "#<#{self.class}:0x#{object_id.to_s(16)} #{
1207
+ to_h(sleaf_child_as: Sycamore::NothingTree::NestedString).inspect}>"
1208
+ end
1209
+
1210
+ ##
1211
+ # Checks if the given object can be converted into a Tree.
1212
+ #
1213
+ # Ideally these would be implemented with Refinements, but since they
1214
+ # aren't available anywhere (I'm looking at you, JRuby), we have to be
1215
+ # content with this.
1216
+ #
1217
+ # @param object [Object]
1218
+ # @return [Boolean]
1219
+ #
1220
+ # @api private
1221
+ #
1222
+ def self.tree_like?(object)
1223
+ case object
1224
+ when Hash, Tree, Absence # ... ?!
1225
+ true
1226
+ else
1227
+ (object.respond_to? :tree_like? and object.tree_like?) # or ...
1228
+ end
1229
+ end
1230
+
1231
+ class << self
1232
+ alias like? tree_like?
1233
+ end
1234
+
1235
+
1236
+ ########################################################################
1237
+ # @group Other standard Ruby methods
1238
+ ########################################################################
1239
+
1240
+ ##
1241
+ # Duplicates the whole tree.
1242
+ #
1243
+ # @return [Tree]
1244
+ #
1245
+ def dup
1246
+ duplicate = self.class.new.add(self)
1247
+ duplicate.taint if tainted?
1248
+ duplicate
1249
+ end
1250
+
1251
+ ##
1252
+ # Clones the whole tree.
1253
+ #
1254
+ def initialize_clone(other)
1255
+ super
1256
+ @data = Hash.new
1257
+ add other
1258
+ end
1259
+
1260
+ ##
1261
+ # Deep freezes the whole tree.
1262
+ #
1263
+ # @see http://ruby-doc.org/core/Object.html#method-i-freeze
1264
+ #
1265
+ def freeze
1266
+ @data.freeze
1267
+ each { |_, child| child.freeze }
1268
+ super
1269
+ end
1270
+
1271
+ end # Tree
1272
+ end # Sycamore