factorylabs-railroad 0.6.0.1

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