metaractor-sycamore 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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