railroad 0.3.2 → 0.3.3
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/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
|