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 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