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.
Files changed (44) hide show
  1. data/History.txt +28 -0
  2. data/License.txt +20 -0
  3. data/README.rdoc +40 -0
  4. data/Rakefile +31 -0
  5. data/VERSION +1 -0
  6. data/bin/depgraph +89 -0
  7. data/config/hoe.rb +71 -0
  8. data/config/requirements.rb +19 -0
  9. data/lib/DepGraph/version.rb +9 -0
  10. data/lib/dependency_types.yaml +11 -0
  11. data/lib/dependency_types_manager.rb +49 -0
  12. data/lib/file_system_node_finder.rb +64 -0
  13. data/lib/graph_creator.rb +122 -0
  14. data/lib/graph_image_creator.rb +125 -0
  15. data/lib/node.rb +44 -0
  16. data/lib/nodefinders/gems_node_finder.rb +58 -0
  17. data/lib/nodefinders/test_node_finder.rb +26 -0
  18. data/script/destroy +14 -0
  19. data/script/destroy.cmd +1 -0
  20. data/script/generate +14 -0
  21. data/script/generate.cmd +1 -0
  22. data/script/txt2html +74 -0
  23. data/script/txt2html.cmd +1 -0
  24. data/setup.rb +1585 -0
  25. data/spec/IntegrationTests/depgraph_spec.rb +90 -0
  26. data/spec/IntegrationTests/file_system_node_finder_spec.rb +54 -0
  27. data/spec/IntegrationTests/gems_node_finder_spec.rb +20 -0
  28. data/spec/IntegrationTests/graph_creator_spec.rb +30 -0
  29. data/spec/IntegrationTests/graph_image_creator_spec.rb +50 -0
  30. data/spec/UnitTests/dependency_types_manager_spec.rb +46 -0
  31. data/spec/UnitTests/file_system_node_finder_spec.rb +62 -0
  32. data/spec/UnitTests/graph_creator_spec.rb +140 -0
  33. data/spec/UnitTests/graph_image_creator_spec.rb +56 -0
  34. data/spec/UnitTests/node_spec.rb +45 -0
  35. data/spec/integration_spec.opts +4 -0
  36. data/spec/spec.opts +6 -0
  37. data/spec/spec_helper.rb +179 -0
  38. data/spec/unit_spec.opts +4 -0
  39. data/tasks/deployment.rake +34 -0
  40. data/tasks/environment.rake +7 -0
  41. data/tasks/rspec.rake +35 -0
  42. data/tasks/website.rake +17 -0
  43. data/todo.txt +4 -0
  44. 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
@@ -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
@@ -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)
@@ -0,0 +1 @@
1
+ @ruby script/destroy %*
@@ -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)
@@ -0,0 +1 @@
1
+ @ruby script/generate %*
@@ -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)