cheind / ruby-snippets

Handy ruby snippets to get the job done quickly!

5467e9f9 » cheind 2008-12-18 refactored for better reada... 1 #
2 # Project:: Ruby-Snippets
3 #
4 # Author:: Christoph Heindl (mailto:christoph.heindl@gmail.com)
5 # Homepage:: http://cheind.blogspot.com
6 #
7 # == Overview
8 #
05c08866 » cheind 2008-12-18 comments 9 # Ruby script that builds inter-project dependencies of boost (http://www.boost.org)
701e4f9a » cheind 2008-12-18 comments 10 # via include file parsing using methods and classes defined in <tt>Dependencies</tt>.
11 #
dcada3be » cheind 2008-12-19 generating images for each ... 12 # == Prerequisites
13 #
14 # To run the application you need to install ruby and RGL (Ruby Graph Library).
15 # After ruby has been installed, install RGL via command line
16 #
17 # > gem install rgl
18 #
19 # See http://rgl.rubyforge.org/rgl/index.html for RGL documentation and install
20 # instructions.
21 #
22 # Graphviz http://www.graphviz.org/ is required to transform graphs to images.
23 #
24 # == What is it?
25 #
26 # The script records all header files within the boost include directory. Each
27 # file path is assigned a vertex name: if file path contains a nested directory
28 # inside boost include directory, then the first nested directory name is used
29 # as a vertex name in the graph. If the file path points directly to the boost
30 # include directory the vertex name as follows:
31 # if a directory with the same name exists (except for the file extension '.hpp')
32 # then the directory name is used. Else, the basename of the file including the
33 # extension is used (considered as mini-library).
34 #
35 # Next, each recorded file is parsed for matching '#include' preprocessor statements.
36 # When such a statement is encountered, the script tries to lookup the file
37 # from previous recordings. If found, the vertex name of the file recorded previously
38 # is used. A dependency is then generated between the vertex name of the file
39 # parsed and the vertex name from the resolved include statement. If the dependency
40 # causes a cycle in the dependency graph, the dependency is not added but error'd
41 # to the logger.
42 #
43 # After all files are parsed, the dependency graph is reduced by removing
44 # edges u -> w, where u -> ... -> w exists and written to a '.dot' file
45 # that can be converted into various formats using http://www.graphviz.org/.
46 #
47 # == What is it not?
48 #
49 # This script is not
50 #
51 # - <b>a complete preprocessor parser</b>: it does not care about conditional
a96710f2 » cheind 2008-12-19 using underscores for file ... 52 # include statements or commented ones. Parsing is based on simple pattern matching
53 # to keep to code small nice. You might however add a complete parser if you like to.
dcada3be » cheind 2008-12-19 generating images for each ... 54 #
55 # - <b>handling cyclic dependencies</b>: some include statements cause cyclic dependencies
56 # that simply result from the choice of mapping to vertex names. I.e. when
57 # file boost/a/detail/detail.hpp includes boost/b/win32/abc.hpp which in turn
58 # includes boost/a/other/other.hpp and the mapping resolves vertex names from
59 # the first nested directory inside boost, we generate a dependency from a -> b and
60 # finally another one from b -> a which will not be added (unless explicitly allowed
61 # see <tt>Dependencies::Walker#on_cycle</tt>).
62 #
a96710f2 » cheind 2008-12-19 using underscores for file ... 63 #
5467e9f9 » cheind 2008-12-18 refactored for better reada... 64
dcada3be » cheind 2008-12-19 generating images for each ... 65 require 'logger'
5467e9f9 » cheind 2008-12-18 refactored for better reada... 66 require 'dependencies/walker'
67 require 'dependencies/dot'
dcada3be » cheind 2008-12-19 generating images for each ... 68 require 'dependencies/all_dependencies'
5467e9f9 » cheind 2008-12-18 refactored for better reada... 69
dcada3be » cheind 2008-12-19 generating images for each ... 70 def usage(notboost=false)
71 puts "ruby #{$0} path_to_boost" unless notboost
72 puts "path #{ARGV[0]} does not seem to be the boost include directory." if notboost
5467e9f9 » cheind 2008-12-18 refactored for better reada... 73 exit(1)
74 end
75
76 # Sanity check for command line arguments
dcada3be » cheind 2008-12-19 generating images for each ... 77 usage(false) unless ARGV.length == 1
5467e9f9 » cheind 2008-12-18 refactored for better reada... 78 exit(1) unless File.directory?(ARGV[0])
dcada3be » cheind 2008-12-19 generating images for each ... 79 usage(true) unless File.exists?(File.join(ARGV[0], 'boost/config.hpp'))
5467e9f9 » cheind 2008-12-18 refactored for better reada... 80
81 boost_dir = ARGV[0]
82
83 # Instance a walker that records files and dependencies between files
dcada3be » cheind 2008-12-19 generating images for each ... 84 logger = Logger.new(STDOUT)
85 w = Dependencies::Walker.new(logger)
5467e9f9 » cheind 2008-12-18 refactored for better reada... 86
87 # Record all file paths of files ending with '.hpp' residing in any directory
88 # nested one-level below boost root include directory
89 w.index(boost_dir, 'boost/*/**/*.hpp') do |path|
90 # When such a file is discoverd, the nested directory name is used as vertex
91 # name in the graph
92 path.split('/')[1]
93 end
94
95 # Index all files residing directly in the boost root include directory.
96 w.index(boost_dir, 'boost/*.hpp') do |path|
97 # The vertex named is determined from the following rule:
98 # When a nested directory with the same name as the file (except for the extension)
99 # exists, then the directory name is used as vertex name.
dcada3be » cheind 2008-12-19 generating images for each ... 100 # Else, the filename is used (considered as mini library)
5467e9f9 » cheind 2008-12-18 refactored for better reada... 101 dirname_exists = File.directory?(File.join(boost_dir, 'boost/', File.basename(path, '.hpp')))
102 if dirname_exists
103 File.basename(path, '.hpp')
104 else
dcada3be » cheind 2008-12-19 generating images for each ... 105 File.basename(path)
5467e9f9 » cheind 2008-12-18 refactored for better reada... 106 end
107 end
108
109 # Read the content of all header files inside the boost directory.
110 w.parse(boost_dir,'boost/**/*.hpp') do |path, file|
111 # Record dependencies in file by matching include statements
112 dependencies = []
113 while (line = file.gets)
114 if line =~ /\#include\s+[\"<]([^\">]+)?/
115 # Try looking up the file inside the boost directory.
116 # On success use the same name as the recorded file.
117 vertex_name = w.try_resolve($1, boost_dir)
118 dependencies << vertex_name if vertex_name
119 end
120 end
121 dependencies
122 end
123
dcada3be » cheind 2008-12-19 generating images for each ... 124 logger.info('Performing transitive reduction on graph')
5467e9f9 » cheind 2008-12-18 refactored for better reada... 125 # Reduce the graph by removing all edges between vertex v -> w, when
126 # and a path v -> ... -> w exists.
dcada3be » cheind 2008-12-19 generating images for each ... 127 boost_graph = w.graph.transitive_reduction
128
d7ae81f6 » cheind 2008-12-19 minor bugfixes 129 Dir.mkdir('images') unless File.directory?('images')
dcada3be » cheind 2008-12-19 generating images for each ... 130
131 # Write to png file using dot and the default template 'dependencies\graph.template'
132 logger.info("Saving dependency graph for boost as 'images/boost_graph.png'")
133 Dependencies.to_png(boost_graph, 'images/boost_graph.png', 'images/boost_graph.dot')
134
135 # Finally plot each project seperately
136 boost_graph.each_vertex do |v|
a96710f2 » cheind 2008-12-19 using underscores for file ... 137 dot_path = "images/#{v.gsub(/\./, '_')}.dot"
138 img_path = "images/#{v.gsub(/\./, '_')}.png"
dcada3be » cheind 2008-12-19 generating images for each ... 139 logger.info("Saving dependency graph for #{v} as '#{img_path}'")
140 g = boost_graph.all_dependencies(v)
141 Dependencies.to_png(g, img_path, dot_path)
142 end