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.
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1 @@
1
+ ruby-2.6.6
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.6
7
+ before_install: gem install bundler -v 1.17.3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in hash_tree.gemspec
6
+ gemspec
@@ -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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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
@@ -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
+
@@ -0,0 +1,3 @@
1
+ module HashTree
2
+ VERSION = "0.1.0"
3
+ end
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: []