sycamore 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 17dce2903bbad75bfd5c528662e0857d2505cf1f
4
- data.tar.gz: b0314f85b8c211cc3fe324dfd304c14e07c96bcb
3
+ metadata.gz: a9ca22a9fa8a7caafbbce1f4a166ff6a43780493
4
+ data.tar.gz: ed6fa35d1e3bdef4d41d3642153437755dc0d708
5
5
  SHA512:
6
- metadata.gz: 008f0f0f027983a40aed89c98af2726711ab7297af755f3894e7b82d8e7b5e84154dfa318add0d27aa51ca56f4d28e66767fca8e0acf6419064f861d20efa0fc
7
- data.tar.gz: e4c7b8096a0aad94b6702747c1fd26e7efa09102ec10c3d14b63672f89bca237cb88d9119439a03c5d10eada7f42714d699bd00653fc62a637419edbd9cbb3fa
6
+ metadata.gz: 55180ebbfd457ba351a0ad933bcf5064e3de0efa9bb032bcc0c8bf4e9493abe108ad2a40d2de82a584beadc57973b92ffb9027aabbb84bd8a6a070de55cab77d
7
+ data.tar.gz: ac5e395633685242d42b6b3710441274425266ecaa2246298c6434146b65df1e2993bc5ad9de266f1d9e94dd9c193fa8be12dfe416b079192932cb4efe5f5ac4
data/.travis.yml CHANGED
@@ -8,3 +8,5 @@ rvm:
8
8
  cache: bundler
9
9
  before_install:
10
10
  - gem update bundler
11
+ before_script:
12
+ - support/travis.sh
data/.yardopts CHANGED
@@ -8,4 +8,6 @@
8
8
  AUTHORS
9
9
  CREDITS
10
10
  LICENSE.txt
11
+ CHANGELOG.md
12
+ CONTRIBUTING.md
11
13
  VERSION
data/CHANGELOG.md CHANGED
@@ -5,11 +5,38 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
5
5
  [Keep a CHANGELOG](http://keepachangelog.com).
6
6
 
7
7
 
8
- ## [Unreleased]
8
+ ## 0.3.0 - 2016-04-23
9
9
 
10
+ ### Added
11
+
12
+ - support `Path` objects as input on the following `Tree` methods:
13
+ - the `Tree.[]` population constructor
14
+ - `fetch`
15
+ - `add`
16
+ - `delete`
17
+ - `replace`
18
+ - `[]=`
19
+ - `include_node?`
20
+ - `leaf?`
21
+ - `strict_leaf?`
22
+ - `strict_leaves?`
23
+ - `internal?`
24
+ - `external?`
25
+ - `Tree#fetch_path` for fetching a child by path
26
+
27
+ ### Fixed
28
+
29
+ - `Tree#add` or `Tree#delete` now fail without making any changes, when given
30
+ invalid input. Previously these command methods performed their operations
31
+ until the invalid input elements were encountered.
32
+ - `Tree#delete` deleted paths, when they matched a given input path partially,
33
+ e.g. `Tree[a: 1] >> a: {1 => 2}` deleted successfully.
34
+
35
+ [Compare v0.2.1...v0.3.0](https://github.com/marcelotto/sycamore/compare/v0.2.1...v0.3.0)
10
36
 
11
37
 
12
- ## [0.2.1] - 2016-04-07
38
+
39
+ ## 0.2.1 - 2016-04-07
13
40
 
14
41
  ### Added
15
42
 
@@ -18,11 +45,13 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
18
45
 
19
46
  ### Fixed
20
47
 
21
- - #2: Rubinius support
48
+ - [#2](https://github.com/marcelotto/sycamore/issues/2): Rubinius support
49
+
50
+ [Compare v0.2.0...v0.2.1](https://github.com/marcelotto/sycamore/compare/v0.2.0...v0.2.1)
22
51
 
23
52
 
24
53
 
25
- ## [0.2.0] - 2016-04-05
54
+ ## 0.2.0 - 2016-04-05
26
55
 
27
56
  ### Added
28
57
 
@@ -31,13 +60,10 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
31
60
  - `Tree#node!` as a more strict variant of `Tree#node`, which raises an error
32
61
  when no node present
33
62
 
63
+ [Compare v0.1.0...v0.2.0](https://github.com/marcelotto/sycamore/compare/v0.1.0...v0.2.0)
64
+
34
65
 
35
66
 
36
67
  ## 0.1.0 - 2016-03-28
37
68
 
38
69
  Initial release
39
-
40
-
41
- [Unreleased]: https://github.com/marcelotto/sycamore/compare/v0.2.1...HEAD
42
- [0.2.1]: https://github.com/marcelotto/sycamore/compare/v0.2.0...v0.2.1
43
- [0.2.0]: https://github.com/marcelotto/sycamore/compare/v0.1.0...v0.2.0
data/Gemfile CHANGED
@@ -2,6 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ group :development do
6
+ gem 'guard-rspec'
7
+ gem 'listen', '< 3.1' # to circumvent the fail for Ruby 2.1 and Rubinius
8
+ gem 'pry'
9
+ end
10
+
5
11
  group :test do
6
- gem 'coveralls', require: false, platform: :mri
12
+ gem 'coveralls', require: false, platform: :mri
7
13
  end
data/README.md CHANGED
@@ -198,6 +198,15 @@ tree.fetch(:z) { :default } # => :default
198
198
 
199
199
  Fetching the child of a leaf behaves almost the same as fetching the child of a non-existing node, i.e. the default value is returned or a `KeyError` gets raised. In order to differentiate these cases, a `Sycamore::ChildError` as a subclass of `KeyError` is raised when accessing the child of a leaf.
200
200
 
201
+ `fetch_path` allows a `dig` similar access with `fetch` semantics, except it requires the path of nodes to be given as an Enumerable.
202
+
203
+ ```ruby
204
+ tree.fetch_path([:y, 2]).node # => "a"
205
+ tree.fetch_path([:y, 3]) # => KeyError: key not found: 3
206
+ tree.fetch_path([:y, 3], :default) # => :default
207
+ tree.fetch_path([:y, 3]) { :default } # => :default
208
+ ```
209
+
201
210
  The number of nodes of a tree can be determined with `size`. This will only count direct nodes.
202
211
 
203
212
  ```ruby
@@ -543,6 +552,8 @@ If you search for multiple nodes, only the paths to child trees containing all o
543
552
  tree.search [:b, :c] # => [#<Sycamore::Path[3]>]
544
553
  ```
545
554
 
555
+ All `Tree` methods for which it makes sense accept path objects as input instead or in combination with nodes or tree structures. This allows it to apply the search results to any of these methods.
556
+
546
557
 
547
558
  ## Getting help
548
559
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.1
1
+ 0.3.0
data/bin/console CHANGED
@@ -1,9 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/setup'
4
- require 'sycamore'
4
+ require 'sycamore/extension'
5
5
 
6
6
  require 'pry'
7
7
  Pry.start
8
-
9
- include Sycamore # TODO: require 'sycamore/extension'
data/bin/setup CHANGED
File without changes
data/lib/sycamore/tree.rb CHANGED
@@ -46,7 +46,7 @@ module Sycamore
46
46
  # the names of all methods, which side-effect-freeze return only a value
47
47
  QUERY_METHODS = PREDICATE_METHODS +
48
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 search
49
+ node node! nodes keys child_of child_at dig fetch fetch_path search
50
50
  size total_size tsize height
51
51
  each each_path paths each_node each_key each_pair] << :[]
52
52
 
@@ -177,6 +177,10 @@ module Sycamore
177
177
  # adds a tree structure of nodes
178
178
  # @param tree_structure [Hash, Tree]
179
179
  #
180
+ # @overload add(path)
181
+ # adds a {Path} of nodes
182
+ # @param path [Path]
183
+ #
180
184
  # @return +self+ as a proper command method
181
185
  #
182
186
  # @raise [InvalidNode] when given a nested node set
@@ -186,21 +190,26 @@ module Sycamore
186
190
  # tree.add :foo
187
191
  # tree.add [:bar, :baz]
188
192
  # tree.add [:node, [:nested, :values]] # => raise Sycamore::InvalidNode, "[:nested, :values] is not a valid tree node"
189
- # tree.add foo: 1, bar: {baz: 2}
193
+ # tree.add foo: 1, bar: {qux: 2}
190
194
  # tree.add foo: [:node, [:nested, :values]] # => raise Sycamore::InvalidNode, "[:nested, :values] is not a valid tree node"
195
+ # tree.add Sycamore::Path[1,2,3]
196
+ # tree.to_h # => {:foo=>1, :bar=>{:qux=>2}, :baz=>nil, 1=>{2=>3}}
191
197
  #
192
198
  # tree = Tree.new
193
199
  # tree[:foo][:bar] << :baz
194
200
  # tree[:foo] << { bar: 1, qux: 2 }
195
201
  # tree.to_h # => {:foo=>{:bar=>[:baz, 1], :qux=>2}}
196
202
  #
197
- # @todo support Paths
198
- #
199
203
  def add(nodes_or_tree)
200
204
  case
201
- when Tree.like?(nodes_or_tree) then add_tree(nodes_or_tree)
202
- when nodes_or_tree.is_a?(Enumerable) then add_nodes(nodes_or_tree)
203
- else add_node(nodes_or_tree)
205
+ when nodes_or_tree.equal?(Nothing) then # do nothing
206
+ when nodes_or_tree.is_a?(Tree) then add_tree(nodes_or_tree)
207
+ when Tree.like?(nodes_or_tree) then add_tree(valid_tree! nodes_or_tree)
208
+ when nodes_or_tree.is_a?(Path) then add_path(nodes_or_tree)
209
+ when nodes_or_tree.is_a?(Enumerable)
210
+ nodes_or_tree.all? { |node| valid_node_element! node }
211
+ nodes_or_tree.each { |node| add(node) }
212
+ else add_node(nodes_or_tree)
204
213
  end
205
214
 
206
215
  self
@@ -208,18 +217,17 @@ module Sycamore
208
217
 
209
218
  alias << add
210
219
 
211
- private def add_node(node)
212
- return self if node.equal? Nothing
213
- return add_tree(node) if Tree.like? node
214
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
215
-
220
+ protected def add_node(node)
216
221
  @data[node] ||= Nothing
217
222
 
218
223
  self
219
224
  end
220
225
 
221
- private def add_nodes(nodes)
222
- nodes.each { |node| add_node(node) }
226
+ ##
227
+ # @api private
228
+ #
229
+ def clear_child_of_node(node)
230
+ @data[valid_node! node] = Nothing
223
231
 
224
232
  self
225
233
  end
@@ -228,7 +236,7 @@ module Sycamore
228
236
  # @api private
229
237
  #
230
238
  def add_node_with_empty_child(node)
231
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
239
+ valid_node! node
232
240
 
233
241
  if @data.fetch(node, Nothing).nothing?
234
242
  @data[node] = new_child(node)
@@ -237,17 +245,6 @@ module Sycamore
237
245
  self
238
246
  end
239
247
 
240
- ##
241
- # @api private
242
- #
243
- def clear_child_of_node(node)
244
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
245
-
246
- @data[node] = Nothing
247
-
248
- self
249
- end
250
-
251
248
  private def add_child(node, children)
252
249
  return add_node(node) if Nothing.like?(children)
253
250
 
@@ -263,6 +260,17 @@ module Sycamore
263
260
  self
264
261
  end
265
262
 
263
+ private def add_path(path)
264
+ return self if path.root?
265
+
266
+ path.parent.inject(self) { |tree, node| # using a {} block to circumvent this Rubinius issue: https://github.com/rubinius/rubinius-code/issues/7
267
+ tree.add_node_with_empty_child(node)
268
+ tree[node]
269
+ }.add_node path.node
270
+
271
+ self
272
+ end
273
+
266
274
  ##
267
275
  # Remove nodes or a tree structure from this tree.
268
276
  #
@@ -281,6 +289,10 @@ module Sycamore
281
289
  # deletes a tree structure of nodes
282
290
  # @param tree_structure [Hash, Tree]
283
291
  #
292
+ # @overload delete(path)
293
+ # deletes a {Path} of nodes
294
+ # @param path [Path]
295
+ #
284
296
  # @return +self+ as a proper command method
285
297
  #
286
298
  # @raise [InvalidNode] when given a nested node set
@@ -295,14 +307,20 @@ module Sycamore
295
307
  # tree.to_h # => {"d" => {foo: :baz}}
296
308
  # tree.delete "d" => {foo: :baz}
297
309
  # tree.to_h # => {}
298
- #
299
- # @todo support Paths
310
+ # tree = Tree[foo: {bar: :baz, qux: nil}]
311
+ # tree.delete Sycamore::Path[:foo, :bar, :baz]
312
+ # tree.to_h # => {foo: :qux}
300
313
  #
301
314
  def delete(nodes_or_tree)
302
315
  case
303
- when Tree.like?(nodes_or_tree) then delete_tree(nodes_or_tree)
304
- when nodes_or_tree.is_a?(Enumerable) then delete_nodes(nodes_or_tree)
305
- else delete_node(nodes_or_tree)
316
+ when nodes_or_tree.is_a?(Tree) then delete_tree(nodes_or_tree)
317
+ when Tree.like?(nodes_or_tree) then delete_tree(valid_tree! nodes_or_tree)
318
+ when nodes_or_tree.is_a?(Path) then delete_path(nodes_or_tree)
319
+ when nodes_or_tree.is_a?(Enumerable)
320
+ nodes_or_tree.all? { |node| valid_node_element! node }
321
+ nodes_or_tree.each { |node| delete node }
322
+ else
323
+ delete_node valid_node!(nodes_or_tree)
306
324
  end
307
325
 
308
326
  self
@@ -310,31 +328,30 @@ module Sycamore
310
328
 
311
329
  alias >> delete
312
330
 
313
- private def delete_node(node)
314
- return delete_tree(node) if Tree.like? node
315
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
316
-
331
+ protected def delete_node(node)
317
332
  @data.delete(node)
318
333
 
319
334
  self
320
335
  end
321
336
 
322
- private def delete_nodes(nodes)
323
- nodes.each { |node| delete_node(node) }
324
-
325
- self
326
- end
327
-
328
- private def delete_tree(tree)
329
- tree.each { |node, child| # using a {} block to circumvent this Rubinius issue: https://github.com/rubinius/rubinius-code/issues/7
330
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
331
- next unless include? node
332
- if Nothing.like?(child) or (child.respond_to?(:empty?) and child.empty?)
333
- delete_node node
337
+ protected def delete_tree(tree)
338
+ tree.each { |node_to_delete, child_to_delete| # using a {} block to circumvent this Rubinius issue: https://github.com/rubinius/rubinius-code/issues/7
339
+ next unless include? node_to_delete
340
+ if Nothing.like?(child_to_delete) or
341
+ (child_to_delete.respond_to?(:empty?) and child_to_delete.empty?)
342
+ delete_node node_to_delete
334
343
  else
335
- child_of(node).tap do |this_child|
336
- this_child.delete child
337
- delete_node(node) if this_child.empty?
344
+ fetch(node_to_delete, Nothing).tap do |child|
345
+ case
346
+ when child.empty? then next
347
+ when Tree.like?(child_to_delete)
348
+ child.delete_tree(child_to_delete)
349
+ when child_to_delete.is_a?(Enumerable)
350
+ child_to_delete.each { |node| child.delete_node node }
351
+ else
352
+ child.delete_node child_to_delete
353
+ end
354
+ delete_node(node_to_delete) if child.empty?
338
355
  end
339
356
  end
340
357
  }
@@ -342,6 +359,19 @@ module Sycamore
342
359
  self
343
360
  end
344
361
 
362
+ protected def delete_path(path)
363
+ case path.length
364
+ when 0 then return self
365
+ when 1 then return delete_node(path.node)
366
+ end
367
+
368
+ parent = fetch_path(path.parent) { return self }
369
+ parent.delete_node(path.node)
370
+ delete_path(path.parent) if parent.empty? and not path.parent.root?
371
+
372
+ self
373
+ end
374
+
345
375
  ##
346
376
  # Replaces the contents of this tree.
347
377
  #
@@ -357,6 +387,10 @@ module Sycamore
357
387
  # Replaces the contents of this tree with a tree structure of nodes.
358
388
  # @param tree_structure [Hash, Tree]
359
389
  #
390
+ # @overload replace(path)
391
+ # Replaces the contents of this tree with a path of nodes.
392
+ # @param path [Path]
393
+ #
360
394
  # @return +self+ as a proper command method
361
395
  #
362
396
  # @raise [InvalidNode] when given a nested node set
@@ -402,6 +436,11 @@ module Sycamore
402
436
  # @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object
403
437
  # @param tree_structure [Hash, Tree]
404
438
  #
439
+ # @overload []=(*path, another_object)
440
+ # Replaces the contents of the child at the given path with another path of nodes.
441
+ # @param path [Array<Object>, Sycamore::Path] a path as a sequence of nodes or a {Path} object
442
+ # @param path_object [Path]
443
+ #
405
444
  # @return the rvalue as for any Ruby assignment
406
445
  #
407
446
  # @raise [InvalidNode] when given a nested node set
@@ -419,8 +458,10 @@ module Sycamore
419
458
  # tree.to_h # => {:foo => :baz, 1 => :baz}
420
459
  # tree[:foo] << :bar
421
460
  # tree.to_h # => {:foo => [:baz, :bar], 1 => :baz}
461
+ # tree[1] = Sycamore::Path[2,3]
462
+ # tree.to_h # => {:foo => [:baz, :bar], 1 => {2 => 3}}
422
463
  # tree[:foo] = Sycamore::Nothing
423
- # tree.to_h # => {:foo => nil, 1 => :baz}
464
+ # tree.to_h # => {:foo => nil, 1 => {2 => 3}}
424
465
  #
425
466
  def []=(*args)
426
467
  path, nodes_or_tree = args[0..-2], args[-1]
@@ -562,7 +603,7 @@ module Sycamore
562
603
  # @todo Should we differentiate the case of a leaf and a not present node? How?
563
604
  #
564
605
  def child_of(node)
565
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
606
+ valid_node! node
566
607
 
567
608
  Nothing.like?(child = @data[node]) ? Absence.at(self, node) : child
568
609
  end
@@ -618,7 +659,7 @@ module Sycamore
618
659
  # - if +default+ is given, then that will be returned;
619
660
  # - if the optional code block is specified, then that will be run and its result returned.
620
661
  #
621
- # @param node [Object]
662
+ # @param node [Object, Path]
622
663
  # @param default [Object] optional
623
664
  # @return [Tree, default]
624
665
  #
@@ -627,31 +668,68 @@ module Sycamore
627
668
  # @raise [ChildError] when no child for the given +node+ present
628
669
  #
629
670
  # @example
630
- # tree = Tree[x: 1, y: nil]
671
+ # tree = Tree[x: 1, y: nil, foo: {bar: :baz}]
631
672
  # tree.fetch(:x) # #<Sycamore::Tree:0x3fc798a63854(1)>
632
- # tree.fetch(:y) # => raise Sycamore::ChildError, "node y has no child tree"
673
+ # tree.fetch(:y) # => raise Sycamore::ChildError, "node :y has no child tree"
633
674
  # tree.fetch(:z) # => raise KeyError, "key not found: :z"
634
675
  # tree.fetch(:z, :default) # => :default
635
676
  # tree.fetch(:y, :default) # => :default
636
677
  # tree.fetch(:z) { :default } # => :default
678
+ # tree.fetch(Sycamore::Path[:foo, :bar]).nodes # => [:baz]
679
+ # tree.fetch(Sycamore::Path[:foo, :missing], :default) # => :default
637
680
  #
638
681
  # @todo Should we differentiate the case of a leaf and a not present node? How?
639
682
  #
640
683
  def fetch(node, *default, &block)
641
- raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
684
+ return fetch_path(node, *default, &block) if node.is_a? Path
685
+ valid_node! node
642
686
 
643
687
  child = @data.fetch(node, *default, &block)
644
688
  if child.equal? Nothing
645
689
  child = case
646
690
  when block_given? then yield
647
691
  when !default.empty? then default.first
648
- else raise ChildError, "node #{node} has no child tree"
692
+ else raise ChildError, "node #{node.inspect} has no child tree"
649
693
  end
650
694
  end
651
695
 
652
696
  child
653
697
  end
654
698
 
699
+ ##
700
+ # The child tree of a node at a path.
701
+ #
702
+ # If the node at the given path can’t be found or has no child tree, it
703
+ # behaves like {#fetch}.
704
+ #
705
+ # @param path [Array<Object>, Path]
706
+ # @param default [Object] optional
707
+ # @return [Tree, default]
708
+ #
709
+ # @raise [InvalidNode] when given an +Enumerable+ as node
710
+ # @raise [KeyError] when the given +node+ can't be found
711
+ # @raise [ChildError] when no child for the given +node+ present
712
+ #
713
+ # @example
714
+ # tree = Tree[foo: {bar: :baz}]
715
+ # tree.fetch_path([:foo, :bar]).nodes # => [:baz]
716
+ # tree.fetch_path [:foo, :bar, :baz] # => raise Sycamore::ChildError, "node :baz has no child tree"
717
+ # tree.fetch_path [:foo, :qux] # => raise KeyError, "key not found: :qux"
718
+ # tree.fetch_path([:a, :b], :default) # => :default
719
+ # tree.fetch_path([:a, :b]) { :default } # => :default
720
+ # tree.fetch_path([:foo, :bar, :baz], :default) # => :default
721
+ #
722
+ def fetch_path(path, *default, &block)
723
+ default_case = block_given? || !default.empty?
724
+ path.inject(self) do |tree, node|
725
+ if default_case
726
+ tree.fetch(node) { return block_given? ? yield : default.first }
727
+ else
728
+ tree.fetch(node)
729
+ end
730
+ end
731
+ end
732
+
655
733
  ##
656
734
  # Iterates over all {#nodes nodes} of this tree.
657
735
  #
@@ -762,17 +840,17 @@ module Sycamore
762
840
  # tree.include_path? Sycamore::Path["c", :foo, :bar] # => true
763
841
  #
764
842
  def include_path?(*args)
765
- raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)' if args.count == 0
766
- first = args.first
767
- if first.is_a? Enumerable
768
- return include_path?(*first) if args.count == 1
769
- raise InvalidNode, "#{first} is not a valid tree node"
843
+ case args.count
844
+ when 0 then raise ArgumentError, 'wrong number of arguments (given 0, expected 1+)'
845
+ when 1 then path = args.first
846
+ else return include_path?(args)
770
847
  end
848
+ path = [path] unless path.is_a? Enumerable
771
849
 
772
- if args.count == 1
773
- include? first
850
+ if path.is_a? Path
851
+ fetch_path(path.parent) { return false }.include? path.node
774
852
  else
775
- include?(first) and child_of(first).include_path?(args[1..-1])
853
+ fetch_path(path[0..-2]) { return false }.include? path.last
776
854
  end
777
855
  end
778
856
 
@@ -781,14 +859,17 @@ module Sycamore
781
859
  ##
782
860
  # Checks if a node exists in the {#nodes nodes} set of this tree.
783
861
  #
784
- # @param node [Object]
862
+ # @param node [Object, Path]
785
863
  # @return [Boolean]
786
864
  #
787
865
  # @example
788
866
  # Tree[1,2,3].include_node? 3 # => true
789
867
  # Tree[1 => 2].include_node? 2 # => false
868
+ # Tree[1 => 2].include_node? Sycamore::Path[1,2] # => true
790
869
  #
791
870
  def include_node?(node)
871
+ return include_path?(node) if node.is_a? Path
872
+
792
873
  @data.include?(node)
793
874
  end
794
875
 
@@ -928,7 +1009,7 @@ module Sycamore
928
1009
  ##
929
1010
  # Checks if the given node has no children.
930
1011
  #
931
- # @param node [Object]
1012
+ # @param node [Object, Path]
932
1013
  # @return [Boolean]
933
1014
  #
934
1015
  # @example
@@ -936,9 +1017,10 @@ module Sycamore
936
1017
  # tree.leaf?(:x) # => false
937
1018
  # tree.leaf?(:y) # => true
938
1019
  # tree.leaf?(:z) # => true
1020
+ # tree.leaf?(Sycamore::Path[:x, 1]) # => true
939
1021
  #
940
1022
  def leaf?(node)
941
- include_node?(node) && child_of(node).empty?
1023
+ include_node?(node) && child_at(node).empty?
942
1024
  end
943
1025
 
944
1026
  ##
@@ -952,9 +1034,10 @@ module Sycamore
952
1034
  # tree.strict_leaf?(:x) # => false
953
1035
  # tree.strict_leaf?(:y) # => false
954
1036
  # tree.strict_leaf?(:z) # => true
1037
+ # tree.strict_leaf?(Sycamore::Path[:x, 1]) # => true
955
1038
  #
956
1039
  def strict_leaf?(node)
957
- include_node?(node) && child_of(node).absent?
1040
+ include_node?(node) && child_at(node).absent?
958
1041
  end
959
1042
 
960
1043
  alias sleaf? strict_leaf?
@@ -968,15 +1051,17 @@ module Sycamore
968
1051
  #
969
1052
  # @overload strict_leaves?(*nodes)
970
1053
  # Checks if all of the given nodes have no children, even not an empty child tree.
971
- # @param nodes [Array<Object>] splat of nodes
1054
+ # @param nodes [Array<Object, Path>] splat of nodes or Path objects
972
1055
  # @return [Boolean]
973
1056
  #
974
1057
  # @example
975
1058
  # Tree[1,2,3].strict_leaves? # => true
976
- # tree = Tree[a: :foo, b: :bar, c: []]
1059
+ # tree = Tree[x: 1, y: [], z: nil]
977
1060
  # tree.strict_leaves? # => false
978
1061
  # tree.strict_leaves?(:x, :y) # => false
979
1062
  # tree.strict_leaves?(:y, :z) # => false
1063
+ # tree.strict_leaves?(:y, :z) # => false
1064
+ # tree.strict_leaves?(:z, Sycamore::Path[:x, 1]) # => true
980
1065
  #
981
1066
  def strict_leaves?(*nodes)
982
1067
  nodes = self.nodes if nodes.empty?
@@ -995,15 +1080,17 @@ module Sycamore
995
1080
  #
996
1081
  # @overload external?(*nodes)
997
1082
  # Checks if all of the given nodes have no children.
998
- # @param nodes [Array<Object>] splat of nodes
1083
+ # @param nodes [Array<Object, Path>] splat of nodes or Path objects
999
1084
  # @return [Boolean]
1000
1085
  #
1001
1086
  # @example
1002
1087
  # Tree[1,2,3].leaves? # => true
1003
1088
  # tree = Tree[x: 1, y: [], z: nil]
1004
- # tree.leaves? # => false
1005
- # tree.leaves?(:x, :y) # => false
1006
- # tree.leaves?(:y, :z) # => true
1089
+ # tree.external? # => false
1090
+ # tree.external?(:x, :y) # => false
1091
+ # tree.external?(:y, :z) # => true
1092
+ # tree.external?(:y, :z) # => true
1093
+ # tree.external?(Sycamore::Path[:x, 1], :y) # => true
1007
1094
  #
1008
1095
  def external?(*nodes)
1009
1096
  nodes = self.nodes if nodes.empty?
@@ -1023,7 +1110,7 @@ module Sycamore
1023
1110
  #
1024
1111
  # @overload internal?(*nodes)
1025
1112
  # Checks if all of the given nodes have children.
1026
- # @param nodes [Array<Object>] splat of nodes
1113
+ # @param nodes [Array<Object, Path>] splat of nodes or Path objects
1027
1114
  # @return [Boolean]
1028
1115
  #
1029
1116
  # @example
@@ -1033,6 +1120,8 @@ module Sycamore
1033
1120
  # tree.internal?(:x, :y) # => false
1034
1121
  # tree.internal?(:y, :z) # => false
1035
1122
  # tree.internal?(:x) # => true
1123
+ # tree.internal?(:x) # => true
1124
+ # tree.internal?(Sycamore::Path[:x, 1]) # => false
1036
1125
  #
1037
1126
  # @todo Does it make sense to support the no arguments variant here and with this semantics?
1038
1127
  # One would expect it to be the negation of #external? without arguments.
@@ -1280,6 +1369,36 @@ module Sycamore
1280
1369
  to_h(sleaf_child_as: Sycamore::NothingTree::NestedString).inspect}>"
1281
1370
  end
1282
1371
 
1372
+ # @api private
1373
+ #
1374
+ private def valid_tree!(tree)
1375
+ tree.each { |node, child| # using a {} block to circumvent this Rubinius issue: https://github.com/rubinius/rubinius-code/issues/7
1376
+ next if child.nil?
1377
+ valid_node!(node)
1378
+ valid_tree!(child) if Tree.like?(child)
1379
+ }
1380
+
1381
+ tree
1382
+ end
1383
+
1384
+ # @api private
1385
+ #
1386
+ private def valid_node_element!(node)
1387
+ raise InvalidNode, "#{node} is not a valid tree node" if
1388
+ node.is_a?(Enumerable) and not node.is_a?(Path) and not Tree.like?(node)
1389
+
1390
+ node
1391
+ end
1392
+
1393
+
1394
+ # @api private
1395
+ #
1396
+ private def valid_node!(node)
1397
+ raise InvalidNode, "#{node} is not a valid tree node" if node.is_a? Enumerable
1398
+
1399
+ node
1400
+ end
1401
+
1283
1402
  ##
1284
1403
  # Checks if the given object can be converted into a Tree.
1285
1404
  #
@@ -1305,7 +1424,6 @@ module Sycamore
1305
1424
  alias like? tree_like?
1306
1425
  end
1307
1426
 
1308
-
1309
1427
  ########################################################################
1310
1428
  # @group Other standard Ruby methods
1311
1429
  ########################################################################
data/support/travis.sh ADDED
@@ -0,0 +1,6 @@
1
+ #/bin/sh
2
+ set -e
3
+ set -x
4
+
5
+ mkdir ~/.yard
6
+ bundle exec yard config -a autoload_plugins yard-doctest
data/sycamore.gemspec CHANGED
@@ -14,18 +14,16 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/marcelotto/sycamore'
15
15
  spec.license = 'MIT'
16
16
 
17
- spec.required_ruby_version = '>= 2.1'
18
-
19
17
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
18
  spec.bindir = 'exe'
21
19
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
20
  spec.require_paths = ['lib']
23
21
 
22
+ spec.required_ruby_version = '>= 2.1'
23
+
24
24
  spec.add_development_dependency 'bundler', '~> 1.9'
25
25
  spec.add_development_dependency 'rake', '~> 10.0'
26
- spec.add_development_dependency 'pry'
27
26
  spec.add_development_dependency 'rspec', '~> 3.4.0'
28
- spec.add_development_dependency 'guard-rspec'
29
27
  spec.add_development_dependency 'yard', '~> 0.8'
30
28
  spec.add_development_dependency 'yard-doctest'
31
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sycamore
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcel Otto
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-04-06 00:00:00.000000000 Z
11
+ date: 2016-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: pry
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -66,20 +52,6 @@ dependencies:
66
52
  - - "~>"
67
53
  - !ruby/object:Gem::Version
68
54
  version: 3.4.0
69
- - !ruby/object:Gem::Dependency
70
- name: guard-rspec
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
55
  - !ruby/object:Gem::Dependency
84
56
  name: yard
85
57
  requirement: !ruby/object:Gem::Requirement
@@ -146,9 +118,8 @@ files:
146
118
  - lib/sycamore/stree.rb
147
119
  - lib/sycamore/tree.rb
148
120
  - lib/sycamore/version.rb
149
- - script/console
150
- - script/console.cmd
151
121
  - support/doctest_helper.rb
122
+ - support/travis.sh
152
123
  - sycamore.gemspec
153
124
  homepage: https://github.com/marcelotto/sycamore
154
125
  licenses:
data/script/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # File: script/console
3
- pry = 'pry'
4
- repl = pry
5
-
6
- # libs = " -r #{File.expand_path(File.dirname(__FILE__)) + '/../lib/sycamore.rb'}"
7
- libs = " -r #{File.expand_path(File.dirname(__FILE__)) + '/../lib/sycamore/extension'}"
8
-
9
- puts "Loading Sycamore gem..."
10
- #exec "#{irb} #{libs} --simple-prompt"
11
- exec "bundle exec #{repl} #{libs}"
12
-
13
- #init_console = " -r #{File.expand_path(File.dirname(__FILE__)) + '/init_console.rb'}"
14
- #exec "pry #{libs} #{init_console}"
data/script/console.cmd DELETED
@@ -1 +0,0 @@
1
- @ruby script/console %*