key_tree 0.4.3 → 0.5.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
  SHA256:
3
- metadata.gz: '0435887525ff8f573dcfb0bfe96a09b48768a5b2c9d33f10a9fb03cedae7440d'
4
- data.tar.gz: 2c733e6d41c19a4fe8f4e9a167212a900acb779bf77c5150a4eab15126673197
3
+ metadata.gz: 47919143e113a4448b85cf800098227c6e47a5bcb92501e7d612722e7c06898f
4
+ data.tar.gz: 2b44f183949638a0a379297c0cb8ea6c50d0aa756c7e5a17e52324c9d3d96957
5
5
  SHA512:
6
- metadata.gz: fba0374da8e56048ba4c98218e42c08673119163a8055ef8e8ae5129d59101e7971e65384f12cb3a0404fde5139732a3a89f605c2f46516dd24529b85367951f
7
- data.tar.gz: d2c8161ae7689454c36103ce914ab5f40b12da89a17f9911e7f1c50a8c1076dd9076289c25fd8fde12ea1c2c73767b7fd8d8241c0bf486a6418ee7c0dd13da1d
6
+ metadata.gz: 2122707f4be42067a53db54c4fa98a13ecc55ff64fddc91c8ec3d40dbd5fddef9c1e3f01028ee3e662ce8e47d7f10bb4a3bc6d2ffbd156291e9223dedb4e283b
7
+ data.tar.gz: a1c2bc1e22162a646d2a88444e70017f093cde440fce3b9eeeefeba65d662c7e2d65ea3bad6f5c7d9f12cb3191a37e7fd4d4a380a1971e1aefa2ff4b2ed73c0b
data/.rubocop.yml CHANGED
@@ -1,3 +1,7 @@
1
1
  Metrics/BlockLength:
2
2
  Exclude:
3
3
  - 'spec/*_spec.rb'
4
+
5
+ Style/BlockDelimiters:
6
+ Exclude:
7
+ - 'spec/*_spec.rb'
data/RELEASE_NOTES.md ADDED
@@ -0,0 +1,51 @@
1
+ # Release Notes
2
+
3
+ ## v0.5.0 – 2018-04-17
4
+
5
+ ### Changed methods
6
+
7
+ * `KeyTree.load(type, serialization, prefix: nil)`
8
+ * `KeyTree::Forest#[key] { |key, original, incoming| }`
9
+ * `KeyTree::Forest#fetch(key) { |key, original, incoming| }`
10
+ * `KeyTree::Forest#flatten { |key, original, incoming| }`
11
+ * `KeyTree::Tree#merge { |key, original, incoming| }`
12
+ * `KeyTree::Tree#merge! { |key, original, incoming| }`
13
+
14
+ ### New methods
15
+
16
+ * `KeyTree::Loader.fallback(loader)`
17
+
18
+ ### New features
19
+
20
+ #### Merge value selection
21
+ Improve merge related methods in `KeyTree::Tree`, and `KeyTree::Forest`
22
+ to take a `Hash#merge` style block argument, to allow control of the result when a key i present on both sides of a merge operation.
23
+
24
+ * 083b25c Add merge value selector to Forest#[]
25
+ * e813e55 Add merge value selection to Forest#fetch
26
+ * 581bc82 Add method to get list of trees with key
27
+ * 0f66f03 Pass merge value selector via Forest#flatten
28
+ * df9b80e Pass any merge selection block to super
29
+
30
+ #### Key prefix for file loading
31
+ When a key file has a name like `prefix@name.ext`, the `prefix` part will be prepended to all keys loaded from the file.
32
+
33
+ * fbe333a Changed call syntax for KeyTree.load
34
+ * 595902c Load keytree with prefix from files with @ in name
35
+ * d23a7e1 Allow prepending a prefix when loading keys
36
+
37
+ #### Fallback for KeyTree loaders
38
+ Allow a fallback class for handling loading of file types where no loader is specified, e.g. to ignore all files with unrecognized extension for `KeyTree.load_all`.
39
+
40
+ * a9d096c Add tree loader fallback
41
+
42
+ ### Bug fixes
43
+
44
+ #### Proper breadth first flattening
45
+
46
+ * ff327f2 Use tree enumarator for Forest#key? and #prefix?
47
+ * 74fa15d Rewrite Forest#[]
48
+ * 177de08 Use tree enumerator in Forest#[]
49
+ * d161fe1 Use tree enumerator in Forest#flatten
50
+ * b0c94df Add breadth-first enumerator for trees
51
+ * 3468f28 Remove forest vs tree sorting nonsense
@@ -11,7 +11,7 @@ module KeyTree
11
11
  def self.[](*contents)
12
12
  contents.reduce(Forest.new) do |result, content|
13
13
  result << KeyTree[content]
14
- end.sort!
14
+ end
15
15
  end
16
16
 
17
17
  # For a numeric key, return the n:th tree in the forest
@@ -22,70 +22,56 @@ module KeyTree
22
22
  # key path matches in trees further away, returning nil. This preserves
23
23
  # the constraints that only leaves may contain a value.
24
24
  #
25
- def [](key)
26
- case key
27
- when Numeric
28
- super(key)
29
- else
30
- detect do |tree_or_forest|
31
- return tree_or_forest[key] if tree_or_forest.key?(key)
32
- return nil if tree_or_forest.prefix?(key)
33
- end
34
- end
25
+ def [](key, &merger)
26
+ return super(key) if key.is_a?(Numeric)
27
+ fetch(key, &merger)
28
+ rescue KeyError
29
+ nil
35
30
  end
36
31
 
37
- # Trees are always smaller than forrests.
38
- # Forests are compared by nesting depth, not number of trees.
39
- #
40
- def <=>(other)
41
- return 0 if self == other
32
+ def fetch(key)
33
+ return tree_with_key(key).fetch(key) unless block_given?
42
34
 
43
- case other
44
- when Forest
45
- depth <=> other.depth
46
- when Tree
47
- 1
48
- else
49
- raise ArgumentError, 'only forests and trees are comparable'
50
- end
35
+ values = trees_with_key(key).map { |tree| tree.fetch(key) }
36
+ values.reverse.reduce { |left, right| yield(key, left, right) }
51
37
  end
52
38
 
53
- # The nesting depth of a forest
54
- def depth
55
- reduce(1) do |result, tree_or_forest|
56
- [result, content_depth(tree_or_forest) + 1].max
57
- end
39
+ def tree_with_key(key)
40
+ result = trees.detect { |tree| tree.prefix?(key) }
41
+ result || raise(KeyError, "key not found: #{key}")
42
+ end
43
+
44
+ def trees_with_key(key)
45
+ result = trees.select { |tree| tree.prefix?(key) }
46
+ raise(KeyError, "key not found: #{key}") if result.empty?
47
+ result
58
48
  end
59
49
 
60
50
  def key?(key)
61
- any? { |tree_or_forest| tree_or_forest.key?(key) }
51
+ trees.any? { |tree| tree.key?(key) }
62
52
  end
63
53
 
64
54
  def prefix?(key)
65
- any? { |tree_or_forest| tree_or_forest.prefix?(key) }
55
+ trees.any? { |tree_or_forest| tree_or_forest.prefix?(key) }
66
56
  end
67
57
 
68
58
  # Flattening a forest produces a tree with the equivalent view of key paths
69
59
  #
70
- def flatten
71
- reduce(Tree[]) do |result, tree_or_forest|
72
- case tree_or_forest
73
- when Forest
74
- tree_or_forest.flatten.merge(result)
75
- else
76
- tree_or_forest.merge(result)
77
- end
60
+ def flatten(&merger)
61
+ trees.reverse_each.reduce(Tree[]) do |result, tree|
62
+ result.merge!(tree, &merger)
78
63
  end
79
64
  end
80
65
 
81
- private
82
-
83
- def content_depth(content)
84
- case content
85
- when Forest
86
- content.depth
87
- else
88
- 0
66
+ # Return a breadth-first Enumerator for all the trees in the forest,
67
+ # and any nested forests
68
+ def trees
69
+ Enumerator.new do |yielder|
70
+ remaining = [self]
71
+ remaining.each do |woods|
72
+ next yielder << woods if woods.is_a?(Tree)
73
+ woods.each { |wood| remaining << wood }
74
+ end
89
75
  end
90
76
  end
91
77
  end
@@ -0,0 +1,10 @@
1
+ module KeyTree
2
+ module Loader
3
+ # KeyTree loader that ignores payload and produces an empty tree
4
+ module Nil
5
+ def self.load(*_drop)
6
+ {}
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,3 +1,5 @@
1
+ require 'key_tree/loader/nil'
2
+
1
3
  module KeyTree
2
4
  # Module to manage key tree loaders
3
5
  module Loader
@@ -8,7 +10,7 @@ module KeyTree
8
10
 
9
11
  def self.[](type)
10
12
  type = type.to_sym if type.respond_to?(:to_sym)
11
- loaders[type]
13
+ loaders[type] || @fallback
12
14
  end
13
15
 
14
16
  def self.[]=(type, loader_class)
@@ -16,6 +18,10 @@ module KeyTree
16
18
  loaders[type] = loader_class
17
19
  end
18
20
 
21
+ def self.fallback(loader)
22
+ @fallback = loader
23
+ end
24
+
19
25
  private_class_method
20
26
 
21
27
  def self.loaders
data/lib/key_tree/tree.rb CHANGED
@@ -54,31 +54,18 @@ module KeyTree
54
54
  keys.any? { |key| key.conflict?(Path[key_or_path]) }
55
55
  end
56
56
 
57
- # All trees are created equal. Forests are always larger than trees.
58
- #
59
- def <=>(other)
60
- case other
61
- when Forest
62
- -1
63
- when Tree
64
- 0
65
- else
66
- raise ArgumentError, 'only trees and forests are comparable'
67
- end
68
- end
69
-
70
57
  # The merging of trees needs some extra consideration; due to the
71
58
  # nature of key paths, prefix conflicts must be deleted
72
59
  #
73
- def merge!(other)
60
+ def merge!(other, &merger)
74
61
  other = Tree[other] unless other.is_a?(Tree)
75
62
  delete_if { |key, _| other.conflict?(key) }
76
63
  super
77
64
  end
78
65
  alias << merge!
79
66
 
80
- def merge(other)
81
- dup.merge!(other)
67
+ def merge(other, &merger)
68
+ dup.merge!(other, &merger)
82
69
  end
83
70
  alias + merge
84
71
  end
data/lib/key_tree.rb CHANGED
@@ -27,35 +27,41 @@ module KeyTree
27
27
  # Load a KeyTree from some external serialization
28
28
  #
29
29
  # load +type+: +serialization+
30
+ # load +key_prefix+, +type+: +serialization+
30
31
  #
31
32
  # +type+ is upcased to form a class name that should provide a
32
33
  # +.load+ class method (like YAML or JSON does).
33
34
  #
34
- # Example:
35
- # load(yaml: "---\na: 1\n")
35
+ # If a +key_prefix+ is given, it will be prepended to the loaded data.
36
+ #
37
+ # Examples:
38
+ # load(:yaml, "---\na: 1\n")
36
39
  # => {"a" => 1}
37
40
  #
38
- def self.load(typed_serialization = {})
39
- unless typed_serialization.size == 1
40
- raise ArgumentError, "pick one: #{typed_serialization.keys}"
41
- end
42
-
43
- type, serialization = typed_serialization.flatten
41
+ # load(:yaml, "---\nb: 2\n", prefix: 'a')
42
+ # => {"a.b" => 2}
43
+ #
44
+ def self.load(type, serialization, prefix: nil)
45
+ type = type.to_sym unless type.nil?
44
46
  loader = Loader[type]
45
- self[loader.load(serialization)].with_meta_data do |meta_data|
46
- meta_data << { load: { type: type,
47
- loader: loader } }
47
+ contents = loader.load(serialization)
48
+ contents = { prefix => contents } unless prefix.nil?
49
+
50
+ self[contents].with_meta_data do |meta_data|
51
+ meta_data << { load: { type: type, loader: loader } }
52
+ meta_data << { load: { prefix: prefix } } unless prefix.nil?
48
53
  end
49
54
  end
50
55
 
51
56
  # Open an external file and load contents into a KeyTree
52
- #
57
+ # When the file basename begins with 'prefix@', the prefix
58
+ # is prepended to all keys in the filee.
53
59
  def self.open(file_name)
54
60
  type = File.extname(file_name)[/[^.]+/]
55
- type = type.to_sym unless type.nil?
61
+ prefix = File.basename(file_name)[/(.+)@/, 1]
56
62
 
57
63
  keytree = File.open(file_name, mode: 'rb:utf-8') do |file|
58
- load_from_file(file, type)
64
+ load_from_file(file, type, prefix)
59
65
  end
60
66
 
61
67
  return keytree unless block_given?
@@ -79,8 +85,8 @@ module KeyTree
79
85
 
80
86
  private_class_method
81
87
 
82
- def self.load_from_file(file, type)
83
- load(type => file.read).with_meta_data do |meta_data|
88
+ def self.load_from_file(file, type, prefix)
89
+ load(type, file.read, prefix: prefix).with_meta_data do |meta_data|
84
90
  file_path = file.path
85
91
  meta_data << { file: { path: file_path,
86
92
  name: File.basename(file_path),
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: key_tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Calle Englund
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-04-07 00:00:00.000000000 Z
11
+ date: 2018-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git-version-bump
@@ -95,6 +95,7 @@ files:
95
95
  - Gemfile
96
96
  - LICENSE.txt
97
97
  - README.md
98
+ - RELEASE_NOTES.md
98
99
  - Rakefile
99
100
  - bin/console
100
101
  - bin/setup
@@ -102,6 +103,7 @@ files:
102
103
  - lib/key_tree.rb
103
104
  - lib/key_tree/forest.rb
104
105
  - lib/key_tree/loader.rb
106
+ - lib/key_tree/loader/nil.rb
105
107
  - lib/key_tree/meta_data.rb
106
108
  - lib/key_tree/path.rb
107
109
  - lib/key_tree/tree.rb