forester 4.3.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,110 +1,98 @@
1
1
  module Forester
2
2
  class TreeNode < Tree::TreeNode
3
-
4
- extend Forwardable
5
- def_delegators :@content, :fields, :has?, :put!, :add!, :del!
6
-
7
- include Aggregators
8
- include Validators
3
+ include Iterators
9
4
  include Mutators
10
- include Views
5
+ include Validators
6
+ include Serializers
11
7
 
12
- alias_method :max_level, :node_height
8
+ def node_level
9
+ node_depth + 1
10
+ end
11
+
12
+ def nodes_of_depth(d) # relative to this node
13
+ d.between?(0, node_height) ? each_level.take(d + 1).last : []
14
+ end
13
15
 
14
16
  def nodes_of_level(l)
15
- l.between?(0, max_level) ? each_level.take(l + 1).last : []
16
- end
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
17
+ nodes_of_depth(l - 1)
36
18
  end
37
19
 
38
- def each_content(options = {})
39
- node_enumerator = each_node(options)
20
+ def path_from_root
21
+ (parentage || []).reverse + [self]
22
+ end
40
23
 
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
24
+ def paths_to_leaves
25
+ paths_to(leaves)
51
26
  end
52
27
 
53
- def each_level
54
- Enumerator.new do |yielder|
55
- level = [self]
56
- until level.empty?
57
- yielder << level
58
- level = level.flat_map(&:children)
59
- end
60
- end
28
+ def paths_to(descendants)
29
+ descendants.map { |node| node.path_from_root.drop(node_depth) }
61
30
  end
62
31
 
63
- def get(field, options = {}, &if_missing)
64
- default_options = {
65
- default: :raise,
66
- subtree: false, # if false, traversal is ignored
67
- traversal: :depth_first
68
- }
69
- options = default_options.merge(options)
32
+ def leaf?
33
+ is_leaf?
34
+ end
70
35
 
71
- return own_and_descendants(field, { traversal: options[:traversal] }, &if_missing) if options[:subtree]
36
+ def leaves
37
+ each_leaf
38
+ end
72
39
 
73
- if has?(field)
74
- content.get(field)
75
- elsif block_given?
76
- yield self
77
- elsif options[:default] != :raise
78
- options[:default]
79
- else
80
- raise ArgumentError, "the node \"#{best_name}\" does not have \"#{field}\""
40
+ def paths_of_length(l)
41
+ paths_to(nodes_of_depth(l))
42
+ end
43
+
44
+ def leaves_when_pruned_to_depth(d)
45
+ ret = []
46
+ each_node(traversal: :breadth_first) do |node|
47
+ relative_depth_of_descendant = node.node_depth - node_depth
48
+ break if relative_depth_of_descendant > d
49
+ ret.push(node) if node.leaf? || (relative_depth_of_descendant == d)
81
50
  end
51
+
52
+ ret
82
53
  end
83
54
 
84
- def contents(options = {})
85
- each_node(options).map(&:content)
55
+ def leaves_when_pruned_to_level(l)
56
+ leaves_when_pruned_to_depth(l - 1)
86
57
  end
87
58
 
88
- def same_as?(other)
89
- return false unless content == other.content
90
- return false unless size == other.size
91
- nodes_of_other = other.each_node.to_a
92
- each_node.with_index do |n, i|
93
- next if i == 0
94
- return false unless n.same_as?(nodes_of_other[i])
95
- end
96
- true
59
+ def paths_to_leaves_when_pruned_to_depth(d)
60
+ paths_to(leaves_when_pruned_to_depth(d))
97
61
  end
98
62
 
99
- private
63
+ def paths_to_leaves_when_pruned_to_level(l)
64
+ paths_to(leaves_when_pruned_to_level(l))
65
+ end
66
+
67
+ def get(field, default = :raise)
68
+ if has_field?(field)
69
+ content[field]
70
+ elsif block_given?
71
+ yield(field, self)
72
+ elsif default != :raise
73
+ default
74
+ else
75
+ missing_key =
76
+ if field.is_a?(Symbol)
77
+ ":#{field}"
78
+ elsif field.is_a?(String)
79
+ "'#{field}'"
80
+ else
81
+ field
82
+ end
83
+ error_message = "key not found: #{missing_key} in node content \"#{content}\""
84
+ raise KeyError, error_message
85
+ end
86
+ end
100
87
 
101
- def best_name
102
- get(:name, default: name)
88
+ def has_field?(field)
89
+ content.key?(field)
103
90
  end
104
91
 
92
+ private
93
+
105
94
  def as_array(object)
106
95
  [object].flatten(1)
107
96
  end
108
-
109
97
  end
110
98
  end
@@ -0,0 +1,57 @@
1
+ module Forester
2
+ module Iterators
3
+ TRAVERSAL_MODES = {
4
+ depth_first: :each,
5
+ breadth_first: :breadth_each,
6
+ preorder: :preordered_each,
7
+ postorder: :postordered_each
8
+ }.freeze
9
+
10
+ def each_node(options = {}, &block)
11
+ default_options = {
12
+ traversal: :depth_first
13
+ }
14
+ options = default_options.merge(options)
15
+
16
+ method_name = traversal_modes[options[:traversal]]
17
+
18
+ if method_name
19
+ send(method_name, &block)
20
+ else
21
+ available = traversal_modes.keys.join(', ')
22
+ raise ArgumentError, "invalid traversal mode: #{options[:traversal]} (#{available})"
23
+ end
24
+ end
25
+
26
+ def each_content(options = {})
27
+ node_enumerator = each_node(options)
28
+
29
+ Enumerator.new do |yielder|
30
+ stop = false
31
+ until stop
32
+ begin
33
+ yielder << node_enumerator.next.content
34
+ rescue StopIteration
35
+ stop = true
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ def each_level
42
+ Enumerator.new do |yielder|
43
+ level = [self]
44
+ until level.empty?
45
+ yielder << level
46
+ level = level.flat_map(&:children)
47
+ end
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def traversal_modes
54
+ TRAVERSAL_MODES
55
+ end
56
+ end
57
+ end
@@ -1,7 +1,6 @@
1
1
  module Forester
2
2
  module Mutators
3
-
4
- def change_parent_to!(new_parent_node, options = {})
3
+ def change_parent_to(new_parent_node, options = {})
5
4
  default_options = {
6
5
  subtree: true
7
6
  }
@@ -12,70 +11,64 @@ module Forester
12
11
  new_parent_node.add(self) # always as its last child
13
12
  end
14
13
 
15
- def add_child_content!(content, options = {}, &block)
16
- new_node = TreeFactory.node_from_hash(content, options, &block)
14
+ def add_child_content(content)
15
+ new_node = Forester.tree_factory.node_from_content(content)
17
16
  add(new_node)
18
17
  end
19
18
 
20
- def add_field!(name, definition, options = {})
21
- add_fields!([name: name, definition: definition], options)
22
- end
23
-
24
- def add_fields!(fields, options = {})
25
- default_options = {
26
- subtree: true
27
- }
28
- options = default_options.merge(options)
29
-
30
- target_nodes = options[:subtree] ? each_node : [self]
19
+ def add_field_in_node(name, definition)
20
+ value = definition.respond_to?(:call) ? definition.call(self) : definition
31
21
 
32
- target_nodes.each { |node| node.add_fields_to_root!(fields) }
22
+ content[name] = value
33
23
  end
34
24
 
35
- def add_fields_to_root!(fields)
25
+ def add_fields_in_node(fields)
36
26
  fields.each do |field|
37
- value = field[:definition]
38
- value = value.call(self) if value.respond_to?(:call)
39
-
40
- put!(field[:name], value)
27
+ add_field_in_node(field[:name], field[:definition])
41
28
  end
42
29
  end
43
30
 
44
- def delete_values!(field, values, options = {})
31
+ def add_field_in_subtree(name, definition)
32
+ add_fields_in_subtree([name: name, definition: definition])
33
+ end
34
+
35
+ def add_fields_in_subtree(fields)
36
+ each_node { |node| node.add_fields_in_node(fields) }
37
+ end
38
+
39
+ def delete_values_in_node(field, values, options = {})
45
40
  default_options = {
46
- percolate: false,
47
- subtree: true
41
+ percolate: false
48
42
  }
49
43
  options = default_options.merge(options)
50
44
 
51
- target_nodes = options[:subtree] ? each_node : [self]
52
-
53
- target_nodes.each { |node| node.delete_values_from_root!(field, values, options[:percolate]) }
54
- end
55
-
56
- def delete_values_from_root!(field, values, percolate)
57
- return unless has?(field)
45
+ return unless has_field?(field)
58
46
  current_value = get(field)
59
- return unless current_value.is_a?(Array)
60
47
 
61
- new_value =
62
- if percolate
63
- current_value & as_array(values)
64
- else
65
- current_value - as_array(values)
66
- end
48
+ operation = options[:percolate] ? :& : :-
49
+ return unless current_value.respond_to?(operation)
50
+
51
+ new_value = current_value.public_send(operation, as_array(values))
67
52
 
68
- put!(field, new_value)
53
+ content[field] = new_value
69
54
  end
70
55
 
71
- def percolate_values!(field, values, options = {})
72
- delete_values!(field, values, options.merge(percolate: true))
56
+ def delete_values_in_subtree(field, values, options = {})
57
+ default_options = {
58
+ percolate: false
59
+ }
60
+ options = default_options.merge(options)
61
+
62
+ each_node { |node| node.delete_values_in_node(field, values, options) }
73
63
  end
74
64
 
75
- def remove_levels_past!(last_level_to_keep)
65
+ def remove_levels_past(last_level_to_keep)
66
+ unless last_level_to_keep >= 1
67
+ raise ArgumentError, "expected a positive integer, got #{last_level_to_keep}"
68
+ end
69
+
76
70
  nodes_of_level(last_level_to_keep).map(&:remove_all!)
77
71
  self
78
72
  end
79
-
80
73
  end
81
74
  end
@@ -0,0 +1,48 @@
1
+ module Forester
2
+ module Serializers
3
+ def as_root_hash(options = {})
4
+ default_options = {
5
+ max_depth: :none,
6
+ children_key: 'children',
7
+ stringify_keys: false,
8
+ symbolize_keys: false,
9
+ include_fields: :all,
10
+ exclude_fields: :none
11
+ }
12
+ options = default_options.merge(options)
13
+
14
+ max_depth = options[:max_depth]
15
+ max_depth = -1 if max_depth == :none
16
+
17
+ adjusted_content = content.each_with_object(content.class.new) do |(k, v), h|
18
+ adjusted_key = k
19
+ adjusted_key = k.to_s if options[:stringify_keys]
20
+ adjusted_key = k.to_sym if options[:symbolize_keys]
21
+
22
+ unless options[:include_fields] == :all
23
+ next unless options[:include_fields].include?(adjusted_key)
24
+ end
25
+
26
+ unless options[:exclude_fields] == :none
27
+ next if options[:exclude_fields].include?(adjusted_key)
28
+ end
29
+
30
+ h[adjusted_key] = v
31
+ end
32
+
33
+ children_key = options[:children_key]
34
+ children_key = children_key.to_s if options[:stringify_keys]
35
+ children_key = children_key.to_sym if options[:symbolize_keys]
36
+
37
+ next_children =
38
+ if max_depth == 0
39
+ []
40
+ else
41
+ next_options = options.merge(max_depth: max_depth - 1)
42
+ children.map { |node| node.as_root_hash(next_options) }
43
+ end
44
+
45
+ adjusted_content.merge(children_key => next_children)
46
+ end
47
+ end
48
+ end
@@ -1,32 +1,21 @@
1
1
  module Forester
2
2
  module Validators
3
-
4
3
  def validate_uniqueness_of_field(field, options = {})
5
4
  validate_uniqueness_of_fields([field], options)
6
5
  end
7
6
 
8
7
  def validate_uniqueness_of_fields(fields, options = {})
9
- default_options = {
10
- combination: false,
11
- first_failure_only: false,
12
- within_subtrees_of_level: 0,
13
- among_siblings_of_level: :not_siblings,
14
- field_for_failures: :name,
15
- as_failure: ->(node) { node.get(options[:field_for_failures]) }
16
- }
17
- options = default_options.merge(options)
18
-
19
- return of_combination_of_fields(fields, options) if options[:combination]
8
+ options = default_validator_options.merge(options)
20
9
 
21
10
  failures = Hash.new(Hash.new([]))
22
11
 
23
- nodes_of_level(options[:within_subtrees_of_level]).each do |subtree|
12
+ nodes_of_depth(options[:within_subtrees_of_depth]).each do |subtree|
24
13
  visited_nodes = []
25
14
  nodes_to_visit =
26
- if options[:among_siblings_of_level] == :not_siblings
15
+ if options[:among_siblings_of_depth] == :not_siblings
27
16
  subtree.each_node
28
17
  else
29
- nodes_of_level(options[:among_siblings_of_level])
18
+ nodes_of_depth(options[:among_siblings_of_depth])
30
19
  end
31
20
 
32
21
  nodes_to_visit.each do |node|
@@ -53,18 +42,18 @@ module Forester
53
42
  result(failures)
54
43
  end
55
44
 
56
- private
45
+ def validate_uniqueness_of_fields_combination(fields, options = {})
46
+ options = default_validator_options.merge(options)
57
47
 
58
- def of_combination_of_fields(fields, options)
59
48
  failures = Hash.new(Hash.new([]))
60
49
 
61
- nodes_of_level(options[:within_subtrees_of_level]).each do |subtree|
50
+ nodes_of_depth(options[:within_subtrees_of_depth]).each do |subtree|
62
51
  visited_nodes = []
63
52
  nodes_to_visit =
64
- if options[:among_siblings_of_level] == :not_siblings
53
+ if options[:among_siblings_of_depth] == :not_siblings
65
54
  subtree.each_node
66
55
  else
67
- nodes_of_level(options[:among_siblings_of_level])
56
+ nodes_of_depth(options[:among_siblings_of_depth])
68
57
  end
69
58
 
70
59
  nodes_to_visit.each do |node|
@@ -91,12 +80,21 @@ module Forester
91
80
 
92
81
  private
93
82
 
83
+ def default_validator_options
84
+ {
85
+ first_failure_only: false,
86
+ within_subtrees_of_depth: 0,
87
+ among_siblings_of_depth: :not_siblings,
88
+ as_failure: ->(node) { node }
89
+ }
90
+ end
91
+
94
92
  def all_have?(field, nodes)
95
93
  all_have_all?([field], nodes)
96
94
  end
97
95
 
98
96
  def all_have_all?(fields, nodes)
99
- nodes.all? { |n| fields.all? { |f| n.has?(f) } }
97
+ nodes.all? { |n| fields.all? { |f| n.has_field?(f) } }
100
98
  end
101
99
 
102
100
  def same_values?(field, nodes)
@@ -110,8 +108,8 @@ module Forester
110
108
  end
111
109
 
112
110
  def prepare_hash(hash, key, subkey)
113
- hash[key] = {} unless hash.has_key?(key)
114
- hash[key][subkey] = [] unless hash[key].has_key?(subkey)
111
+ hash[key] = {} unless hash.key?(key)
112
+ hash[key][subkey] = [] unless hash[key].key?(subkey)
115
113
  end
116
114
 
117
115
  def add_failure_if_new(failures, key, subkey, value)
@@ -129,6 +127,5 @@ module Forester
129
127
  failures: failures
130
128
  }
131
129
  end
132
-
133
130
  end
134
131
  end