key_tree 0.4.3 → 0.5.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
  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