DepGraph 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt CHANGED
@@ -1 +1,19 @@
1
+ 0.9.0
1
2
 
3
+ * Added dynamic loading of node finders from directory lib/nodefinders.
4
+ Example:
5
+ see 'lib/nodefinders/TestNodeFinder.rb' which can be used like this:
6
+ depgraph -type test
7
+ * Added functionality to exclude the nodes the user is not interested in.
8
+ Example:
9
+ depgraph -type ruby_requires -exc "client, server"
10
+ * A transitive reduction can be applied to the graph.
11
+ Example:
12
+ depgraph -type csproj -trans
13
+ * Disconnected nodes don't show up anymore.
14
+ * The type of output can be selected by changing the file extension.
15
+ Example:
16
+ depgraph -type test -output test.dot
17
+ * Separated unit tests and integration tests in different folders.
18
+ * Renamed option -dirs to -location
19
+ * Some more refactorings.
data/Manifest.txt CHANGED
@@ -7,12 +7,13 @@ bin/depgraph
7
7
  config/hoe.rb
8
8
  config/requirements.rb
9
9
  lib/DepGraph/version.rb
10
- lib/dependable_filter_manager.rb
10
+ lib/dependency_types_manager.rb
11
11
  lib/dependency_types.yaml
12
- lib/dependent.rb
13
- lib/file_system_dependent_finder.rb
14
- lib/graph.rb
12
+ lib/node.rb
13
+ lib/file_system_node_finder.rb
14
+ lib/graph_image_creator.rb
15
15
  lib/graph_creator.rb
16
+ lib/nodefinders/test_node_finder.rb
16
17
  script/destroy
17
18
  script/destroy.cmd
18
19
  script/generate
@@ -20,16 +21,20 @@ script/generate.cmd
20
21
  script/txt2html
21
22
  script/txt2html.cmd
22
23
  setup.rb
23
- spec/dependable_filter_manager_spec.rb
24
- spec/dependent_spec.rb
25
- spec/depgraph_spec.rb
26
- spec/file_system_dependent_finder_spec.rb
27
- spec/graph_creator_spec.rb
28
- spec/graph_spec.rb
24
+ spec/IntegrationTests/depgraph_spec.rb
25
+ spec/IntegrationTests/file_system_node_finder_spec.rb
26
+ spec/IntegrationTests/graph_creator_spec.rb
27
+ spec/IntegrationTests/graph_image_creator_spec.rb
28
+ spec/UnitTests/dependency_types_manager_spec.rb
29
+ spec/UnitTests/node_spec.rb
30
+ spec/UnitTests/graph_creator_spec.rb
31
+ spec/UnitTests/graph_image_creator_spec.rb
32
+ spec/UnitTests/file_system_node_finder_spec.rb
33
+ spec/integration_spec.opts
29
34
  spec/spec.opts
30
35
  spec/spec_helper.rb
36
+ spec/unit_spec.opts
31
37
  tasks/deployment.rake
32
38
  tasks/environment.rake
33
39
  tasks/rspec.rake
34
40
  tasks/website.rake
35
- todo.txt
data/bin/depgraph CHANGED
@@ -16,54 +16,67 @@ rescue LoadError
16
16
  exit
17
17
  end
18
18
 
19
- require 'dependable_filter_manager'
19
+ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
20
+
20
21
  require 'graph_creator'
21
22
 
23
+
22
24
  module CommandLineParameters extend OptiFlagSet
23
25
  flag "type" do
24
- dependency_types = DepGraph::DependableFilterManager.types
26
+ dependency_types = DepGraph::GraphCreator.types
25
27
  description "Type of dependencies to analyze: #{dependency_types.join(', ')}"
26
28
  value_in_set dependency_types
27
29
  end
28
30
 
31
+ optional_flag "exc" do
32
+ description "The node names to be excluded.\nUse double quotes and commas for multiple nodes."
33
+ end
34
+
29
35
  optional_flag "output" do
30
- description "The file name of the image."
36
+ description "The file name of the graph image."
31
37
  end
32
38
 
33
-
34
39
  optional_flag "from" do
35
- description "Regular expresion that must be met by the dependency source."
40
+ description "Regular expression that must be met by the dependency source."
36
41
  end
37
42
 
38
43
  optional_flag "to" do
39
- description "Regular expresion that must be met by the dependency target."
44
+ description "Regular expression that must be met by the dependency target."
45
+ end
46
+
47
+ optional_flag "location" do
48
+ description "A string defining the place where the nodes to analyse are found.\nThis are directories for most dependency types.\n
49
+ Use double quotes and commas for more than one directory."
40
50
  end
41
51
 
42
- optional_flag "dirs" do
43
- description "Directories to traverse for dependency files. Use double quotes and commas for more than one directory."
52
+ optional_switch_flag "trans" do
53
+ description "Applies a transitive reduction to the graph"
44
54
  end
45
55
 
46
56
  usage_flag "h","help","?"
47
-
48
57
 
49
58
  and_process!
50
59
  end
51
60
 
52
61
  type = CommandLineParameters.flags.type
53
62
  output = CommandLineParameters.flags.output || 'dependency_graph.png'
54
- dirs = CommandLineParameters.flags.dirs || '.'
63
+ location = CommandLineParameters.flags.location || '.'
55
64
  from = CommandLineParameters.flags.from if CommandLineParameters.flags.from?
56
65
  to = CommandLineParameters.flags.to if CommandLineParameters.flags.to?
66
+ exc = CommandLineParameters.flags.exc || ''
67
+ trans = CommandLineParameters.flags.trans?
57
68
 
58
69
  puts "Creating #{type} dependency graph. Please wait..."
59
70
 
60
71
  dep_grapher = DepGraph::GraphCreator.new(type)
61
- dep_grapher.dirs = dirs.split(',')
62
- dep_grapher.from= from
72
+ dep_grapher.location = location.split(',')
73
+ dep_grapher.from = from
63
74
  dep_grapher.to = to
75
+ dep_grapher.trans = trans
76
+ dep_grapher.excluded_nodes = exc.split(',')
64
77
 
65
78
  if dep_grapher.create_image(output)
66
- puts "Created #{output}.\nDone."
79
+ puts "Done.\nResults in #{output}."
67
80
  else
68
81
  puts "No dependency graph image created."
69
82
  end
data/config/hoe.rb CHANGED
@@ -1,12 +1,12 @@
1
1
  require 'DepGraph/version'
2
2
 
3
3
  AUTHOR = 'Daniel Cadenas Ni�n' # can also be an array of Authors
4
- EMAIL = "depgraph-devel@rubyforge.org"
4
+ EMAIL = "dcadenas@gmail.com"
5
5
  DESCRIPTION = "A tool to create dependency graph images from source code directories"
6
6
  GEM_NAME = 'DepGraph' # what ppl will type to install your gem
7
7
  RUBYFORGE_PROJECT = 'DepGraph' # The unix name for your project
8
8
  HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
- DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT.downcase}"
10
10
 
11
11
  @config_file = "~/.rubyforge/user-config.yml"
12
12
  @config = nil
@@ -1,7 +1,7 @@
1
1
  module DepGraph #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 8
4
+ MINOR = 9
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -1,7 +1,7 @@
1
1
  require 'yaml'
2
2
 
3
3
  module DepGraph
4
- class DependableFilterManager
4
+ class DependencyTypesManager
5
5
  def self.dependency_types_file
6
6
  File.join(File.dirname(__FILE__), 'dependency_types.yaml')
7
7
  end
@@ -16,27 +16,27 @@ module DepGraph
16
16
  @@dependable_dependency_types.map {|type, _| type.to_sym}
17
17
  end
18
18
 
19
- def initialize(dependent_type = :anything)
20
- @dependent_type = dependent_type.to_s
19
+ def initialize(node_type = :anything)
20
+ @node_type = node_type.to_s
21
21
  end
22
22
 
23
23
  def dependable_regexp
24
- get_dependent_type_parameters(@dependent_type)['dependable_regexp']
24
+ get_node_type_parameters(@node_type)['dependable_regexp']
25
25
  end
26
26
 
27
27
  def dependable_regexp_capture_group_index
28
- get_dependent_type_parameters(@dependent_type)['capture_group_index']
28
+ get_node_type_parameters(@node_type)['capture_group_index']
29
29
  end
30
30
 
31
31
  def file_name_pattern
32
- get_dependent_type_parameters(@dependent_type)['file_name_pattern']
32
+ get_node_type_parameters(@node_type)['file_name_pattern']
33
33
  end
34
34
 
35
- def get_dependent_type_parameters(dependent_type)
36
- dependent_type_parameters = @@dependable_dependency_types[dependent_type]
35
+ def get_node_type_parameters(node_type)
36
+ node_type_parameters = @@dependable_dependency_types[node_type]
37
37
 
38
- if dependent_type_parameters
39
- return dependent_type_parameters
38
+ if node_type_parameters
39
+ return node_type_parameters
40
40
  else
41
41
  return default_parameters = {
42
42
  'dependable_regexp' => /.+/,
@@ -0,0 +1,60 @@
1
+ require 'node'
2
+ require 'dependency_types_manager'
3
+
4
+ module DepGraph
5
+ class FileSystemNodeFinder
6
+ attr_accessor :dependable_filter, :dependable_filter_capture_group_index, :file_name_pattern
7
+ attr_writer :location
8
+
9
+ def initialize(node_type)
10
+
11
+ dependable_filter_manager = DependencyTypesManager.new node_type
12
+ @file_name_pattern = dependable_filter_manager.file_name_pattern
13
+ @dependable_filter = dependable_filter_manager.dependable_regexp
14
+ @dependable_filter_capture_group_index = dependable_filter_manager.dependable_regexp_capture_group_index
15
+ @location = ['.']
16
+ end
17
+
18
+ def location=(locs)
19
+ @location = locs.map {|d| d.strip}
20
+ end
21
+
22
+ def get_nodes
23
+ files = []
24
+ @location.each do |dir|
25
+ files += Dir.glob(dir.strip + '/**/' + @file_name_pattern)
26
+ end
27
+
28
+ nodes = []
29
+ files.each { |file| nodes << create_node_from_file(file) }
30
+ return nodes
31
+ end
32
+
33
+ def load_dependencies_from_string(node, dependencies_string)
34
+ fail 'The dependable finder Regexp was not set' unless @dependable_filter
35
+
36
+ dependencies_string.scan(@dependable_filter).each do |matches|
37
+ dependable = (matches.respond_to? :to_ary) ? matches[@dependable_filter_capture_group_index] : matches
38
+ node.depends_on(dependable) unless node.depends_on? dependable
39
+ end
40
+ end
41
+
42
+ private
43
+ def create_node_from_file file
44
+ node = Node.new remove_extension(file)
45
+
46
+ File.open(file) do |f|
47
+ f.each_line do |line|
48
+ load_dependencies_from_string(node, line)
49
+ end
50
+ end
51
+
52
+ return node
53
+ end
54
+
55
+ def remove_extension file_path
56
+ file_extension_regexp = /\.[^\.]+$/
57
+ return File.basename(file_path).gsub(file_extension_regexp, '')
58
+ end
59
+ end
60
+ end
data/lib/graph_creator.rb CHANGED
@@ -1,85 +1,122 @@
1
1
  require 'rubygems'
2
- require 'dependent'
3
- require 'graph'
4
- require 'file_system_dependent_finder'
2
+ require 'node'
3
+ require 'graph_image_creator'
4
+ require 'file_system_node_finder'
5
+ require 'dependency_types_manager'
5
6
 
6
7
  module DepGraph
7
8
  class GraphCreator
8
- attr_accessor :dependent_finder
9
+ attr_writer :graph_image_creator_class, :from, :to, :node_finder, :trans
9
10
 
10
- def initialize(dependent_type = :none)
11
- @dependent_finder = FileSystemDependentFinder.new(dependent_type)
12
- @graph_class = Graph
11
+ def initialize(node_type = :none)
12
+ @node_finder = get_node_finder(node_type)
13
+ @graph_image_creator_class = GraphImageCreator
14
+ @trans = false
13
15
  end
14
16
 
15
- def graph_class= the_graph_class
16
- @graph_class = the_graph_class
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
17
22
  end
18
23
 
19
- def dirs=(directories)
20
- @dependent_finder.dirs = directories
24
+ def location=(loc)
25
+ @node_finder.location = loc
21
26
  end
22
27
 
23
- def from=(from_filter)
24
- @from_filter = from_filter
28
+ def excluded_nodes=(exc)
29
+ @excluded_nodes = exc.map {|e| e.strip}
25
30
  end
26
31
 
27
- def to=(to_filter)
28
- @to_filter = to_filter
32
+ def create_image(image_file_name = 'dependency_graph.png')
33
+ create_graph.create_image(image_file_name)
29
34
  end
30
-
31
35
 
32
36
  def create_graph
33
- nodes = @dependent_finder.get_dependents.uniq
34
- return nil if nodes.size < 2
35
-
36
- dependents = apply_from_filter(nodes)
37
- dependents = apply_to_filter(dependents)
38
-
39
- graph = @graph_class.new
40
-
41
- dependents.each do |dependent|
42
- graph.add_node dependent
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)
43
46
  end
44
47
 
45
- dependents.each do |dependent|
46
- dependent.dependencies.each do |dependable|
47
- graph.add_edge(dependent, dependable) if dependents.include? dependable
48
+ nodes.each do |node|
49
+ node.dependencies.each do |dependable|
50
+ graph.add_edge(node, dependable) if nodes.include? dependable
48
51
  end
49
52
  end
50
53
 
54
+ graph.trans = @trans
55
+
51
56
  return graph
52
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
53
73
 
54
- def apply_from_filter(nodes)
55
- return nodes unless @from_filter
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)
56
88
 
57
- selected_dependents = nodes.select do |node|
58
- node.name.match(@from_filter) or nodes.any?{|dependent| dependent.name.match(@from_filter) and dependent.depends_on?(node)}
89
+ nodes.reject do |node|
90
+ node.name.match(regexps)
59
91
  end
92
+ end
93
+
94
+ def apply_from_filter(nodes)
95
+ return nodes unless @from
60
96
 
61
- return selected_dependents
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
62
100
  end
63
101
 
64
102
  def apply_to_filter(nodes)
65
- return nodes unless @to_filter
103
+ return nodes unless @to
66
104
 
67
- selected_dependents = nodes.select do |node|
68
- node.name.match(@to_filter) or nodes.any?{|dependable| node.depends_on?(dependable) and dependable.name.match(@to_filter)}
105
+ nodes.select do |node|
106
+ node.name.match(@to) or node.dependencies.any? {|d| d.name.match(@to)}
69
107
  end
70
-
71
- return selected_dependents
72
108
  end
73
-
74
109
 
75
- def create_image(image_file_name = 'dependency_graph.png')
76
- graph = create_graph
77
-
78
- if graph
79
- return graph.create_image(image_file_name)
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 }
80
113
  else
81
- return false
114
+ lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1]
82
115
  end
83
116
  end
117
+
118
+ def deep_const_get str
119
+ return str.split("::").inject(Object) {|a,b| a.const_get(b) }
120
+ end
84
121
  end
85
122
  end
@@ -2,10 +2,13 @@ require 'rubygems'
2
2
  require 'graphviz'
3
3
 
4
4
  module DepGraph
5
- class Graph
5
+ class GraphImageCreator
6
+ attr_writer :trans
7
+
6
8
  def initialize
7
9
  @nodes = []
8
10
  @edges = []
11
+ @trans = false
9
12
  end
10
13
 
11
14
  def node_count
@@ -69,21 +72,54 @@ module DepGraph
69
72
  unless @output_generation
70
73
  @output_generation = lambda {|nodes, edges, image_file_name|
71
74
  #TODO: Could we catch Graphviz errors that the wrapper couldn't catch?
72
- g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial', :file => image_file_name)
75
+ g = GraphViz::new( "G", :use => 'dot', :mode => 'major', :rankdir => 'LR', :concentrate => 'true', :fontname => 'Arial')
73
76
 
74
-
75
- nodes.each do |node|
76
- g.add_node(quotify(node))
77
- end
78
-
79
- edges.each do |from, to|
80
- g.add_edge(quotify(from), quotify(to))
81
- end
82
-
83
- g.output( :output => "png")
77
+ load_nodes(g, nodes)
78
+ load_edges(g, edges)
79
+
80
+ create_output(g, image_file_name)
81
+
84
82
  return true
85
83
  }
86
84
  end
87
85
  end
86
+
87
+ def load_nodes(g, nodes)
88
+ nodes.each do |node|
89
+ g.add_node(quotify(node))
90
+ end
91
+ end
92
+
93
+ def load_edges(g, edges)
94
+ edges.each do |from, to|
95
+ g.add_edge(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(:file => 'temp.dot', :output => '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(:file => image_file_name, :output => output_type)
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
88
124
  end
89
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,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