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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1450e6fa82ffadb91424237f6c9527bc0e9bd9d2
4
- data.tar.gz: ec8325c5dc3bde7784651b4d5039edcb7f694631
3
+ metadata.gz: 8a6a1cffe04ece8277a5828f27c9f36f1ea2ba31
4
+ data.tar.gz: bdafe433a52570d2e30535698febf2a50ebf8b47
5
5
  SHA512:
6
- metadata.gz: 3ca55344989f3b2de019c3dfc5d3cc648c745cb1d8810fc0af44c5a8723b3ad951615a6e959385271bb8d6ced06e1c43182b4efe8e9776038be2cfa39201681c
7
- data.tar.gz: 523d4c868e6d329854a29758d29ab9ffdf8dad56daafdcd341ed9cdd5cae91921c12b1d9961e8eaec6b4f39acb418b894f4c94fc644d88ca9e8f90b05cace538
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* and *enzymator*, this gem lets you build trees and run queries against them.
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 arbitrary, configurable aggregations on trees. Simple examples can be found in the tests.
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-07-27'
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,9 @@
1
+ module Forester
2
+ module NodeContent
3
+ class Base < SimpleDelegator
4
+
5
+
6
+
7
+ end
8
+ end
9
+ end
@@ -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 = NodeContentFactory.from_hash(hash, children_key)
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.to_sym, key.to_s].map { |k| hash[k] }.compact.first || default || hash.fetch(root_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 = TreeFactory.from_hash(child, children_key)
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)
@@ -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
- content.public_send(field, { yield_to: self }, &block)
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 field_names
31
- content.field_names
41
+ def name
42
+ content.get(:name, super)
32
43
  end
33
44
 
34
45
  def contents
@@ -3,7 +3,7 @@ module Forester
3
3
 
4
4
  def add_field!(name, definition)
5
5
  value = if definition.respond_to? :call then definition.call(self) else definition end
6
- content.set!(name, value)
6
+ put!(name, value)
7
7
  end
8
8
 
9
9
  def add_field_to_subtree!(name, definition)
@@ -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
@@ -1,7 +1,7 @@
1
1
  module Forester
2
2
  class Version
3
3
  MAJOR = 3
4
- MINOR = 0
4
+ MINOR = 1
5
5
  PATCH = 0
6
6
  PRE = nil
7
7
 
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'
@@ -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.0.0
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-07-27 00:00:00.000000000 Z
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/aggregators.rb
83
- - lib/forester/hash_with_indifferent_access.rb
84
- - lib/forester/mutators.rb
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
@@ -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