nono-railroad 0.7.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,203 @@
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
+ # RailRoad models diagram
8
+ class ModelsDiagram < AppDiagram
9
+
10
+ def initialize(options)
11
+ #options.exclude.map! {|e| "app/models/" + e}
12
+ super options
13
+ @graph.diagram_type = 'Models'
14
+ # Processed habtm associations
15
+ @habtm = []
16
+ end
17
+
18
+ # Process model files
19
+ def generate
20
+ STDERR.print "Generating models diagram\n" if @options.verbose
21
+ base = "(app/models/|lib/)"
22
+ files = Dir.glob("app/models/**/*.rb")
23
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
24
+ files += Dir.glob("lib/**/*.rb") if @options.libraries
25
+
26
+ files -= @options.exclude
27
+ (files + ['filter_condition_type']).each do |file|
28
+ model_name =
29
+ @options.classes_by_files[file] ||
30
+ (model_path = file.gsub(/^#{base}([\w_\/\\]+)\.rb/, '\2')).camelize
31
+ STDERR.print "Processing #{file} ...\n" if @options.verbose
32
+ # Hack to skip all xxx_related.rb files
33
+ next if /_related/i =~ model_name
34
+
35
+ klass = begin
36
+ model_name.constantize
37
+ rescue LoadError
38
+ STDERR.print "\t#{model_name} raised LoadError.\n" if @options.verbose
39
+ oldlen = model_path.length
40
+ model_path.gsub!(/.*[\/\\]/, '')
41
+ model_name = model_path.camelize
42
+ if oldlen > model_path.length
43
+ retry
44
+ end
45
+ STDERR.print "\tDone trying to remove slashes, skipping this model.\n" if @options.verbose
46
+ next
47
+ rescue NameError
48
+ STDERR.print "\t#{model_name} raised NameError, skipping this model.\n" if @options.verbose
49
+ next
50
+ end
51
+
52
+ process_class klass
53
+ STDERR.print "Done #{file}\n" if @options.verbose
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ # Load model classes
60
+ def load_classes
61
+ begin
62
+ disable_stdout
63
+ files = Dir.glob("app/models/**/*.rb")
64
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
65
+ files -= @options.exclude
66
+ files.each {|file| get_model_class(file) }
67
+ enable_stdout
68
+ rescue LoadError
69
+ enable_stdout
70
+ print_error "model classes"
71
+ raise
72
+ end
73
+ end # load_classes
74
+
75
+ # This method is taken from the annotate models gem
76
+ # http://github.com/ctran/annotate_models/tree/master
77
+ #
78
+ # Retrieve the classes belonging to the model names we're asked to process
79
+ # Check for namespaced models in subdirectories as well as models
80
+ # in subdirectories without namespacing.
81
+ def get_model_class(file)
82
+ model = file.sub(/^.*app\/models\//, '').sub(/\.rb$/, '').camelize
83
+ parts = model.split('::')
84
+ begin
85
+ parts.inject(Object) {|klass, part| klass.const_get(part) }
86
+ rescue LoadError
87
+ Object.const_get(parts.last)
88
+ end
89
+ end
90
+
91
+ # Process a model class
92
+ def process_class(current_class)
93
+
94
+ STDERR.print "\tProcessing #{current_class} ...\n" if @options.verbose
95
+
96
+ generated = false
97
+
98
+ # Is current_clas derived from ActiveRecord::Base?
99
+ if current_class.respond_to?'reflect_on_all_associations'
100
+
101
+
102
+ node_attribs = []
103
+ if @options.brief || current_class.abstract_class? || current_class.superclass != ActiveRecord::Base
104
+ node_type = 'model-brief'
105
+ else
106
+ node_type = 'model'
107
+
108
+ # Collect model's content columns
109
+
110
+ content_columns = current_class.content_columns
111
+
112
+ if @options.hide_magic
113
+ magic_fields = [
114
+ # Restful Authentication
115
+ "login", "crypted_password", "salt", "remember_token", "remember_token_expires_at", "activation_code", "activated_at",
116
+ # From patch #13351
117
+ # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
118
+ "created_at", "created_on", "updated_at", "updated_on",
119
+ "lock_version", "type", "id", "position", "parent_id", "lft",
120
+ "rgt", "quote", "template"
121
+ ]
122
+ magic_fields << current_class.table_name + "_count" if current_class.respond_to? 'table_name'
123
+ content_columns = current_class.content_columns.select {|c| ! magic_fields.include? c.name}
124
+ else
125
+ content_columns = current_class.content_columns
126
+ end
127
+
128
+ content_columns.each do |a|
129
+ content_column = a.name
130
+ content_column += ' :' + a.type.to_s unless @options.hide_types
131
+ node_attribs << content_column
132
+ end
133
+ end
134
+ @graph.add_node [node_type, current_class.name, node_attribs]
135
+ generated = true
136
+ # Process class associations
137
+ associations = current_class.reflect_on_all_associations
138
+ if @options.inheritance && ! @options.transitive
139
+ superclass_associations = current_class.superclass.reflect_on_all_associations
140
+
141
+ associations = associations.select{|a| ! superclass_associations.include? a}
142
+ # This doesn't works!
143
+ # associations -= current_class.superclass.reflect_on_all_associations
144
+ end
145
+ associations.each do |a|
146
+ process_association current_class.name, a
147
+ end
148
+ elsif @options.all && (current_class.is_a? Class)
149
+ # Not ActiveRecord::Base model
150
+ node_type = @options.brief ? 'class-brief' : 'class'
151
+ @graph.add_node [node_type, current_class.name]
152
+ generated = true
153
+ elsif @options.modules && (current_class.is_a? Module)
154
+ @graph.add_node ['module', current_class.name]
155
+ end
156
+
157
+ # Only consider meaningful inheritance relations for generated classes
158
+ if @options.inheritance && generated &&
159
+ (current_class.superclass != ActiveRecord::Base) &&
160
+ (current_class.superclass != Object)
161
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
162
+ end
163
+
164
+ STDERR.print "\tDone #{current_class}\n" if @options.verbose
165
+ end # process_class
166
+
167
+ # Process a model association
168
+ def process_association(class_name, assoc)
169
+ begin
170
+ STDERR.print "\t\tProcessing model association #{assoc.name.to_s} ..." if @options.verbose
171
+
172
+ # Skip "belongs_to" associations
173
+ return if assoc.macro.to_s == 'belongs_to'
174
+
175
+ # Only non standard association names needs a label
176
+
177
+ # from patch #12384
178
+ # if assoc.class_name == assoc.name.to_s.singularize.camelize
179
+ assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
180
+ if assoc_class_name == assoc.name.to_s.singularize.camelize
181
+ assoc_name = ''
182
+ else
183
+ assoc_name = assoc.name.to_s
184
+ end
185
+ # STDERR.print "#{assoc_name}\n"
186
+ if assoc.macro.to_s == 'has_one'
187
+ assoc_type = 'one-one'
188
+ elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
189
+ assoc_type = 'one-many'
190
+ else # habtm or has_many, :through
191
+ return if @habtm.include? [assoc.class_name, class_name, assoc_name]
192
+ assoc_type = 'many-many'
193
+ @habtm << [class_name, assoc.class_name, assoc_name]
194
+ end
195
+ # from patch #12384
196
+ # @graph.add_edge [assoc_type, class_name, assoc.class_name, assoc_name]
197
+ @graph.add_edge [assoc_type, class_name, assoc_class_name, assoc_name]
198
+ ensure
199
+ STDERR.print " done.\n" if @options.verbose
200
+ end
201
+ end # process_association
202
+
203
+ end # class ModelsDiagram
@@ -0,0 +1,181 @@
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
+ :classes_by_files => {},
19
+ :inheritance => false,
20
+ :join => false,
21
+ :label => false,
22
+ :modules => false,
23
+ :hide_magic => false,
24
+ :hide_types => false,
25
+ :hide_public => false,
26
+ :hide_protected => false,
27
+ :hide_private => false,
28
+ :hide_underscore => false,
29
+ :plugins_models => false,
30
+ :libraries => false,
31
+ :root => '',
32
+ :transitive => false,
33
+ :verbose => false,
34
+ :xmi => false,
35
+ :command => '' }
36
+ super(init_options)
37
+ end # initialize
38
+
39
+ def parse(args)
40
+ @opt_parser = OptionParser.new do |opts|
41
+ opts.banner = "Usage: #{APP_NAME} [options] command"
42
+ opts.separator ""
43
+ opts.separator "Common options:"
44
+ opts.on("-b", "--brief", "Generate compact diagram",
45
+ " (no attributes nor methods)") do |b|
46
+ self.brief = b
47
+ end
48
+ opts.on("-e", "--exclude=file1[,fileN]", Array, "Exclude given files") do |list|
49
+ self.exclude = list
50
+ end
51
+ opts.on("-c", "--class-map=file1,MyClass1[,fileN,MyClassN]", Array, "Map files to classes they contain") do |list|
52
+ self.classes_by_files = Hash[*list]
53
+ end
54
+ opts.on("-i", "--inheritance", "Include inheritance relations") do |i|
55
+ self.inheritance = i
56
+ end
57
+ opts.on("-l", "--label", "Add a label with diagram information",
58
+ " (type, date, migration, version)") do |l|
59
+ self.label = l
60
+ end
61
+ opts.on("-o", "--output FILE", "Write diagram to file FILE") do |f|
62
+ self.output = f
63
+ end
64
+ opts.on("-r", "--root PATH", "Set PATH as the application root") do |r|
65
+ self.root = r
66
+ end
67
+ opts.on("-v", "--verbose", "Enable verbose output",
68
+ " (produce messages to STDOUT)") do |v|
69
+ self.verbose = v
70
+ end
71
+ opts.on("-x", "--xmi", "Produce XMI instead of DOT",
72
+ " (for UML tools)") do |x|
73
+ self.xmi = x
74
+ end
75
+ opts.separator ""
76
+ opts.separator "Models diagram options:"
77
+ opts.on("-a", "--all", "Include all models",
78
+ " (not only ActiveRecord::Base derived)") do |a|
79
+ self.all = a
80
+ end
81
+ opts.on("--hide-magic", "Hide magic field names") do |h|
82
+ self.hide_magic = h
83
+ end
84
+ opts.on("--hide-types", "Hide attributes type") do |h|
85
+ self.hide_types = h
86
+ end
87
+ opts.on("-j", "--join", "Concentrate edges") do |j|
88
+ self.join = j
89
+ end
90
+ opts.on("-m", "--modules", "Include modules") do |m|
91
+ self.modules = m
92
+ end
93
+ opts.on("-p", "--plugins-models", "Include plugins models") do |p|
94
+ self.plugins_models = p
95
+ end
96
+ opts.on("-y", "--libraries", "Include application library") do |y|
97
+ self.libraries = y
98
+ end
99
+ opts.on("-t", "--transitive", "Include transitive associations",
100
+ "(through inheritance)") do |t|
101
+ self.transitive = t
102
+ end
103
+ opts.separator ""
104
+ opts.separator "Controllers diagram options:"
105
+ opts.on("--hide-public", "Hide public methods") do |h|
106
+ self.hide_public = h
107
+ end
108
+ opts.on("--hide-protected", "Hide protected methods") do |h|
109
+ self.hide_protected = h
110
+ end
111
+ opts.on("--hide-private", "Hide private methods") do |h|
112
+ self.hide_private = h
113
+ end
114
+ opts.on("--hide-underscore", "Hide methods that begin with an underscore") do |h|
115
+ self.hide_underscore = h
116
+ end
117
+ opts.separator ""
118
+ opts.separator "Other options:"
119
+ opts.on("-h", "--help", "Show this message") do
120
+ STDOUT.print "#{opts}\n"
121
+ exit
122
+ end
123
+ opts.on("--version", "Show version and copyright") do
124
+ STDOUT.print "#{APP_HUMAN_NAME} version #{APP_VERSION.join('.')}\n\n" +
125
+ "#{COPYRIGHT}\nThis is free software; see the source " +
126
+ "for copying conditions.\n\n"
127
+ exit
128
+ end
129
+ opts.separator ""
130
+ opts.separator "Commands (you must supply one of these):"
131
+ opts.on("-M", "--models", "Generate models 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 = 'models'
137
+ end
138
+ end
139
+ opts.on("-C", "--controllers", "Generate controllers diagram") do |c|
140
+ if self.command != ''
141
+ STDERR.print "Error: Can only generate one diagram type\n\n"
142
+ exit 1
143
+ else
144
+ self.command = 'controllers'
145
+ end
146
+ end
147
+ # From Ana Nelson's patch
148
+ opts.on("-A", "--aasm", "Generate \"acts as state machine\" diagram") do |c|
149
+ if self.command == 'controllers'
150
+ STDERR.print "Error: Can only generate one diagram type\n\n"
151
+ exit 1
152
+ else
153
+ self.command = 'aasm'
154
+ end
155
+ end
156
+ opts.separator ""
157
+ opts.separator "For bug reporting and additional information, please see:"
158
+ opts.separator "http://railroad.rubyforge.org/"
159
+ end # do
160
+
161
+ begin
162
+ @opt_parser.parse!(args)
163
+ rescue OptionParser::AmbiguousOption
164
+ option_error "Ambiguous option"
165
+ rescue OptionParser::InvalidOption
166
+ option_error "Invalid option"
167
+ rescue OptionParser::InvalidArgument
168
+ option_error "Invalid argument"
169
+ rescue OptionParser::MissingArgument
170
+ option_error "Missing argument"
171
+ end
172
+ end # parse
173
+
174
+ private
175
+
176
+ def option_error(msg)
177
+ STDERR.print "Error: #{msg}\n\n #{@opt_parser}\n"
178
+ exit 1
179
+ end
180
+
181
+ end # class OptionsStruct