tangle 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8eb75fcbb95f4ed4a3dd8f24c2142d9a6ddda1cafaf4dfbbb8e6ef60cab9131
4
- data.tar.gz: dd3823ce25297ddc173e6bf7867d8c61e6d795157f99655b54f7297ac960744c
3
+ metadata.gz: ecbc32a01243a5579254a7beca7175df838759511ca5af297668ec54ab245ff2
4
+ data.tar.gz: b437ade2b5f953312b845a3d9fd5679d6e40b7c98a437b5ce6677f866824de9f
5
5
  SHA512:
6
- metadata.gz: b55e6aa75a2f1225cbcbda72050f3d374222453dfd4a75221bb01d8b9aa452f96832a38ac7b960a3edbe8913135127e72db0b94a96024138e5b29bddd0bd81b5
7
- data.tar.gz: d580c9be8c1559c0055d0a3f59b2cde590c5e7b0b42e821f4cbba1ac3bbd85e43616f43fd652c362de979a3d0bcdd8e43b94aff61dd29ad2d684038cce63197e
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, like coloring or graphviz export.
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
- * ~~DAG~~
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.
@@ -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
@@ -8,10 +8,6 @@ module Tangle
8
8
  class Graph < Tangle::Graph
9
9
  Edge = Tangle::Directed::Edge
10
10
  DEFAULT_MIXINS = [Tangle::Mixin::Ancestry].freeze
11
-
12
- def initialize(mixins: DEFAULT_MIXINS, **kvargs)
13
- super
14
- end
15
11
  end
16
12
  end
17
13
  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
- @vertices ||= Set[vertex1, vertex2]
21
- @graph = graph
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
- # Duplicate an edge into another graph, replacing original vertices with
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
- # dup_into(graph) => Edge or nil
49
+ # clone_into(graph) => Edge or nil
50
50
  #
51
51
  # Raises an ArgumentError if graph would remain the same.
52
52
  #
53
- def dup_into(graph)
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
- self.class.new(*vertices, graph: graph)
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, optionally preloading it with vertices and edges
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
- # When +array_or_hash+ is a hash, it contains the objects as values and
21
- # their names as keys. When +array_or_hash+ is an array the objects will
22
- # get assigned unique names (within the graph).
20
+ # Graph[+vertices+] => Graph
21
+ # Graph[+vertices+, +edges+) => Graph
23
22
  #
24
- # +vertices+ can contain anything, and the Vertex object that is created
25
- # will delegate all missing methods to its content.
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(vertices: nil, edges: nil,
39
- mixins: [Tangle::Mixin::Connectedness])
40
- @vertices_by_id = {}
41
- @vertices_by_name = {}
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
- graph = self.class.new
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
@@ -8,9 +8,10 @@ module Tangle
8
8
  class Graph < Tangle::Graph
9
9
  Edge = Tangle::Simple::Edge
10
10
 
11
- def initialize(**kvargs)
12
- @edges ||= Set[]
13
- super
11
+ private
12
+
13
+ def initialize_edges
14
+ @edges = Set[]
14
15
  end
15
16
  end
16
17
  end
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 < SimpleDelegator
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
- delegate: nil,
25
- vertex_id: object_id)
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
- # Duplicate a vertex in a new graph, keeping all other contained attributes
37
- # End users should probably use Graph#subgrap instead.
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
- # dup_into(new_graph) => Vertex
34
+ # clone_into(new_graph) => Vertex
40
35
  #
41
36
  # Raises an ArgumentError if graph would remain the same.
42
37
  #
43
- def dup_into(graph)
38
+ def clone_into(graph)
44
39
  raise ArgumentError if graph == @graph
45
40
 
46
- Vertex.new(
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
@@ -1,5 +1,5 @@
1
1
 
2
- lib = File.expand_path('../lib', __FILE__)
2
+ lib = File.expand_path('lib', __dir__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'tangle/version'
5
5
 
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.5.1
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-02-20 00:00:00.000000000 Z
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