hash_tree 0.1.0 → 0.1.1
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 +4 -4
- data/.ruby-version +1 -1
- data/hash_tree.gemspec +10 -1
- data/lib/hash_tree/version.rb +1 -1
- data/lib/hash_tree.rb +116 -20
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6e76908b94b4a03beeb674da5e4e4b810094198e216d5be3b43f4acbd672f8e3
|
4
|
+
data.tar.gz: 3e83394d0f76fb228895a15b5953bff081ae5e853e648d6ca0b0cd18ae5a95a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d90748a40a23b0d8d1648649b41124a3ad8ae55896dfe73ff95173648c9913142844d0b20aff1a8709cdeae39d1483da648183c40e13d56f8ae709ae321dc0cd
|
7
|
+
data.tar.gz: 5a52fb194d2de6d47298a2fa36391c82f945e255412d5175cdd04c3a76990d0872421cb0e3e5d4ef04208b373eef71078bceaa633c39757f8af48c150b212127
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby-2.
|
1
|
+
ruby-2.7.1
|
data/hash_tree.gemspec
CHANGED
@@ -36,8 +36,17 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
37
|
spec.require_paths = ["lib"]
|
38
38
|
|
39
|
-
spec.add_development_dependency "bundler", "~>
|
39
|
+
spec.add_development_dependency "bundler", "~> 2.2.10"
|
40
40
|
spec.add_development_dependency "rake", "~> 12.3.3"
|
41
41
|
spec.add_development_dependency "rspec", "~> 3.0"
|
42
42
|
spec.add_development_dependency "indented_io"
|
43
|
+
|
44
|
+
# In development mode override load paths for gems whose source are located
|
45
|
+
# as siblings of this project directory
|
46
|
+
if File.directory?("#{__dir__}/.git")
|
47
|
+
local_projects = Dir["../*"].select { |path|
|
48
|
+
File.directory?(path) && File.exist?("#{path}/Gemfile")
|
49
|
+
}.map { |relpath| "#{File.absolute_path(relpath)}/lib" }
|
50
|
+
$LOAD_PATH.unshift *local_projects
|
51
|
+
end
|
43
52
|
end
|
data/lib/hash_tree/version.rb
CHANGED
data/lib/hash_tree.rb
CHANGED
@@ -4,17 +4,34 @@ require "indented_io"
|
|
4
4
|
# Simple tree of hashes
|
5
5
|
#
|
6
6
|
# TODO
|
7
|
-
# o
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
7
|
+
# o Different interfaces:
|
8
|
+
# Stable (the default - attributes are cached recursively)
|
9
|
+
# Dynamic (for trees with non-trivial edits - attributes are cached dynamically)
|
10
|
+
# Dot (for dot functionality
|
11
|
+
# Algorithms
|
12
|
+
#
|
13
|
+
# o XPath?!
|
14
|
+
# o enumerators: descendants, ancestors, preorder, etc. etc.
|
15
|
+
# o HashTree::Array
|
11
16
|
# o Semantics of <=>: strictly partial so it returns nil on no common path?
|
12
17
|
# o Drop <=> ?
|
13
18
|
# o Implement #common to return common path of two elements
|
14
|
-
# o
|
19
|
+
# o Make is possible to include HashTree as a module instead of deriving from class
|
20
|
+
# o []=
|
21
|
+
# o #traverse filter?
|
22
|
+
# o more elaborate selectors - emit, skip
|
23
|
+
# :include => emit node and continue to children
|
24
|
+
# :fetch => emit node but skip children
|
25
|
+
# :exclude => skip node and continue to children
|
26
|
+
# :prune => skip node and children
|
15
27
|
#
|
28
|
+
# + each
|
29
|
+
# + traverse
|
30
|
+
# + HashTree::Set, HashTree::Map
|
31
|
+
|
16
32
|
module HashTree
|
17
33
|
class Error < StandardError; end
|
34
|
+
class KeyNotFound < Error; end
|
18
35
|
|
19
36
|
# The base Node type for HashTree implementations. It is not supposed to be called
|
20
37
|
# from user-code
|
@@ -25,9 +42,13 @@ module HashTree
|
|
25
42
|
# Hash from key to child node
|
26
43
|
attr_reader :children
|
27
44
|
|
28
|
-
def initialize(parent, key)
|
45
|
+
def initialize(parent, key, attach: true)
|
29
46
|
@children = {}
|
30
|
-
|
47
|
+
if attach
|
48
|
+
parent&.do_attach(key, self)
|
49
|
+
else
|
50
|
+
@parent = parent
|
51
|
+
end
|
31
52
|
end
|
32
53
|
|
33
54
|
# Attach a child to self
|
@@ -52,8 +73,14 @@ module HashTree
|
|
52
73
|
# Returns true iff key is included in children
|
53
74
|
def key?(key) @children.key?(key) end
|
54
75
|
|
76
|
+
# List of keys
|
77
|
+
def keys() @children.keys end
|
78
|
+
|
79
|
+
# List of values
|
80
|
+
def values() @children.values end
|
81
|
+
|
55
82
|
# The root object or self if parent is nil
|
56
|
-
def root() @root ||=
|
83
|
+
def root() @root ||= (parent&.root || self) end
|
57
84
|
|
58
85
|
# List of parents up to the root element. If include_self is true, also
|
59
86
|
# include self as the first element
|
@@ -67,17 +94,73 @@ module HashTree
|
|
67
94
|
(@ancestors ||= parents(false).reverse) + (include_self ? [self] : [])
|
68
95
|
end
|
69
96
|
|
70
|
-
# Recursively lookup object by dot-separated list of keys
|
97
|
+
# Recursively lookup object by dot-separated list of keys. Note that for
|
98
|
+
# this to work, key names must not contain dot characters ('.')
|
71
99
|
#
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
100
|
+
# The reverse method, #path, is only defined for HashTree::Set because a
|
101
|
+
# HashTree::Map node doesn't know its own key
|
102
|
+
def dot(path_or_keys, raise_on_not_found: true)
|
103
|
+
keys = path_or_keys.is_a?(String) ? path_or_keys.split(".") : path_or_keys
|
104
|
+
key = keys.shift or return self
|
105
|
+
if child = dot_lookup(key)
|
106
|
+
child.send(:dot, keys, raise_on_not_found: raise_on_not_found)
|
107
|
+
elsif raise_on_not_found
|
108
|
+
raise KeyNotFound, "Can't lookup '#{key}' in #{self.path.inspect}"
|
109
|
+
else
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# True if path_or_keys address a node
|
115
|
+
def dot?(path_or_keys)
|
116
|
+
!dot(path_or_keys, raise_on_not_found: false).nil?
|
117
|
+
end
|
118
|
+
|
119
|
+
# List of [key, child] tuples
|
120
|
+
def each(&block)
|
121
|
+
if block_given?
|
122
|
+
children.each { |key, child| yield(key, child) }
|
123
|
+
else
|
124
|
+
children.each
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Return list of nodes in preorder
|
129
|
+
def preorder
|
130
|
+
[self] + values.inject([]) { |a,e| a + e.preorder }
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return list of nodes in postorder
|
134
|
+
def postorder
|
135
|
+
values.inject([]) { |a,e| a + e.postorder } + [self]
|
136
|
+
end
|
137
|
+
|
138
|
+
# EXPERIMENTAL
|
139
|
+
#
|
140
|
+
# Process nodes in preorder. The block is called with the current node as
|
141
|
+
# argument and can return true to process its child nodes or false or nil
|
142
|
+
# to skip them. #traverse doesn't accumulate a result, this is the
|
143
|
+
# responsibily of the block
|
144
|
+
def traverse(&block)
|
145
|
+
values.each { |child| child.traverse(&block) } if yield(self)
|
146
|
+
end
|
147
|
+
|
148
|
+
# EXPERIMENTAL
|
149
|
+
#
|
150
|
+
# Process nodes in postorder (bottom-up). The block is called with the
|
151
|
+
# current node and a list of aggregated children. The optional filter
|
152
|
+
# argument is a lambda. The lambda is called with the current node as
|
153
|
+
# argument and the node if skipped if the lambda returns falsy
|
154
|
+
def aggregate(filter = lambda { |node| true }, &block)
|
155
|
+
if filter.nil? || filter.call(self)
|
156
|
+
yield(self, values.map { |child| child.aggregate(filter, &block) }.compact)
|
157
|
+
else
|
158
|
+
nil
|
159
|
+
end
|
77
160
|
end
|
78
161
|
|
79
162
|
protected
|
80
|
-
# Attach a child node
|
163
|
+
# Attach a child node
|
81
164
|
def do_attach(key, child)
|
82
165
|
!@children.key?(key) or raise Error, "Duplicate child key: #{key.inspect}"
|
83
166
|
!child.parent or raise Error, "Child is already attached"
|
@@ -86,6 +169,13 @@ module HashTree
|
|
86
169
|
child.send(:clear_cached_properties)
|
87
170
|
end
|
88
171
|
|
172
|
+
# Lookup key in current object. This method is used by #dot to lookup a
|
173
|
+
# child element and can be overridden to make #dot cross branches in the
|
174
|
+
# hierarchy. This is useful when you use HashTree to implement a graph
|
175
|
+
def dot_lookup(key)
|
176
|
+
self[key]
|
177
|
+
end
|
178
|
+
|
89
179
|
private
|
90
180
|
# Recursively clear cached properties like @parents in each node in the
|
91
181
|
# tree. Should be called whenever the node is attached or detached from a
|
@@ -96,7 +186,7 @@ module HashTree
|
|
96
186
|
# themselves constructed recursively so that iff a node has a cached
|
97
187
|
# property, then all its parents will also cache it
|
98
188
|
def clear_cached_properties()
|
99
|
-
if@root || @parents || @ancestors || @path
|
189
|
+
if @root || @parents || @ancestors || @path
|
100
190
|
@root = nil
|
101
191
|
@parents = nil
|
102
192
|
@ancestors = nil
|
@@ -109,15 +199,21 @@ module HashTree
|
|
109
199
|
class Set < Node
|
110
200
|
alias node_attach attach
|
111
201
|
|
112
|
-
# Key of this node
|
202
|
+
# Key of this node.
|
203
|
+
# TODO: Make it possible/required to alias this method to provide an internal key
|
113
204
|
attr_reader :key
|
114
205
|
|
115
|
-
def initialize(parent, key)
|
116
|
-
super(parent, @key = key)
|
206
|
+
def initialize(parent, key, attach: true)
|
207
|
+
super(parent, @key = key, attach: attach)
|
117
208
|
end
|
118
209
|
|
119
210
|
def attach(child) do_attach(child.key, child) end
|
120
211
|
|
212
|
+
def retach(node)
|
213
|
+
node.parent.detach(node.key)
|
214
|
+
attach(node)
|
215
|
+
end
|
216
|
+
|
121
217
|
# Unique dot-separated list of keys leading from the root object to
|
122
218
|
# self. Note that the root object is not included in the path so that
|
123
219
|
#
|
@@ -126,7 +222,7 @@ module HashTree
|
|
126
222
|
# is always true
|
127
223
|
#
|
128
224
|
# Note that for this to work, keys may not contain a dots ('.')
|
129
|
-
def path() @path ||= ancestors(true)[1..-1].join(".") end
|
225
|
+
def path() @path ||= ancestors(true)[1..-1].map(&:key).join(".") end
|
130
226
|
|
131
227
|
# A set node is rendered as its key
|
132
228
|
def to_s() key.to_s end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hash_tree
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Claus Rasmussen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 2.2.10
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 2.2.10
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
- !ruby/object:Gem::Version
|
105
105
|
version: '0'
|
106
106
|
requirements: []
|
107
|
-
rubygems_version: 3.
|
107
|
+
rubygems_version: 3.2.26
|
108
108
|
signing_key:
|
109
109
|
specification_version: 4
|
110
110
|
summary: A tree implemented as nested hashes
|