railroad 0.3.2 → 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +3 -0
- data/lib/railroad +153 -155
- data/rake.gemspec +1 -1
- metadata +2 -2
data/ChangeLog
CHANGED
data/lib/railroad
CHANGED
@@ -16,76 +16,75 @@
|
|
16
16
|
|
17
17
|
APP_NAME = "railroad"
|
18
18
|
APP_HUMAN_NAME = "RailRoad"
|
19
|
-
APP_VERSION = [0,3,
|
19
|
+
APP_VERSION = [0,3,3]
|
20
20
|
COPYRIGHT = "Copyright (C) 2007 Javier Smaldone"
|
21
21
|
|
22
|
+
require 'ostruct'
|
22
23
|
|
23
|
-
# Command line options
|
24
|
-
class
|
24
|
+
# Command line options structure
|
25
|
+
class OptionsStruct < OpenStruct
|
25
26
|
|
26
27
|
require 'optparse'
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
opts = OptionParser.new do |opts|
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
init_options = { :all => false,
|
31
|
+
:brief => false,
|
32
|
+
:inheritance => false,
|
33
|
+
:join => false,
|
34
|
+
:label => false,
|
35
|
+
:modules => false,
|
36
|
+
:hide_types => false,
|
37
|
+
:hide_public => false,
|
38
|
+
:hide_protected => false,
|
39
|
+
:hide_private => false,
|
40
|
+
:command => '' }
|
41
|
+
super(init_options)
|
42
|
+
end # initialize
|
43
|
+
|
44
|
+
def parse(args)
|
45
|
+
@opt_parser = OptionParser.new do |opts|
|
47
46
|
opts.banner = "Usage: #{APP_NAME} [options] command"
|
48
47
|
opts.separator ""
|
49
48
|
opts.separator "Common options:"
|
50
49
|
opts.on("-b", "--brief", "Generate compact diagram",
|
51
50
|
" (no attributes nor methods)") do |b|
|
52
|
-
|
51
|
+
self.brief = b
|
53
52
|
end
|
54
53
|
opts.on("-i", "--inheritance", "Include inheritance relations") do |i|
|
55
|
-
|
54
|
+
self.inheritance = i
|
56
55
|
end
|
57
56
|
opts.on("-l", "--label", "Add a label with diagram information",
|
58
57
|
" (type, date, migration, version)") do |l|
|
59
|
-
|
58
|
+
self.label = l
|
60
59
|
end
|
61
60
|
opts.on("-o", "--output FILE", "Write diagram to file FILE") do |f|
|
62
|
-
|
61
|
+
self.output = f
|
63
62
|
end
|
64
63
|
opts.separator ""
|
65
64
|
opts.separator "Models diagram options:"
|
66
65
|
opts.on("-a", "--all", "Include all models",
|
67
66
|
" (not only ActiveRecord::Base derived)") do |a|
|
68
|
-
|
67
|
+
self.all = a
|
69
68
|
end
|
70
69
|
opts.on("--hide-types", "Hide attributes type") do |h|
|
71
|
-
|
70
|
+
self.hide_types = h
|
72
71
|
end
|
73
72
|
opts.on("-j", "--join", "Concentrate edges") do |j|
|
74
|
-
|
73
|
+
self.join = j
|
75
74
|
end
|
76
75
|
opts.on("-m", "--modules", "Include modules") do |m|
|
77
|
-
|
76
|
+
self.modules = m
|
78
77
|
end
|
79
78
|
opts.separator ""
|
80
79
|
opts.separator "Controllers diagram options:"
|
81
80
|
opts.on("--hide-public", "Hide public methods") do |h|
|
82
|
-
|
81
|
+
self.hide_public = h
|
83
82
|
end
|
84
83
|
opts.on("--hide-protected", "Hide protected methods") do |h|
|
85
|
-
options.hide_protected = h
|
84
|
+
@options.hide_protected = h
|
86
85
|
end
|
87
86
|
opts.on("--hide-private", "Hide private methods") do |h|
|
88
|
-
options.hide_private = h
|
87
|
+
@options.hide_private = h
|
89
88
|
end
|
90
89
|
opts.separator ""
|
91
90
|
opts.separator "Other options:"
|
@@ -102,50 +101,51 @@ class OptionsParser
|
|
102
101
|
opts.separator ""
|
103
102
|
opts.separator "Commands (you must supply one of these):"
|
104
103
|
opts.on("-M", "--models", "Generate models diagram") do |c|
|
105
|
-
if
|
106
|
-
STDERR.print "Error: Can't generate models AND
|
104
|
+
if self.command == 'controllers'
|
105
|
+
STDERR.print "Error: Can't generate models AND " +
|
106
|
+
"controllers diagram\n\n"
|
107
107
|
exit 1
|
108
108
|
else
|
109
|
-
|
109
|
+
self.command = 'models'
|
110
110
|
end
|
111
111
|
end
|
112
112
|
opts.on("-C", "--controllers", "Generate controllers diagram") do |c|
|
113
|
-
if
|
114
|
-
STDERR.print "Error: Can't generate models AND
|
113
|
+
if self.command == 'models'
|
114
|
+
STDERR.print "Error: Can't generate models AND " +
|
115
|
+
"controllers diagram\n\n"
|
115
116
|
exit 1
|
116
117
|
else
|
117
|
-
|
118
|
+
self.command = 'controllers'
|
118
119
|
end
|
119
120
|
end
|
120
121
|
opts.separator ""
|
121
|
-
opts.separator "For bug reporting
|
122
|
+
opts.separator "For bug reporting and additional information, please see:"
|
122
123
|
opts.separator "http://railroad.rubyforge.org"
|
123
124
|
end
|
124
125
|
|
125
126
|
begin
|
126
|
-
|
127
|
+
@opt_parser.parse!(args)
|
127
128
|
rescue OptionParser::AmbiguousOption
|
128
|
-
option_error "Ambiguous option"
|
129
|
+
option_error "Ambiguous option"
|
129
130
|
rescue OptionParser::InvalidOption
|
130
|
-
option_error "Invalid option"
|
131
|
+
option_error "Invalid option"
|
131
132
|
rescue OptionParser::InvalidArgument
|
132
|
-
option_error "Invalid argument"
|
133
|
+
option_error "Invalid argument"
|
133
134
|
rescue OptionParser::MissingArgument
|
134
|
-
option_error "Missing argument"
|
135
|
+
option_error "Missing argument"
|
135
136
|
rescue
|
136
|
-
option_error "Unknown error"
|
137
|
+
option_error "Unknown error"
|
137
138
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
139
|
+
end # initialize
|
140
|
+
|
141
141
|
private
|
142
142
|
|
143
|
-
def
|
144
|
-
STDERR.print "Error: #{msg}\n\n #{
|
143
|
+
def option_error(msg)
|
144
|
+
STDERR.print "Error: #{msg}\n\n #{@opt_parser}\n"
|
145
145
|
exit 1
|
146
146
|
end
|
147
147
|
|
148
|
-
end # class
|
148
|
+
end # class OptionsStruct
|
149
149
|
|
150
150
|
|
151
151
|
# Root class for RailRoad diagrams
|
@@ -154,6 +154,7 @@ class AppDiagram
|
|
154
154
|
def initialize(options)
|
155
155
|
@options = options
|
156
156
|
load_environment
|
157
|
+
load_classes
|
157
158
|
end
|
158
159
|
|
159
160
|
private
|
@@ -174,6 +175,13 @@ class AppDiagram
|
|
174
175
|
STDOUT.reopen(@old_stdout)
|
175
176
|
end
|
176
177
|
|
178
|
+
# Print diagram header
|
179
|
+
def print_header(type)
|
180
|
+
print "digraph #{type.downcase}_diagram {\n" +
|
181
|
+
"\tgraph[overlap=false, splines=true]\n"
|
182
|
+
print_info(type) if @options.label
|
183
|
+
end
|
184
|
+
|
177
185
|
|
178
186
|
# Print diagram label
|
179
187
|
def print_info(type)
|
@@ -184,7 +192,19 @@ class AppDiagram
|
|
184
192
|
", fontsize=14]\n"
|
185
193
|
end
|
186
194
|
|
187
|
-
# Print
|
195
|
+
# Print an edge
|
196
|
+
def print_edge(from, to, attributes)
|
197
|
+
print "\t#{node(from)} -> #{node(to)} [#{attributes}]\n"
|
198
|
+
end
|
199
|
+
|
200
|
+
# Print a node
|
201
|
+
def print_node(name, attributes=nil)
|
202
|
+
print "\t#{node(name)}"
|
203
|
+
print " [#{attributes}]" if attributes && attributes != ''
|
204
|
+
print "\n"
|
205
|
+
end
|
206
|
+
|
207
|
+
# Print error when loading Rails application
|
188
208
|
def print_error(type)
|
189
209
|
STDERR.print "Error loading #{type}.\n (Are you running " +
|
190
210
|
"#{APP_NAME} on the aplication's root directory?)\n\n"
|
@@ -198,7 +218,7 @@ class AppDiagram
|
|
198
218
|
enable_stdout
|
199
219
|
rescue LoadError
|
200
220
|
enable_stdout
|
201
|
-
print_error "
|
221
|
+
print_error "application environment"
|
202
222
|
raise
|
203
223
|
end
|
204
224
|
end
|
@@ -209,28 +229,22 @@ end # class AppDiagram
|
|
209
229
|
# RailRoad models diagram
|
210
230
|
class ModelsDiagram < AppDiagram
|
211
231
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
load_classes
|
216
|
-
|
217
|
-
# DOT diagram header
|
218
|
-
print "digraph models_diagram {\n"
|
219
|
-
print "\tgraph[concentrate=true]\n" if @options.join
|
220
|
-
print_info "Models" if @options.label
|
221
|
-
|
232
|
+
def initialize(options)
|
233
|
+
super options
|
222
234
|
# Processed habtm associations
|
223
235
|
@habtm = []
|
236
|
+
end
|
224
237
|
|
238
|
+
# Generate models diagram
|
239
|
+
def generate
|
240
|
+
print_header 'Models'
|
225
241
|
models_files = Dir.glob("app/models/**/*.rb")
|
226
242
|
models_files.each do |m|
|
227
243
|
# Extract the class name from the file name
|
228
|
-
|
229
|
-
process_class
|
244
|
+
class_name = m.split('/')[2..-1].join('/').split('.').first.camelize
|
245
|
+
process_class class_name.constantize
|
230
246
|
end
|
231
247
|
print "}\n"
|
232
|
-
|
233
|
-
|
234
248
|
end # generate
|
235
249
|
|
236
250
|
private
|
@@ -251,50 +265,49 @@ class ModelsDiagram < AppDiagram
|
|
251
265
|
# Process model class
|
252
266
|
def process_class(current_class)
|
253
267
|
|
254
|
-
|
268
|
+
class_printed = false
|
255
269
|
|
256
270
|
# Is current_clas derived from ActiveRecord::Base?
|
257
271
|
if current_class.respond_to?'reflect_on_all_associations'
|
258
272
|
|
259
273
|
# Print the node
|
260
274
|
if @options.brief
|
261
|
-
|
275
|
+
node_attrib = ''
|
262
276
|
else
|
263
|
-
|
264
|
-
"[shape=Mrecord, label=\"{#{current_class.name}|"
|
277
|
+
node_attrib = 'shape=Mrecord, label="{' + current_class.name + '|'
|
265
278
|
current_class.content_columns.each do |a|
|
266
|
-
|
267
|
-
|
268
|
-
|
279
|
+
node_attrib += a.name
|
280
|
+
node_attrib += ' :' + a.type.to_s unless @options.hide_types
|
281
|
+
node_attrib += '\l'
|
269
282
|
end
|
270
|
-
|
283
|
+
node_attrib += '}"'
|
271
284
|
end
|
272
|
-
|
285
|
+
print_node current_class.name, node_attrib
|
286
|
+
class_printed = true
|
273
287
|
# Iterate over the class associations
|
274
|
-
current_class.reflect_on_all_associations.each
|
288
|
+
current_class.reflect_on_all_associations.each do |a|
|
275
289
|
process_association(current_class, a)
|
276
|
-
|
290
|
+
end
|
277
291
|
elsif @options.all && (current_class.is_a? Class)
|
278
292
|
# Not ActiveRecord::Base model
|
279
293
|
if @options.brief
|
280
|
-
|
294
|
+
node_attrib = 'shape=box'
|
281
295
|
else
|
282
|
-
|
283
|
-
"[shape=record, label=\"{#{current_class.name}|}\"]\n"
|
296
|
+
node_attrib = 'shape=record, label="{' + current_class.name + '|}"'
|
284
297
|
end
|
285
|
-
|
298
|
+
print_node current_class.name, node_attrib
|
299
|
+
class_printed = true
|
286
300
|
elsif @options.modules && (current_class.is_a? Module)
|
287
|
-
|
288
|
-
|
301
|
+
print_node current_class.name,
|
302
|
+
'shape=box, style=dotted, label="' + current_class.name + '"'
|
289
303
|
end
|
290
304
|
|
291
305
|
# Only consider meaningful inheritance relations for printed classes
|
292
|
-
if @options.inheritance &&
|
306
|
+
if @options.inheritance && class_printed &&
|
293
307
|
(current_class.superclass != ActiveRecord::Base) &&
|
294
308
|
(current_class.superclass != Object)
|
295
|
-
|
296
|
-
|
297
|
-
" [arrowhead=\"onormal\"]\n"
|
309
|
+
print_edge(current_class.name, current_class.superclass.name,
|
310
|
+
'arrowhead="onormal"')
|
298
311
|
end
|
299
312
|
|
300
313
|
end # process_class
|
@@ -302,38 +315,31 @@ class ModelsDiagram < AppDiagram
|
|
302
315
|
# Process model association
|
303
316
|
def process_association(current_class, association)
|
304
317
|
|
318
|
+
# Skip "belongs_to" associations
|
319
|
+
return if association.macro.to_s == 'belongs_to'
|
320
|
+
|
321
|
+
assoc_attrib = ""
|
322
|
+
assoc_name = ""
|
305
323
|
# Only non standard association names needs a label
|
306
324
|
if association.class_name != association.name.to_s.singularize.camelize
|
307
|
-
|
308
|
-
|
309
|
-
params = ""
|
325
|
+
association_name = association.name.to_s
|
326
|
+
assoc_attrib += 'label="' + association_name + '", '
|
310
327
|
end
|
311
328
|
|
312
|
-
#
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
return
|
327
|
-
else
|
328
|
-
params += ', headlabel="n", arrowtail="normal"'
|
329
|
-
@habtm << "#{current_class.name} -> #{association.class_name}"
|
330
|
-
end
|
331
|
-
end
|
332
|
-
# Print the edge
|
333
|
-
print "\t\t#{node(current_class.name)} -> " +
|
334
|
-
"#{node(association.class_name)} " +
|
335
|
-
"[ #{params} ]\n"
|
336
|
-
end
|
329
|
+
# Tail label with the association arity
|
330
|
+
assoc_attrib += association.macro.to_s == 'has_one' ? 'taillabel="1"' : 'taillabel="n"'
|
331
|
+
|
332
|
+
# Use double-headed arrows for habtm and has_many, :through
|
333
|
+
if association.macro.to_s == 'has_and_belongs_to_many' ||
|
334
|
+
(association.macro.to_s == 'has_many' && association.options[:through])
|
335
|
+
|
336
|
+
# Skip habtm associations already considered
|
337
|
+
return if @habtm.include? [association.class_name, current_class.name,
|
338
|
+
association_name]
|
339
|
+
@habtm << [current_class.name, association.class_name,association_name]
|
340
|
+
assoc_attrib += ', headlabel="n", arrowtail="normal"'
|
341
|
+
end
|
342
|
+
print_edge(current_class.name, association.class_name, assoc_attrib)
|
337
343
|
end # process_association
|
338
344
|
|
339
345
|
end # class ModelsDiagram
|
@@ -341,33 +347,25 @@ end # class ModelsDiagram
|
|
341
347
|
|
342
348
|
# RailRoad controllers diagram
|
343
349
|
class ControllersDiagram < AppDiagram
|
350
|
+
|
351
|
+
def initialize(options)
|
352
|
+
super options
|
353
|
+
@app_controller = ApplicationController
|
354
|
+
end
|
344
355
|
|
345
356
|
# Generate controllers diagram
|
346
357
|
def generate
|
347
358
|
|
348
|
-
|
349
|
-
|
350
|
-
# DOT diagram header
|
351
|
-
print "digraph controllers_diagram {\n" +
|
352
|
-
"\tgraph[overlap=false, splines=true]\n"
|
353
|
-
print_info "Controllers" if @options.label
|
354
|
-
|
355
|
-
@app_controller = ApplicationController
|
359
|
+
print_header 'Controllers'
|
356
360
|
|
357
361
|
controllers_files = Dir.glob("app/controllers/**/*_controller.rb")
|
358
362
|
controllers_files << 'app/controllers/application.rb'
|
359
363
|
controllers_files.each do |c|
|
360
|
-
|
361
364
|
# Extract the class name from the file name
|
362
365
|
class_name = c.split('/')[2..-1].join('/').split('.').first.camelize
|
363
|
-
|
364
366
|
# ApplicationController's file is 'application.rb'
|
365
|
-
if class_name == 'Application'
|
366
|
-
class_name = 'ApplicationController'
|
367
|
-
end
|
368
|
-
|
367
|
+
class_name += 'Controller' if class_name == 'Application'
|
369
368
|
process_class class_name.constantize
|
370
|
-
|
371
369
|
end
|
372
370
|
print "}\n"
|
373
371
|
end # generate
|
@@ -380,9 +378,7 @@ class ControllersDiagram < AppDiagram
|
|
380
378
|
disable_stdout
|
381
379
|
# ApplicationController must be loaded first
|
382
380
|
require "app/controllers/application.rb"
|
383
|
-
Dir.glob("app/controllers/**/*_controller.rb")
|
384
|
-
require c
|
385
|
-
end
|
381
|
+
Dir.glob("app/controllers/**/*_controller.rb") {|c| require c }
|
386
382
|
enable_stdout
|
387
383
|
rescue LoadError
|
388
384
|
enable_stdout
|
@@ -394,35 +390,35 @@ class ControllersDiagram < AppDiagram
|
|
394
390
|
# Proccess controller class
|
395
391
|
def process_class(current_class)
|
396
392
|
|
397
|
-
# Print the node
|
398
393
|
if @options.brief
|
399
|
-
|
394
|
+
print_node current_class.name
|
395
|
+
|
400
396
|
elsif current_class.is_a? Class
|
401
|
-
|
402
|
-
"[shape=Mrecord, label=\"{#{current_class.name}|"
|
397
|
+
node_attrib = 'shape=Mrecord, label="{' + current_class.name + '|'
|
403
398
|
current_class.public_instance_methods(false).sort.each { |m|
|
404
|
-
|
399
|
+
node_attrib += m + '\l'
|
405
400
|
} unless @options.hide_public
|
406
|
-
|
401
|
+
node_attrib += '|'
|
407
402
|
current_class.protected_instance_methods(false).sort.each { |m|
|
408
|
-
|
403
|
+
node_attrib += m + '\l'
|
409
404
|
} unless @options.hide_protected
|
410
|
-
|
405
|
+
node_attrib += '|'
|
411
406
|
current_class.private_instance_methods(false).sort.each { |m|
|
412
|
-
|
407
|
+
node_attrib += m + '\l'
|
413
408
|
} unless @options.hide_private
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
409
|
+
node_attrib += '}"'
|
410
|
+
print_node current_class.name, node_attrib
|
411
|
+
|
412
|
+
elsif @options.modules && current_class.is_a?(Module)
|
413
|
+
print_node current_class.name,
|
414
|
+
'shape=box, style=dotted, label="' + current_class.name + '"'
|
418
415
|
end
|
419
416
|
|
420
|
-
# Print the inheritance edge
|
417
|
+
# Print the inheritance edge (only for ApplicationControllers)
|
421
418
|
if @options.inheritance &&
|
422
419
|
(@app_controller.subclasses.include? current_class.name)
|
423
|
-
|
424
|
-
|
425
|
-
"[arrowhead=\"onormal\"]\n"
|
420
|
+
print_edge(current_class.name, current_class.superclass.name,
|
421
|
+
'arrowhead="onormal"')
|
426
422
|
end
|
427
423
|
|
428
424
|
end # process_class
|
@@ -432,7 +428,9 @@ end # class ControllersDiagram
|
|
432
428
|
|
433
429
|
# Main program
|
434
430
|
|
435
|
-
options =
|
431
|
+
options = OptionsStruct.new
|
432
|
+
|
433
|
+
options.parse ARGV
|
436
434
|
|
437
435
|
if options.command == 'models'
|
438
436
|
diagram = ModelsDiagram.new options
|
@@ -449,7 +447,7 @@ if options.output
|
|
449
447
|
begin
|
450
448
|
STDOUT.reopen(options.output)
|
451
449
|
rescue
|
452
|
-
STDERR.print "Error: Cannot write to #{options.output}\n\n"
|
450
|
+
STDERR.print "Error: Cannot write diagram to #{options.output}\n\n"
|
453
451
|
exit 2
|
454
452
|
end
|
455
453
|
end
|
data/rake.gemspec
CHANGED
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
|
|
3
3
|
specification_version: 1
|
4
4
|
name: railroad
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.3.
|
7
|
-
date: 2007-04-
|
6
|
+
version: 0.3.3
|
7
|
+
date: 2007-04-10 00:00:00 -03:00
|
8
8
|
summary: A DOT diagram generator for Ruby on Rail applications
|
9
9
|
require_paths:
|
10
10
|
- lib
|