gexf 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|