gexf 0.0.2
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.
- data/.gitignore +0 -0
- data/lib/gexf.rb +21 -0
- data/lib/gexf/attribute.rb +92 -0
- data/lib/gexf/attribute/assignable.rb +81 -0
- data/lib/gexf/attribute/definable.rb +20 -0
- data/lib/gexf/document.rb +112 -0
- data/lib/gexf/edge.rb +73 -0
- data/lib/gexf/edgeset.rb +7 -0
- data/lib/gexf/graph.rb +82 -0
- data/lib/gexf/node.rb +52 -0
- data/lib/gexf/nodeset.rb +19 -0
- data/lib/gexf/set_of_sets.rb +38 -0
- data/lib/gexf/support.rb +18 -0
- data/lib/gexf/version.rb +3 -0
- data/lib/gexf/xml_serializer.rb +93 -0
- data/spec/gexf/attribute/assignable_spec.rb +118 -0
- data/spec/gexf/attribute/definable_spec.rb +66 -0
- data/spec/gexf/attribute_spec.rb +179 -0
- data/spec/gexf/edge_spec.rb +70 -0
- data/spec/gexf/edgeset_spec.rb +105 -0
- data/spec/gexf/graph_spec.rb +172 -0
- data/spec/gexf/node_spec.rb +105 -0
- data/spec/gexf/nodeset_spec.rb +4 -0
- data/spec/spec_helper.rb +2 -0
- metadata +125 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
BOOLEAN_GETTERS = %w(directed? undirected? mutual?)
|
4
|
+
|
5
|
+
describe GEXF::Edge do
|
6
|
+
|
7
|
+
let(:id) { 1 }
|
8
|
+
let(:type) { nil }
|
9
|
+
let(:source_id) { 2 }
|
10
|
+
let(:target_id) { 232 }
|
11
|
+
let(:graph) { double('graph', :edges => []) }
|
12
|
+
let(:weight) { nil }
|
13
|
+
let(:label) { 'my-edge' }
|
14
|
+
let(:options) { { :graph => graph, :type => type, :weight => weight, :label => label } }
|
15
|
+
let(:arguments){ [id, source_id, target_id, options] }
|
16
|
+
|
17
|
+
subject { GEXF::Edge.new(*arguments) }
|
18
|
+
|
19
|
+
describe "#new" do
|
20
|
+
its(:id) { should == id.to_s }
|
21
|
+
its(:source_id) { should == source_id.to_s }
|
22
|
+
its(:target_id) { should == target_id.to_s }
|
23
|
+
its(:weight) { should == 1.0 }
|
24
|
+
its(:label) { should == label }
|
25
|
+
its(:type) { should == GEXF::Edge::UNDIRECTED }
|
26
|
+
|
27
|
+
[:id, :source_id, :target_id, :graph].each do |param|
|
28
|
+
context "when :#{param} is missing" do
|
29
|
+
let(param) { nil }
|
30
|
+
|
31
|
+
it "raises an ArgumentError" do
|
32
|
+
expect { subject }.to raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "non-positive :weight" do
|
38
|
+
let(:weight) { -10 }
|
39
|
+
|
40
|
+
it "raises an ArgumentError" do
|
41
|
+
expect { subject }.to raise_error(ArgumentError)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "invalid type" do
|
46
|
+
let(:type) { :FOOBAR }
|
47
|
+
|
48
|
+
it "raises an ArgumentError" do
|
49
|
+
expect { subject }.to raise_error(ArgumentError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
BOOLEAN_GETTERS.each do |method|
|
55
|
+
edge_type = GEXF::Edge.const_get(method[0..-2].upcase.to_sym)
|
56
|
+
other_getters = BOOLEAN_GETTERS - [method]
|
57
|
+
|
58
|
+
describe(method) do
|
59
|
+
context "when type is :#{edge_type}" do
|
60
|
+
let(:type) { edge_type }
|
61
|
+
|
62
|
+
its(method) { should be_true }
|
63
|
+
|
64
|
+
other_getters.each do |other_type|
|
65
|
+
its(other_type) { should be_false }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GEXF::EdgeSet do
|
4
|
+
|
5
|
+
def make_edge(attr={})
|
6
|
+
mock('edge', :source_id => attr[:source_id],
|
7
|
+
:target_id => attr[:target_id],
|
8
|
+
:directed? => attr[:type] == GEXF::Edge::DIRECTED,
|
9
|
+
:undirected? => attr[:type] == GEXF::Edge::UNDIRECTED)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:source_id) { '1' }
|
13
|
+
let(:target_id) { '11' }
|
14
|
+
let(:type) { GEXF::Edge::DIRECTED }
|
15
|
+
let(:attrs) { { :type => type,
|
16
|
+
:source_id => source_id,
|
17
|
+
:target_id => target_id }}
|
18
|
+
|
19
|
+
let(:arguments) { [] }
|
20
|
+
let(:edge) { make_edge(attrs) }
|
21
|
+
|
22
|
+
let(:edge2) { make_edge(:type => GEXF::Edge::UNDIRECTED,
|
23
|
+
:source_id => '2',
|
24
|
+
:target_id => '22') }
|
25
|
+
|
26
|
+
let(:edge3) { make_edge(:type => GEXF::Edge::DIRECTED,
|
27
|
+
:source_id => '3',
|
28
|
+
:target_id => '33') }
|
29
|
+
|
30
|
+
let(:edge4) { make_edge(:type => GEXF::Edge::DIRECTED,
|
31
|
+
:source_id => '3',
|
32
|
+
:target_id => '22') }
|
33
|
+
|
34
|
+
|
35
|
+
let(:edgeset) { GEXF::EdgeSet.new(*arguments) }
|
36
|
+
|
37
|
+
describe "#<<" do
|
38
|
+
subject { edgeset << edge }
|
39
|
+
|
40
|
+
context "when edge is directed" do
|
41
|
+
it "adds the edge to the @data hash, using the :source_id as key" do
|
42
|
+
subject.to_hash.should have_key(edge.source_id)
|
43
|
+
subject.to_hash.should_not have_key(edge.target_id)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "when edge is undirected" do
|
48
|
+
let(:type) { GEXF::Edge::UNDIRECTED }
|
49
|
+
|
50
|
+
it "adds the edge to the data hash, using both :source_id, and the :target_id as keys" do
|
51
|
+
subject.to_hash.should have_key(edge.source_id)
|
52
|
+
subject.to_hash.should have_key(edge.target_id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "#count" do
|
58
|
+
let(:other_edge) { make_edge(:type => other_edge_type,
|
59
|
+
:source_id => '3',
|
60
|
+
:target_id => '33') }
|
61
|
+
|
62
|
+
subject { edgeset << edge << other_edge }
|
63
|
+
|
64
|
+
context "when adding two directed edges" do
|
65
|
+
let(:other_edge_type) { GEXF::Edge::DIRECTED }
|
66
|
+
|
67
|
+
it "creates 2 keys in the hash, returns 2" do
|
68
|
+
subject.should have(2).items
|
69
|
+
subject.to_hash.keys.should have(2).items
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when adding a directed and an undirected edge" do
|
74
|
+
let(:other_edge_type) { GEXF::Edge::UNDIRECTED }
|
75
|
+
|
76
|
+
it "creates 3 keys in the hash, returns 2" do
|
77
|
+
subject.should have(2).items
|
78
|
+
subject.to_hash.keys.should have(3).items
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#map" do
|
84
|
+
subject { edgeset << edge << edge2 << edge3 << edge4 }
|
85
|
+
|
86
|
+
it "returns a list of unique edges" do
|
87
|
+
subject.map(&:source_id).should == ['1', '2', '3', '3']
|
88
|
+
subject.map(&:target_id).should == ['11', '22', '33', '22']
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "#select" do
|
94
|
+
subject { edgeset << edge << edge2 << edge3 }
|
95
|
+
|
96
|
+
it "selects two directed edges" do
|
97
|
+
subject.select(&:directed?).should include(edge, edge3)
|
98
|
+
end
|
99
|
+
it "selects an undirected edge" do
|
100
|
+
subject.select(&:undirected?).should include(edge2)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
@@ -0,0 +1,172 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GEXF::Graph do
|
4
|
+
let(:edgetype) { nil }
|
5
|
+
let(:idtype) { nil }
|
6
|
+
let(:mode) { nil }
|
7
|
+
let(:opts) {{ :defaultedgetype => edgetype,
|
8
|
+
:idtype => idtype,
|
9
|
+
:mode => mode }}
|
10
|
+
|
11
|
+
let(:graph) { GEXF::Graph.new(opts) }
|
12
|
+
|
13
|
+
|
14
|
+
describe "default getters" do
|
15
|
+
subject { graph }
|
16
|
+
|
17
|
+
context "when params are valid" do
|
18
|
+
its(:defaultedgetype) { should == GEXF::Edge::UNDIRECTED }
|
19
|
+
its(:idtype) { should == GEXF::Graph::STRING }
|
20
|
+
its(:mode) { should == GEXF::Graph::STATIC }
|
21
|
+
its(:nodes) { should be_empty }
|
22
|
+
its(:edges) { should be_empty }
|
23
|
+
end
|
24
|
+
|
25
|
+
context "when params are not valid" do
|
26
|
+
[:defaultedgetype, :idtype, :mode].each do |param|
|
27
|
+
|
28
|
+
let(param) { :FOO }
|
29
|
+
|
30
|
+
describe "when #{param} is invalid" do
|
31
|
+
it "raises an argument error" do
|
32
|
+
expect { subject }.to raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#define_node_attribute" do
|
40
|
+
let(:attr_type) { GEXF::Attribute::STRING }
|
41
|
+
let(:attr_id) { nil }
|
42
|
+
let(:default) { 'home sweet home' }
|
43
|
+
let(:title) { 'page-title' }
|
44
|
+
let(:attr_opts) {{ :default => default,
|
45
|
+
:attr_id => attr_id }}
|
46
|
+
|
47
|
+
|
48
|
+
subject { graph.define_node_attribute(title, attr_opts) }
|
49
|
+
|
50
|
+
it "delegates to @nodes" do
|
51
|
+
|
52
|
+
new_attribute = double('attribute')
|
53
|
+
|
54
|
+
graph.nodes.should_receive(:define_attribute).
|
55
|
+
with('1', title, attr_opts).
|
56
|
+
and_return(new_attribute)
|
57
|
+
|
58
|
+
subject.should == new_attribute
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#define_edge_attribute" do
|
63
|
+
let(:attr_type) { GEXF::Attribute::STRING }
|
64
|
+
let(:attr_id) { nil }
|
65
|
+
let(:default) { 'home sweet home' }
|
66
|
+
let(:title) { 'page-title' }
|
67
|
+
let(:attr_opts) {{ :default => default,
|
68
|
+
:attr_id => attr_id }}
|
69
|
+
|
70
|
+
|
71
|
+
subject { graph.define_edge_attribute(title, attr_opts) }
|
72
|
+
|
73
|
+
it "delegates to @nodes" do
|
74
|
+
|
75
|
+
new_attribute = double('attribute')
|
76
|
+
|
77
|
+
graph.edges.should_receive(:define_attribute).
|
78
|
+
with('1', title, attr_opts).
|
79
|
+
and_return(new_attribute)
|
80
|
+
|
81
|
+
subject.should == new_attribute
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#create_node" do
|
86
|
+
let(:node_id) { 'node_21' }
|
87
|
+
let(:node_opts) {{ :label => 'my node' }}
|
88
|
+
let(:create_opts) { node_opts }
|
89
|
+
|
90
|
+
before(:each) do
|
91
|
+
@node = mock('node')
|
92
|
+
nodeset = mock('nodeset')
|
93
|
+
|
94
|
+
GEXF::NodeSet.stub(:new).and_return(nodeset)
|
95
|
+
nodeset.should_receive(:<<).at_least(:once).with(@node)
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when :id is provided" do
|
99
|
+
let(:create_opts) { node_opts.merge(:id => node_id) }
|
100
|
+
|
101
|
+
it "uses the id provided to create a node" do
|
102
|
+
GEXF::Node.should_receive(:new).
|
103
|
+
with(node_id, graph, node_opts).
|
104
|
+
and_return(@node)
|
105
|
+
|
106
|
+
graph.create_node(create_opts) == @node
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
context "when :id is not provided" do
|
111
|
+
it "auto-assigns the id using the internal counter" do
|
112
|
+
3.times do |n|
|
113
|
+
GEXF::Node.should_receive(:new).
|
114
|
+
once.ordered.
|
115
|
+
with((n+1).to_s, graph, node_opts).
|
116
|
+
and_return(@node)
|
117
|
+
end
|
118
|
+
|
119
|
+
3.times { graph.create_node(create_opts) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#create_edge" do
|
125
|
+
let(:edge_label) { 'bar' }
|
126
|
+
let(:source_id) { '2' }
|
127
|
+
let(:target_id) { '32' }
|
128
|
+
let(:source) { mock('source', :id => source_id) }
|
129
|
+
let(:target) { mock('target', :id => target_id) }
|
130
|
+
let(:edge_opts) {{:label => edge_label }}
|
131
|
+
|
132
|
+
subject { graph.create_edge(source, target, edge_opts) }
|
133
|
+
|
134
|
+
before(:each) do
|
135
|
+
@edge = mock('edge')
|
136
|
+
edgeset = mock('edgeset')
|
137
|
+
|
138
|
+
GEXF::EdgeSet.stub(:new).and_return(edgeset)
|
139
|
+
edgeset.should_receive(:<<).at_least(:once).with(@edge)
|
140
|
+
end
|
141
|
+
|
142
|
+
context "when no :id or :type param is provided" do
|
143
|
+
it "auto-assigns the edge :id using the internal counter, and the :type using :edgetype" do
|
144
|
+
|
145
|
+
GEXF::Edge.should_receive(:new).
|
146
|
+
with('1', source.id, target.id, edge_opts.merge(:graph => graph,
|
147
|
+
:type => graph.defaultedgetype)).
|
148
|
+
and_return(@edge)
|
149
|
+
|
150
|
+
subject.should == @edge
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
context "when an :id and a :type are provided" do
|
155
|
+
|
156
|
+
let(:edge_id) { 'myedge-33' }
|
157
|
+
let(:edge_opts) {{ :label => edge_label,
|
158
|
+
:id => edge_id,
|
159
|
+
:type => GEXF::Edge::DIRECTED }}
|
160
|
+
|
161
|
+
it "passes the params to the Edge constructor" do
|
162
|
+
expected_opts = edge_opts.reject {|k,v| k == :id }.merge(:graph => graph)
|
163
|
+
|
164
|
+
GEXF::Edge.should_receive(:new).
|
165
|
+
with(edge_id, source.id, target.id, expected_opts).
|
166
|
+
and_return(@edge)
|
167
|
+
|
168
|
+
subject.should == @edge
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GEXF::Node do
|
4
|
+
|
5
|
+
let(:indegree) { GEXF::Attribute.new('1', 'indegree', type: GEXF::Attribute::INTEGER) }
|
6
|
+
let(:outdegree) { GEXF::Attribute.new('2', 'outdegree', type: GEXF::Attribute::INTEGER) }
|
7
|
+
|
8
|
+
let(:attribute_definitions) {{ '1' => indegree,
|
9
|
+
'2' => outdegree }}
|
10
|
+
|
11
|
+
let(:id) { 22 }
|
12
|
+
let(:idtype) { GEXF::Graph::STRING }
|
13
|
+
let(:collection) { mock('nodeset', :attribute_definitions => attribute_definitions) }
|
14
|
+
let(:edges) { mock('edgeset') }
|
15
|
+
let(:graph) { double('graph', :nodes => collection, :idtype => idtype, :edges => edges) }
|
16
|
+
let(:label) { 'foo' }
|
17
|
+
let(:attributes) {{ }}
|
18
|
+
let(:options) {{ :label => label, :attributes => attributes }}
|
19
|
+
let(:arguments) { [id, graph, options] }
|
20
|
+
let(:node) { GEXF::Node.new(*arguments) }
|
21
|
+
|
22
|
+
subject { node }
|
23
|
+
|
24
|
+
it "includes the GEXF::Attribute::Assignable module" do
|
25
|
+
subject.class.include?(GEXF::Attribute::Assignable)
|
26
|
+
end
|
27
|
+
|
28
|
+
context "when graph :idtype is string" do
|
29
|
+
its(:id) { should == id.to_s }
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when graph :idtype is integer" do
|
33
|
+
let(:idtype) { GEXF::Graph::INTEGER }
|
34
|
+
its(:id) { should == id.to_i }
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "#label" do
|
38
|
+
context "option provided" do
|
39
|
+
its(:label) { should == label }
|
40
|
+
end
|
41
|
+
context "option omitted" do
|
42
|
+
let(:options) {{}}
|
43
|
+
its(:label) { should == id.to_s }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "attributes" do
|
48
|
+
context "when no attributes are defined" do
|
49
|
+
let(:attribute_definitions) {{}}
|
50
|
+
its(:attributes) { should be_empty }
|
51
|
+
end
|
52
|
+
context "when no attributes are supplied" do
|
53
|
+
its(:attributes) { should == { 'indegree' => nil, 'outdegree' => nil }}
|
54
|
+
end
|
55
|
+
context "when attributes are supplied" do
|
56
|
+
let(:attributes) {{ :outdegree => '24' }}
|
57
|
+
its(:attributes) { should == { 'indegree' => nil, 'outdegree' => 24 }}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "#connect_to" do
|
62
|
+
|
63
|
+
let(:edge_opts) {{ :type => GEXF::Edge::DIRECTED }}
|
64
|
+
let(:other_node) { mock('other_node') }
|
65
|
+
let(:new_edge) { mock('new-edge') }
|
66
|
+
|
67
|
+
subject { node.connect_to(other_node, edge_opts) }
|
68
|
+
|
69
|
+
it "adds the target to the nodes collection, creates and returns a new edge" do
|
70
|
+
|
71
|
+
collection.should_receive(:<<).with(other_node)
|
72
|
+
|
73
|
+
graph.should_receive(:create_edge).
|
74
|
+
with(node, other_node, edge_opts).
|
75
|
+
and_return(new_edge)
|
76
|
+
|
77
|
+
subject.should == new_edge
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "#create_and_connect_to" do
|
82
|
+
let(:edge_opts) {{ :type => GEXF::Edge::DIRECTED }}
|
83
|
+
let(:node_attrs) {{ :label => 'other node'}}
|
84
|
+
let(:new_node) { mock('new_node') }
|
85
|
+
|
86
|
+
subject { node.create_and_connect_to(node_attrs, edge_opts) }
|
87
|
+
|
88
|
+
it "creates the node, adds it to nodes collection, and returns it" do
|
89
|
+
graph.should_receive(:create_node).with(node_attrs).and_return(new_node)
|
90
|
+
collection.should_receive(:<<).with(new_node)
|
91
|
+
graph.should_receive(:create_edge).with(node, new_node, edge_opts)
|
92
|
+
|
93
|
+
subject.should == new_node
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#connections" do
|
98
|
+
subject { node.connections }
|
99
|
+
|
100
|
+
it "delegates to graph.edges[node.id]" do
|
101
|
+
edges.should_receive(:[]).with(node.id)
|
102
|
+
subject
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|