shift-railroad 0.5.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,88 @@
1
+ # RailRoad - RoR diagrams generator
2
+ # http://railroad.rubyforge.org
3
+ #
4
+ # Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
5
+ # See COPYING for more details
6
+
7
+ require 'app_diagram'
8
+
9
+ # RailRoad controllers diagram
10
+ class ControllersDiagram < AppDiagram
11
+
12
+ # as of Rails 2.3 the file is no longer application.rb but instead
13
+ # application_controller.rb
14
+ APP_CONTROLLER = File.exist?('app/controllers/application.rb') ? 'app/controllers/application.rb' : 'app/controllers/application_controller.rb'
15
+
16
+ def initialize(options)
17
+ #options.exclude.map! {|e| "app/controllers/" + e}
18
+ super options
19
+ @graph.diagram_type = 'Controllers'
20
+ end
21
+
22
+ # Process controller files
23
+ def generate
24
+ STDERR.print "Generating controllers diagram\n" if @options.verbose
25
+
26
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
27
+ # only add APP_CONTROLLER if it isn't already included from the glob above
28
+ files << APP_CONTROLLER unless files.include? APP_CONTROLLER
29
+ files.each do |f|
30
+ class_name = extract_class_name(f)
31
+ # ApplicationController's file is 'application.rb' in Rails < 2.3
32
+ class_name += 'Controller' if class_name == 'Application'
33
+ process_class class_name.constantize
34
+ end
35
+ end # generate
36
+
37
+ private
38
+
39
+ # Load controller classes
40
+ def load_classes
41
+ begin
42
+ disable_stdout
43
+ # ApplicationController must be loaded first
44
+ require APP_CONTROLLER
45
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
46
+ files.each {|c| require c }
47
+ enable_stdout
48
+ rescue LoadError
49
+ enable_stdout
50
+ print_error "controller classes"
51
+ raise
52
+ end
53
+ end # load_classes
54
+
55
+ # Proccess a controller class
56
+ def process_class(current_class)
57
+
58
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
59
+
60
+ if @options.brief
61
+ @graph.add_node ['controller-brief', current_class.name]
62
+ elsif current_class.is_a? Class
63
+ # Collect controller's methods
64
+ node_attribs = {:public => [],
65
+ :protected => [],
66
+ :private => []}
67
+ current_class.public_instance_methods(false).sort.each { |m|
68
+ node_attribs[:public] << m
69
+ } unless @options.hide_public
70
+ current_class.protected_instance_methods(false).sort.each { |m|
71
+ node_attribs[:protected] << m
72
+ } unless @options.hide_protected
73
+ current_class.private_instance_methods(false).sort.each { |m|
74
+ node_attribs[:private] << m
75
+ } unless @options.hide_private
76
+ @graph.add_node ['controller', current_class.name, node_attribs]
77
+ elsif @options.modules && current_class.is_a?(Module)
78
+ @graph.add_node ['module', current_class.name]
79
+ end
80
+
81
+ # Generate the inheritance edge (only for ApplicationControllers)
82
+ if @options.inheritance &&
83
+ (ApplicationController.subclasses.include? current_class.name)
84
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
85
+ end
86
+ end # process_class
87
+
88
+ end # class ControllersDiagram
@@ -0,0 +1,133 @@
1
+ # RailRoad - RoR diagrams generator
2
+ # http://railroad.rubyforge.org
3
+ #
4
+ # Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
5
+ # See COPYING for more details
6
+
7
+
8
+ # RailRoad diagram structure
9
+ class DiagramGraph
10
+
11
+ def initialize
12
+ @diagram_type = ''
13
+ @show_label = false
14
+ @nodes = []
15
+ @edges = []
16
+ end
17
+
18
+ def add_node(node)
19
+ @nodes << node
20
+ end
21
+
22
+ def add_edge(edge)
23
+ @edges << edge
24
+ end
25
+
26
+ def diagram_type= (type)
27
+ @diagram_type = type
28
+ end
29
+
30
+ def show_label= (value)
31
+ @show_label = value
32
+ end
33
+
34
+
35
+ # Generate DOT graph
36
+ def to_dot
37
+ return dot_header +
38
+ @nodes.map{|n| dot_node n[0], n[1], n[2]}.join +
39
+ @edges.map{|e| dot_edge e[0], e[1], e[2], e[3]}.join +
40
+ dot_footer
41
+ end
42
+
43
+ # Generate XMI diagram (not yet implemented)
44
+ def to_xmi
45
+ STDERR.print "Sorry. XMI output not yet implemented.\n\n"
46
+ return ""
47
+ end
48
+
49
+ private
50
+
51
+ # Build DOT diagram header
52
+ def dot_header
53
+ result = "digraph #{@diagram_type.downcase}_diagram {\n" +
54
+ "\tgraph[overlap=false, splines=true]\n"
55
+ result += dot_label if @show_label
56
+ return result
57
+ end
58
+
59
+ # Build DOT diagram footer
60
+ def dot_footer
61
+ return "}\n"
62
+ end
63
+
64
+ # Build diagram label
65
+ def dot_label
66
+ return "\t_diagram_info [shape=\"plaintext\", " +
67
+ "label=\"#{@diagram_type} diagram\\l" +
68
+ "Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l" +
69
+ "Migration version: " +
70
+ "#{ActiveRecord::Migrator.current_version}\\l" +
71
+ "Generated by #{APP_HUMAN_NAME} #{APP_VERSION.join('.')}"+
72
+ "\\l\", fontsize=14]\n"
73
+ end
74
+
75
+ # Build a DOT graph node
76
+ def dot_node(type, name, attributes=nil)
77
+ case type
78
+ when 'model'
79
+ options = 'shape=Mrecord, label="{' + name + '|'
80
+ options += attributes.join('\l')
81
+ options += '\l}"'
82
+ when 'model-brief'
83
+ options = ''
84
+ when 'class'
85
+ options = 'shape=record, label="{' + name + '|}"'
86
+ when 'class-brief'
87
+ options = 'shape=box'
88
+ when 'controller'
89
+ options = 'shape=Mrecord, label="{' + name + '|'
90
+ public_methods = attributes[:public].join('\l')
91
+ protected_methods = attributes[:protected].join('\l')
92
+ private_methods = attributes[:private].join('\l')
93
+ options += public_methods + '\l|' + protected_methods + '\l|' +
94
+ private_methods + '\l'
95
+ options += '}"'
96
+ when 'controller-brief'
97
+ options = ''
98
+ when 'module'
99
+ options = 'shape=box, style=dotted, label="' + name + '"'
100
+ when 'aasm'
101
+ # Return subgraph format
102
+ return "subgraph cluster_#{name.downcase} {\n\tlabel = #{quote(name)}\n\t#{attributes.join("\n ")}}"
103
+ end # case
104
+ return "\t#{quote(name)} [#{options}]\n"
105
+ end # dot_node
106
+
107
+ # Build a DOT graph edge
108
+ def dot_edge(type, from, to, name = '')
109
+ options = name != '' ? "label=\"#{name}\", " : ''
110
+ case type
111
+ when 'one-one'
112
+ #options += 'taillabel="1"'
113
+ options += 'arrowtail=odot, arrowhead=dot, dir=both'
114
+ when 'one-many'
115
+ #options += 'taillabel="n"'
116
+ options += 'arrowtail=crow, arrowhead=dot, dir=both'
117
+ when 'many-many'
118
+ #options += 'taillabel="n", headlabel="n", arrowtail="normal"'
119
+ options += 'arrowtail=crow, arrowhead=crow, dir=both'
120
+ when 'is-a'
121
+ options += 'arrowhead="none", arrowtail="onormal"'
122
+ when 'event'
123
+ options += "fontsize=10"
124
+ end
125
+ return "\t#{quote(from)} -> #{quote(to)} [#{options}]\n"
126
+ end # dot_edge
127
+
128
+ # Quotes a class name
129
+ def quote(name)
130
+ '"' + name.to_s + '"'
131
+ end
132
+
133
+ end # class DiagramGraph
@@ -0,0 +1,155 @@
1
+ # RailRoad - RoR diagrams generator
2
+ # http://railroad.rubyforge.org
3
+ #
4
+ # Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
5
+ # See COPYING for more details
6
+
7
+ require 'app_diagram'
8
+
9
+ # RailRoad models diagram
10
+ class ModelsDiagram < AppDiagram
11
+
12
+ def initialize(options)
13
+ #options.exclude.map! {|e| "app/models/" + e}
14
+ super options
15
+ @graph.diagram_type = 'Models'
16
+ # Processed habtm associations
17
+ @habtm = []
18
+ end
19
+
20
+ # Process model files
21
+ def generate
22
+ STDERR.print "Generating models diagram\n" if @options.verbose
23
+ files = Dir.glob("app/models/**/*.rb")
24
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
25
+ files -= @options.exclude
26
+ files.each do |f|
27
+ process_class extract_class_name(f).constantize
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Load model classes
34
+ def load_classes
35
+ begin
36
+ disable_stdout
37
+ files = Dir.glob("app/models/**/*.rb")
38
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
39
+ files -= @options.exclude
40
+ files.each {|m| require m }
41
+ enable_stdout
42
+ rescue LoadError
43
+ enable_stdout
44
+ print_error "model classes"
45
+ raise
46
+ end
47
+ end # load_classes
48
+
49
+ # Process a model class
50
+ def process_class(current_class)
51
+
52
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
53
+
54
+ generated = false
55
+
56
+ # Is current_clas derived from ActiveRecord::Base?
57
+ if current_class.respond_to?'reflect_on_all_associations'
58
+
59
+
60
+ node_attribs = []
61
+ if @options.brief || current_class.abstract_class?
62
+ node_type = 'model-brief'
63
+ else
64
+ node_type = 'model'
65
+
66
+ # Collect model's content columns
67
+
68
+ content_columns = current_class.content_columns
69
+
70
+ if @options.hide_magic
71
+ # From patch #13351
72
+ # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
73
+ magic_fields = [
74
+ "created_at", "created_on", "updated_at", "updated_on",
75
+ "lock_version", "type", "id", "position", "parent_id", "lft",
76
+ "rgt", "quote", "template"
77
+ ]
78
+ magic_fields << current_class.table_name + "_count" if current_class.respond_to? 'table_name'
79
+ content_columns = current_class.content_columns.select {|c| ! magic_fields.include? c.name}
80
+ else
81
+ content_columns = current_class.content_columns
82
+ end
83
+
84
+ content_columns.each do |a|
85
+ content_column = a.name
86
+ content_column += ' :' + a.type.to_s unless @options.hide_types
87
+ node_attribs << content_column
88
+ end
89
+ end
90
+ @graph.add_node [node_type, current_class.name, node_attribs]
91
+ generated = true
92
+ # Process class associations
93
+ associations = current_class.reflect_on_all_associations
94
+ if @options.inheritance && ! @options.transitive
95
+ superclass_associations = current_class.superclass.reflect_on_all_associations
96
+
97
+ associations = associations.select{|a| ! superclass_associations.include? a}
98
+ # This doesn't works!
99
+ # associations -= current_class.superclass.reflect_on_all_associations
100
+ end
101
+ associations.each do |a|
102
+ process_association current_class.name, a
103
+ end
104
+ elsif @options.all && (current_class.is_a? Class)
105
+ # Not ActiveRecord::Base model
106
+ node_type = @options.brief ? 'class-brief' : 'class'
107
+ @graph.add_node [node_type, current_class.name]
108
+ generated = true
109
+ elsif @options.modules && (current_class.is_a? Module)
110
+ @graph.add_node ['module', current_class.name]
111
+ end
112
+
113
+ # Only consider meaningful inheritance relations for generated classes
114
+ if @options.inheritance && generated &&
115
+ (current_class.superclass != ActiveRecord::Base) &&
116
+ (current_class.superclass != Object)
117
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
118
+ end
119
+
120
+ end # process_class
121
+
122
+ # Process a model association
123
+ def process_association(class_name, assoc)
124
+
125
+ STDERR.print "\t\tProcessing model association #{assoc.name.to_s}\n" if @options.verbose
126
+
127
+ # Skip "belongs_to" associations
128
+ return if assoc.macro.to_s == 'belongs_to' && !@options.show_belongs_to
129
+
130
+ # Only non standard association names needs a label
131
+
132
+ # from patch #12384
133
+ # if assoc.class_name == assoc.name.to_s.singularize.camelize
134
+ assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
135
+ if assoc_class_name == assoc.name.to_s.singularize.camelize
136
+ assoc_name = ''
137
+ else
138
+ assoc_name = assoc.name.to_s
139
+ end
140
+
141
+ if assoc.macro.to_s == 'has_one'
142
+ assoc_type = 'one-one'
143
+ elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
144
+ assoc_type = 'one-many'
145
+ else # habtm or has_many, :through
146
+ return if @habtm.include? [assoc.class_name, class_name, assoc_name]
147
+ assoc_type = 'many-many'
148
+ @habtm << [class_name, assoc.class_name, assoc_name]
149
+ end
150
+ # from patch #12384
151
+ # @graph.add_edge [assoc_type, class_name, assoc.class_name, assoc_name]
152
+ @graph.add_edge [assoc_type, class_name, assoc_class_name, assoc_name]
153
+ end # process_association
154
+
155
+ end # class ModelsDiagram
@@ -0,0 +1,173 @@
1
+ # RailRoad - RoR diagrams generator
2
+ # http://railroad.rubyforge.org
3
+ #
4
+ # Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
5
+ # See COPYING for more details
6
+
7
+ require 'ostruct'
8
+
9
+ # RailRoad command line options parser
10
+ class OptionsStruct < OpenStruct
11
+
12
+ require 'optparse'
13
+
14
+ def initialize
15
+ init_options = { :all => false,
16
+ :brief => false,
17
+ :exclude => [],
18
+ :inheritance => false,
19
+ :join => false,
20
+ :label => false,
21
+ :modules => false,
22
+ :hide_magic => false,
23
+ :hide_types => false,
24
+ :hide_public => false,
25
+ :hide_protected => false,
26
+ :hide_private => false,
27
+ :plugins_models => false,
28
+ :root => '',
29
+ :show_belongs_to => false,
30
+ :transitive => false,
31
+ :verbose => false,
32
+ :xmi => false,
33
+ :command => '' }
34
+ super(init_options)
35
+ end # initialize
36
+
37
+ def parse(args)
38
+ @opt_parser = OptionParser.new do |opts|
39
+ opts.banner = "Usage: #{APP_NAME} [options] command"
40
+ opts.separator ""
41
+ opts.separator "Common options:"
42
+ opts.on("-b", "--brief", "Generate compact diagram",
43
+ " (no attributes nor methods)") do |b|
44
+ self.brief = b
45
+ end
46
+ opts.on("-e", "--exclude file1[,fileN]", Array, "Exclude given files") do |list|
47
+ self.exclude = list
48
+ end
49
+ opts.on("-i", "--inheritance", "Include inheritance relations") do |i|
50
+ self.inheritance = i
51
+ end
52
+ opts.on("-l", "--label", "Add a label with diagram information",
53
+ " (type, date, migration, version)") do |l|
54
+ self.label = l
55
+ end
56
+ opts.on("-o", "--output FILE", "Write diagram to file FILE") do |f|
57
+ self.output = f
58
+ end
59
+ opts.on("-r", "--root PATH", "Set PATH as the application root") do |r|
60
+ self.root = r
61
+ end
62
+ opts.on("-v", "--verbose", "Enable verbose output",
63
+ " (produce messages to STDOUT)") do |v|
64
+ self.verbose = v
65
+ end
66
+ opts.on("-x", "--xmi", "Produce XMI instead of DOT",
67
+ " (for UML tools)") do |x|
68
+ self.xmi = x
69
+ end
70
+ opts.separator ""
71
+ opts.separator "Models diagram options:"
72
+ opts.on("-a", "--all", "Include all models",
73
+ " (not only ActiveRecord::Base derived)") do |a|
74
+ self.all = a
75
+ end
76
+ opts.on("--show-belongs_to", "Show belongs_to associations") do |s|
77
+ self.show_belongs_to = s
78
+ end
79
+ opts.on("--hide-magic", "Hide magic field names") do |h|
80
+ self.hide_magic = h
81
+ end
82
+ opts.on("--hide-types", "Hide attributes type") do |h|
83
+ self.hide_types = h
84
+ end
85
+ opts.on("-j", "--join", "Concentrate edges") do |j|
86
+ self.join = j
87
+ end
88
+ opts.on("-m", "--modules", "Include modules") do |m|
89
+ self.modules = m
90
+ end
91
+ opts.on("-p", "--plugins-models", "Include plugins models") do |p|
92
+ self.plugins_models = p
93
+ end
94
+ opts.on("-t", "--transitive", "Include transitive associations",
95
+ "(through inheritance)") do |t|
96
+ self.transitive = t
97
+ end
98
+ opts.separator ""
99
+ opts.separator "Controllers diagram options:"
100
+ opts.on("--hide-public", "Hide public methods") do |h|
101
+ self.hide_public = h
102
+ end
103
+ opts.on("--hide-protected", "Hide protected methods") do |h|
104
+ self.hide_protected = h
105
+ end
106
+ opts.on("--hide-private", "Hide private methods") do |h|
107
+ self.hide_private = h
108
+ end
109
+ opts.separator ""
110
+ opts.separator "Other options:"
111
+ opts.on("-h", "--help", "Show this message") do
112
+ STDOUT.print "#{opts}\n"
113
+ exit
114
+ end
115
+ opts.on("--version", "Show version and copyright") do
116
+ STDOUT.print "#{APP_HUMAN_NAME} version #{APP_VERSION.join('.')}\n\n" +
117
+ "#{COPYRIGHT}\nThis is free software; see the source " +
118
+ "for copying conditions.\n\n"
119
+ exit
120
+ end
121
+ opts.separator ""
122
+ opts.separator "Commands (you must supply one of these):"
123
+ opts.on("-M", "--models", "Generate models diagram") do |c|
124
+ if self.command != ''
125
+ STDERR.print "Error: Can only generate one diagram type\n\n"
126
+ exit 1
127
+ else
128
+ self.command = 'models'
129
+ end
130
+ end
131
+ opts.on("-C", "--controllers", "Generate controllers diagram") do |c|
132
+ if self.command != ''
133
+ STDERR.print "Error: Can only generate one diagram type\n\n"
134
+ exit 1
135
+ else
136
+ self.command = 'controllers'
137
+ end
138
+ end
139
+ # From Ana Nelson's patch
140
+ opts.on("-A", "--aasm", "Generate \"acts as state machine\" diagram") do |c|
141
+ if self.command == 'controllers'
142
+ STDERR.print "Error: Can only generate one diagram type\n\n"
143
+ exit 1
144
+ else
145
+ self.command = 'aasm'
146
+ end
147
+ end
148
+ opts.separator ""
149
+ opts.separator "For bug reporting and additional information, please see:"
150
+ opts.separator "http://railroad.rubyforge.org/"
151
+ end # do
152
+
153
+ begin
154
+ @opt_parser.parse!(args)
155
+ rescue OptionParser::AmbiguousOption
156
+ option_error "Ambiguous option"
157
+ rescue OptionParser::InvalidOption
158
+ option_error "Invalid option"
159
+ rescue OptionParser::InvalidArgument
160
+ option_error "Invalid argument"
161
+ rescue OptionParser::MissingArgument
162
+ option_error "Missing argument"
163
+ end
164
+ end # parse
165
+
166
+ private
167
+
168
+ def option_error(msg)
169
+ STDERR.print "Error: #{msg}\n\n #{@opt_parser}\n"
170
+ exit 1
171
+ end
172
+
173
+ end # class OptionsStruct