tangle 0.5.1 → 0.6.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 +4 -4
- data/README.md +32 -2
- data/lib/tangle/directed/edge.rb +7 -5
- data/lib/tangle/directed/graph.rb +0 -4
- data/lib/tangle/edge.rb +21 -8
- data/lib/tangle/graph.rb +39 -94
- data/lib/tangle/graph_private.rb +30 -0
- data/lib/tangle/graph_protected.rb +45 -0
- data/lib/tangle/mixin.rb +11 -1
- data/lib/tangle/simple/graph.rb +4 -3
- data/lib/tangle/vertex.rb +16 -20
- data/tangle.gemspec +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ecbc32a01243a5579254a7beca7175df838759511ca5af297668ec54ab245ff2
|
4
|
+
data.tar.gz: b437ade2b5f953312b845a3d9fd5679d6e40b7c98a437b5ce6677f866824de9f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8af8b8acc6f0400e03eb760a3baffe12987b5f797bd952cf9a271312163433b1129670056991dd5febe959c74fa894e99a048e22e470c48241f140eff1b4836
|
7
|
+
data.tar.gz: c455890c8be488f6d38d83f003f731a5db70426464f14c85107344d277297446c73225e8ca615d556e8bacf079934bd5ff9724a60716a7212485dcbae870e2f8
|
data/README.md
CHANGED
@@ -2,19 +2,21 @@
|
|
2
2
|
|
3
3
|
# Tangle
|
4
4
|
|
5
|
-
Tangle aims to untangle your graphs, by providing a set of classes for managing different types of graphs, and mixins for adding specific feature sets
|
5
|
+
Tangle aims to untangle your graphs, by providing a set of classes for managing different types of graphs, and mixins for adding specific feature sets.
|
6
6
|
|
7
7
|
**Graph types**:
|
8
8
|
* SimpleGraph
|
9
9
|
* MultiGraph
|
10
10
|
* DiGraph
|
11
|
-
*
|
11
|
+
* DAG
|
12
12
|
* ~~Tree~~
|
13
13
|
|
14
14
|
**Feature mixins**:
|
15
15
|
* Connectedness
|
16
16
|
* Ancestry
|
17
|
+
* ~~Vertex ordering~~
|
17
18
|
* ~~Coloring~~
|
19
|
+
* ~~GraphViz~~
|
18
20
|
|
19
21
|
## Installation
|
20
22
|
|
@@ -51,6 +53,34 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
51
53
|
|
52
54
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
53
55
|
|
56
|
+
### Mixin API
|
57
|
+
|
58
|
+
A mixin is a module with optional submodules for each of the classes
|
59
|
+
`Graph`, `Vertex`, and `Edge`. If the mixin needs initial state it
|
60
|
+
should provide a keyword initializer `#initialize_kwarg_KEYWORD(argument)`,
|
61
|
+
that will be called when the object is `#initalize`d with a matching kwarg.
|
62
|
+
|
63
|
+
Example:
|
64
|
+
```ruby
|
65
|
+
module WeightedEdges
|
66
|
+
module Edge
|
67
|
+
def initialize_kwarg_weight(weight)
|
68
|
+
@weight = weight
|
69
|
+
end
|
70
|
+
|
71
|
+
def weight
|
72
|
+
@weight
|
73
|
+
end
|
74
|
+
|
75
|
+
def weight=(new_weight)
|
76
|
+
@weight = new_weight
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
Tangle::Graph.new(mixins: [WeightedEdges])
|
82
|
+
```
|
83
|
+
|
54
84
|
## Contributing
|
55
85
|
|
56
86
|
Bug reports and pull requests are welcome on GitHub at https://github.com/notCalle/tangle. Pull requests should be rebased to HEAD of `master` before submitting, and all commits must be signed with valid GPG key. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/lib/tangle/directed/edge.rb
CHANGED
@@ -6,11 +6,6 @@ module Tangle
|
|
6
6
|
# An edge in a directed graph
|
7
7
|
#
|
8
8
|
class Edge < Tangle::Edge
|
9
|
-
def initialize(vertex1, vertex2 = vertex1, graph: nil)
|
10
|
-
@child, @parent = @vertices = [vertex1, vertex2]
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
9
|
def parent?(vertex)
|
15
10
|
@parent == vertex
|
16
11
|
end
|
@@ -26,6 +21,13 @@ module Tangle
|
|
26
21
|
def child(_vertex = nil)
|
27
22
|
@child
|
28
23
|
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def with_vertices(vertex1, vertex2 = vertex1)
|
28
|
+
@child, @parent = @vertices = [vertex1, vertex2]
|
29
|
+
self
|
30
|
+
end
|
29
31
|
end
|
30
32
|
end
|
31
33
|
end
|
data/lib/tangle/edge.rb
CHANGED
@@ -16,11 +16,11 @@ module Tangle
|
|
16
16
|
#
|
17
17
|
# End users should probably use Graph#add_edge instead.
|
18
18
|
#
|
19
|
-
def initialize(vertex1, vertex2 = vertex1, graph: nil)
|
20
|
-
|
21
|
-
|
19
|
+
def initialize(vertex1, vertex2 = vertex1, graph: nil, **kwargs)
|
20
|
+
with_graph(graph)
|
21
|
+
with_vertices(vertex1, vertex2)
|
22
22
|
|
23
|
-
initialize_mixins
|
23
|
+
initialize_mixins(**kwargs)
|
24
24
|
|
25
25
|
validate_edge
|
26
26
|
end
|
@@ -41,22 +41,23 @@ module Tangle
|
|
41
41
|
@vertices.find { |other| other != for_vertex } || for_vertex
|
42
42
|
end
|
43
43
|
|
44
|
-
#
|
44
|
+
# Clone an edge into another graph, replacing original vertices with
|
45
45
|
# their already prepared duplicates in the other graph. Returns nil if any
|
46
46
|
# of the vertices does not exist in the other graph.
|
47
47
|
# End users should probably use Graph#subgraph instead.
|
48
48
|
#
|
49
|
-
#
|
49
|
+
# clone_into(graph) => Edge or nil
|
50
50
|
#
|
51
51
|
# Raises an ArgumentError if graph would remain the same.
|
52
52
|
#
|
53
|
-
def
|
53
|
+
def clone_into(graph)
|
54
54
|
raise ArgumentError if graph == @graph
|
55
55
|
|
56
56
|
vertices = @vertices.map do |vertex|
|
57
57
|
graph.get_vertex(vertex.vertex_id)
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
|
+
clone.with_graph(graph).with_vertices(*vertices)
|
60
61
|
rescue KeyError
|
61
62
|
nil
|
62
63
|
end
|
@@ -78,6 +79,18 @@ module Tangle
|
|
78
79
|
|
79
80
|
def_delegators :@vertices, :include?, :hash
|
80
81
|
|
82
|
+
protected
|
83
|
+
|
84
|
+
def with_graph(graph)
|
85
|
+
@graph = graph
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
def with_vertices(vertex1, vertex2 = vertex1)
|
90
|
+
@vertices = Set[vertex1, vertex2]
|
91
|
+
self
|
92
|
+
end
|
93
|
+
|
81
94
|
private
|
82
95
|
|
83
96
|
def validate_edge
|
data/lib/tangle/graph.rb
CHANGED
@@ -1,32 +1,46 @@
|
|
1
1
|
require 'tangle/mixin'
|
2
2
|
require 'tangle/vertex'
|
3
3
|
require 'tangle/edge'
|
4
|
+
require 'tangle/graph_private'
|
5
|
+
require 'tangle/graph_protected'
|
4
6
|
|
5
7
|
module Tangle
|
6
8
|
#
|
7
9
|
# Base class for all kinds of graphs
|
8
10
|
#
|
9
11
|
class Graph
|
12
|
+
include Tangle::GraphPrivate
|
13
|
+
include Tangle::GraphProtected
|
10
14
|
include Tangle::Mixin::Initialize
|
11
15
|
Edge = Tangle::Edge
|
16
|
+
DEFAULT_MIXINS = [Tangle::Mixin::Connectedness].freeze
|
12
17
|
|
13
|
-
# Initialize a new graph,
|
14
|
-
#
|
15
|
-
# Graph.new() => Graph
|
16
|
-
# Graph.new(vertices: +array_or_hash+) => Graph
|
17
|
-
# Graph.new(vertices: +array_or_hash+, edges: +array_or_hash+) => Graph
|
18
|
-
# Graph.new(mixins: [MixinModule, ...], ...) => Graph
|
18
|
+
# Initialize a new graph, preloading it with vertices and edges
|
19
19
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# get assigned unique names (within the graph).
|
20
|
+
# Graph[+vertices+] => Graph
|
21
|
+
# Graph[+vertices+, +edges+) => Graph
|
23
22
|
#
|
24
|
-
# +vertices+
|
25
|
-
#
|
23
|
+
# When +vertices+ is a hash, it contains initialization kwargs as
|
24
|
+
# values and vertex names as keys. When +vertices+ is an array of
|
25
|
+
# initialization kwargs, the vertices will be be anonymous.
|
26
26
|
#
|
27
27
|
# +edges+ can contain an array of exactly two, either names of vertices
|
28
28
|
# or vertices.
|
29
29
|
#
|
30
|
+
# Any kwarg supported by Graph.new is also allowed.
|
31
|
+
#
|
32
|
+
def self.[](vertices, edges = {}, **kwargs)
|
33
|
+
graph = new(**kwargs)
|
34
|
+
graph.add_vertices(vertices)
|
35
|
+
edges.each { |from, to| graph.add_edge(from, to) }
|
36
|
+
graph
|
37
|
+
end
|
38
|
+
|
39
|
+
# Initialize a new graph, optionally preloading it with vertices and edges
|
40
|
+
#
|
41
|
+
# Graph.new() => Graph
|
42
|
+
# Graph.new(mixins: [MixinModule, ...], ...) => Graph
|
43
|
+
#
|
30
44
|
# +mixins+ is an array of modules that can be mixed into the various
|
31
45
|
# classes that makes up a graph. Initialization of a Graph, Vertex or Edge
|
32
46
|
# looks for submodules in each mixin, with the same name and extends
|
@@ -35,15 +49,10 @@ module Tangle
|
|
35
49
|
# Any subclass of Graph should also subclass Edge to manage its unique
|
36
50
|
# constraints.
|
37
51
|
#
|
38
|
-
def initialize(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@edges ||= []
|
43
|
-
|
44
|
-
initialize_mixins(mixins)
|
45
|
-
initialize_vertices(vertices)
|
46
|
-
initialize_edges(edges)
|
52
|
+
def initialize(mixins: self.class::DEFAULT_MIXINS, **kwargs)
|
53
|
+
initialize_mixins(mixins, **kwargs)
|
54
|
+
initialize_vertices
|
55
|
+
initialize_edges
|
47
56
|
end
|
48
57
|
|
49
58
|
# Get all edges.
|
@@ -85,12 +94,20 @@ module Tangle
|
|
85
94
|
#
|
86
95
|
# Optional named arguments:
|
87
96
|
# name: unique name or label for vertex
|
88
|
-
# contents: delegate object for missing methods
|
89
97
|
#
|
90
98
|
def add_vertex(**kvargs)
|
91
99
|
insert_vertex(Vertex.new(graph: self, **kvargs))
|
92
100
|
end
|
93
101
|
|
102
|
+
def add_vertices(vertices)
|
103
|
+
case vertices
|
104
|
+
when Hash
|
105
|
+
vertices.each { |name, kwargs| add_vertex(name: name, **kwargs) }
|
106
|
+
else
|
107
|
+
vertices.each { |kwargs| add_vertex(**kwargs) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
94
111
|
def get_vertex(name_or_vertex)
|
95
112
|
case name_or_vertex
|
96
113
|
when Vertex
|
@@ -109,81 +126,9 @@ module Tangle
|
|
109
126
|
# Unless a selector is provided, the subgraph contains the entire graph.
|
110
127
|
#
|
111
128
|
def subgraph(&selector)
|
112
|
-
|
113
|
-
|
114
|
-
dup_vertices_into(graph, &selector)
|
115
|
-
dup_edges_into(graph)
|
116
|
-
|
117
|
-
graph
|
129
|
+
clone.with_vertices(vertices(&selector)).with_edges(edges)
|
118
130
|
end
|
119
|
-
alias dup subgraph
|
120
131
|
|
121
132
|
attr_reader :mixins
|
122
|
-
|
123
|
-
protected
|
124
|
-
|
125
|
-
# Insert a prepared vertex into the graph
|
126
|
-
#
|
127
|
-
def insert_vertex(vertex)
|
128
|
-
raise ArgumentError unless vertex.graph.eql?(self)
|
129
|
-
|
130
|
-
@vertices_by_name[vertex.name] = vertex unless vertex.name.nil?
|
131
|
-
@vertices_by_id[vertex.vertex_id] = vertex
|
132
|
-
end
|
133
|
-
|
134
|
-
# Insert a prepared edge into the graph
|
135
|
-
#
|
136
|
-
def insert_edge(edge)
|
137
|
-
raise ArgumentError unless edge.graph.eql?(self)
|
138
|
-
|
139
|
-
@edges << edge
|
140
|
-
edge
|
141
|
-
end
|
142
|
-
|
143
|
-
private
|
144
|
-
|
145
|
-
def initialize_vertices(vertices)
|
146
|
-
return if vertices.nil?
|
147
|
-
|
148
|
-
case vertices
|
149
|
-
when Hash
|
150
|
-
initialize_named_vertices(vertices)
|
151
|
-
else
|
152
|
-
initialize_anonymous_vertices(vertices)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
def initialize_named_vertices(vertices)
|
157
|
-
vertices.each do |name, delegate|
|
158
|
-
add_vertex(name: name, delegate: delegate)
|
159
|
-
end
|
160
|
-
end
|
161
|
-
|
162
|
-
def initialize_anonymous_vertices(vertices)
|
163
|
-
vertices.each do |delegate|
|
164
|
-
add_vertex(delegate: delegate)
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
def initialize_edges(edges)
|
169
|
-
return if edges.nil?
|
170
|
-
|
171
|
-
edges.each do |vertices|
|
172
|
-
add_edge(*vertices)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def dup_vertices_into(graph, &selector)
|
177
|
-
vertices(&selector).each do |vertex|
|
178
|
-
graph.insert_vertex(vertex.dup_into(graph))
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
def dup_edges_into(graph)
|
183
|
-
edges.each do |edge|
|
184
|
-
new_edge = edge.dup_into(graph)
|
185
|
-
graph.insert_edge(new_edge) unless new_edge.nil?
|
186
|
-
end
|
187
|
-
end
|
188
133
|
end
|
189
134
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Tangle
|
2
|
+
#
|
3
|
+
# The private bits of Graph
|
4
|
+
#
|
5
|
+
module GraphPrivate
|
6
|
+
private
|
7
|
+
|
8
|
+
def initialize_vertices
|
9
|
+
@vertices_by_id = {}
|
10
|
+
@vertices_by_name = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize_edges
|
14
|
+
@edges = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def clone_vertices_into(graph, &selector)
|
18
|
+
vertices(&selector).each do |vertex|
|
19
|
+
graph.insert_vertex(vertex.clone_into(graph))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def clone_edges_into(graph)
|
24
|
+
edges.each do |edge|
|
25
|
+
new_edge = edge.clone_into(graph)
|
26
|
+
graph.insert_edge(new_edge) unless new_edge.nil?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Tangle
|
2
|
+
#
|
3
|
+
# The protected bits of Graph
|
4
|
+
#
|
5
|
+
module GraphProtected
|
6
|
+
protected
|
7
|
+
|
8
|
+
# Insert a prepared vertex into the graph
|
9
|
+
#
|
10
|
+
def insert_vertex(vertex)
|
11
|
+
raise ArgumentError unless vertex.graph.eql?(self)
|
12
|
+
|
13
|
+
@vertices_by_name[vertex.name] = vertex unless vertex.name.nil?
|
14
|
+
@vertices_by_id[vertex.vertex_id] = vertex
|
15
|
+
end
|
16
|
+
|
17
|
+
# Insert a prepared edge into the graph
|
18
|
+
#
|
19
|
+
def insert_edge(edge)
|
20
|
+
raise ArgumentError unless edge.graph.eql?(self)
|
21
|
+
|
22
|
+
@edges << edge
|
23
|
+
edge
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_vertices(vertices = [])
|
27
|
+
initialize_vertices
|
28
|
+
|
29
|
+
vertices.each do |vertex|
|
30
|
+
insert_vertex(vertex.clone_into(self))
|
31
|
+
end
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_edges(edges = [])
|
36
|
+
initialize_edges
|
37
|
+
|
38
|
+
edges.each do |edge|
|
39
|
+
new_edge = edge.clone_into(self)
|
40
|
+
insert_edge(new_edge) unless new_edge.nil?
|
41
|
+
end
|
42
|
+
self
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/tangle/mixin.rb
CHANGED
@@ -9,7 +9,7 @@ module Tangle
|
|
9
9
|
module Initialize
|
10
10
|
private
|
11
11
|
|
12
|
-
def initialize_mixins(mixins = nil)
|
12
|
+
def initialize_mixins(mixins = nil, **kwargs)
|
13
13
|
case klass = self.class.name[/[^:]+$/].to_sym
|
14
14
|
when :Graph
|
15
15
|
@mixins = mixins
|
@@ -18,6 +18,7 @@ module Tangle
|
|
18
18
|
end
|
19
19
|
|
20
20
|
extend_with_mixins(klass, mixins) unless mixins.nil?
|
21
|
+
initialize_kwargs(**kwargs) unless kwargs.empty?
|
21
22
|
end
|
22
23
|
|
23
24
|
def extend_with_mixins(klass, mixins)
|
@@ -25,6 +26,15 @@ module Tangle
|
|
25
26
|
extend(mixin.const_get(klass)) if mixin.const_defined?(klass)
|
26
27
|
end
|
27
28
|
end
|
29
|
+
|
30
|
+
def initialize_kwargs(**kwargs)
|
31
|
+
kwargs.each do |keyword, argument|
|
32
|
+
initializer = "initialize_kwarg_#{keyword}".to_sym
|
33
|
+
can_init = respond_to?(initializer)
|
34
|
+
raise ArgumentError, "unknown keyword: #{keyword}" unless can_init
|
35
|
+
send initializer, argument
|
36
|
+
end
|
37
|
+
end
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
data/lib/tangle/simple/graph.rb
CHANGED
data/lib/tangle/vertex.rb
CHANGED
@@ -6,8 +6,7 @@ module Tangle
|
|
6
6
|
#
|
7
7
|
# A named vertex in a graph
|
8
8
|
#
|
9
|
-
class Vertex
|
10
|
-
include PP::ObjectMixin
|
9
|
+
class Vertex
|
11
10
|
include Tangle::Mixin::Initialize
|
12
11
|
|
13
12
|
# Create a new vertex
|
@@ -17,38 +16,29 @@ module Tangle
|
|
17
16
|
# Named arguments:
|
18
17
|
# graph: a Graph or nil for an orphaned vertex
|
19
18
|
# name: anything that's hashable and unique within the graph
|
20
|
-
# delegate: delegate object for missing methods
|
21
19
|
#
|
22
20
|
def initialize(graph: nil,
|
23
21
|
name: nil,
|
24
|
-
|
25
|
-
|
26
|
-
super(delegate) unless delegate.nil?
|
27
|
-
|
22
|
+
vertex_id: object_id,
|
23
|
+
**kwargs)
|
28
24
|
@graph = graph
|
29
25
|
@name = name
|
30
|
-
@delegate = delegate
|
31
26
|
@vertex_id = vertex_id
|
32
27
|
|
33
|
-
initialize_mixins
|
28
|
+
initialize_mixins(**kwargs)
|
34
29
|
end
|
35
30
|
|
36
|
-
#
|
37
|
-
# End users should probably use Graph#
|
31
|
+
# Clone a vertex in a new graph, keeping all other contained attributes
|
32
|
+
# End users should probably use Graph#subgraph instead.
|
38
33
|
#
|
39
|
-
#
|
34
|
+
# clone_into(new_graph) => Vertex
|
40
35
|
#
|
41
36
|
# Raises an ArgumentError if graph would remain the same.
|
42
37
|
#
|
43
|
-
def
|
38
|
+
def clone_into(graph)
|
44
39
|
raise ArgumentError if graph == @graph
|
45
40
|
|
46
|
-
|
47
|
-
graph: graph,
|
48
|
-
name: @name,
|
49
|
-
delegate: @delegate,
|
50
|
-
vertex_id: @vertex_id
|
51
|
-
)
|
41
|
+
clone.with_graph(graph)
|
52
42
|
end
|
53
43
|
|
54
44
|
# Return all edges that touch this vertex
|
@@ -92,7 +82,13 @@ module Tangle
|
|
92
82
|
|
93
83
|
attr_reader :graph
|
94
84
|
attr_reader :name
|
95
|
-
attr_reader :delegate
|
96
85
|
attr_reader :vertex_id
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def with_graph(graph)
|
90
|
+
@graph = graph
|
91
|
+
self
|
92
|
+
end
|
97
93
|
end
|
98
94
|
end
|
data/tangle.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tangle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.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-
|
11
|
+
date: 2018-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: git-version-bump
|
@@ -106,6 +106,8 @@ files:
|
|
106
106
|
- lib/tangle/edge.rb
|
107
107
|
- lib/tangle/errors.rb
|
108
108
|
- lib/tangle/graph.rb
|
109
|
+
- lib/tangle/graph_private.rb
|
110
|
+
- lib/tangle/graph_protected.rb
|
109
111
|
- lib/tangle/mixin.rb
|
110
112
|
- lib/tangle/mixin/ancestry.rb
|
111
113
|
- lib/tangle/mixin/connectedness.rb
|