hash_tree 0.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 +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/README.md +84 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/hash_tree.gemspec +43 -0
- data/lib/hash_tree.rb +141 -0
- data/lib/hash_tree/version.rb +3 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1b474a91f66ef7115da2bd73b98c436c6409301e332de18ed2439d4262e3cda6
|
4
|
+
data.tar.gz: 3314e15480623ee8e9a984b0054835bea8c1d77d48cc885eedf653dfa4346ba6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d92a9ec50b0032e3b8a675336d70ec3a5e238905ce71e990750997691bf776104f80d083be7cd59b32c1f59c08c2333f6f4f7ddd2970b9e8e03fb1af16c1965b
|
7
|
+
data.tar.gz: e2bfbd59bcbede4045e7aaae6b68a837cec99395ef00562732af9819581b981d9fde25c3b7c3d3e777c5f86fac931754760fa74e5d2dc7c0d8a90dd674b5b331
|
data/.gitignore
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
/.bundle/
|
2
|
+
/.yardoc
|
3
|
+
/_yardoc/
|
4
|
+
/coverage/
|
5
|
+
/doc/
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/tmp/
|
9
|
+
|
10
|
+
# rspec failure tracking
|
11
|
+
.rspec_status
|
12
|
+
|
13
|
+
# Ignore auto-generated main file
|
14
|
+
/main
|
15
|
+
|
16
|
+
# Ignore Gemfile.lock. See https://stackoverflow.com/questions/4151495/should-gemfile-lock-be-included-in-gitignore
|
17
|
+
/Gemfile.lock
|
18
|
+
|
19
|
+
# Put your personal ignore files in /home/clr/.config/git/ignore
|
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.6.6
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# HashTree
|
2
|
+
|
3
|
+
The `HashTree` module contains the Set and Map classes that implements a tree where each node acts as a hash over child nodes. `HashTree::Set` stores the key internally while `HashTree::Map` use an external key
|
4
|
+
|
5
|
+
`HashTree` is still work-in-progress
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
Basic usage. Read the source for full(er) documentation
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
require 'hash_tree'
|
13
|
+
|
14
|
+
class MyNode < HashTree::Set
|
15
|
+
attr_reader :name
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a root node
|
19
|
+
root = MyNode.new(nil, "ROOT")
|
20
|
+
|
21
|
+
# Build hierarchy through constructor
|
22
|
+
child1 = MyNode.new(root, "CHILD1")
|
23
|
+
child11 = MyNode.new(child1, "CHILD11")
|
24
|
+
|
25
|
+
# Build hierarchy using #attach
|
26
|
+
child2 = MyNode.new(root, "CHILD2")
|
27
|
+
child1.attach(child12)
|
28
|
+
|
29
|
+
# Parent object
|
30
|
+
root.parent # -> nil
|
31
|
+
child1.parent # -> root
|
32
|
+
|
33
|
+
# Lookup value
|
34
|
+
puts child1["CHILD1"] # -> "CHILD1"
|
35
|
+
|
36
|
+
# Check if key exists
|
37
|
+
child1.key?("CHILD11") # -> true
|
38
|
+
child1.key?("no key") # -> false
|
39
|
+
|
40
|
+
# Get the root object
|
41
|
+
child11.root # -> root
|
42
|
+
|
43
|
+
# List of parents up to the root element
|
44
|
+
child11.parents # -> [child1, root]
|
45
|
+
|
46
|
+
# List of ancestors from the root down to parent
|
47
|
+
child11.ancestors # -> [root, child1]
|
48
|
+
|
49
|
+
# String of dot-separated keys leading from the root down to self
|
50
|
+
child11.path # -> "CHILD1.CHILD11"
|
51
|
+
|
52
|
+
# Recursively lookup element by string dot-separated keys
|
53
|
+
root.dot("CHILD1.CHILD11") -> child11
|
54
|
+
```
|
55
|
+
|
56
|
+
## Implementation
|
57
|
+
|
58
|
+
`HashTree` caches properties to avoid repetitive recursive lookups in `#parents`, `#ancestors` etc. The cache is reset every time a node is attached to or detached from a parent
|
59
|
+
|
60
|
+
## Installation
|
61
|
+
|
62
|
+
Add this line to your application's Gemfile:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
gem 'hash_tree'
|
66
|
+
```
|
67
|
+
|
68
|
+
And then execute:
|
69
|
+
|
70
|
+
$ bundle
|
71
|
+
|
72
|
+
Or install it yourself as:
|
73
|
+
|
74
|
+
$ gem install hash_tree
|
75
|
+
|
76
|
+
## Development
|
77
|
+
|
78
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
79
|
+
|
80
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
81
|
+
|
82
|
+
## Contributing
|
83
|
+
|
84
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/clrgit/hash_tree.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "hash_tree"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/hash_tree.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "hash_tree/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "hash_tree"
|
8
|
+
spec.version = HashTree::VERSION
|
9
|
+
spec.authors = ["Claus Rasmussen"]
|
10
|
+
spec.email = ["claus.l.rasmussen@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A tree implemented as nested hashes}
|
13
|
+
spec.description = %q{HashTree::Set and HashTree::Map provides simple tree
|
14
|
+
operations. It is still very much work-in-progress }
|
15
|
+
spec.homepage = "https://github.com/clrgit/hash_tree"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
#
|
22
|
+
# spec.metadata["homepage_uri"] = spec.homepage
|
23
|
+
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
24
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
25
|
+
# else
|
26
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
27
|
+
# "public gem pushes."
|
28
|
+
# end
|
29
|
+
|
30
|
+
# Specify which files should be added to the gem when it is released.
|
31
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
32
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
33
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
34
|
+
end
|
35
|
+
spec.bindir = "exe"
|
36
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
37
|
+
spec.require_paths = ["lib"]
|
38
|
+
|
39
|
+
spec.add_development_dependency "bundler", "~> 1.17"
|
40
|
+
spec.add_development_dependency "rake", "~> 12.3.3"
|
41
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
42
|
+
spec.add_development_dependency "indented_io"
|
43
|
+
end
|
data/lib/hash_tree.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
|
2
|
+
require "indented_io"
|
3
|
+
|
4
|
+
# Simple tree of hashes
|
5
|
+
#
|
6
|
+
# TODO
|
7
|
+
# o each
|
8
|
+
# o traverse
|
9
|
+
# o #ancestors and #parents as enumerators
|
10
|
+
# o HashTree::Set, HashTree::Map, HashTree::Array
|
11
|
+
# o Semantics of <=>: strictly partial so it returns nil on no common path?
|
12
|
+
# o Drop <=> ?
|
13
|
+
# o Implement #common to return common path of two elements
|
14
|
+
# o modularize?
|
15
|
+
#
|
16
|
+
module HashTree
|
17
|
+
class Error < StandardError; end
|
18
|
+
|
19
|
+
# The base Node type for HashTree implementations. It is not supposed to be called
|
20
|
+
# from user-code
|
21
|
+
class Node
|
22
|
+
# Parent node. nil for the root node
|
23
|
+
attr_reader :parent
|
24
|
+
|
25
|
+
# Hash from key to child node
|
26
|
+
attr_reader :children
|
27
|
+
|
28
|
+
def initialize(parent, key)
|
29
|
+
@children = {}
|
30
|
+
parent&.do_attach(key, self)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Attach a child to self
|
34
|
+
#
|
35
|
+
# Implementation is in #do_attach to share code with HashTree::Set#attach
|
36
|
+
# that only takes one parameters
|
37
|
+
def attach(key, child) do_attach(key, child) end
|
38
|
+
|
39
|
+
# Detach a child from self
|
40
|
+
def detach(key, ignore_not_attached: false)
|
41
|
+
@children.key?(key) or raise Error, "Non-existing child key: #{key.inspect}"
|
42
|
+
child = children[key]
|
43
|
+
ignore_not_attached || child.parent or raise Error, "Child is not attached"
|
44
|
+
child.instance_variable_set(:@parent, nil)
|
45
|
+
@children.delete(key)
|
46
|
+
child.send(:clear_cached_properties)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Lookup node by key
|
50
|
+
def [](key) @children[key] end
|
51
|
+
|
52
|
+
# Returns true iff key is included in children
|
53
|
+
def key?(key) @children.key?(key) end
|
54
|
+
|
55
|
+
# The root object or self if parent is nil
|
56
|
+
def root() @root ||= parents.last || self end
|
57
|
+
|
58
|
+
# List of parents up to the root element. If include_self is true, also
|
59
|
+
# include self as the first element
|
60
|
+
def parents(include_self = false)
|
61
|
+
(include_self ? [self] : []) + (@parents ||= (parent&.parents(true) || []))
|
62
|
+
end
|
63
|
+
|
64
|
+
# List of parents from the root element down to parent. If include_self is
|
65
|
+
# true, also include self as the last element
|
66
|
+
def ancestors(include_self = false)
|
67
|
+
(@ancestors ||= parents(false).reverse) + (include_self ? [self] : [])
|
68
|
+
end
|
69
|
+
|
70
|
+
# Recursively lookup object by dot-separated list of keys
|
71
|
+
#
|
72
|
+
# Note that for this to work, keys may not contain a dots ('.')
|
73
|
+
def dot(path)
|
74
|
+
path.split(".").inject(self) { |a,e|
|
75
|
+
a[e] or raise Error, "Can't lookup '#{e}' in #{a.path.inspect}"
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
# Attach a child node to self
|
81
|
+
def do_attach(key, child)
|
82
|
+
!@children.key?(key) or raise Error, "Duplicate child key: #{key.inspect}"
|
83
|
+
!child.parent or raise Error, "Child is already attached"
|
84
|
+
child.instance_variable_set(:@parent, self)
|
85
|
+
@children[key] = child
|
86
|
+
child.send(:clear_cached_properties)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
# Recursively clear cached properties like @parents in each node in the
|
91
|
+
# tree. Should be called whenever the node is attached or detached from a
|
92
|
+
# tree
|
93
|
+
#
|
94
|
+
# Note that to speed up the process, it stops recursing when a node has no
|
95
|
+
# cached properties. This is using the fact that the cached properties are
|
96
|
+
# themselves constructed recursively so that iff a node has a cached
|
97
|
+
# property, then all its parents will also cache it
|
98
|
+
def clear_cached_properties()
|
99
|
+
if@root || @parents || @ancestors || @path
|
100
|
+
@root = nil
|
101
|
+
@parents = nil
|
102
|
+
@ancestors = nil
|
103
|
+
@path = nil
|
104
|
+
children.values.each { |c| c.clear_cached_properties }
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class Set < Node
|
110
|
+
alias node_attach attach
|
111
|
+
|
112
|
+
# Key of this node
|
113
|
+
attr_reader :key
|
114
|
+
|
115
|
+
def initialize(parent, key)
|
116
|
+
super(parent, @key = key)
|
117
|
+
end
|
118
|
+
|
119
|
+
def attach(child) do_attach(child.key, child) end
|
120
|
+
|
121
|
+
# Unique dot-separated list of keys leading from the root object to
|
122
|
+
# self. Note that the root object is not included in the path so that
|
123
|
+
#
|
124
|
+
# obj.parent.nil? || obj.root.dot(obj.path) == obj
|
125
|
+
#
|
126
|
+
# is always true
|
127
|
+
#
|
128
|
+
# Note that for this to work, keys may not contain a dots ('.')
|
129
|
+
def path() @path ||= ancestors(true)[1..-1].join(".") end
|
130
|
+
|
131
|
+
# A set node is rendered as its key
|
132
|
+
def to_s() key.to_s end
|
133
|
+
end
|
134
|
+
|
135
|
+
class Map < Node
|
136
|
+
# A map node is rendered as its object_id
|
137
|
+
def to_s() object_id.to_s end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hash_tree
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Claus Rasmussen
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.17'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.17'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 12.3.3
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 12.3.3
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: indented_io
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: "HashTree::Set and HashTree::Map provides simple tree\n operations.
|
70
|
+
It is still very much work-in-progress "
|
71
|
+
email:
|
72
|
+
- claus.l.rasmussen@gmail.com
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- ".rspec"
|
79
|
+
- ".ruby-version"
|
80
|
+
- ".travis.yml"
|
81
|
+
- Gemfile
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- bin/console
|
85
|
+
- bin/setup
|
86
|
+
- hash_tree.gemspec
|
87
|
+
- lib/hash_tree.rb
|
88
|
+
- lib/hash_tree/version.rb
|
89
|
+
homepage: https://github.com/clrgit/hash_tree
|
90
|
+
licenses: []
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubygems_version: 3.0.8
|
108
|
+
signing_key:
|
109
|
+
specification_version: 4
|
110
|
+
summary: A tree implemented as nested hashes
|
111
|
+
test_files: []
|