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 +4 -4
- data/.travis.yml +2 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +35 -9
- data/Gemfile +7 -1
- data/README.md +11 -0
- data/VERSION +1 -1
- data/bin/console +1 -3
- data/bin/setup +0 -0
- data/lib/sycamore/tree.rb +195 -77
- data/support/travis.sh +6 -0
- data/sycamore.gemspec +2 -4
- metadata +3 -32
- data/script/console +0 -14
- data/script/console.cmd +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9ca22a9fa8a7caafbbce1f4a166ff6a43780493
|
4
|
+
data.tar.gz: ed6fa35d1e3bdef4d41d3642153437755dc0d708
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55180ebbfd457ba351a0ad933bcf5064e3de0efa9bb032bcc0c8bf4e9493abe108ad2a40d2de82a584beadc57973b92ffb9027aabbb84bd8a6a070de55cab77d
|
7
|
+
data.tar.gz: ac5e395633685242d42b6b3710441274425266ecaa2246298c6434146b65df1e2993bc5ad9de266f1d9e94dd9c193fa8be12dfe416b079192932cb4efe5f5ac4
|
data/.travis.yml
CHANGED
data/.yardopts
CHANGED
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
|
-
##
|
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
|
-
|
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
|
-
##
|
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',
|
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.
|
1
|
+
0.3.0
|
data/bin/console
CHANGED
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: {
|
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
|
202
|
-
when nodes_or_tree.is_a?(
|
203
|
-
|
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
|
-
|
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
|
-
|
222
|
-
|
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
|
-
|
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
|
-
#
|
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
|
304
|
-
when
|
305
|
-
|
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
|
-
|
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
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
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 =>
|
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
|
-
|
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
|
-
|
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
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
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
|
773
|
-
include?
|
850
|
+
if path.is_a? Path
|
851
|
+
fetch_path(path.parent) { return false }.include? path.node
|
774
852
|
else
|
775
|
-
|
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) &&
|
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) &&
|
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[
|
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.
|
1005
|
-
# tree.
|
1006
|
-
# tree.
|
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
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.
|
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-
|
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 %*
|