metaractor-sycamore 0.4.1

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