forester 3.0.0 → 3.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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/forester.gemspec +1 -1
- data/lib/forester/node_content/base.rb +9 -0
- data/lib/forester/node_content/dictionary.rb +102 -0
- data/lib/forester/node_content/factory.rb +27 -0
- data/lib/forester/node_content/list.rb +31 -0
- data/lib/forester/tree_factory.rb +3 -3
- data/lib/forester/tree_node.rb +15 -4
- data/lib/forester/{mutators.rb → tree_node_ext/mutators.rb} +1 -1
- data/lib/forester/tree_node_ext/views.rb +25 -0
- data/lib/forester/version.rb +1 -1
- data/lib/forester.rb +10 -6
- data/test/test_mutators.rb +6 -0
- metadata +9 -8
- data/lib/forester/hash_with_indifferent_access.rb +0 -28
- data/lib/forester/node_content.rb +0 -37
- data/lib/forester/node_content_factory.rb +0 -19
- data/lib/forester/views.rb +0 -22
- /data/lib/forester/{aggregators.rb → tree_node_ext/aggregators.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a6a1cffe04ece8277a5828f27c9f36f1ea2ba31
|
4
|
+
data.tar.gz: bdafe433a52570d2e30535698febf2a50ebf8b47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a0ef594c8f942bf12044e5d232f7d7bf07c2be20d914378dc5b32727b8c6220d382eb71b49f71eca090aafd506d83db448523bb0fa4a7e90cbc0c4fba78c931
|
7
|
+
data.tar.gz: d7ad4fe1a9257eceaa20f6e66b13446535909a8eb68356b8240755c3ba3bd9711a1bd0e7e35dd673e47ff9bf1ea5bd656ba09edcfff0ce1e2d445f4c4308c07c
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Forester
|
2
2
|
|
3
|
-
Based on *rubytree
|
3
|
+
Based on *rubytree*, this gem lets you build trees and run queries against them.
|
4
4
|
|
5
5
|
## FAQ
|
6
6
|
|
@@ -18,7 +18,7 @@ Because I didn't feel the need to copy the whole codebase. All I needed was to e
|
|
18
18
|
|
19
19
|
- What can I do with forester?
|
20
20
|
|
21
|
-
Everything you can do with rubytree, possibly in a more intention-revealing way, plus
|
21
|
+
Everything you can do with rubytree, possibly in a more intention-revealing way, plus some configurable aggregations on trees. Simple examples can be found in the tests.
|
22
22
|
|
23
23
|
## Installation
|
24
24
|
|
data/forester.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'forester/version'
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = 'forester'
|
8
8
|
s.version = Forester::Version
|
9
|
-
s.date = '2016-
|
9
|
+
s.date = '2016-08-20'
|
10
10
|
s.summary = "A gem to represent and interact with tree data structures"
|
11
11
|
s.description = "Based on rubytree, this gem lets you build trees and run queries against them."
|
12
12
|
s.authors = ["Eugenio Bruno"]
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Forester
|
2
|
+
module NodeContent
|
3
|
+
class Dictionary < Base
|
4
|
+
|
5
|
+
def fields
|
6
|
+
keys
|
7
|
+
end
|
8
|
+
|
9
|
+
def has_key?(key, indifferent = true)
|
10
|
+
if indifferent
|
11
|
+
equivs(key).any? { |k| super(k) }
|
12
|
+
else
|
13
|
+
super(key)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
alias_method :has?, :has_key?
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
super(best key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch(key, default = :yield_to_block, &block)
|
23
|
+
best_key = best key
|
24
|
+
if default == :yield_to_block
|
25
|
+
super(best_key, &block)
|
26
|
+
else
|
27
|
+
super(best_key, default, &block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :get, :fetch
|
31
|
+
|
32
|
+
def []=(key, value, options = {})
|
33
|
+
default_options = {
|
34
|
+
symbolize_key: true
|
35
|
+
}
|
36
|
+
options = default_options.merge(options)
|
37
|
+
|
38
|
+
super(symbolize_if(options[:symbolize_key]).call(key), value)
|
39
|
+
end
|
40
|
+
alias_method :put!, :[]=
|
41
|
+
|
42
|
+
def delete(key, default = :yield_to_block, &block)
|
43
|
+
best_key = best key
|
44
|
+
if default == :yield_to_block
|
45
|
+
super(best_key, &block)
|
46
|
+
else
|
47
|
+
super(best_key, default, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias_method :del!, :delete
|
51
|
+
|
52
|
+
def to_hash(options = {})
|
53
|
+
default_options = {
|
54
|
+
fields_to_include: fields, # all of them
|
55
|
+
stringify_keys: false,
|
56
|
+
symbolize_keys: false
|
57
|
+
}
|
58
|
+
options = default_options.merge(options)
|
59
|
+
|
60
|
+
convert_key = stringify_if options[:stringify_keys]
|
61
|
+
convert_key = symbolize_if options[:symbolize_keys]
|
62
|
+
|
63
|
+
each_with_object({}) do |(k, v), hash|
|
64
|
+
if equivs(k).any? { |eq| options[:fields_to_include].include?(eq) }
|
65
|
+
hash[convert_key.call(k)] = v
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def merge(dictionary)
|
71
|
+
self.class.new(super)
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def equivs(key)
|
77
|
+
[key, key.to_s, key.to_s.to_sym].uniq
|
78
|
+
end
|
79
|
+
|
80
|
+
def best(key)
|
81
|
+
equivs(key).find { |k| has_key?(k, false) } || key
|
82
|
+
end
|
83
|
+
|
84
|
+
def stringify_if(bool)
|
85
|
+
if bool
|
86
|
+
->(k) { k.to_s }
|
87
|
+
else
|
88
|
+
->(k) { k }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def symbolize_if(bool)
|
93
|
+
if bool
|
94
|
+
->(k) { k.to_sym }
|
95
|
+
else
|
96
|
+
->(k) { k }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Forester
|
2
|
+
module NodeContent
|
3
|
+
class Factory
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def from_hash(hash, children_key, indifferent = true)
|
8
|
+
ret = without_key(hash, children_key)
|
9
|
+
ret = Dictionary.new(ret) if indifferent
|
10
|
+
ret
|
11
|
+
end
|
12
|
+
|
13
|
+
def from_array(array)
|
14
|
+
List.new(array)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def without_key(hash, key)
|
20
|
+
hash.reject { |k, _| k.to_s == key.to_s }
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Forester
|
2
|
+
module NodeContent
|
3
|
+
class List < Base
|
4
|
+
|
5
|
+
def include?(*args)
|
6
|
+
super
|
7
|
+
end
|
8
|
+
alias_method :has?, :include?
|
9
|
+
|
10
|
+
def fetch(*args)
|
11
|
+
super
|
12
|
+
end
|
13
|
+
alias_method :get, :fetch
|
14
|
+
|
15
|
+
def push(*args)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
alias_method :add!, :push
|
19
|
+
|
20
|
+
def delete(*args)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
alias_method :del!, :delete
|
24
|
+
|
25
|
+
def to_array
|
26
|
+
Array.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -21,19 +21,19 @@ module Forester
|
|
21
21
|
|
22
22
|
def from_hash(hash, children_key, uid = SecureRandom.uuid)
|
23
23
|
name = uid
|
24
|
-
content =
|
24
|
+
content = NodeContent::Factory.from_hash(hash, children_key)
|
25
25
|
TreeNode.new(name, content)
|
26
26
|
end
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
30
|
def fetch_indifferently(hash, key, default = nil)
|
31
|
-
[key.
|
31
|
+
[key, key.to_s, key.to_s.to_sym].uniq.map { |k| hash[k] }.compact.first || default || hash.fetch(root_key)
|
32
32
|
end
|
33
33
|
|
34
34
|
def with_children(tree_node, children, children_key)
|
35
35
|
children.each do |child|
|
36
|
-
child_node
|
36
|
+
child_node = from_hash(child, children_key)
|
37
37
|
child_children = fetch_indifferently(child, children_key, []) # nth level
|
38
38
|
|
39
39
|
tree_node << with_children(child_node, child_children, children_key)
|
data/lib/forester/tree_node.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Forester
|
2
2
|
class TreeNode < Tree::TreeNode
|
3
3
|
|
4
|
+
extend Forwardable
|
5
|
+
def_delegators :@content, :fields, :has?, :put!, :add!, :del!
|
6
|
+
|
4
7
|
include Aggregators
|
5
8
|
include Mutators
|
6
9
|
include Views
|
@@ -23,12 +26,20 @@ module Forester
|
|
23
26
|
|
24
27
|
alias_method :each_node, :breadth_each
|
25
28
|
|
26
|
-
def get(field, &block)
|
27
|
-
|
29
|
+
def get(field, default = :raise_error, &block)
|
30
|
+
if has?(field)
|
31
|
+
content.get(field)
|
32
|
+
elsif block_given?
|
33
|
+
yield self
|
34
|
+
elsif default != :raise_error
|
35
|
+
default
|
36
|
+
else
|
37
|
+
raise ArgumentError.new("the node \"#{name}\" does not have \"#{field}\"")
|
38
|
+
end
|
28
39
|
end
|
29
40
|
|
30
|
-
def
|
31
|
-
content.
|
41
|
+
def name
|
42
|
+
content.get(:name, super)
|
32
43
|
end
|
33
44
|
|
34
45
|
def contents
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Forester
|
2
|
+
module Views
|
3
|
+
|
4
|
+
def as_nested_hash(options = {})
|
5
|
+
default_options = {
|
6
|
+
fields_to_include: fields, # all of them
|
7
|
+
stringify_keys: false,
|
8
|
+
symbolize_keys: false
|
9
|
+
}
|
10
|
+
options = default_options.merge(options)
|
11
|
+
|
12
|
+
hash = content.to_hash(options)
|
13
|
+
|
14
|
+
children_key = :children
|
15
|
+
children_key = children_key.to_s if options[:stringify_keys]
|
16
|
+
|
17
|
+
hash.merge(
|
18
|
+
{
|
19
|
+
children_key => children.map { |node| node.as_nested_hash(options) }
|
20
|
+
}
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
data/lib/forester/version.rb
CHANGED
data/lib/forester.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
1
|
require 'tree'
|
2
|
+
require 'forwardable'
|
2
3
|
require 'securerandom'
|
3
4
|
require 'yaml'
|
4
5
|
|
5
|
-
require 'forester/aggregators'
|
6
|
-
require 'forester/mutators'
|
7
|
-
require 'forester/views'
|
6
|
+
require 'forester/tree_node_ext/aggregators'
|
7
|
+
require 'forester/tree_node_ext/mutators'
|
8
|
+
require 'forester/tree_node_ext/views'
|
9
|
+
|
10
|
+
require 'forester/node_content/base'
|
11
|
+
require 'forester/node_content/dictionary'
|
12
|
+
require 'forester/node_content/list'
|
13
|
+
require 'forester/node_content/factory'
|
14
|
+
|
8
15
|
require 'forester/tree_node'
|
9
16
|
require 'forester/tree_factory'
|
10
|
-
require 'forester/node_content'
|
11
|
-
require 'forester/node_content_factory'
|
12
|
-
require 'forester/hash_with_indifferent_access'
|
data/test/test_mutators.rb
CHANGED
@@ -8,9 +8,15 @@ class TestMutators < Minitest::Test
|
|
8
8
|
path_to_trees = "#{File.dirname(__FILE__)}/trees"
|
9
9
|
tree = Forester::TreeFactory.from_yaml_file("#{path_to_trees}/simple_tree.yml")
|
10
10
|
|
11
|
+
tree.add_field_to_subtree!('number_four', 4)
|
12
|
+
|
13
|
+
assert_equal 4, tree.get(:number_four)
|
14
|
+
assert_equal 4, tree.get('number_four')
|
15
|
+
|
11
16
|
tree.add_field_to_subtree!(:number_five, 5)
|
12
17
|
|
13
18
|
assert_equal 5, tree.get(:number_five)
|
19
|
+
assert_equal 5, tree.get('number_five')
|
14
20
|
|
15
21
|
number_one = 1
|
16
22
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forester
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eugenio Bruno
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubytree
|
@@ -79,15 +79,16 @@ files:
|
|
79
79
|
- Rakefile
|
80
80
|
- forester.gemspec
|
81
81
|
- lib/forester.rb
|
82
|
-
- lib/forester/
|
83
|
-
- lib/forester/
|
84
|
-
- lib/forester/
|
85
|
-
- lib/forester/node_content.rb
|
86
|
-
- lib/forester/node_content_factory.rb
|
82
|
+
- lib/forester/node_content/base.rb
|
83
|
+
- lib/forester/node_content/dictionary.rb
|
84
|
+
- lib/forester/node_content/factory.rb
|
85
|
+
- lib/forester/node_content/list.rb
|
87
86
|
- lib/forester/tree_factory.rb
|
88
87
|
- lib/forester/tree_node.rb
|
88
|
+
- lib/forester/tree_node_ext/aggregators.rb
|
89
|
+
- lib/forester/tree_node_ext/mutators.rb
|
90
|
+
- lib/forester/tree_node_ext/views.rb
|
89
91
|
- lib/forester/version.rb
|
90
|
-
- lib/forester/views.rb
|
91
92
|
- test/test_mutators.rb
|
92
93
|
- test/test_treenode.rb
|
93
94
|
- test/test_views.rb
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Forester
|
2
|
-
class HashWithIndifferentAccess < SimpleDelegator
|
3
|
-
|
4
|
-
def has_key?(key)
|
5
|
-
equivs(key).any? { |k| super(k) }
|
6
|
-
end
|
7
|
-
|
8
|
-
def fetch(key, default = nil, &block)
|
9
|
-
maybe_key = equivs(key).select { |k| __getobj__.has_key?(k) }
|
10
|
-
if maybe_key.empty?
|
11
|
-
super(key)
|
12
|
-
else
|
13
|
-
super(maybe_key.first)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def [](key)
|
18
|
-
equivs(key).map { |k| super(k) }.compact.first || super
|
19
|
-
end
|
20
|
-
|
21
|
-
protected
|
22
|
-
|
23
|
-
def equivs(key)
|
24
|
-
[key.to_sym, key.to_s]
|
25
|
-
end
|
26
|
-
|
27
|
-
end
|
28
|
-
end
|
@@ -1,37 +0,0 @@
|
|
1
|
-
module Forester
|
2
|
-
class NodeContent
|
3
|
-
|
4
|
-
def initialize(hash)
|
5
|
-
@hash = HashWithIndifferentAccess.new(hash)
|
6
|
-
end
|
7
|
-
|
8
|
-
def field_names
|
9
|
-
@hash.keys
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_hash
|
13
|
-
@hash.each_with_object({}) do |(k, v), hash|
|
14
|
-
hash[k.to_sym] = v
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def set!(key, value)
|
19
|
-
@hash[key] = value
|
20
|
-
end
|
21
|
-
|
22
|
-
def method_missing(name, *args, &block)
|
23
|
-
if @hash.has_key?(name)
|
24
|
-
@hash[name]
|
25
|
-
elsif block_given?
|
26
|
-
yield args.fetch(0, {yield_to: self})[:yield_to]
|
27
|
-
else
|
28
|
-
raise ArgumentError.new("the node \"#{self.name}\" does not have any \"#{name}\"")
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def respond_to_missing?(name, include_private = false)
|
33
|
-
@hash.has_key?(name) || super
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|
37
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Forester
|
2
|
-
class NodeContentFactory
|
3
|
-
|
4
|
-
class << self
|
5
|
-
|
6
|
-
def from_hash(hash, children_key)
|
7
|
-
NodeContent.new(HashWithIndifferentAccess.new(without_key(hash, children_key)))
|
8
|
-
end
|
9
|
-
|
10
|
-
private
|
11
|
-
|
12
|
-
def without_key(hash, key)
|
13
|
-
hash.reject { |k, _| k.to_s == key.to_s }
|
14
|
-
end
|
15
|
-
|
16
|
-
end
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
data/lib/forester/views.rb
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
module Forester
|
2
|
-
module Views
|
3
|
-
|
4
|
-
def as_nested_hash(options = {})
|
5
|
-
default_options = {
|
6
|
-
fields_to_include: field_names,
|
7
|
-
stringify_keys: false
|
8
|
-
}
|
9
|
-
options = default_options.merge(options)
|
10
|
-
|
11
|
-
hash = content.to_hash
|
12
|
-
hash.select! { |k, _| options[:fields_to_include].map(&:to_s).include? k.to_s }
|
13
|
-
hash = hash.each_with_object({}) { |(k, v), h| h[k.to_s] = v } if options[:stringify_keys]
|
14
|
-
|
15
|
-
children_key = :children
|
16
|
-
children_key = children_key.to_s if options[:stringify_keys]
|
17
|
-
|
18
|
-
hash.merge({ children_key => children.map { |node| node.as_nested_hash(options) } })
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
File without changes
|