DepGraph 0.8.0 → 0.9.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 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