kvdag 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9dc47fb88bc0c77952ce8e6f30522c7e7bc584a4
4
+ data.tar.gz: 917cb05694be1c5122cf593627d43c709b76edc1
5
+ SHA512:
6
+ metadata.gz: 5ca02a82d6c95cf6284198d2a47a77544f824054f95e828f2ffc6eb7b0ca5b359d59564ffad8f19653e96e3007321bd958815aefd3692829bdaeb5b8bb70f70a
7
+ data.tar.gz: 3f75cd8ee7498d565fb53e0465b908c0ba056eb577ba8ceebf9a63a2ed44138004445456db92424936ba2f4adb6dcfa1fc9c803415bb9becb478e2c1ba4b9124
Binary file
@@ -0,0 +1,2 @@
1
+ r C���0�(�'���=,@���J
2
+ �[�C�ve��R��Y�"���.!�S���9�)�F
@@ -0,0 +1,53 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ ## Specific to RubyMotion:
17
+ .dat*
18
+ .repl_history
19
+ build/
20
+ *.bridgesupport
21
+ build-iPhoneOS/
22
+ build-iPhoneSimulator/
23
+
24
+ ## Specific to RubyMotion (use of CocoaPods):
25
+ #
26
+ # We recommend against adding the Pods directory to your .gitignore. However
27
+ # you should judge for yourself, the pros and cons are mentioned at:
28
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
29
+ #
30
+ # vendor/Pods/
31
+
32
+ ## Documentation cache and generated files:
33
+ /.yardoc/
34
+ /_yardoc/
35
+ /doc/
36
+ /rdoc/
37
+
38
+ ## Environment normalization:
39
+ /.bundle/
40
+ /vendor/bundle
41
+ /lib/bundler/man/
42
+
43
+ # for a library or gem, you might want to ignore these files since the code is
44
+ # intended to run in multiple environments; otherwise, check them in:
45
+ Gemfile.lock
46
+ .ruby-version
47
+ .ruby-gemset
48
+
49
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
+ .rvmrc
51
+ *~
52
+ # Ignore personal preferences for rspec output
53
+ .rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kvdag.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 saab-simc-admin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,77 @@
1
+ # KVDAG
2
+
3
+ Implements a Directed Acyclic Graph for Key-Value searches.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'kvdag'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install kvdag
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Development
26
+
27
+ After checking out the repo, run `bin/setup` to install
28
+ dependencies. Then, run `rake spec` to run the tests. You can also run
29
+ `bin/console` for an interactive prompt that will allow you to
30
+ experiment.
31
+
32
+ To install this gem onto your local machine, run `bundle exec rake
33
+ install`.
34
+
35
+ ## Contributing
36
+
37
+ We are happy to accept contributions in the form of issues and pull
38
+ requests on
39
+ [GitHub](https://github.com/saab-simc-admin/keyvaluedag). Please
40
+ follow these guidelines to make the experience as smooth as possible:
41
+
42
+ - All development takes place in feature branches, with master only
43
+ accepting non-fast-forward merges.
44
+
45
+ - Others shall be able to use the KeyValue DAG library to build their
46
+ own tools. If you introduce API changes, please increment version
47
+ numbers according to [semantic versioning](http://semver.org/).
48
+
49
+ - All code will be reviewed before it is merged. To help the reviewer,
50
+ send your work as a series of logically separate changes, not as one
51
+ gigantic squash commit. Make sure bisection will work by ensuring
52
+ the code actually works after each change.
53
+
54
+ - GnuPG sign all your commits and tags, with a key that is [validated
55
+ by
56
+ GitHub](https://help.github.com/articles/about-gpg-commit-and-tag-signatures/).
57
+
58
+ - GitHub's web UI cannot generate signed merges when accepting pull
59
+ requests. Instead, we use [a custom
60
+ tool](https://github.com/saab-simc-admin/workflow-tools/tree/master/git-ghpr)
61
+ to accept them. You can still send them through the web as usual.
62
+
63
+ - Your code shall be signed by you. Therefore, the maintainer cannot
64
+ fix any merge conflicts arising from your pull request. If there
65
+ are any conflicts, please rebase onto current master before
66
+ sending your pull request.
67
+
68
+ - Document your work.
69
+
70
+ - At an absolute minimum, Ruby code shall have
71
+ [RDoc](https://rdoc.github.io/rdoc/) blocks documenting each
72
+ function.
73
+
74
+ - Write your commit messages in the usual Git style: a short summary
75
+ in the first line, then paragraphs of explanatory text, line
76
+ wrapped.
77
+
@@ -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 "kvdag"
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
@@ -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,35 @@
1
+ #-*- ruby -*-
2
+ # coding: utf-8
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'kvdag/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'kvdag'
9
+ spec.version = KVDAG::VERSION
10
+ spec.summary = 'Directed Acyclic Graph for Key-Value searches'
11
+ spec.description = spec.summary
12
+ spec.homepage = 'https://github.com/saab-simc-admin/keyvaluedag'
13
+ spec.authors = ['Calle Englund']
14
+ spec.email = ['calle.englund@saabgroup.com']
15
+ spec.license = 'MIT'
16
+
17
+ spec.has_rdoc = true
18
+
19
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/})
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.platform = Gem::Platform::RUBY
27
+ spec.required_ruby_version = '~>2'
28
+ spec.add_runtime_dependency 'activesupport', '~>4'
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.13"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+ spec.add_development_dependency "rspec-collection_matchers", "~> 1.1.2"
34
+
35
+ end
@@ -0,0 +1,68 @@
1
+ require 'active_support'
2
+ require "kvdag/version"
3
+ require 'kvdag/error'
4
+ require 'kvdag/attrnode'
5
+ require 'kvdag/vertex'
6
+ require 'kvdag/edge'
7
+ require 'kvdag/keypathhash'
8
+
9
+ # Directed Acyclic Graph for multiple inheritance key-value lookup
10
+
11
+ class KVDAG
12
+ include Enumerable
13
+
14
+ attr_reader :hash_proxy_class
15
+
16
+ # Create a new KVDAG, optionally using a specialized
17
+ # hash class for storing key-values. The default is to use
18
+ # a dot-separated keypath proxy for storing key-values like
19
+ #
20
+ # hsh["a.b.c"] = value
21
+ #
22
+ # in a tree of hashes
23
+ #
24
+ # {"a" => {"b" => {"c" => value}}}
25
+ #
26
+ # The default hash proxy will also stringify all keys, and
27
+ # makes all merge operations deep merges.
28
+
29
+ private :initialize
30
+ def initialize(hash_proxy_class = KVDAG::KeyPathHashProxy)
31
+ @vertices = Set.new
32
+ @hash_proxy_class = hash_proxy_class
33
+ end
34
+
35
+ def inspect
36
+ "#<%s:%x(%d vertices, %d edges)>" % [self.class, self.object_id,
37
+ vertices.length, edges.length]
38
+ end
39
+
40
+ # Create a new vertex in this DAG, optionally loaded with
41
+ # key-values.
42
+
43
+ def vertex(attrs = {})
44
+ KVDAG::Vertex.new(self, attrs)
45
+ end
46
+
47
+ # Return the set of all vertices, possibly filtered by
48
+ # Vertex#match? expressions.
49
+
50
+ def vertices(filter = {})
51
+ return @vertices if filter.empty?
52
+
53
+ Set.new(@vertices.select{|vertex| vertex.match?(filter) })
54
+ end
55
+
56
+ # Return the set of all edges
57
+
58
+ def edges
59
+ @vertices.reduce(Set.new) {|edges,vertex| edges + vertex.edges}
60
+ end
61
+
62
+ # Enumerate all vertices in the DAG, possibly filtered
63
+ # by Vertex#match? expressions.
64
+
65
+ def each(filter = {}, &block)
66
+ vertices(filter).each(&block)
67
+ end
68
+ end
@@ -0,0 +1,116 @@
1
+ class KVDAG
2
+
3
+ # Mixin with common methods for managing the +attrs+ of
4
+ # vertices and edges in a KVDAG.
5
+
6
+ module AttributeNode
7
+ attr_reader :attrs
8
+
9
+ # Returns the value for an +attr+. If the +attr+ can't be found,
10
+ # it will raise a KeyError exception.
11
+ #
12
+ # +options+:
13
+ # [+shallow+] If true, lookup is limited to attrs defined in
14
+ # this attrnode. The default is lookup in the tree
15
+ # returned by +to_hash_proxy+.
16
+
17
+ def fetch(attr, options = {})
18
+ case
19
+ when (options[:shallow])
20
+ @attrs.fetch(attr)
21
+ when self.respond_to?(:to_hash_proxy)
22
+ to_hash_proxy.fetch(attr)
23
+ else
24
+ to_hash.fetch(attr)
25
+ end
26
+ end
27
+
28
+ # Return the value for an +attr+, or +nil+ if the +attr+ can't be found.
29
+ #
30
+ # +options+:
31
+ # [+shallow+] If true, lookup is limited to attrs defined in
32
+ # this attrnode. The default is lookup in the tree
33
+ # returned by +to_hash_proxy+.
34
+
35
+ def [](attr, options = {})
36
+ fetch(attr, options)
37
+ rescue
38
+ nil
39
+ end
40
+
41
+ # Set the +value+ for an +attr+ in this attrnode.
42
+
43
+ def []=(attr, value)
44
+ @attrs[attr] = value
45
+ end
46
+
47
+ def to_hash
48
+ if self.respond_to?(:to_hash_proxy) then
49
+ to_hash_proxy.to_hash
50
+ else
51
+ @attrs
52
+ end
53
+ end
54
+
55
+ def merge!(other)
56
+ @attrs.merge!(other.to_hash)
57
+ self
58
+ end
59
+
60
+ # Filter the key-value view of a vertex by a list of key prefixes,
61
+ # and return a hash_proxy containing only those trees.
62
+ #
63
+ # :call-seq:
64
+ # filter("key.path1", ..., "key.pathN") -> hash_proxy
65
+
66
+ def filter(*keys)
67
+ if self.respond_to?(:to_hash_proxy) then
68
+ to_hash_proxy.filter(*keys)
69
+ else
70
+ raise NotImplementedError.new("not implemented for plain hash")
71
+ end
72
+ end
73
+
74
+ # :call-seq:
75
+ # match?() -> true
76
+ # match?(method: match, ...) -> true or false
77
+ # match?(one?:{ matches }, ...) -> true or false
78
+ # match?(any?:{ matches }, ...) -> true or false
79
+ # match?(all?:{ matches }, ...) -> true of false
80
+ # match?(none?:{ matches }, ...) -> true or false
81
+ #
82
+ # Checks if the key-value view visible from a node matches all of
83
+ # set of filters. An empty filter set is considered a match.
84
+ #
85
+ # Any +method+ given will be matched against its result:
86
+ # match === self.send(method)
87
+ #
88
+ # +matches+ should be a hash with 'key.path' strings at keys,
89
+ # and +match+ values to check for equality:
90
+ # match === self[key]
91
+ #
92
+ # Examples:
93
+ #
94
+ # node.match?(class:KVDAG::Vertex)
95
+ # node.match?(none?:{'key' => Integer})
96
+ # node.match?(all?:{'key' => /this|that/})
97
+ # node.match?(any?:{'key1' => 'this', 'key2' => 'that'})
98
+ # node.match?(one?:{'key1' => 'this', 'key2' => 'that'})
99
+
100
+ def match?(filter={})
101
+ valid_enumerators = [:none?, :one?, :any?, :all?]
102
+
103
+ filter.all? do |item|
104
+ method, match = item
105
+ if valid_enumerators.include?(method)
106
+ match.send(method) do |match_item|
107
+ key, value = match_item
108
+ value === self[key]
109
+ end
110
+ else
111
+ match === self.send(method)
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,47 @@
1
+ class KVDAG
2
+
3
+ # An edge to a vertex in a KVDAG
4
+
5
+ class Edge
6
+ include AttributeNode
7
+
8
+ # Return the target vertex of this edge
9
+
10
+ attr_reader :to_vertex
11
+
12
+ # Create a new edge towards a vertex in a KVDAG,
13
+ # optionally loaded with key-values
14
+ #
15
+ # N.B: KVDAG::Edge.new should never be called directly,
16
+ # always use KVDAG::Vertex#edge to create edges.
17
+
18
+ private :initialize
19
+ def initialize(dag, target, attrs = {})
20
+ @to_vertex = target
21
+ @attrs = dag.hash_proxy_class.new(attrs)
22
+ end
23
+
24
+ def inspect
25
+ "#<%s @attr=%s @to_vertex=%s>" % [self.class, @attrs.to_hash, @to_vertex]
26
+ end
27
+
28
+ alias to_s inspect
29
+
30
+ # Return the proxied key-value hash tree visible from this edge
31
+ # via its target vertex and all its ancestors
32
+ #
33
+ # Calling to_hash instead will return a regular hash tree, without
34
+ # any special properties, e.g. for serializing as YAML or JSON.
35
+
36
+ def to_hash_proxy
37
+ result = @to_vertex.to_hash_proxy
38
+ result.merge!(@attrs)
39
+ end
40
+
41
+ # Is the +target+ vertex reachable via this edge?
42
+
43
+ def reachable?(target)
44
+ @to_vertex.equal?(target) || @to_vertex.reachable?(target)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,12 @@
1
+ class KVDAG
2
+
3
+ # Vertices belong to different DAG:s
4
+
5
+ class VertexError < StandardError
6
+ end
7
+
8
+ # Inserted edge would cause a cycle.
9
+
10
+ class CyclicError < StandardError
11
+ end
12
+ end
@@ -0,0 +1,97 @@
1
+ require 'delegate'
2
+ require 'active_support/core_ext/hash'
3
+
4
+ class KVDAG
5
+ class KeyPathHashProxy < DelegateClass(Hash)
6
+ class KeyPath < Array
7
+ private :initialize
8
+ def initialize(keypath)
9
+ keypath = keypath.split(".") if keypath.is_a?(String)
10
+ super
11
+ end
12
+ end
13
+
14
+ private :initialize
15
+ def initialize(hash = {})
16
+ raise TypeError.new("Must be initialized with a `hash`") unless hash.is_a?(Hash)
17
+ @hash = hash.deep_stringify_keys
18
+ super(@hash)
19
+ end
20
+
21
+ def merge(other, &block)
22
+ self.class.new(@hash.deep_merge(other.deep_stringify_keys, &block))
23
+ end
24
+
25
+ def merge!(other, &block)
26
+ @hash.deep_merge!(other.deep_stringify_keys, &block)
27
+ self
28
+ end
29
+
30
+ # :call-seq:
31
+ # fetch("key.path") -> value or KeyError
32
+ # fetch(["key", "path"]) -> value or KeyError
33
+ #
34
+ # Return the value at a specified keypath. If the keypath
35
+ # does not specify a terminal value, return the remaining
36
+ # subtree instead.
37
+ #
38
+ # Raises a KeyError exception if the keypath is not found.
39
+
40
+ def fetch(keypath)
41
+ *keysubpath, key = KeyPath.new(keypath)
42
+ hash = @hash
43
+
44
+ keysubpath.each {|key| hash = hash.fetch(key)}
45
+ hash.fetch(key)
46
+ rescue KeyError
47
+ raise KeyError.new("keypath not found: #{keypath.inspect}")
48
+ end
49
+
50
+ # :call-seq:
51
+ # []("key.path") -> value or nil
52
+ # [](["key", "path"]) -> value or nil
53
+ #
54
+ # Return the value at a specified keypath. If the keypath
55
+ # does not specify a terminal value, return the remaining
56
+ # subtree instead.
57
+ #
58
+ # Returns nil if the keypath is not found.
59
+
60
+ def [](keypath)
61
+ fetch(keypath)
62
+ rescue
63
+ nil
64
+ end
65
+
66
+ def []=(keypath, value)
67
+ *keypath, key = KeyPath.new(keypath)
68
+
69
+ if keypath.empty? then
70
+ hash = @hash
71
+ else
72
+ if not hash = self[keypath] then
73
+ self[keypath] = hash = Hash.new
74
+ end
75
+ end
76
+
77
+ hash[key] = value
78
+ end
79
+
80
+ # :call-seq:
81
+ # filter("key.path1", ..., "key.pathN") -> KeyPathHashProxy
82
+ #
83
+ # Filter a keypathhash tree by a list of keypath prefixes, and
84
+ # return a new keypathhash containing only those trees.
85
+ #
86
+ # Raises a KeyError exception if any of the specified keypaths
87
+ # cannot be found.
88
+
89
+ def filter(*keypaths)
90
+ result = self.class.new
91
+ keypaths.each do |keypath|
92
+ result[keypath] = self.fetch(keypath)
93
+ end
94
+ result
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,3 @@
1
+ class KVDAG
2
+ VERSION = '0.1.3'
3
+ end
@@ -0,0 +1,176 @@
1
+ class KVDAG
2
+
3
+ # A vertex in a KVDAG
4
+
5
+ class Vertex
6
+ include AttributeNode
7
+ include Comparable
8
+ attr_reader :dag
9
+ attr_reader :edges
10
+
11
+ # Create a new vertex in a KVDAG, optionally loaded
12
+ # with key-values.
13
+ #
14
+ # N.B: KVDAG::Vertex.new should never be called directly,
15
+ # always use KVDAG#vertex to create vertices.
16
+
17
+ private :initialize
18
+ def initialize(dag, attrs = {})
19
+ @edges = Set.new
20
+ @dag = dag
21
+ @attrs = dag.hash_proxy_class.new(attrs)
22
+ @child_cache = Set.new
23
+
24
+ @dag.vertices << self
25
+ end
26
+
27
+ def inspect
28
+ "#<%s @attr=%s @edges=%s>" % [self.class, @attrs.to_hash, @edges.to_a]
29
+ end
30
+
31
+ alias to_s inspect
32
+
33
+ # :call-seq:
34
+ # vtx.parents -> all parents
35
+ # vtx.parents(filter) -> parents matching +filter+
36
+ # vtx.parents {|cld| ... } -> call block with each parent
37
+ #
38
+ # Returns the set of all direct parents, possibly filtered by #match?
39
+ # expressions. If a block is given, call it with each parent.
40
+
41
+ def parents(filter={}, &block)
42
+ result = Set.new(edges.map {|edge|
43
+ edge.to_vertex
44
+ }.select {|parent|
45
+ parent.match?(filter)
46
+ })
47
+
48
+ if block_given?
49
+ result.each(&block)
50
+ else
51
+ result
52
+ end
53
+ end
54
+
55
+ # :call-seq:
56
+ # vtx.children -> all children
57
+ # vtx.children(filter) -> children matching +filter+
58
+ # vtx.children {|cld| ... } -> call block with each child
59
+ #
60
+ # Returns the set of all direct children, possibly filtered by #match?
61
+ # expressions. If a block is given, call it with each child.
62
+
63
+ def children(filter={}, &block)
64
+ result = @child_cache.select {|child|
65
+ child.match?(filter)
66
+ }
67
+
68
+ if block_given?
69
+ result.each(&block)
70
+ else
71
+ result
72
+ end
73
+ end
74
+
75
+ # Is +other+ vertex reachable via any of my #edges?
76
+ #
77
+ # A KVDAG::VertexError is raised if vertices belong
78
+ # to different KVDAG.
79
+
80
+ def reachable?(other)
81
+ raise VertexError.new("Not in the same DAG") unless @dag.equal?(other.dag)
82
+
83
+ equal?(other) || parents.any? {|parent| parent.reachable?(other)}
84
+ end
85
+
86
+ # Am I reachable from +other+ via any of its #edges?
87
+ #
88
+ # A KVDAG::VertexError is raised if vertices belong
89
+ # to different KVDAG.
90
+
91
+ def reachable_from?(other)
92
+ other.reachable?(self)
93
+ end
94
+
95
+ # Return the set of this object and all its parents, and their
96
+ # parents, recursively
97
+ #
98
+ # This is the same as all #reachable? vertices.
99
+
100
+
101
+ def ancestors
102
+ result = Set.new([self])
103
+ parents.each {|p| result += p.ancestors }
104
+ result
105
+ end
106
+
107
+ # Return the set of this object and all its children, and their
108
+ # children, recursively
109
+ #
110
+ # This is the same as all #reachable_from? vertices.
111
+
112
+ def descendants
113
+ result = Set.new([self])
114
+ children.each {|c| result += c.descendants }
115
+ result
116
+ end
117
+
118
+ # Comparable ordering for a DAG:
119
+ #
120
+ # Reachable vertices are lesser.
121
+ # Unreachable vertices are equal.
122
+
123
+ def <=>(other)
124
+ return -1 if reachable?(other)
125
+ return 1 if reachable_from?(other)
126
+ return 0
127
+ end
128
+
129
+ # Create an edge towards an +other+ vertex, optionally
130
+ # loaded with key-values.
131
+ #
132
+ # A KVDAG::VertexError is raised if vertices belong
133
+ # to different KVDAG.
134
+ #
135
+ # A KVDAG::CyclicError is raised if the edge would
136
+ # cause a cycle in the KVDAG.
137
+
138
+ def edge(other, attrs = {})
139
+ other = other.to_vertex unless other.is_a?(Vertex)
140
+ raise VertexError.new("Not in the same DAG") if @dag != other.dag
141
+ raise CyclicError.new("Would become cyclic") if other.reachable?(self)
142
+
143
+ edge = Edge.new(@dag, other, attrs)
144
+ @edges << edge
145
+ other.add_child(self)
146
+ edge
147
+ end
148
+
149
+ # Return the proxied key-value hash tree visible from this vertex
150
+ # via its edges and all its ancestors.
151
+ #
152
+ # Calling #to_hash instead will return a regular hash tree, without
153
+ # any special properties, e.g. for serializing as YAML or JSON.
154
+
155
+ def to_hash_proxy
156
+ result = @dag.hash_proxy_class.new
157
+ edges.each do |edge|
158
+ result.merge!(edge.to_hash_proxy)
159
+ end
160
+ result.merge!(@attrs)
161
+ end
162
+
163
+ protected
164
+
165
+ # Cache the fact that the +other+ vertex has created an edge to
166
+ # us.
167
+ #
168
+ # Do not call this except from #edge, which performs all required
169
+ # sanity checks.
170
+
171
+ def add_child(other)
172
+ @child_cache << other
173
+ end
174
+
175
+ end
176
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kvdag
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Calle Englund
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain:
11
+ - |
12
+ -----BEGIN CERTIFICATE-----
13
+ MIIDljCCAn6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBIMRYwFAYDVQQDDA1jYWxs
14
+ ZS5lbmdsdW5kMRkwFwYKCZImiZPyLGQBGRYJc2FhYmdyb3VwMRMwEQYKCZImiZPy
15
+ LGQBGRYDY29tMB4XDTE2MTEwMjA5MjYyN1oXDTE3MTEwMjA5MjYyN1owSDEWMBQG
16
+ A1UEAwwNY2FsbGUuZW5nbHVuZDEZMBcGCgmSJomT8ixkARkWCXNhYWJncm91cDET
17
+ MBEGCgmSJomT8ixkARkWA2NvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
18
+ ggEBAM7OxaztzD0LyOwK1mPcg3BhioX1EDVbD/qAFOAzBSGGlAhtmHMqAkyvJMvs
19
+ iiG7xvBidWUapxiEiBwamXiOTSrp2eW+XSXW9omdWHXjBZcwHqwb1VmAlYRDkSHf
20
+ dzcM/z4xlV+DJw/pFyMRWzqNdVBtWTbVXAFGjJSqQ6q21ACYJldV9U71AIpXo+oF
21
+ VEMf6PZS2uhB1G+FgAtnX/xmy7OM1Cy3qc/CaJbWSddpegxWJMUn2HNQxFwIe40g
22
+ WoEoiFA7qQg9DnR/5i3lW6QyfIaA5k9cv2su1VyjqKLbkFTTTjYw0P1BJmvfXjtc
23
+ rMl+3HCWYj6UunZwfZi2wDGsBkkCAwEAAaOBijCBhzAJBgNVHRMEAjAAMAsGA1Ud
24
+ DwQEAwIEsDAdBgNVHQ4EFgQUwHCMEKgrIMaiTkTVLKZn6yOD1SIwJgYDVR0RBB8w
25
+ HYEbY2FsbGUuZW5nbHVuZEBzYWFiZ3JvdXAuY29tMCYGA1UdEgQfMB2BG2NhbGxl
26
+ LmVuZ2x1bmRAc2FhYmdyb3VwLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAP9OnE0jP
27
+ 2vRHI/vnOkgCvLFNoOqK/YB4yDVVW69Pza+xIXcmUBvl7DQ+bBdF5AK0B1A7U0rp
28
+ Pbdj0bpQtWxmUmMIbnE1w6iuVCXAabsyUfHY4mlztToWXMVOXc1SPlJ/S2XXaRd5
29
+ fiNj/nBTb0YTQA0E4pZ0Aud80qZ2WLdc6FfzHUEMW91BL3bhLeDL40noHK5Lvk52
30
+ phzVHIrDjCowUMTnGiPZCXEo4KZW76KwYYV6oQ6LzcrYBw5mJ4XpdgQKZgnTnRBP
31
+ f8wtQllq82VF0AXUYeLtTh1f+DW3WW5BO1e2OCu5eOV7dbyaVPaNK/+rHjCN8kM/
32
+ DGZSwUoNADmVkQ==
33
+ -----END CERTIFICATE-----
34
+ date: 2016-11-02 00:00:00.000000000 Z
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: activesupport
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ version: '4'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ~>
48
+ - !ruby/object:Gem::Version
49
+ version: '4'
50
+ - !ruby/object:Gem::Dependency
51
+ name: bundler
52
+ requirement: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ~>
55
+ - !ruby/object:Gem::Version
56
+ version: '1.13'
57
+ type: :development
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ version: '1.13'
64
+ - !ruby/object:Gem::Dependency
65
+ name: rake
66
+ requirement: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ~>
69
+ - !ruby/object:Gem::Version
70
+ version: '10.0'
71
+ type: :development
72
+ prerelease: false
73
+ version_requirements: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '10.0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: rspec
80
+ requirement: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ~>
83
+ - !ruby/object:Gem::Version
84
+ version: '3.0'
85
+ type: :development
86
+ prerelease: false
87
+ version_requirements: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ version: '3.0'
92
+ - !ruby/object:Gem::Dependency
93
+ name: rspec-collection_matchers
94
+ requirement: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ~>
97
+ - !ruby/object:Gem::Version
98
+ version: 1.1.2
99
+ type: :development
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ~>
104
+ - !ruby/object:Gem::Version
105
+ version: 1.1.2
106
+ description: Directed Acyclic Graph for Key-Value searches
107
+ email:
108
+ - calle.englund@saabgroup.com
109
+ executables: []
110
+ extensions: []
111
+ extra_rdoc_files: []
112
+ files:
113
+ - .gitignore
114
+ - Gemfile
115
+ - LICENSE
116
+ - README.md
117
+ - Rakefile
118
+ - bin/console
119
+ - bin/setup
120
+ - kvdag.gemspec
121
+ - lib/kvdag.rb
122
+ - lib/kvdag/attrnode.rb
123
+ - lib/kvdag/edge.rb
124
+ - lib/kvdag/error.rb
125
+ - lib/kvdag/keypathhash.rb
126
+ - lib/kvdag/version.rb
127
+ - lib/kvdag/vertex.rb
128
+ homepage: https://github.com/saab-simc-admin/keyvaluedag
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ~>
139
+ - !ruby/object:Gem::Version
140
+ version: '2'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.0.14
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Directed Acyclic Graph for Key-Value searches
152
+ test_files: []
Binary file