graph 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data.tar.gz.sig +0 -0
- data/.autotest +1 -0
- data/History.txt +8 -0
- data/Manifest.txt +3 -0
- data/bin/graph +0 -140
- data/lib/dep_analyzer.rb +140 -0
- data/lib/freebsd_analyzer.rb +2 -0
- data/lib/graph.rb +12 -8
- data/lib/macports_analyzer.rb +2 -0
- data/lib/rubygems/commands/graph_command.rb +17 -0
- data/lib/rubygems_analyzer.rb +2 -0
- data/lib/rubygems_plugin.rb +4 -0
- data/test/test_graph.rb +92 -2
- metadata +17 -6
- metadata.gz.sig +0 -0
data.tar.gz.sig
CHANGED
Binary file
|
data/.autotest
CHANGED
data/History.txt
CHANGED
data/Manifest.txt
CHANGED
data/bin/graph
CHANGED
@@ -1,145 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby -w
|
2
2
|
|
3
|
-
require 'graph'
|
4
|
-
require 'set'
|
5
|
-
require 'tsort'
|
6
|
-
|
7
|
-
class Cache
|
8
|
-
def initialize(cache, timeout=24)
|
9
|
-
@cache = cache
|
10
|
-
@timeout = timeout
|
11
|
-
end
|
12
|
-
|
13
|
-
def cache(id, timeout=@timeout)
|
14
|
-
Dir.mkdir @cache unless test ?d, @cache
|
15
|
-
path = File.join @cache, id
|
16
|
-
|
17
|
-
age = test(?f, path) ? (Time.now - test( ?M, path )) / 3600 : -1
|
18
|
-
|
19
|
-
if age >= 0 and timeout > age then
|
20
|
-
warn "from cache" if $DEBUG
|
21
|
-
data = File.read(path)
|
22
|
-
else
|
23
|
-
warn "NOT from cache (#{age} hours old)" if $DEBUG
|
24
|
-
data = yield
|
25
|
-
File.open(path, "w") do |f|
|
26
|
-
f.write data
|
27
|
-
end
|
28
|
-
end
|
29
|
-
return data
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class Set
|
34
|
-
def push *v
|
35
|
-
v.each do |o|
|
36
|
-
add(o)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
class Hash
|
42
|
-
include TSort
|
43
|
-
|
44
|
-
alias tsort_each_node each_key
|
45
|
-
|
46
|
-
def tsort_each_child(node, &block)
|
47
|
-
fetch(node).each(&block)
|
48
|
-
end
|
49
|
-
|
50
|
-
def minvert
|
51
|
-
r = Hash.new { |h,k| h[k] = [] }
|
52
|
-
invert.each do |keys, val|
|
53
|
-
keys.each do |key|
|
54
|
-
r[key] << val
|
55
|
-
end
|
56
|
-
end
|
57
|
-
r.each do |k,v|
|
58
|
-
r[k] = v.sort
|
59
|
-
end
|
60
|
-
r
|
61
|
-
end
|
62
|
-
|
63
|
-
def transitive
|
64
|
-
r = Hash.new { |h,k| h[k] = [] }
|
65
|
-
each do |k,v|
|
66
|
-
r[k].push(*t(k))
|
67
|
-
end
|
68
|
-
r
|
69
|
-
end
|
70
|
-
|
71
|
-
def t(k)
|
72
|
-
r = Set.new
|
73
|
-
self[k].each do |v|
|
74
|
-
r.push(v, *t(v))
|
75
|
-
end
|
76
|
-
r.to_a.sort
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
class DepAnalyzer < Cache
|
81
|
-
def initialize
|
82
|
-
super ".#{self.class}.cache"
|
83
|
-
end
|
84
|
-
|
85
|
-
def run
|
86
|
-
g = Graph.new
|
87
|
-
ports = {}
|
88
|
-
installed.each do |port|
|
89
|
-
ports[port] = nil
|
90
|
-
end
|
91
|
-
|
92
|
-
old = {}
|
93
|
-
outdated.each do |port|
|
94
|
-
old[port] = nil
|
95
|
-
end
|
96
|
-
|
97
|
-
all_ports = ports.keys
|
98
|
-
|
99
|
-
ports.each_key do |port|
|
100
|
-
deps = self.deps(port)
|
101
|
-
# remove things that don't intersect with installed list
|
102
|
-
deps -= (deps - all_ports)
|
103
|
-
deps.each do |dep|
|
104
|
-
g[port] << dep
|
105
|
-
end
|
106
|
-
ports[port] = deps
|
107
|
-
end
|
108
|
-
|
109
|
-
indies = ports.keys - ports.minvert.keys
|
110
|
-
indies.each do |k|
|
111
|
-
g.attribs[k] << "color = blue"
|
112
|
-
end
|
113
|
-
old.each do |k,v|
|
114
|
-
if indies.include? k then
|
115
|
-
g.attribs[k] << "color = purple4"
|
116
|
-
else
|
117
|
-
g.attribs[k] << "color = red"
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
puts "Looks like you can nuke:\n\t#{indies.join("\n\t")}"
|
122
|
-
|
123
|
-
unless ARGV.empty? then
|
124
|
-
ARGV.each do |pkg|
|
125
|
-
hits = ports.transitive[pkg]
|
126
|
-
sorted = ports.tsort.reverse
|
127
|
-
topo = [pkg] + sorted.select { |o| hits.include? o }
|
128
|
-
prune = ports.dup
|
129
|
-
topo.each do |k|
|
130
|
-
prune.delete(k)
|
131
|
-
end
|
132
|
-
topo -= prune.values.flatten.uniq
|
133
|
-
|
134
|
-
puts topo.join(' ')
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
g.save "#{self.class}"
|
139
|
-
system "open #{self.class}.png"
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
3
|
type = (ARGV.shift || "macports")
|
144
4
|
|
145
5
|
require "#{type}_analyzer"
|
data/lib/dep_analyzer.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'graph'
|
2
|
+
require 'set'
|
3
|
+
require 'tsort'
|
4
|
+
|
5
|
+
class Cache
|
6
|
+
def initialize(cache, timeout=24)
|
7
|
+
@cache = cache
|
8
|
+
@timeout = timeout
|
9
|
+
end
|
10
|
+
|
11
|
+
def cache(id, timeout=@timeout)
|
12
|
+
Dir.mkdir @cache unless test ?d, @cache
|
13
|
+
path = File.join @cache, id
|
14
|
+
|
15
|
+
age = test(?f, path) ? (Time.now - test( ?M, path )) / 3600 : -1
|
16
|
+
|
17
|
+
if age >= 0 and timeout > age then
|
18
|
+
warn "from cache" if $DEBUG
|
19
|
+
data = File.read(path)
|
20
|
+
else
|
21
|
+
warn "NOT from cache (#{age} hours old)" if $DEBUG
|
22
|
+
data = yield
|
23
|
+
File.open(path, "w") do |f|
|
24
|
+
f.write data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return data
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Set
|
32
|
+
def push *v
|
33
|
+
v.each do |o|
|
34
|
+
add(o)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Hash
|
40
|
+
include TSort
|
41
|
+
|
42
|
+
alias tsort_each_node each_key
|
43
|
+
|
44
|
+
def tsort_each_child(node, &block)
|
45
|
+
fetch(node).each(&block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def minvert
|
49
|
+
r = Hash.new { |h,k| h[k] = [] }
|
50
|
+
invert.each do |keys, val|
|
51
|
+
keys.each do |key|
|
52
|
+
r[key] << val
|
53
|
+
end
|
54
|
+
end
|
55
|
+
r.each do |k,v|
|
56
|
+
r[k] = v.sort
|
57
|
+
end
|
58
|
+
r
|
59
|
+
end
|
60
|
+
|
61
|
+
def transitive
|
62
|
+
r = Hash.new { |h,k| h[k] = [] }
|
63
|
+
each do |k,v|
|
64
|
+
r[k].push(*t(k))
|
65
|
+
end
|
66
|
+
r
|
67
|
+
end
|
68
|
+
|
69
|
+
def t(k)
|
70
|
+
r = Set.new
|
71
|
+
self[k].each do |v|
|
72
|
+
r.push(v, *t(v))
|
73
|
+
end
|
74
|
+
r.to_a.sort
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class DepAnalyzer < Cache
|
79
|
+
def initialize
|
80
|
+
super ".#{self.class}.cache"
|
81
|
+
end
|
82
|
+
|
83
|
+
def run(argv = ARGV)
|
84
|
+
g = Graph.new
|
85
|
+
ports = {}
|
86
|
+
installed.each do |port|
|
87
|
+
ports[port] = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
old = {}
|
91
|
+
outdated.each do |port|
|
92
|
+
old[port] = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
all_ports = ports.keys
|
96
|
+
|
97
|
+
ports.each_key do |port|
|
98
|
+
deps = self.deps(port)
|
99
|
+
# remove things that don't intersect with installed list
|
100
|
+
deps -= (deps - all_ports)
|
101
|
+
deps.each do |dep|
|
102
|
+
g[port] << dep
|
103
|
+
end
|
104
|
+
ports[port] = deps
|
105
|
+
end
|
106
|
+
|
107
|
+
indies = ports.keys - ports.minvert.keys
|
108
|
+
indies.each do |k|
|
109
|
+
g.attribs[k] << "color = blue"
|
110
|
+
end
|
111
|
+
old.each do |k,v|
|
112
|
+
if indies.include? k then
|
113
|
+
g.attribs[k] << "color = purple4"
|
114
|
+
else
|
115
|
+
g.attribs[k] << "color = red"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
puts "Looks like you can nuke:\n\t#{indies.join("\n\t")}"
|
120
|
+
|
121
|
+
unless argv.empty? then
|
122
|
+
argv.each do |pkg|
|
123
|
+
hits = ports.transitive[pkg]
|
124
|
+
sorted = ports.tsort.reverse
|
125
|
+
topo = [pkg] + sorted.select { |o| hits.include? o }
|
126
|
+
prune = ports.dup
|
127
|
+
topo.each do |k|
|
128
|
+
prune.delete(k)
|
129
|
+
end
|
130
|
+
topo -= prune.values.flatten.uniq
|
131
|
+
|
132
|
+
puts topo.join(' ')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
g.save "#{self.class}"
|
137
|
+
system "open #{self.class}.png"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
data/lib/freebsd_analyzer.rb
CHANGED
data/lib/graph.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/local/bin/ruby -w
|
2
2
|
|
3
3
|
class Graph < Hash
|
4
|
-
VERSION = '1.
|
4
|
+
VERSION = '1.1.0'
|
5
5
|
|
6
6
|
attr_reader :attribs
|
7
7
|
attr_reader :prefix
|
@@ -10,10 +10,10 @@ class Graph < Hash
|
|
10
10
|
|
11
11
|
def initialize
|
12
12
|
super { |h,k| h[k] = [] }
|
13
|
-
@prefix
|
13
|
+
@prefix = []
|
14
|
+
@order = []
|
14
15
|
@attribs = Hash.new { |h,k| h[k] = [] }
|
15
|
-
@edge
|
16
|
-
@order = []
|
16
|
+
@edge = Hash.new { |h,k| h[k] = Hash.new { |h2,k2| h2[k2] = [] } }
|
17
17
|
end
|
18
18
|
|
19
19
|
def []= key, val
|
@@ -65,25 +65,29 @@ class Graph < Hash
|
|
65
65
|
result = []
|
66
66
|
result << "digraph absent"
|
67
67
|
result << " {"
|
68
|
+
|
68
69
|
@prefix.each do |line|
|
69
|
-
result << line
|
70
|
+
result << " #{line};"
|
70
71
|
end
|
72
|
+
|
71
73
|
@attribs.sort.each do |node, attribs|
|
72
|
-
result << " #{node.inspect} [ #{attribs.join ','} ]"
|
74
|
+
result << " #{node.inspect} [ #{attribs.join ','} ];"
|
73
75
|
end
|
76
|
+
|
74
77
|
each_pair do |from, to|
|
75
78
|
edge = @edge[from][to].join ", "
|
76
79
|
edge = " [ #{edge} ]" unless edge.empty?
|
77
80
|
result << " #{from.inspect} -> #{to.inspect}#{edge};"
|
78
81
|
end
|
82
|
+
|
79
83
|
result << " }"
|
80
84
|
result.join "\n"
|
81
85
|
end
|
82
86
|
|
83
87
|
def save path, type="png"
|
84
88
|
File.open "#{path}.dot", "w" do |f|
|
85
|
-
f.
|
89
|
+
f.write self.to_s
|
86
90
|
end
|
87
|
-
system "dot -T#{type} #{path}.dot > #{path}.#{type}"
|
91
|
+
system "dot -T#{type} #{path}.dot > #{path}.#{type}" if type
|
88
92
|
end
|
89
93
|
end
|
data/lib/macports_analyzer.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rubygems/command'
|
2
|
+
require 'rubygems_analyzer'
|
3
|
+
|
4
|
+
class Gem::Commands::GraphCommand < Gem::Command
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super 'graph', 'Graph dependency relationships of installed gems'
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
RubygemsAnalyzer.new.run options[:args]
|
12
|
+
|
13
|
+
say "Graph saved to:\n\tRubygemsAnalyzer.png"
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
data/lib/rubygems_analyzer.rb
CHANGED
data/test/test_graph.rb
CHANGED
@@ -1,8 +1,98 @@
|
|
1
1
|
require "test/unit"
|
2
|
+
require "tmpdir"
|
2
3
|
require "graph"
|
3
4
|
|
4
5
|
class TestGraph < Test::Unit::TestCase
|
5
|
-
def
|
6
|
-
|
6
|
+
def setup
|
7
|
+
@graph = Graph.new
|
8
|
+
@graph["a"] << "b"
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_to_s_empty
|
12
|
+
assert_equal util_dot, Graph.new.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_delete
|
16
|
+
assert_equal %w(b), @graph.delete("a")
|
17
|
+
assert_equal [], @graph.order
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_filter_size
|
21
|
+
@graph.filter_size 2
|
22
|
+
assert @graph.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_invert
|
26
|
+
@graph["a"] << "c"
|
27
|
+
invert = @graph.invert
|
28
|
+
assert_equal %w(a), invert["b"]
|
29
|
+
assert_equal %w(a), invert["c"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_keys_by_count
|
33
|
+
@graph["a"] << "c"
|
34
|
+
@graph["d"] << "e" << "f" << "g"
|
35
|
+
assert_equal %w(d a), @graph.keys_by_count
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_order
|
39
|
+
assert_equal %w(a), @graph.order
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_save
|
43
|
+
util_save "png"
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_save_nil
|
47
|
+
util_save nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_to_s
|
51
|
+
expected = util_dot '"a" -> "b"'
|
52
|
+
assert_equal expected, @graph.to_s
|
53
|
+
|
54
|
+
@graph["a"] << "c"
|
55
|
+
|
56
|
+
expected = util_dot '"a" -> "b"', '"a" -> "c"'
|
57
|
+
assert_equal expected, @graph.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_to_s_prefix
|
61
|
+
@graph.prefix << "blah"
|
62
|
+
@graph["a"] << "c"
|
63
|
+
|
64
|
+
expected = util_dot('blah', '"a" -> "b"', '"a" -> "c"')
|
65
|
+
assert_equal expected, @graph.to_s
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_to_s_attrib
|
69
|
+
@graph.attribs["a"] << "color = blue"
|
70
|
+
@graph["a"] << "c"
|
71
|
+
|
72
|
+
expected = util_dot('"a" [ color = blue ]', '"a" -> "b"', '"a" -> "c"')
|
73
|
+
assert_equal expected, @graph.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
def util_dot(*lines)
|
77
|
+
lines = lines.map { |l| " #{l};" }.join("\n")
|
78
|
+
"digraph absent\n {\n#{lines}\n }".sub(/\n\n/, "\n")
|
79
|
+
end
|
80
|
+
|
81
|
+
def util_save type
|
82
|
+
path = File.join(Dir.tmpdir, "blah.#{$$}")
|
83
|
+
|
84
|
+
$x = nil
|
85
|
+
|
86
|
+
def @graph.system(*args)
|
87
|
+
$x = args
|
88
|
+
end
|
89
|
+
|
90
|
+
@graph.save(path, type)
|
91
|
+
|
92
|
+
assert_equal @graph.to_s, File.read("#{path}.dot")
|
93
|
+
expected = ["dot -T#{type} #{path}.dot > #{path}.png"] if type
|
94
|
+
assert_equal expected, $x
|
95
|
+
ensure
|
96
|
+
File.unlink path rescue nil
|
7
97
|
end
|
8
98
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: graph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Davis
|
@@ -30,7 +30,7 @@ cert_chain:
|
|
30
30
|
FBHgymkyj/AOSqKRIpXPhjC6
|
31
31
|
-----END CERTIFICATE-----
|
32
32
|
|
33
|
-
date: 2009-
|
33
|
+
date: 2009-04-16 00:00:00 -07:00
|
34
34
|
default_executable:
|
35
35
|
dependencies:
|
36
36
|
- !ruby/object:Gem::Dependency
|
@@ -41,9 +41,15 @@ dependencies:
|
|
41
41
|
requirements:
|
42
42
|
- - ">="
|
43
43
|
- !ruby/object:Gem::Version
|
44
|
-
version: 1.12.
|
44
|
+
version: 1.12.2
|
45
45
|
version:
|
46
|
-
description:
|
46
|
+
description: |-
|
47
|
+
Graph is a type of hash that outputs in graphviz's dot format. It
|
48
|
+
comes with a command-line interface that is easily pluggable.
|
49
|
+
|
50
|
+
It ships with plugins to graph dependencies and status of installed
|
51
|
+
rubygems, mac ports, and freebsd ports, coloring leaf nodes blue,
|
52
|
+
outdated nodes red, and outdated leaf nodes purple (red+blue).
|
47
53
|
email:
|
48
54
|
- ryand-ruby@zenspider.com
|
49
55
|
executables:
|
@@ -61,13 +67,18 @@ files:
|
|
61
67
|
- README.txt
|
62
68
|
- Rakefile
|
63
69
|
- bin/graph
|
70
|
+
- lib/dep_analyzer.rb
|
64
71
|
- lib/freebsd_analyzer.rb
|
65
72
|
- lib/graph.rb
|
66
73
|
- lib/macports_analyzer.rb
|
74
|
+
- lib/rubygems/commands/graph_command.rb
|
67
75
|
- lib/rubygems_analyzer.rb
|
76
|
+
- lib/rubygems_plugin.rb
|
68
77
|
- test/test_graph.rb
|
69
78
|
has_rdoc: true
|
70
79
|
homepage: http://rubyforge.org/projects/seattlerb
|
80
|
+
licenses: []
|
81
|
+
|
71
82
|
post_install_message:
|
72
83
|
rdoc_options:
|
73
84
|
- --main
|
@@ -89,9 +100,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
100
|
requirements: []
|
90
101
|
|
91
102
|
rubyforge_project: seattlerb
|
92
|
-
rubygems_version: 1.3.
|
103
|
+
rubygems_version: 1.3.2
|
93
104
|
signing_key:
|
94
|
-
specification_version:
|
105
|
+
specification_version: 3
|
95
106
|
summary: Graph is a type of hash that outputs in graphviz's dot format
|
96
107
|
test_files:
|
97
108
|
- test/test_graph.rb
|
metadata.gz.sig
CHANGED
Binary file
|