sycamore 0.1.0

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