shift-railroad 0.5.8

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.
@@ -0,0 +1,88 @@
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 'app_diagram'
8
+
9
+ # RailRoad controllers diagram
10
+ class ControllersDiagram < AppDiagram
11
+
12
+ # as of Rails 2.3 the file is no longer application.rb but instead
13
+ # application_controller.rb
14
+ APP_CONTROLLER = File.exist?('app/controllers/application.rb') ? 'app/controllers/application.rb' : 'app/controllers/application_controller.rb'
15
+
16
+ def initialize(options)
17
+ #options.exclude.map! {|e| "app/controllers/" + e}
18
+ super options
19
+ @graph.diagram_type = 'Controllers'
20
+ end
21
+
22
+ # Process controller files
23
+ def generate
24
+ STDERR.print "Generating controllers diagram\n" if @options.verbose
25
+
26
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
27
+ # only add APP_CONTROLLER if it isn't already included from the glob above
28
+ files << APP_CONTROLLER unless files.include? APP_CONTROLLER
29
+ files.each do |f|
30
+ class_name = extract_class_name(f)
31
+ # ApplicationController's file is 'application.rb' in Rails < 2.3
32
+ class_name += 'Controller' if class_name == 'Application'
33
+ process_class class_name.constantize
34
+ end
35
+ end # generate
36
+
37
+ private
38
+
39
+ # Load controller classes
40
+ def load_classes
41
+ begin
42
+ disable_stdout
43
+ # ApplicationController must be loaded first
44
+ require APP_CONTROLLER
45
+ files = Dir.glob("app/controllers/**/*_controller.rb") - @options.exclude
46
+ files.each {|c| require c }
47
+ enable_stdout
48
+ rescue LoadError
49
+ enable_stdout
50
+ print_error "controller classes"
51
+ raise
52
+ end
53
+ end # load_classes
54
+
55
+ # Proccess a controller class
56
+ def process_class(current_class)
57
+
58
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
59
+
60
+ if @options.brief
61
+ @graph.add_node ['controller-brief', current_class.name]
62
+ elsif current_class.is_a? Class
63
+ # Collect controller's methods
64
+ node_attribs = {:public => [],
65
+ :protected => [],
66
+ :private => []}
67
+ current_class.public_instance_methods(false).sort.each { |m|
68
+ node_attribs[:public] << m
69
+ } unless @options.hide_public
70
+ current_class.protected_instance_methods(false).sort.each { |m|
71
+ node_attribs[:protected] << m
72
+ } unless @options.hide_protected
73
+ current_class.private_instance_methods(false).sort.each { |m|
74
+ node_attribs[:private] << m
75
+ } unless @options.hide_private
76
+ @graph.add_node ['controller', current_class.name, node_attribs]
77
+ elsif @options.modules && current_class.is_a?(Module)
78
+ @graph.add_node ['module', current_class.name]
79
+ end
80
+
81
+ # Generate the inheritance edge (only for ApplicationControllers)
82
+ if @options.inheritance &&
83
+ (ApplicationController.subclasses.include? current_class.name)
84
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
85
+ end
86
+ end # process_class
87
+
88
+ end # class ControllersDiagram
@@ -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,155 @@
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 'app_diagram'
8
+
9
+ # RailRoad models diagram
10
+ class ModelsDiagram < AppDiagram
11
+
12
+ def initialize(options)
13
+ #options.exclude.map! {|e| "app/models/" + e}
14
+ super options
15
+ @graph.diagram_type = 'Models'
16
+ # Processed habtm associations
17
+ @habtm = []
18
+ end
19
+
20
+ # Process model files
21
+ def generate
22
+ STDERR.print "Generating models diagram\n" if @options.verbose
23
+ files = Dir.glob("app/models/**/*.rb")
24
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
25
+ files -= @options.exclude
26
+ files.each do |f|
27
+ process_class extract_class_name(f).constantize
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # Load model classes
34
+ def load_classes
35
+ begin
36
+ disable_stdout
37
+ files = Dir.glob("app/models/**/*.rb")
38
+ files += Dir.glob("vendor/plugins/**/app/models/*.rb") if @options.plugins_models
39
+ files -= @options.exclude
40
+ files.each {|m| require m }
41
+ enable_stdout
42
+ rescue LoadError
43
+ enable_stdout
44
+ print_error "model classes"
45
+ raise
46
+ end
47
+ end # load_classes
48
+
49
+ # Process a model class
50
+ def process_class(current_class)
51
+
52
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
53
+
54
+ generated = false
55
+
56
+ # Is current_clas derived from ActiveRecord::Base?
57
+ if current_class.respond_to?'reflect_on_all_associations'
58
+
59
+
60
+ node_attribs = []
61
+ if @options.brief || current_class.abstract_class?
62
+ node_type = 'model-brief'
63
+ else
64
+ node_type = 'model'
65
+
66
+ # Collect model's content columns
67
+
68
+ content_columns = current_class.content_columns
69
+
70
+ if @options.hide_magic
71
+ # From patch #13351
72
+ # http://wiki.rubyonrails.org/rails/pages/MagicFieldNames
73
+ magic_fields = [
74
+ "created_at", "created_on", "updated_at", "updated_on",
75
+ "lock_version", "type", "id", "position", "parent_id", "lft",
76
+ "rgt", "quote", "template"
77
+ ]
78
+ magic_fields << current_class.table_name + "_count" if current_class.respond_to? 'table_name'
79
+ content_columns = current_class.content_columns.select {|c| ! magic_fields.include? c.name}
80
+ else
81
+ content_columns = current_class.content_columns
82
+ end
83
+
84
+ content_columns.each do |a|
85
+ content_column = a.name
86
+ content_column += ' :' + a.type.to_s unless @options.hide_types
87
+ node_attribs << content_column
88
+ end
89
+ end
90
+ @graph.add_node [node_type, current_class.name, node_attribs]
91
+ generated = true
92
+ # Process class associations
93
+ associations = current_class.reflect_on_all_associations
94
+ if @options.inheritance && ! @options.transitive
95
+ superclass_associations = current_class.superclass.reflect_on_all_associations
96
+
97
+ associations = associations.select{|a| ! superclass_associations.include? a}
98
+ # This doesn't works!
99
+ # associations -= current_class.superclass.reflect_on_all_associations
100
+ end
101
+ associations.each do |a|
102
+ process_association current_class.name, a
103
+ end
104
+ elsif @options.all && (current_class.is_a? Class)
105
+ # Not ActiveRecord::Base model
106
+ node_type = @options.brief ? 'class-brief' : 'class'
107
+ @graph.add_node [node_type, current_class.name]
108
+ generated = true
109
+ elsif @options.modules && (current_class.is_a? Module)
110
+ @graph.add_node ['module', current_class.name]
111
+ end
112
+
113
+ # Only consider meaningful inheritance relations for generated classes
114
+ if @options.inheritance && generated &&
115
+ (current_class.superclass != ActiveRecord::Base) &&
116
+ (current_class.superclass != Object)
117
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
118
+ end
119
+
120
+ end # process_class
121
+
122
+ # Process a model association
123
+ def process_association(class_name, assoc)
124
+
125
+ STDERR.print "\t\tProcessing model association #{assoc.name.to_s}\n" if @options.verbose
126
+
127
+ # Skip "belongs_to" associations
128
+ return if assoc.macro.to_s == 'belongs_to' && !@options.show_belongs_to
129
+
130
+ # Only non standard association names needs a label
131
+
132
+ # from patch #12384
133
+ # if assoc.class_name == assoc.name.to_s.singularize.camelize
134
+ assoc_class_name = (assoc.class_name.respond_to? 'underscore') ? assoc.class_name.underscore.singularize.camelize : assoc.class_name
135
+ if assoc_class_name == assoc.name.to_s.singularize.camelize
136
+ assoc_name = ''
137
+ else
138
+ assoc_name = assoc.name.to_s
139
+ end
140
+
141
+ if assoc.macro.to_s == 'has_one'
142
+ assoc_type = 'one-one'
143
+ elsif assoc.macro.to_s == 'has_many' && (! assoc.options[:through])
144
+ assoc_type = 'one-many'
145
+ else # habtm or has_many, :through
146
+ return if @habtm.include? [assoc.class_name, class_name, assoc_name]
147
+ assoc_type = 'many-many'
148
+ @habtm << [class_name, assoc.class_name, assoc_name]
149
+ end
150
+ # from patch #12384
151
+ # @graph.add_edge [assoc_type, class_name, assoc.class_name, assoc_name]
152
+ @graph.add_edge [assoc_type, class_name, assoc_class_name, assoc_name]
153
+ end # process_association
154
+
155
+ end # class ModelsDiagram
@@ -0,0 +1,173 @@
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
+ :inheritance => false,
19
+ :join => false,
20
+ :label => false,
21
+ :modules => false,
22
+ :hide_magic => false,
23
+ :hide_types => false,
24
+ :hide_public => false,
25
+ :hide_protected => false,
26
+ :hide_private => false,
27
+ :plugins_models => false,
28
+ :root => '',
29
+ :show_belongs_to => false,
30
+ :transitive => false,
31
+ :verbose => false,
32
+ :xmi => false,
33
+ :command => '' }
34
+ super(init_options)
35
+ end # initialize
36
+
37
+ def parse(args)
38
+ @opt_parser = OptionParser.new do |opts|
39
+ opts.banner = "Usage: #{APP_NAME} [options] command"
40
+ opts.separator ""
41
+ opts.separator "Common options:"
42
+ opts.on("-b", "--brief", "Generate compact diagram",
43
+ " (no attributes nor methods)") do |b|
44
+ self.brief = b
45
+ end
46
+ opts.on("-e", "--exclude file1[,fileN]", Array, "Exclude given files") do |list|
47
+ self.exclude = list
48
+ end
49
+ opts.on("-i", "--inheritance", "Include inheritance relations") do |i|
50
+ self.inheritance = i
51
+ end
52
+ opts.on("-l", "--label", "Add a label with diagram information",
53
+ " (type, date, migration, version)") do |l|
54
+ self.label = l
55
+ end
56
+ opts.on("-o", "--output FILE", "Write diagram to file FILE") do |f|
57
+ self.output = f
58
+ end
59
+ opts.on("-r", "--root PATH", "Set PATH as the application root") do |r|
60
+ self.root = r
61
+ end
62
+ opts.on("-v", "--verbose", "Enable verbose output",
63
+ " (produce messages to STDOUT)") do |v|
64
+ self.verbose = v
65
+ end
66
+ opts.on("-x", "--xmi", "Produce XMI instead of DOT",
67
+ " (for UML tools)") do |x|
68
+ self.xmi = x
69
+ end
70
+ opts.separator ""
71
+ opts.separator "Models diagram options:"
72
+ opts.on("-a", "--all", "Include all models",
73
+ " (not only ActiveRecord::Base derived)") do |a|
74
+ self.all = a
75
+ end
76
+ opts.on("--show-belongs_to", "Show belongs_to associations") do |s|
77
+ self.show_belongs_to = s
78
+ end
79
+ opts.on("--hide-magic", "Hide magic field names") do |h|
80
+ self.hide_magic = h
81
+ end
82
+ opts.on("--hide-types", "Hide attributes type") do |h|
83
+ self.hide_types = h
84
+ end
85
+ opts.on("-j", "--join", "Concentrate edges") do |j|
86
+ self.join = j
87
+ end
88
+ opts.on("-m", "--modules", "Include modules") do |m|
89
+ self.modules = m
90
+ end
91
+ opts.on("-p", "--plugins-models", "Include plugins models") do |p|
92
+ self.plugins_models = p
93
+ end
94
+ opts.on("-t", "--transitive", "Include transitive associations",
95
+ "(through inheritance)") do |t|
96
+ self.transitive = t
97
+ end
98
+ opts.separator ""
99
+ opts.separator "Controllers diagram options:"
100
+ opts.on("--hide-public", "Hide public methods") do |h|
101
+ self.hide_public = h
102
+ end
103
+ opts.on("--hide-protected", "Hide protected methods") do |h|
104
+ self.hide_protected = h
105
+ end
106
+ opts.on("--hide-private", "Hide private methods") do |h|
107
+ self.hide_private = h
108
+ end
109
+ opts.separator ""
110
+ opts.separator "Other options:"
111
+ opts.on("-h", "--help", "Show this message") do
112
+ STDOUT.print "#{opts}\n"
113
+ exit
114
+ end
115
+ opts.on("--version", "Show version and copyright") do
116
+ STDOUT.print "#{APP_HUMAN_NAME} version #{APP_VERSION.join('.')}\n\n" +
117
+ "#{COPYRIGHT}\nThis is free software; see the source " +
118
+ "for copying conditions.\n\n"
119
+ exit
120
+ end
121
+ opts.separator ""
122
+ opts.separator "Commands (you must supply one of these):"
123
+ opts.on("-M", "--models", "Generate models diagram") do |c|
124
+ if self.command != ''
125
+ STDERR.print "Error: Can only generate one diagram type\n\n"
126
+ exit 1
127
+ else
128
+ self.command = 'models'
129
+ end
130
+ end
131
+ opts.on("-C", "--controllers", "Generate controllers 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 = 'controllers'
137
+ end
138
+ end
139
+ # From Ana Nelson's patch
140
+ opts.on("-A", "--aasm", "Generate \"acts as state machine\" diagram") do |c|
141
+ if self.command == 'controllers'
142
+ STDERR.print "Error: Can only generate one diagram type\n\n"
143
+ exit 1
144
+ else
145
+ self.command = 'aasm'
146
+ end
147
+ end
148
+ opts.separator ""
149
+ opts.separator "For bug reporting and additional information, please see:"
150
+ opts.separator "http://railroad.rubyforge.org/"
151
+ end # do
152
+
153
+ begin
154
+ @opt_parser.parse!(args)
155
+ rescue OptionParser::AmbiguousOption
156
+ option_error "Ambiguous option"
157
+ rescue OptionParser::InvalidOption
158
+ option_error "Invalid option"
159
+ rescue OptionParser::InvalidArgument
160
+ option_error "Invalid argument"
161
+ rescue OptionParser::MissingArgument
162
+ option_error "Missing argument"
163
+ end
164
+ end # parse
165
+
166
+ private
167
+
168
+ def option_error(msg)
169
+ STDERR.print "Error: #{msg}\n\n #{@opt_parser}\n"
170
+ exit 1
171
+ end
172
+
173
+ end # class OptionsStruct