factorylabs-railroad 0.6.0.1

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,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