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.
Files changed (4) hide show
  1. data/ChangeLog +3 -0
  2. data/lib/railroad +153 -155
  3. data/rake.gemspec +1 -1
  4. metadata +2 -2
data/ChangeLog CHANGED
@@ -1,3 +1,6 @@
1
+ Version 0.3.3 (apr 10 2007)
2
+ - Code cleanup
3
+
1
4
  Version 0.3.2 (apr 9 2007)
2
5
  - Disable STDOUT when loading applications classes, avoiding
3
6
  messing up the DOT output.
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,2]
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 parser
24
- class OptionsParser
24
+ # Command line options structure
25
+ class OptionsStruct < OpenStruct
25
26
 
26
27
  require 'optparse'
27
- require 'ostruct'
28
-
29
- # Parse arguments from command line
30
- def self.parse(args)
31
-
32
- options = OpenStruct.new
33
-
34
- options.all = false
35
- options.brief = false
36
- options.inheritance = false
37
- options.join = false
38
- options.label = false
39
- options.modules = false
40
- options.hide_types = false
41
- options.hide_public = false
42
- options.hide_protected = false
43
- options.hide_private = false
44
- options.command = ''
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
- options.brief = b
51
+ self.brief = b
53
52
  end
54
53
  opts.on("-i", "--inheritance", "Include inheritance relations") do |i|
55
- options.inheritance = i
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
- options.label = l
58
+ self.label = l
60
59
  end
61
60
  opts.on("-o", "--output FILE", "Write diagram to file FILE") do |f|
62
- options.output = f
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
- options.all = a
67
+ self.all = a
69
68
  end
70
69
  opts.on("--hide-types", "Hide attributes type") do |h|
71
- options.hide_types = h
70
+ self.hide_types = h
72
71
  end
73
72
  opts.on("-j", "--join", "Concentrate edges") do |j|
74
- options.join = j
73
+ self.join = j
75
74
  end
76
75
  opts.on("-m", "--modules", "Include modules") do |m|
77
- options.modules = m
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
- options.hide_public = h
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 options.command == 'controllers'
106
- STDERR.print "Error: Can't generate models AND controllers diagram\n\n"
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
- options.command = 'models'
109
+ self.command = 'models'
110
110
  end
111
111
  end
112
112
  opts.on("-C", "--controllers", "Generate controllers diagram") do |c|
113
- if options.command == 'models'
114
- STDERR.print "Error: Can't generate models AND controllers diagram\n\n"
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
- options.command = 'controllers'
118
+ self.command = 'controllers'
118
119
  end
119
120
  end
120
121
  opts.separator ""
121
- opts.separator "For bug reporting instructions and additional information, please see:"
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
- opts.parse!(args)
127
+ @opt_parser.parse!(args)
127
128
  rescue OptionParser::AmbiguousOption
128
- option_error "Ambiguous option", opts
129
+ option_error "Ambiguous option"
129
130
  rescue OptionParser::InvalidOption
130
- option_error "Invalid option", opts
131
+ option_error "Invalid option"
131
132
  rescue OptionParser::InvalidArgument
132
- option_error "Invalid argument", opts
133
+ option_error "Invalid argument"
133
134
  rescue OptionParser::MissingArgument
134
- option_error "Missing argument", opts
135
+ option_error "Missing argument"
135
136
  rescue
136
- option_error "Unknown error", opts
137
+ option_error "Unknown error"
137
138
  end
138
- options
139
- end # self.parse
140
-
139
+ end # initialize
140
+
141
141
  private
142
142
 
143
- def self.option_error(msg, opts)
144
- STDERR.print "Error: #{msg}\n\n #{opts}\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 OptionsParser
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 error when loading application classes
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 "app environment"
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
- # Generate models diagram
213
- def generate
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
- current_class = m.split('/')[2..-1].join('/').split('.').first.camelize.constantize
229
- process_class current_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
- printed = false
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
- print "\t#{node(current_class.name)}\n"
275
+ node_attrib = ''
262
276
  else
263
- print "\t#{node(current_class.name)} " +
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
- print "#{a.name}"
267
- print " :#{a.type.to_s}" unless @options.hide_types
268
- print "\\l"
279
+ node_attrib += a.name
280
+ node_attrib += ' :' + a.type.to_s unless @options.hide_types
281
+ node_attrib += '\l'
269
282
  end
270
- print "}\"]\n"
283
+ node_attrib += '}"'
271
284
  end
272
- printed = true
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 { |a|
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
- print "\t#{node(current_class.name)} [shape=box]\n"
294
+ node_attrib = 'shape=box'
281
295
  else
282
- print "\t#{node(current_class.name)}" +
283
- "[shape=record, label=\"{#{current_class.name}|}\"]\n"
296
+ node_attrib = 'shape=record, label="{' + current_class.name + '|}"'
284
297
  end
285
- printed = true
298
+ print_node current_class.name, node_attrib
299
+ class_printed = true
286
300
  elsif @options.modules && (current_class.is_a? Module)
287
- print "\t#{node(current_class.name)}" +
288
- "[shape=box, style=dotted, label=\"#{current_class.name}\"]\n"
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 && printed &&
306
+ if @options.inheritance && class_printed &&
293
307
  (current_class.superclass != ActiveRecord::Base) &&
294
308
  (current_class.superclass != Object)
295
- print "\t\t#{node(current_class.name)} -> " +
296
- "#{node(current_class.superclass.name)}" +
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
- params = "label=\"#{association.name.to_s}\", "
308
- else
309
- params = ""
325
+ association_name = association.name.to_s
326
+ assoc_attrib += 'label="' + association_name + '", '
310
327
  end
311
328
 
312
- # Skip "belongs_to" associations
313
- if association.macro.to_s != 'belongs_to'
314
- # Put a tail label with the association arity
315
- if association.macro.to_s == 'has_one'
316
- params += 'taillabel="1"'
317
- else
318
- params += 'taillabel="n"'
319
- end
320
-
321
- # Use double-headed arrows for habtm and has_many, :through
322
- if association.macro.to_s == 'has_and_belongs_to_many' ||
323
- (association.macro.to_s == 'has_many' && association.options[:through])
324
- if @habtm.include? "#{association.class_name} -> #{current_class.name}"
325
- # habtm association already considered
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
- load_classes
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") do |c|
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
- print "\t#{node(current_class.name)}\n"
394
+ print_node current_class.name
395
+
400
396
  elsif current_class.is_a? Class
401
- print "\t#{node(current_class.name)} " +
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
- print "#{m}\\l"
399
+ node_attrib += m + '\l'
405
400
  } unless @options.hide_public
406
- print "|"
401
+ node_attrib += '|'
407
402
  current_class.protected_instance_methods(false).sort.each { |m|
408
- print "#{m}\\l"
403
+ node_attrib += m + '\l'
409
404
  } unless @options.hide_protected
410
- print "|"
405
+ node_attrib += '|'
411
406
  current_class.private_instance_methods(false).sort.each { |m|
412
- print "#{m}\\l"
407
+ node_attrib += m + '\l'
413
408
  } unless @options.hide_private
414
- print "}\"]\n"
415
- elsif @options.modules && (current_class.is_a? Module)
416
- print "\t#{node(current_class.name)}" +
417
- "[shape=box, style=dotted, label=\"#{current_class.name}\"]\n"
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
- print "\t#{node(current_class.name)} -> " +
424
- "#{node(current_class.superclass.name)} " +
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 = OptionsParser.parse ARGV
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
@@ -1,7 +1,7 @@
1
1
  require 'rubygems'
2
2
  SPEC = Gem::Specification.new do |s|
3
3
  s.name = "railroad"
4
- s.version = "0.3.2"
4
+ s.version = "0.3.3"
5
5
  s.author = "Javier Smaldone"
6
6
  s.email = "javier@smaldone.com.ar"
7
7
  s.homepage = "http://railroad.rubyforge.org"
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.2
7
- date: 2007-04-09 00:00:00 -03:00
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