railroad 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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