forester 3.0.0 → 3.1.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: 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