forester 4.2.1 → 4.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: 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