forester 4.2.1 → 4.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 074b7d10b4541bc5b2e70d3013990b687ad32e7c
4
- data.tar.gz: 99fd514939cd284d71b6388f167d7bc5226a2ce0
3
+ metadata.gz: 10d063cbc60a90d3e5ebe19867e2b47ae74f59fe
4
+ data.tar.gz: b2fb62970c91d0f75c18f01efb0f7e502da1e05e
5
5
  SHA512:
6
- metadata.gz: 19320bf3a2d4dbe72f192d0ad370cb0f082206e572371e8013ce036e0f645062025300420ae10e25ab1e368300bf45ced1223dfc973e040f53d759be1b5d025f
7
- data.tar.gz: 202fe5a6556f0df1fe6f12b9c547da61d835151ca23e0bbfb5261aa1505828a6a4fe0feb09dfb42341ff7769f1213cbc5e38f602a6980144ebe2c32cda8ddb24
6
+ metadata.gz: 53099d57189bde522ff5310b9532ba91f9338beec8f2676735ea837a1c0e23977f333ad177e15d6268981bb0ba24dd104d89b1d5e75ec1c660e0288db87a1cd5
7
+ data.tar.gz: 8a1e31896c0469536e4279e3095d573cb84f048e689f2693e70134dbaa0263720034cf97f4ceac72628e45720a10388dac085e133afb1292ffa3375a005fa648
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ Gemfile.lock
31
+ .ruby-version
32
+ .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0
4
+ - 2.1
5
+ - 2.2
6
+ - 2.3
7
+ - 2.4
8
+ - jruby
9
+ before_install:
10
+ - gem install bundler
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Forester
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/forester.svg)](https://badge.fury.io/rb/forester)
4
+
3
5
  Based on *rubytree*, this gem lets you build trees and run queries against them.
4
6
 
5
7
  ## FAQ
data/Rakefile CHANGED
@@ -1,8 +1,17 @@
1
1
  require 'rake/testtask'
2
2
 
3
3
  Rake::TestTask.new do |t|
4
+ t.libs << 'test'
4
5
  t.pattern = "test/**/test_*.rb"
5
6
  end
6
7
 
7
8
  desc "Run tests"
8
- task :default => :test
9
+ task default: :test
10
+
11
+ desc 'Start a REPL session'
12
+ task :console do
13
+ require 'forester'
14
+ require 'pry'
15
+ ARGV.clear
16
+ Pry.start
17
+ end
data/forester.gemspec CHANGED
@@ -7,24 +7,24 @@ Gem::Specification.new do |s|
7
7
  s.name = 'forester'
8
8
  s.version = Forester::Version
9
9
  s.date = '2017-02-26'
10
- s.summary = "A gem to represent and interact with tree data structures"
11
- s.description = "Based on rubytree, this gem lets you build trees and run queries against them."
12
- s.authors = ["Eugenio Bruno"]
10
+ s.summary = "A trees library"
11
+ s.description = "Forester is a collection of utilities to represent and interact with tree data structures."
12
+ s.authors = "Eugenio Bruno"
13
13
  s.email = 'eugeniobruno@gmail.com'
14
14
  s.homepage = 'https://github.com/eugeniobruno/forester'
15
15
  s.license = 'MIT'
16
16
 
17
17
  s.files = `git ls-files`.split($/)
18
- s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) }
19
18
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
20
19
  s.require_paths = ['lib']
21
20
 
22
21
  s.required_ruby_version = '>= 2.0.0'
23
22
 
24
- s.add_runtime_dependency 'rubytree', ['0.9.7']
23
+ s.add_runtime_dependency 'rubytree', '0.9.7'
25
24
 
26
- s.add_development_dependency 'rake', ['~> 11.2']
27
- s.add_development_dependency 'minitest', ['~> 5.9']
28
- s.add_development_dependency 'pry-byebug', ['~> 3.4']
25
+ s.add_development_dependency 'rake', '~> 12.0'
26
+ s.add_development_dependency 'minitest', '~> 5.10'
27
+ s.add_development_dependency 'simplecov', '~> 0.14'
28
+ s.add_development_dependency 'pry-byebug', '~> 3.4'
29
29
 
30
30
  end
@@ -76,7 +76,7 @@ module Forester
76
76
  self.class.new(super)
77
77
  end
78
78
 
79
- protected
79
+ private
80
80
 
81
81
  def equivs(key)
82
82
  [key, key.to_s, key.to_s.to_sym].uniq
@@ -1,25 +1,23 @@
1
1
  module Forester
2
2
  module NodeContent
3
- class Factory
3
+ module Factory
4
4
 
5
- class << self
5
+ extend self
6
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
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
16
12
 
17
- private
13
+ def from_array(array)
14
+ List.new(array)
15
+ end
18
16
 
19
- def without_key(hash, key)
20
- hash.reject { |k, _| k.to_s == key.to_s }
21
- end
17
+ private
22
18
 
19
+ def without_key(hash, key)
20
+ hash.reject { |k, _| k.to_s == key.to_s }
23
21
  end
24
22
 
25
23
  end
@@ -1,62 +1,75 @@
1
1
  module Forester
2
- class TreeFactory
2
+ module TreeFactory
3
3
 
4
- class << self
4
+ extend self
5
5
 
6
- def from_yaml_file(file, options = {})
7
- from_hash_with_root_key(YAML.load_file(file), options)
8
- end
6
+ def node_from_hash(hash, options = {}, &block)
7
+ # TODO remove the whole node_content folder in next major version
8
+ # and let the user choose a class for the contents via a custom parser.
9
+ # That class should have the interface of the one used by the default parser.
10
+ # The default parser should be the constructor of Hash::Accessible, which
11
+ # is defined in the gem 'hash_ext'. Add it as a runtime dependency.
12
+ do_node_from_hash(hash, nil, options, &block)
13
+ end
9
14
 
10
- def from_root_hash(hash, options = {})
11
- from_hash_with_root_key({ root: hash }, options)
12
- end
15
+ def from_yaml_file(file, options = {})
16
+ from_hash_with_root_key(YAML.load_file(file), options)
17
+ end
13
18
 
14
- def from_hash_with_root_key(hash, options = {})
15
- default_options = {
16
- max_level: :last,
17
- children_key: :children,
18
- root_key: :root
19
- }
20
- options = default_options.merge(options)
19
+ def from_root_hash(hash, options = {})
20
+ from_hash_with_root_key({ root: hash }, options)
21
+ end
21
22
 
22
- dummy_root = TreeNode.new('<TEMP>')
23
- real_root = fetch_indifferently(hash, options[:root_key])
23
+ def from_hash_with_root_key(hash, options = {})
24
+ default_options = {
25
+ max_level: :last,
26
+ children_key: :children,
27
+ root_key: :root
28
+ }
29
+ options = default_options.merge(options)
24
30
 
25
- max_level = options[:max_level]
26
- max_level = -2 if max_level == :last
31
+ options[:max_level] = -2 if options[:max_level] == :last
27
32
 
28
- tree = with_children(dummy_root, [real_root], options[:children_key], max_level + 1).first_child
29
- tree.detached_subtree_copy
30
- end
33
+ dummy_root = TreeNode.new('<TEMP>')
34
+ real_root = fetch_indifferently(hash, options[:root_key])
35
+
36
+ tree = with_children(dummy_root, [real_root], options[:children_key], options[:max_level] + 1).first_child
37
+ tree.detached_subtree_copy
38
+ end
31
39
 
32
- protected
40
+ private
33
41
 
34
- def fetch_indifferently(hash, key, default = nil)
35
- [key, key.to_s, key.to_s.to_sym].uniq.map { |k| hash[k] }.compact.first || default || hash.fetch(root_key)
36
- end
42
+ def fetch_indifferently(hash, key, default = nil)
43
+ [key, key.to_s, key.to_s.to_sym].uniq.map { |k| hash[k] }.compact.first || default || hash.fetch(root_key)
44
+ end
37
45
 
38
- def with_children(tree_node, children, children_key, levels_remaining)
39
- return tree_node if levels_remaining == 0
40
- children.each do |child|
41
- child_node = node_from_hash(child, children_key)
42
- child_children = fetch_indifferently(child, children_key, [])
46
+ def with_children(tree_node, children, children_key, levels_remaining)
47
+ return tree_node if levels_remaining == 0
48
+ children.each do |child_hash|
49
+ child_node = do_node_from_hash(child_hash, children_key)
50
+ child_children = fetch_indifferently(child_hash, children_key, [])
43
51
 
44
- tree_node << with_children(child_node, child_children, children_key, levels_remaining - 1)
45
- end
46
- tree_node
52
+ tree_node << with_children(child_node, child_children, children_key, levels_remaining - 1)
47
53
  end
54
+ tree_node
55
+ end
48
56
 
49
- def node_from_hash(hash, children_key, options = {})
50
- default_options = {
51
- uid: SecureRandom.uuid
52
- }
53
- options = default_options.merge(options)
57
+ def do_node_from_hash(hash, children_key, options = {}, &block)
58
+ content = NodeContent::Factory.from_hash(hash, children_key)
59
+ node(content, options, &block)
60
+ end
54
61
 
55
- name = options[:uid]
56
- content = NodeContent::Factory.from_hash(hash, children_key)
57
- TreeNode.new(name, content)
58
- end
62
+ def node(content, options = {})
63
+ default_options = {
64
+ uid: SecureRandom.uuid
65
+ }
66
+ options = default_options.merge(options)
67
+
68
+ name = options[:uid]
69
+ new_node = TreeNode.new(name, content)
59
70
 
71
+ yield new_node if block_given?
72
+ new_node
60
73
  end
61
74
 
62
75
  end
@@ -10,12 +10,46 @@ module Forester
10
10
  include Views
11
11
 
12
12
  alias_method :max_level, :node_height
13
- alias_method :each_node, :breadth_each
14
13
 
15
14
  def nodes_of_level(l)
16
15
  l.between?(0, max_level) ? each_level.take(l + 1).last : []
17
16
  end
18
17
 
18
+ def each_node(options = {})
19
+ default_options = {
20
+ traversal: :breadth_first
21
+ }
22
+ options = default_options.merge(options)
23
+
24
+ case options[:traversal]
25
+ when :breadth_first
26
+ breadth_each
27
+ when :depth_first
28
+ each
29
+ when :postorder
30
+ postordered_each
31
+ when :preorder
32
+ preordered_each
33
+ else
34
+ raise ArgumentError, "invalid traversal mode: #{options[:traversal]}"
35
+ end
36
+ end
37
+
38
+ def each_content(options = {})
39
+ node_enumerator = each_node(options)
40
+
41
+ Enumerator.new do |yielder|
42
+ stop = false
43
+ until stop
44
+ begin
45
+ yielder << node_enumerator.next.content
46
+ rescue StopIteration
47
+ stop = true
48
+ end
49
+ end
50
+ end
51
+ end
52
+
19
53
  def each_level
20
54
  Enumerator.new do |yielder|
21
55
  level = [self]
@@ -29,11 +63,12 @@ module Forester
29
63
  def get(field, options = {}, &if_missing)
30
64
  default_options = {
31
65
  default: :raise,
32
- subtree: false
66
+ subtree: false, # if false, traversal is ignored
67
+ traversal: :depth_first
33
68
  }
34
69
  options = default_options.merge(options)
35
70
 
36
- return own_and_descendants(field, &if_missing) if options[:subtree]
71
+ return own_and_descendants(field, { traversal: options[:traversal] }, &if_missing) if options[:subtree]
37
72
 
38
73
  if has?(field)
39
74
  content.get(field)
@@ -42,12 +77,12 @@ module Forester
42
77
  elsif options[:default] != :raise
43
78
  options[:default]
44
79
  else
45
- raise ArgumentError.new("the node \"#{name}\" does not have \"#{field}\"")
80
+ raise ArgumentError, "the node \"#{best_name}\" does not have \"#{field}\""
46
81
  end
47
82
  end
48
83
 
49
- def contents
50
- each_node.map(&:content)
84
+ def contents(options = {})
85
+ each_node(options).map(&:content)
51
86
  end
52
87
 
53
88
  def same_as?(other)
@@ -61,7 +96,11 @@ module Forester
61
96
  true
62
97
  end
63
98
 
64
- protected
99
+ private
100
+
101
+ def best_name
102
+ get(:name, default: name)
103
+ end
65
104
 
66
105
  def as_array(object)
67
106
  [object].flatten(1)
@@ -1,10 +1,17 @@
1
1
  module Forester
2
2
  module Aggregators
3
3
 
4
- def own_and_descendants(field, &if_missing)
4
+ def own_and_descendants(field, options = {}, &if_missing)
5
+ default_options = {
6
+ traversal: :depth_first
7
+ }
8
+ options = default_options.merge(options)
9
+
5
10
  if_missing = -> (node) { [] } unless block_given?
6
11
 
7
- flat_map { |node| as_array(node.get(field, &if_missing)) }
12
+ each_node(traversal: options[:traversal]).flat_map do |node|
13
+ as_array(node.get(field, &if_missing))
14
+ end
8
15
  end
9
16
 
10
17
  def nodes_with(field, values, options = {})
@@ -49,6 +56,9 @@ module Forester
49
56
  found_nodes = nodes_with(options[:by_field], options[:keywords], single: options[:single_node] )
50
57
 
51
58
  return found_nodes if options[:then_get] == :nodes
59
+ # TODO this method should never return [nil]. This happens when single_node
60
+ # is true and no matches are found. Moreover, if then_get is not :nodes,
61
+ # it should not raise. Both cases should return [].
52
62
 
53
63
  found_nodes.flat_map do |node|
54
64
  node.get(options[:then_get], subtree: options[:subtree])
@@ -1,8 +1,24 @@
1
1
  module Forester
2
2
  module Mutators
3
3
 
4
+ def change_parent_to!(new_parent_node, options = {})
5
+ default_options = {
6
+ subtree: true
7
+ }
8
+ options = default_options.merge(options)
9
+
10
+ children.each { |child| parent.add(child) } unless options[:subtree]
11
+
12
+ new_parent_node.add(self) # always as its last child
13
+ end
14
+
15
+ def add_child_content!(content, options = {}, &block)
16
+ new_node = TreeFactory.node_from_hash(content, options, &block)
17
+ add(new_node)
18
+ end
19
+
4
20
  def add_field!(name, definition, options = {})
5
- add_fields!([{ name: name, definition: definition }], options)
21
+ add_fields!([name: name, definition: definition], options)
6
22
  end
7
23
 
8
24
  def add_fields!(fields, options = {})
@@ -27,7 +27,7 @@ module Forester
27
27
  children.map { |node| node.as_root_hash(next_options) }
28
28
  end
29
29
 
30
- hash.merge({ children_key => next_children })
30
+ hash.merge(children_key => next_children)
31
31
  end
32
32
 
33
33
  end
@@ -1,8 +1,8 @@
1
1
  module Forester
2
2
  class Version
3
3
  MAJOR = 4
4
- MINOR = 2
5
- PATCH = 1
4
+ MINOR = 3
5
+ PATCH = 0
6
6
  PRE = nil
7
7
 
8
8
  class << self
@@ -0,0 +1,41 @@
1
+ require 'simplecov'
2
+ SimpleCov.start do
3
+ add_filter 'test/'
4
+ end
5
+
6
+ require 'minitest/autorun'
7
+
8
+ require 'pry-byebug'
9
+
10
+ require 'forester'
11
+
12
+ class Forester::Test < Minitest::Test
13
+
14
+ private
15
+
16
+ PATH_TO_TREES = "#{File.dirname(__FILE__)}/trees"
17
+ PATH_TO_SIMPLE_TREE = "#{PATH_TO_TREES}/simple_tree.yml"
18
+ TREE = Forester::TreeFactory.from_yaml_file(PATH_TO_SIMPLE_TREE)
19
+
20
+ BINARY_TREE = Forester::TreeFactory.node_from_hash(name: :top) do |parent|
21
+ parent.add_child_content!(name: :left) do |left|
22
+ left.add_child_content!(name: :left_left) do |left_left|
23
+ left_left.add_child_content!(name: :left_left_left)
24
+ end
25
+ left.add_child_content!(name: :left_right)
26
+ end
27
+ parent.add_child_content!(name: :right) do |right|
28
+ right.add_child_content!(name: :right_left)
29
+ right.add_child_content!(name: :right_right)
30
+ end
31
+ end
32
+
33
+ def tree
34
+ TREE
35
+ end
36
+
37
+ def binary_tree
38
+ BINARY_TREE
39
+ end
40
+
41
+ end
@@ -0,0 +1,48 @@
1
+ require 'minitest_helper'
2
+
3
+ class TestAdHocTree < Forester::Test
4
+
5
+ def test_content
6
+ assert_equal({ number: 1 }, Forester::TreeFactory.node_from_hash(number: 1).content)
7
+ end
8
+
9
+ def test_each_node_type
10
+ assert_instance_of Enumerator, binary_tree.each_node
11
+ end
12
+
13
+ def test_each_content_type
14
+ assert_instance_of Enumerator, binary_tree.each_content
15
+ end
16
+
17
+ def test_each_content_depth_first
18
+ expected = %i(top left left_left left_left_left left_right right right_left right_right)
19
+ assert_equal expected, binary_tree.get(:name, subtree: true, traversal: :depth_first)
20
+ assert_equal expected, binary_tree.each_node(traversal: :depth_first).map { |n| n.get(:name) }
21
+ assert_equal expected, binary_tree.each_content(traversal: :depth_first).map { |c| c[:name] }
22
+ end
23
+
24
+ def test_each_content_breadth_first
25
+ expected = %i(top left right left_left left_right right_left right_right left_left_left)
26
+
27
+ assert_equal expected, binary_tree.get(:name, subtree: true, traversal: :breadth_first)
28
+ assert_equal expected, binary_tree.each_node(traversal: :breadth_first).map { |n| n.get(:name) }
29
+ assert_equal expected, binary_tree.each_node.map { |n| n.get(:name) }
30
+ assert_equal expected, binary_tree.each_content(traversal: :breadth_first).map { |c| c[:name] }
31
+ assert_equal expected, binary_tree.each_content.map { |c| c[:name] }
32
+ end
33
+
34
+ def test_each_content_postorder
35
+ expected = %i(left_left_left left_left left_right left right_left right_right right top)
36
+ assert_equal expected, binary_tree.get(:name, subtree: true, traversal: :postorder)
37
+ assert_equal expected, binary_tree.each_node(traversal: :postorder).map { |n| n.get(:name) }
38
+ assert_equal expected, binary_tree.each_content(traversal: :postorder).map { |c| c[:name] }
39
+ end
40
+
41
+ def test_each_content_preorder
42
+ expected = %i(top left left_left left_left_left left_right right right_left right_right)
43
+ assert_equal expected, binary_tree.get(:name, subtree: true, traversal: :preorder)
44
+ assert_equal expected, binary_tree.each_node(traversal: :preorder).map { |n| n.get(:name) }
45
+ assert_equal expected, binary_tree.each_content(traversal: :preorder).map { |c| c[:name] }
46
+ end
47
+
48
+ end
@@ -1,21 +1,29 @@
1
- require 'minitest/autorun'
2
- require 'forester'
1
+ require 'minitest_helper'
3
2
 
4
- require_relative './simple_tree_helper'
5
-
6
- class TestAggregators < Minitest::Test
7
-
8
- include SimpleTreeHelper
3
+ class TestAggregators < Forester::Test
9
4
 
10
5
  def test_group_by_sibling_subtrees
11
-
12
6
  expected = {
13
- "First node of level 2" => ["Already in level 2", "I want to be the very best", "like no one ever was"],
14
- "Second node of level 2" => ["I have a sibling to my left", "She wants to catch them all"],
15
- "Third node of level 2" => ["Reached level 3", "It's dark", "A hidden secret lies in the deepest leaves...", "Just kidding.", "Could forester handle trees with hundreds of levels?", "Maybe."]
7
+ "First node of level 2" => [
8
+ "Already in level 2",
9
+ "I want to be the very best",
10
+ "like no one ever was"
11
+ ],
12
+ "Second node of level 2" => [
13
+ "I have a sibling to my left",
14
+ "She wants to catch them all"
15
+ ],
16
+ "Third node of level 2" => [
17
+ "Reached level 3",
18
+ "It's dark",
19
+ "A hidden secret lies in the deepest leaves...",
20
+ "Just kidding.",
21
+ "Could forester handle trees with hundreds of levels?",
22
+ "Maybe."
23
+ ]
16
24
  }
17
25
 
18
- actual = @@tree.group_by_sibling_subtrees(
26
+ actual = tree.group_by_sibling_subtrees(
19
27
  level: 2,
20
28
  aggregation_field: 'strings'
21
29
  )
@@ -24,14 +32,13 @@ class TestAggregators < Minitest::Test
24
32
  end
25
33
 
26
34
  def test_group_by_sibling_subtrees_with_ancestry
27
-
28
35
  expected = {
29
36
  ["First node of level 1", "First node of level 2"] => ["Already in level 2", "I want to be the very best", "like no one ever was"],
30
37
  ["First node of level 1", "Second node of level 2"] => ["I have a sibling to my left", "She wants to catch them all"],
31
38
  ["Second node of level 1", "Third node of level 2"] => ["Reached level 3", "It's dark", "A hidden secret lies in the deepest leaves...", "Just kidding.", "Could forester handle trees with hundreds of levels?", "Maybe."]
32
39
  }
33
40
 
34
- actual = @@tree.group_by_sibling_subtrees(
41
+ actual = tree.group_by_sibling_subtrees(
35
42
  level: 2,
36
43
  aggregation_field: 'strings',
37
44
  ancestry_in_keys: true
@@ -41,9 +48,14 @@ class TestAggregators < Minitest::Test
41
48
  end
42
49
 
43
50
  def test_nodes_with
44
- expected_names = ["A hidden secret lies in the deepest leaves...", "Just kidding.", "Could forester handle trees with hundreds of levels?", "Maybe."]
45
-
46
- found_nodes = @@tree.nodes_with('name', 'Second node of level 3')
51
+ expected_names = [
52
+ "A hidden secret lies in the deepest leaves...",
53
+ "Just kidding.",
54
+ "Could forester handle trees with hundreds of levels?",
55
+ "Maybe."
56
+ ]
57
+
58
+ found_nodes = tree.nodes_with('name', 'Second node of level 3')
47
59
  assert_equal 1, found_nodes.length
48
60
 
49
61
  actual_names = found_nodes.flat_map do |node|
@@ -54,24 +66,23 @@ class TestAggregators < Minitest::Test
54
66
  end
55
67
 
56
68
  def test_search
57
-
58
69
  expected = [7]
59
70
 
60
- actual_1 = @@tree.search({
71
+ actual_1 = tree.search({
61
72
  by_field: 'name',
62
73
  keywords: 'Second node of level 3',
63
74
  then_get: 'value',
64
75
  subtree: false
65
76
  })
66
77
 
67
- actual_2 = @@tree.search({
78
+ actual_2 = tree.search({
68
79
  by_field: 'name',
69
80
  keywords: ['Second node of level 3', 'Not present name'],
70
81
  then_get: 'value',
71
82
  subtree: false
72
83
  })
73
84
 
74
- actual_3 = @@tree.search({
85
+ actual_3 = tree.search({
75
86
  by_field: 'strings',
76
87
  keywords: 'A hidden secret lies in the deepest leaves...',
77
88
  then_get: 'value',
@@ -82,7 +93,7 @@ class TestAggregators < Minitest::Test
82
93
  assert_equal expected, actual_3
83
94
 
84
95
  expected_values = [7, 8, 9]
85
- actual_values = @@tree.search({
96
+ actual_values = tree.search({
86
97
  by_field: 'name',
87
98
  keywords: 'Second node of level 3',
88
99
  then_get: 'value',
@@ -1,89 +1,96 @@
1
- require 'minitest/autorun'
2
- require 'forester'
1
+ require 'minitest_helper'
3
2
 
4
- require_relative './simple_tree_helper'
5
-
6
- class TestMutators < Minitest::Test
7
-
8
- def setup
9
- path_to_trees = "#{File.dirname(__FILE__)}/trees"
10
- path_to_simple_tree = "#{path_to_trees}/simple_tree.yml"
11
- @tree = Forester::TreeFactory.from_yaml_file(path_to_simple_tree)
12
- end
3
+ class TestMutators < Forester::Test
13
4
 
14
5
  def test_add_field
6
+ tree.add_field!('number_four', 4)
15
7
 
16
- @tree.add_field!('number_four', 4)
8
+ assert_equal 4, tree.get(:number_four)
9
+ assert_equal 4, tree.get('number_four')
17
10
 
18
- assert_equal 4, @tree.get(:number_four)
19
- assert_equal 4, @tree.get('number_four')
11
+ tree.add_field!(:number_five, 5)
20
12
 
21
- @tree.add_field!(:number_five, 5)
22
-
23
- assert_equal 5, @tree.get(:number_five)
24
- assert_equal 5, @tree.get('number_five')
13
+ assert_equal 5, tree.get(:number_five)
14
+ assert_equal 5, tree.get('number_five')
25
15
 
26
16
  number_one = 1
27
17
 
28
- @tree.add_field!(:number_six, -> (node) { node.get(:number_five) + number_one })
29
-
30
- assert_equal 6, @tree.get(:number_six)
18
+ tree.add_field!(:number_six, -> (node) { node.get(:number_five) + number_one })
31
19
 
20
+ assert_equal 6, tree.get(:number_six)
32
21
  end
33
22
 
34
23
  def test_delete_values
35
-
36
24
  node_1, node_2, node_3 = nodes_with_tags
37
25
 
38
- @tree.delete_values!(:tags, [])
26
+ tree.delete_values!(:tags, [])
39
27
  assert_equal ['First tag', 'Second tag', 'Third tag'], node_1.get(:tags)
40
28
  assert_equal ['Second tag', 'Third tag'], node_2.get(:tags)
41
29
  assert_equal ['Third tag'], node_3.get(:tags)
42
30
 
43
- @tree.delete_values!(:tags, ['First tag'])
31
+ tree.delete_values!(:tags, ['First tag'])
44
32
  assert_equal ['Second tag', 'Third tag'], node_1.get(:tags)
45
33
  assert_equal ['Second tag', 'Third tag'], node_2.get(:tags)
46
34
  assert_equal ['Third tag'], node_3.get(:tags)
47
35
 
48
- @tree.delete_values!(:tags, ['First tag', 'Second tag', 'Third tag'])
36
+ tree.delete_values!(:tags, ['First tag', 'Second tag', 'Third tag'])
49
37
  assert_equal [], node_1.get(:tags)
50
38
  assert_equal [], node_2.get(:tags)
51
39
  assert_equal [], node_3.get(:tags)
52
-
53
40
  end
54
41
 
55
42
  def test_percolate_values
56
-
57
43
  node_1, node_2, node_3 = nodes_with_tags
58
44
 
59
- @tree.percolate_values!(:tags, ['First tag', 'Second tag', 'Third tag'])
45
+ tree.percolate_values!(:tags, ['First tag', 'Second tag', 'Third tag'])
60
46
  assert_equal ['First tag', 'Second tag', 'Third tag'], node_1.get(:tags)
61
47
  assert_equal ['Second tag', 'Third tag'], node_2.get(:tags)
62
48
  assert_equal ['Third tag'], node_3.get(:tags)
63
49
 
64
- @tree.percolate_values!(:tags, ['First tag'])
50
+ tree.percolate_values!(:tags, ['First tag'])
65
51
  assert_equal ['First tag'], node_1.get(:tags)
66
52
  assert_equal [], node_2.get(:tags)
67
53
  assert_equal [], node_3.get(:tags)
68
54
 
69
- @tree.percolate_values!(:tags, [])
55
+ tree.percolate_values!(:tags, [])
70
56
  assert_equal [], node_1.get(:tags)
71
57
  assert_equal [], node_2.get(:tags)
72
58
  assert_equal [], node_3.get(:tags)
59
+ end
60
+
61
+ def test_change_parent_to
62
+ node_to_move = binary_tree.search(single_node: true, by_field: :name, keywords: [:left_left]).first
63
+ old_parent = binary_tree.search(single_node: true, by_field: :name, keywords: [:left]).first
64
+ new_parent = binary_tree.search(single_node: true, by_field: :name, keywords: [:right]).first
73
65
 
66
+ node_to_move.change_parent_to!(new_parent)
67
+
68
+ expected = %i(top left left_right right right_left right_right left_left left_left_left)
69
+ assert_equal expected, binary_tree.get(:name, subtree: true, traversal: :depth_first)
70
+
71
+ node_to_move.change_parent_to!(old_parent, subtree: false)
72
+
73
+ expected = %i(top left left_right left_left right right_left right_right left_left_left)
74
+ assert_equal expected, binary_tree.get(:name, subtree: true, traversal: :depth_first)
74
75
  end
75
76
 
76
- protected
77
+ private
78
+
79
+ def tree
80
+ @mutable_tree ||= super.detached_subtree_copy
81
+ end
82
+
83
+ def binary_tree
84
+ @mutable_binary_tree ||= super.detached_subtree_copy
85
+ end
77
86
 
78
87
  def nodes_with_tags
79
88
  [1, 6, 9].map do |n|
80
-
81
- @tree.search({
89
+ tree.search({
82
90
  single_node: true,
83
91
  by_field: :value,
84
92
  keywords: n
85
93
  }).first
86
-
87
94
  end
88
95
  end
89
96
 
@@ -1,20 +1,17 @@
1
- require 'minitest/autorun'
2
- require 'forester'
1
+ require 'minitest_helper'
3
2
 
4
- require_relative './simple_tree_helper'
5
-
6
- class TestTreeFactory < Minitest::Test
7
-
8
- include SimpleTreeHelper
3
+ class TestTreeFactory < Forester::Test
9
4
 
10
5
  def test_from_root_hash
11
6
  hash = YAML.load_file(PATH_TO_SIMPLE_TREE)
12
7
 
13
- whole_trees = [:last, 29, 4].map { |ml| new_with_max_level(hash, ml) }
8
+ whole_tree = from_root_hash(hash)
9
+
10
+ whole_trees = [whole_tree] + [29, 4].map { |ml| from_root_hash(hash, max_level: ml) }
14
11
 
15
12
  assert(whole_trees.product(whole_trees).all? { |t1, t2| t1.same_as?(t2) })
16
13
 
17
- pruned_trees = (0..2).map { |ml| new_with_max_level(hash, ml) }
14
+ pruned_trees = (0..2).map { |ml| from_root_hash(hash, max_level: ml) }
18
15
 
19
16
  pruned_trees.each_with_index do |t, i|
20
17
  assert_equal(i, t.max_level)
@@ -24,10 +21,10 @@ class TestTreeFactory < Minitest::Test
24
21
  end
25
22
  end
26
23
 
27
- protected
24
+ private
28
25
 
29
- def new_with_max_level(hash, max_level)
30
- Forester::TreeFactory.from_root_hash(hash['root'], max_level: max_level)
26
+ def from_root_hash(hash, options = {})
27
+ Forester::TreeFactory.from_root_hash(hash['root'], options)
31
28
  end
32
29
 
33
30
  end
@@ -1,34 +1,31 @@
1
- require 'minitest/autorun'
2
- require 'forester'
1
+ require 'minitest_helper'
3
2
 
4
- require_relative './simple_tree_helper'
5
-
6
- class TestTreeNode < Minitest::Test
7
-
8
- include SimpleTreeHelper
3
+ class TestTreeNode < Forester::Test
9
4
 
10
5
  def test_size
11
- assert_equal 10, @@tree.size
6
+ assert_equal 10, tree.size
12
7
  end
13
8
 
14
9
  def test_values
15
10
  values = (0..9).to_a
16
11
 
17
12
  expected = values.reduce(:+)
18
- actual = @@tree.reduce(0) { |acum, node| acum + node.get('value') }
13
+ actual = tree.reduce(0) { |acum, node| acum + node.get('value') }
19
14
 
20
15
  assert_equal expected, actual
21
16
 
22
- assert_equal values, @@tree.get('value', subtree: true)
17
+ assert_equal values, tree.get('value', subtree: true)
23
18
  end
24
19
 
25
20
  def test_missing_values
26
- assert_equal 0, @@tree.get('value')
27
- assert_equal 'no', @@tree.get('whatever', default: 'no')
28
- assert_equal 'no', @@tree.get('whatever', default: 'missing') { 'no' }
29
- assert_equal 'no', @@tree.get('whatever') { 'no' }
30
- assert_equal 'no', @@tree.get('whatever') { |n| 'no' }
31
- assert_equal 1, @@tree.get('whatever') { |n| n.get('value') + 1 }
21
+ assert_equal 0, tree.get('value')
22
+ assert_equal 'no', tree.get('whatever', default: 'no')
23
+ assert_equal 'no', tree.get('whatever', default: 'missing') { 'no' }
24
+ assert_equal 'no', tree.get('whatever') { 'no' }
25
+ assert_equal 'no', tree.get('whatever') { |n| 'no' }
26
+ assert_equal 1, tree.get('whatever') { |n| n.get('value') + 1 }
27
+
28
+ assert_raises(ArgumentError) { tree.get('whatever') }
32
29
  end
33
30
 
34
31
  def test_levels
@@ -39,7 +36,7 @@ class TestTreeNode < Minitest::Test
39
36
  ["First node of level 3", "Second node of level 3"],
40
37
  ["First node of level 4", "Second node of level 4"]
41
38
  ]
42
- actual = @@tree.each_level.map { |l| l.map { |n| n.get('name') } }.to_a
39
+ actual = tree.each_level.map { |l| l.map { |n| n.get('name') } }.to_a
43
40
 
44
41
  assert_equal expected, actual
45
42
  end
@@ -1,11 +1,6 @@
1
- require 'minitest/autorun'
2
- require 'forester'
1
+ require 'minitest_helper'
3
2
 
4
- require_relative './simple_tree_helper'
5
-
6
- class TestValidators < Minitest::Test
7
-
8
- include SimpleTreeHelper
3
+ class TestValidators < Forester::Test
9
4
 
10
5
  def test_validate_uniqueness_of_field_uniques
11
6
  expected = {
@@ -15,7 +10,7 @@ class TestValidators < Minitest::Test
15
10
  }
16
11
 
17
12
  ['name', :name, 'special', 'ghost'].each do |field|
18
- actual = @@tree.validate_uniqueness_of_field(field)
13
+ actual = tree.validate_uniqueness_of_field(field)
19
14
  assert_equal expected, actual
20
15
  end
21
16
 
@@ -35,7 +30,7 @@ class TestValidators < Minitest::Test
35
30
  }
36
31
  }
37
32
 
38
- actual = @@tree.validate_uniqueness_of_field(:color)
33
+ actual = tree.validate_uniqueness_of_field(:color)
39
34
  assert_equal expected, actual
40
35
  end
41
36
 
@@ -52,7 +47,7 @@ class TestValidators < Minitest::Test
52
47
  }
53
48
  }
54
49
 
55
- actual = @@tree.validate_uniqueness_of_field(:color, {
50
+ actual = tree.validate_uniqueness_of_field(:color, {
56
51
  first_failure_only: true
57
52
  })
58
53
  assert_equal expected, actual
@@ -71,7 +66,7 @@ class TestValidators < Minitest::Test
71
66
  }
72
67
  }
73
68
 
74
- actual = @@tree.validate_uniqueness_of_field(:color, {
69
+ actual = tree.validate_uniqueness_of_field(:color, {
75
70
  among_siblings_of_level: 1
76
71
  })
77
72
 
@@ -85,7 +80,7 @@ class TestValidators < Minitest::Test
85
80
  failures: {}
86
81
  }
87
82
 
88
- actual = @@tree.validate_uniqueness_of_field(:color, {
83
+ actual = tree.validate_uniqueness_of_field(:color, {
89
84
  among_siblings_of_level: 2
90
85
  })
91
86
 
@@ -105,7 +100,7 @@ class TestValidators < Minitest::Test
105
100
  }
106
101
  }
107
102
 
108
- actual = @@tree.validate_uniqueness_of_field(:color, {
103
+ actual = tree.validate_uniqueness_of_field(:color, {
109
104
  within_subtrees_of_level: 1,
110
105
  })
111
106
 
@@ -125,7 +120,7 @@ class TestValidators < Minitest::Test
125
120
  }
126
121
  }
127
122
 
128
- actual = @@tree.validate_uniqueness_of_field(:color, {
123
+ actual = tree.validate_uniqueness_of_field(:color, {
129
124
  within_subtrees_of_level: 3,
130
125
  })
131
126
 
@@ -139,7 +134,7 @@ class TestValidators < Minitest::Test
139
134
  failures: {}
140
135
  }
141
136
 
142
- actual = @@tree.validate_uniqueness_of_field(:color, {
137
+ actual = tree.validate_uniqueness_of_field(:color, {
143
138
  within_subtrees_of_level: 4,
144
139
  })
145
140
 
@@ -160,7 +155,7 @@ class TestValidators < Minitest::Test
160
155
  }
161
156
  }
162
157
 
163
- actual = @@tree.validate_uniqueness_of_fields(['name', 'color'])
158
+ actual = tree.validate_uniqueness_of_fields(['name', 'color'])
164
159
 
165
160
  assert_equal expected, actual
166
161
  end
@@ -172,7 +167,7 @@ class TestValidators < Minitest::Test
172
167
  failures: {}
173
168
  }
174
169
 
175
- actual = @@tree.validate_uniqueness_of_fields(['name', 'color'], {
170
+ actual = tree.validate_uniqueness_of_fields(['name', 'color'], {
176
171
  combination: true
177
172
  })
178
173
 
@@ -192,7 +187,7 @@ class TestValidators < Minitest::Test
192
187
  }
193
188
  }
194
189
 
195
- actual = @@tree.validate_uniqueness_of_fields(['color', 'tone'], {
190
+ actual = tree.validate_uniqueness_of_fields(['color', 'tone'], {
196
191
  first_failure_only: true
197
192
  })
198
193
 
@@ -217,7 +212,7 @@ class TestValidators < Minitest::Test
217
212
  }
218
213
  }
219
214
 
220
- actual = @@tree.validate_uniqueness_of_fields(['color', 'tone'])
215
+ actual = tree.validate_uniqueness_of_fields(['color', 'tone'])
221
216
 
222
217
  assert_equal expected, actual
223
218
  end
@@ -235,7 +230,7 @@ class TestValidators < Minitest::Test
235
230
  }
236
231
  }
237
232
 
238
- actual = @@tree.validate_uniqueness_of_fields(['color', 'tone'], {
233
+ actual = tree.validate_uniqueness_of_fields(['color', 'tone'], {
239
234
  combination: true
240
235
  })
241
236
 
data/test/test_views.rb CHANGED
@@ -1,18 +1,13 @@
1
- require 'minitest/autorun'
2
- require 'forester'
1
+ require 'minitest_helper'
3
2
 
4
- require_relative './simple_tree_helper'
5
-
6
- class TestViews < Minitest::Test
7
-
8
- include SimpleTreeHelper
3
+ class TestViews < Forester::Test
9
4
 
10
5
  def test_as_root_hash
11
- hash = (YAML.load(File.read(PATH_TO_SIMPLE_TREE)))
6
+ hash = YAML.load_file(PATH_TO_SIMPLE_TREE)
12
7
  add_empty_children_keys(hash['root'])
13
8
 
14
9
  expected = hash['root']
15
- actual = @@tree.as_root_hash(stringify_keys: true)
10
+ actual = tree.as_root_hash(stringify_keys: true)
16
11
 
17
12
  assert_equal expected, actual
18
13
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forester
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.1
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eugenio Bruno
@@ -30,28 +30,42 @@ dependencies:
30
30
  requirements:
31
31
  - - ~>
32
32
  - !ruby/object:Gem::Version
33
- version: '11.2'
33
+ version: '12.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ~>
39
39
  - !ruby/object:Gem::Version
40
- version: '11.2'
40
+ version: '12.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ~>
46
46
  - !ruby/object:Gem::Version
47
- version: '5.9'
47
+ version: '5.10'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
- version: '5.9'
54
+ version: '5.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '0.14'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: pry-byebug
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -66,13 +80,15 @@ dependencies:
66
80
  - - ~>
67
81
  - !ruby/object:Gem::Version
68
82
  version: '3.4'
69
- description: Based on rubytree, this gem lets you build trees and run queries against
70
- them.
83
+ description: Forester is a collection of utilities to represent and interact with
84
+ tree data structures.
71
85
  email: eugeniobruno@gmail.com
72
86
  executables: []
73
87
  extensions: []
74
88
  extra_rdoc_files: []
75
89
  files:
90
+ - .gitignore
91
+ - .travis.yml
76
92
  - Gemfile
77
93
  - LICENSE
78
94
  - README.md
@@ -90,7 +106,8 @@ files:
90
106
  - lib/forester/tree_node_ext/validators.rb
91
107
  - lib/forester/tree_node_ext/views.rb
92
108
  - lib/forester/version.rb
93
- - test/simple_tree_helper.rb
109
+ - test/minitest_helper.rb
110
+ - test/test_ad_hoc_tree.rb
94
111
  - test/test_aggregators.rb
95
112
  - test/test_mutators.rb
96
113
  - test/test_tree_factory.rb
@@ -121,9 +138,10 @@ rubyforge_project:
121
138
  rubygems_version: 2.4.8
122
139
  signing_key:
123
140
  specification_version: 4
124
- summary: A gem to represent and interact with tree data structures
141
+ summary: A trees library
125
142
  test_files:
126
- - test/simple_tree_helper.rb
143
+ - test/minitest_helper.rb
144
+ - test/test_ad_hoc_tree.rb
127
145
  - test/test_aggregators.rb
128
146
  - test/test_mutators.rb
129
147
  - test/test_tree_factory.rb
@@ -1,5 +0,0 @@
1
- module SimpleTreeHelper
2
- PATH_TO_TREES = "#{File.dirname(__FILE__)}/trees"
3
- PATH_TO_SIMPLE_TREE = "#{PATH_TO_TREES}/simple_tree.yml"
4
- @@tree = Forester::TreeFactory.from_yaml_file(PATH_TO_SIMPLE_TREE)
5
- end