depgraph 0.11.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.
- data/History.txt +28 -0
- data/License.txt +20 -0
- data/README.rdoc +40 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/bin/depgraph +89 -0
- data/config/hoe.rb +71 -0
- data/config/requirements.rb +19 -0
- data/lib/DepGraph/version.rb +9 -0
- data/lib/dependency_types.yaml +11 -0
- data/lib/dependency_types_manager.rb +49 -0
- data/lib/file_system_node_finder.rb +64 -0
- data/lib/graph_creator.rb +122 -0
- data/lib/graph_image_creator.rb +125 -0
- data/lib/node.rb +44 -0
- data/lib/nodefinders/gems_node_finder.rb +58 -0
- data/lib/nodefinders/test_node_finder.rb +26 -0
- data/script/destroy +14 -0
- data/script/destroy.cmd +1 -0
- data/script/generate +14 -0
- data/script/generate.cmd +1 -0
- data/script/txt2html +74 -0
- data/script/txt2html.cmd +1 -0
- data/setup.rb +1585 -0
- data/spec/IntegrationTests/depgraph_spec.rb +90 -0
- data/spec/IntegrationTests/file_system_node_finder_spec.rb +54 -0
- data/spec/IntegrationTests/gems_node_finder_spec.rb +20 -0
- data/spec/IntegrationTests/graph_creator_spec.rb +30 -0
- data/spec/IntegrationTests/graph_image_creator_spec.rb +50 -0
- data/spec/UnitTests/dependency_types_manager_spec.rb +46 -0
- data/spec/UnitTests/file_system_node_finder_spec.rb +62 -0
- data/spec/UnitTests/graph_creator_spec.rb +140 -0
- data/spec/UnitTests/graph_image_creator_spec.rb +56 -0
- data/spec/UnitTests/node_spec.rb +45 -0
- data/spec/integration_spec.opts +4 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +179 -0
- data/spec/unit_spec.opts +4 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/tasks/rspec.rake +35 -0
- data/tasks/website.rake +17 -0
- data/todo.txt +4 -0
- metadata +128 -0
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'node'
|
3
|
+
require 'graph_image_creator'
|
4
|
+
require 'file_system_node_finder'
|
5
|
+
require 'dependency_types_manager'
|
6
|
+
|
7
|
+
module DepGraph
|
8
|
+
class GraphCreator
|
9
|
+
attr_writer :graph_image_creator_class, :from, :to, :node_finder, :trans
|
10
|
+
|
11
|
+
def initialize(node_type = :none)
|
12
|
+
@node_finder = get_node_finder(node_type)
|
13
|
+
@graph_image_creator_class = GraphImageCreator
|
14
|
+
@trans = false
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.types
|
18
|
+
node_finders_dir = File.join(File.dirname(__FILE__), 'nodefinders')
|
19
|
+
node_finders = Dir.glob(File.join(node_finders_dir, '*_node_finder.rb'))
|
20
|
+
node_finders = node_finders.map {|nf| nf.gsub(node_finders_dir + '/', '').gsub('_node_finder.rb', '').to_sym}
|
21
|
+
return DependencyTypesManager.types + node_finders
|
22
|
+
end
|
23
|
+
|
24
|
+
def location=(loc)
|
25
|
+
@node_finder.location = loc
|
26
|
+
end
|
27
|
+
|
28
|
+
def excluded_nodes=(exc)
|
29
|
+
@excluded_nodes = exc.map {|e| e.strip}
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_image(image_file_name = 'dependency_graph.png')
|
33
|
+
create_graph.create_image(image_file_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_graph
|
37
|
+
nodes = @node_finder.get_nodes.uniq
|
38
|
+
nodes = apply_filters(nodes)
|
39
|
+
nodes = remove_disconnected_nodes(nodes)
|
40
|
+
|
41
|
+
graph = @graph_image_creator_class.new
|
42
|
+
return graph if nodes.size < 2
|
43
|
+
|
44
|
+
nodes.each do |node|
|
45
|
+
graph.add_node(node)
|
46
|
+
end
|
47
|
+
|
48
|
+
nodes.each do |node|
|
49
|
+
node.dependencies.each do |dependable|
|
50
|
+
graph.add_edge(node, dependable) if nodes.include? dependable
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
graph.trans = @trans
|
55
|
+
|
56
|
+
return graph
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def get_node_finder(node_type)
|
61
|
+
begin
|
62
|
+
begin
|
63
|
+
require "nodefinders/#{node_type.to_s}_node_finder"
|
64
|
+
rescue LoadError
|
65
|
+
end
|
66
|
+
|
67
|
+
node_finder_class = deep_const_get("DepGraph::NodeFinders::#{camelize(node_type.to_s)}NodeFinder")
|
68
|
+
@node_finder = node_finder_class.new
|
69
|
+
rescue
|
70
|
+
@node_finder = FileSystemNodeFinder.new(node_type)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def remove_disconnected_nodes(nodes)
|
75
|
+
nodes.select do |node|
|
76
|
+
nodes.any? {|n| n.depends_on?(node) or node.depends_on?(n)}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def apply_filters(nodes)
|
81
|
+
apply_exclude_filter apply_to_filter(apply_from_filter(nodes))
|
82
|
+
end
|
83
|
+
|
84
|
+
def apply_exclude_filter(nodes)
|
85
|
+
return nodes unless @excluded_nodes
|
86
|
+
|
87
|
+
regexps = Regexp.union(*@excluded_nodes)
|
88
|
+
|
89
|
+
nodes.reject do |node|
|
90
|
+
node.name.match(regexps)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def apply_from_filter(nodes)
|
95
|
+
return nodes unless @from
|
96
|
+
|
97
|
+
nodes.select do |node|
|
98
|
+
node.name.match(@from) or nodes.any?{|n| n.name.match(@from) and n.depends_on?(node)}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def apply_to_filter(nodes)
|
103
|
+
return nodes unless @to
|
104
|
+
|
105
|
+
nodes.select do |node|
|
106
|
+
node.name.match(@to) or node.dependencies.any? {|d| d.name.match(@to)}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
111
|
+
if first_letter_in_uppercase
|
112
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
|
113
|
+
else
|
114
|
+
lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def deep_const_get str
|
119
|
+
return str.split("::").inject(Object) {|a,b| a.const_get(b) }
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'graphviz'
|
3
|
+
|
4
|
+
module DepGraph
|
5
|
+
class GraphImageCreator
|
6
|
+
attr_writer :trans
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@nodes = []
|
10
|
+
@edges = []
|
11
|
+
@trans = false
|
12
|
+
end
|
13
|
+
|
14
|
+
def node_count
|
15
|
+
@nodes.size
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_node(node_name)
|
19
|
+
fail 'Node name not specified' if node_name.to_str.empty?
|
20
|
+
@nodes << node_name.to_str
|
21
|
+
end
|
22
|
+
|
23
|
+
def has_node?(node_name)
|
24
|
+
@nodes.include? node_name
|
25
|
+
end
|
26
|
+
|
27
|
+
def edge_count
|
28
|
+
@edges.size
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_edge(source_node_name, target_node_name)
|
32
|
+
fail "Cannot create a dependency to the unregistered node #{target_node_name}" unless @nodes.include?(target_node_name.to_str)
|
33
|
+
fail "Cannot create a dependency from the unregistered node #{source_node_name}" unless @nodes.include?(source_node_name.to_str)
|
34
|
+
|
35
|
+
@edges << [source_node_name.to_str, target_node_name.to_str]
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_edge?(node1, node2)
|
39
|
+
@edges.include? [node1, node2]
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset
|
43
|
+
@nodes.clear
|
44
|
+
@edges.clear
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_image(image_file_name)
|
48
|
+
begin
|
49
|
+
return false if @nodes.size == 0 or @edges.size == 0
|
50
|
+
|
51
|
+
set_default_output_generation_unless_is_set
|
52
|
+
|
53
|
+
return @output_generation.call(@nodes, @edges, image_file_name)
|
54
|
+
|
55
|
+
rescue => e
|
56
|
+
puts e.message
|
57
|
+
puts e.backtrace
|
58
|
+
return false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def output_generation= output_generation_lambda
|
63
|
+
@output_generation = output_generation_lambda
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def quotify(a_string)
|
68
|
+
'"' + a_string + '"'
|
69
|
+
end
|
70
|
+
|
71
|
+
def set_default_output_generation_unless_is_set
|
72
|
+
unless @output_generation
|
73
|
+
@output_generation = lambda {|nodes, edges, image_file_name|
|
74
|
+
#TODO: Could we catch Graphviz errors that the wrapper couldn't catch?
|
75
|
+
g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial')
|
76
|
+
|
77
|
+
load_nodes(g, nodes)
|
78
|
+
load_edges(g, edges)
|
79
|
+
|
80
|
+
create_output(g, image_file_name)
|
81
|
+
|
82
|
+
return true
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def load_nodes(g, nodes)
|
88
|
+
nodes.each do |node|
|
89
|
+
g.add_nodes(quotify(node))
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def load_edges(g, edges)
|
94
|
+
edges.each do |from, to|
|
95
|
+
g.add_edges(quotify(from), quotify(to))
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_output(g, image_file_name)
|
100
|
+
output_type = get_output_type(image_file_name)
|
101
|
+
|
102
|
+
if @trans
|
103
|
+
begin
|
104
|
+
g.output(:dot => 'temp.dot')
|
105
|
+
system "tred temp.dot|dot -T#{output_type} > #{image_file_name}"
|
106
|
+
ensure
|
107
|
+
File.delete('temp.dot')
|
108
|
+
end
|
109
|
+
else
|
110
|
+
g.output(output_type => image_file_name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def get_output_type(image_file_name)
|
115
|
+
#png is the default output type
|
116
|
+
output_type = 'png'
|
117
|
+
|
118
|
+
image_file_name.scan(/.+\.([^\.]*)$/) do |matches|
|
119
|
+
output_type = matches[0]
|
120
|
+
end
|
121
|
+
|
122
|
+
return output_type
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/node.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module DepGraph
|
2
|
+
|
3
|
+
#A node knows its dependables
|
4
|
+
class Node
|
5
|
+
include Comparable
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
def initialize(node_uri)
|
9
|
+
fail 'Empty uris are not allowed' if node_uri.empty?
|
10
|
+
@name = node_uri
|
11
|
+
@dependencies = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_str
|
15
|
+
@name
|
16
|
+
end
|
17
|
+
|
18
|
+
def <=> other_node
|
19
|
+
@name <=> other_node.to_str
|
20
|
+
end
|
21
|
+
|
22
|
+
def eql? other_node
|
23
|
+
(self <=> other_node) == 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def hash
|
27
|
+
@name.hash
|
28
|
+
end
|
29
|
+
|
30
|
+
def depends_on node
|
31
|
+
node = Node.new(node) unless node.respond_to? :name
|
32
|
+
|
33
|
+
@dependencies << node
|
34
|
+
end
|
35
|
+
|
36
|
+
def depends_on? node
|
37
|
+
@dependencies.include? node
|
38
|
+
end
|
39
|
+
|
40
|
+
def dependencies
|
41
|
+
@dependencies
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'node'
|
2
|
+
|
3
|
+
module DepGraph
|
4
|
+
module NodeFinders
|
5
|
+
class GemsNodeFinder
|
6
|
+
def initialize
|
7
|
+
@spec_directories = Gem::Specification.dirs
|
8
|
+
end
|
9
|
+
|
10
|
+
def location=(loc)
|
11
|
+
@spec_directories = loc
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_nodes
|
15
|
+
require 'rubygems'
|
16
|
+
|
17
|
+
fail 'The gem specification directories were not set' unless @spec_directories and @spec_directories.size > 0
|
18
|
+
|
19
|
+
nodes = {}
|
20
|
+
@spec_directories.each do |spec_directory|
|
21
|
+
Dir["#{spec_directory}/**/*.gemspec"].each do |gemspec_file_name|
|
22
|
+
add_nodes_from_gemspec(nodes, gemspec_file_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
return nodes.values.sort
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def add_nodes_from_gemspec(nodes, gemspec_file_name)
|
31
|
+
gem_dependencies = get_gemspec_dependencies(gemspec_file_name)
|
32
|
+
gem_name = get_gemspec_name(gemspec_file_name)
|
33
|
+
|
34
|
+
nodes[gem_name] ||= Node.new(gem_name)
|
35
|
+
gem_dependencies.each do |gem_dependency|
|
36
|
+
nodes[gem_dependency] ||= Node.new(gem_dependency)
|
37
|
+
nodes[gem_name].depends_on(nodes[gem_dependency])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_gemspec_dependencies(gemspec_file_name)
|
42
|
+
gem_dependencies = []
|
43
|
+
content = File.read(gemspec_file_name)
|
44
|
+
content.scan(/add_dependency\(%q<([^>]+)>/) do |matches|
|
45
|
+
matches.each do |match|
|
46
|
+
gem_dependencies << match
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
return gem_dependencies
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_gemspec_name(gemspec_file_name)
|
54
|
+
File.basename(gemspec_file_name).match(/(.+)-\d+/)[1].to_s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'node'
|
2
|
+
|
3
|
+
module DepGraph
|
4
|
+
module NodeFinders
|
5
|
+
|
6
|
+
#This is a simple example of a custom node finder with a hard coded graph.
|
7
|
+
#Note that the file must be named [nodetype]_node_finder.rb containing a class named [Nodetype]NodeFinder
|
8
|
+
#To use this example do: depgraph -type test
|
9
|
+
class TestNodeFinder
|
10
|
+
def location=(loc)
|
11
|
+
#we will ignore location in this example
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_nodes
|
15
|
+
#let's return a hardcoded graph with 2 nodes and one dependency between them
|
16
|
+
|
17
|
+
node1 = Node.new('node1')
|
18
|
+
node2 = Node.new('node2')
|
19
|
+
|
20
|
+
node1.depends_on(node2)
|
21
|
+
|
22
|
+
return [node1, node2]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/destroy.cmd
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
@ruby script/destroy %*
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
data/script/generate.cmd
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
@ruby script/generate %*
|
data/script/txt2html
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
begin
|
5
|
+
require 'newgem'
|
6
|
+
rescue LoadError
|
7
|
+
puts "\n\nGenerating the website requires the newgem RubyGem"
|
8
|
+
puts "Install: gem install newgem\n\n"
|
9
|
+
exit(1)
|
10
|
+
end
|
11
|
+
require 'redcloth'
|
12
|
+
require 'syntax/convertors/html'
|
13
|
+
require 'erb'
|
14
|
+
require File.dirname(__FILE__) + '/../lib/DepGraph/version.rb'
|
15
|
+
|
16
|
+
version = DepGraph::VERSION::STRING
|
17
|
+
download = 'http://rubyforge.org/projects/DepGraph'
|
18
|
+
|
19
|
+
class Fixnum
|
20
|
+
def ordinal
|
21
|
+
# teens
|
22
|
+
return 'th' if (10..19).include?(self % 100)
|
23
|
+
# others
|
24
|
+
case self % 10
|
25
|
+
when 1: return 'st'
|
26
|
+
when 2: return 'nd'
|
27
|
+
when 3: return 'rd'
|
28
|
+
else return 'th'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Time
|
34
|
+
def pretty
|
35
|
+
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def convert_syntax(syntax, source)
|
40
|
+
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
41
|
+
end
|
42
|
+
|
43
|
+
if ARGV.length >= 1
|
44
|
+
src, template = ARGV
|
45
|
+
template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
|
46
|
+
|
47
|
+
else
|
48
|
+
puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
|
49
|
+
exit!
|
50
|
+
end
|
51
|
+
|
52
|
+
template = ERB.new(File.open(template).read)
|
53
|
+
|
54
|
+
title = nil
|
55
|
+
body = nil
|
56
|
+
File.open(src) do |fsrc|
|
57
|
+
title_text = fsrc.readline
|
58
|
+
body_text = fsrc.read
|
59
|
+
syntax_items = []
|
60
|
+
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
|
61
|
+
ident = syntax_items.length
|
62
|
+
element, syntax, source = $1, $2, $3
|
63
|
+
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
|
64
|
+
"syntax-temp-#{ident}"
|
65
|
+
}
|
66
|
+
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
67
|
+
body = RedCloth.new(body_text).to_html
|
68
|
+
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
69
|
+
end
|
70
|
+
stat = File.stat(src)
|
71
|
+
created = stat.ctime
|
72
|
+
modified = stat.mtime
|
73
|
+
|
74
|
+
$stdout << template.result(binding)
|