prailroady 1.5.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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.travis.yml +3 -0
  4. data/AUTHORS.rdoc +23 -0
  5. data/Gemfile +2 -0
  6. data/Gemfile.lock +33 -0
  7. data/Guardfile +49 -0
  8. data/LICENSE.rdoc +340 -0
  9. data/README.md +34 -0
  10. data/Rakefile +12 -0
  11. data/bin/prailroady +58 -0
  12. data/lib/prailroady.rb +5 -0
  13. data/lib/prailroady/aasm_diagram.rb +105 -0
  14. data/lib/prailroady/app_diagram.rb +103 -0
  15. data/lib/prailroady/controllers_diagram.rb +103 -0
  16. data/lib/prailroady/diagram_graph.rb +125 -0
  17. data/lib/prailroady/models_diagram.rb +357 -0
  18. data/lib/prailroady/options_struct.rb +204 -0
  19. data/lib/prailroady/railtie.rb +11 -0
  20. data/lib/prailroady/version.rb +3 -0
  21. data/prailroady.gemspec +25 -0
  22. data/tasks/prailroady.rake +139 -0
  23. data/test/file_fixture/app/controllers/application_controller.rb +2 -0
  24. data/test/file_fixture/app/controllers/dummy1_controller.rb +0 -0
  25. data/test/file_fixture/app/controllers/dummy2_controller.rb +0 -0
  26. data/test/file_fixture/app/controllers/sub-dir/sub_dummy_controller.rb +0 -0
  27. data/test/file_fixture/app/models/concerns/author_settings.rb +12 -0
  28. data/test/file_fixture/app/models/concerns/taggable.rb +0 -0
  29. data/test/file_fixture/app/models/dummy1.rb +0 -0
  30. data/test/file_fixture/app/models/dummy2.rb +0 -0
  31. data/test/file_fixture/app/models/sub-dir/sub_dummy.rb +0 -0
  32. data/test/file_fixture/lib/app/controllers/dummy/dummy_controller.rb +0 -0
  33. data/test/file_fixture/lib/app/models/dummy1.rb +0 -0
  34. data/test/lib/railroady/aasm_diagram_spec.rb +54 -0
  35. data/test/lib/railroady/app_diagram_spec.rb +14 -0
  36. data/test/lib/railroady/controllers_diagram_spec.rb +56 -0
  37. data/test/lib/railroady/core_ext_spec.rb +13 -0
  38. data/test/lib/railroady/diagram_graph_spec.rb +53 -0
  39. data/test/lib/railroady/models_diagram_spec.rb +162 -0
  40. data/test/spec_helper.rb +5 -0
  41. metadata +159 -0
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # Use online preview
2
+
3
+ https://planttext.com/
4
+
5
+ # Or install local
6
+
7
+ ## Mac users
8
+
9
+ Brew users can install via:
10
+
11
+ brew install graphviz
12
+
13
+ MacPorts users can install in via
14
+
15
+ sudo port install graphviz
16
+
17
+ ## Install Plantuml
18
+
19
+ http://plantuml.com/download
20
+
21
+ ## Install Atom Preview
22
+
23
+ https://atom.io/packages/plantuml-viewer
24
+
25
+ https://atom.io/packages/plantuml-preview
26
+
27
+ ## Run generate
28
+
29
+ ```bash
30
+ prailroady -o models.pu -M # only model name
31
+ prailroady -o models.pu -M -l # with attributes
32
+ ```
33
+
34
+ Then open `models.pu` using atom preview or use online preview.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib/prailroady'
8
+ t.test_files = FileList['test/lib/prailroady/*_spec.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task default: :test
data/bin/prailroady ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # PrailRoady - RoR diagrams generator
4
+ # http://github.com/preston/railroady
5
+ #
6
+ # PrailRoady generates models and controllers diagrams in DOT language
7
+ # for a Rails v3 application.
8
+ #
9
+ # Copyright 2007-2008 - Javier Smaldone (http://www.smaldone.com.ar)
10
+ #
11
+ # This program is free software; you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation; either version 2 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # Modifications 2010-2015 by Preston Lee.
17
+ #
18
+
19
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
20
+ require 'prailroady'
21
+
22
+ APP_NAME = 'prailroady'
23
+ APP_HUMAN_NAME = 'PrailRoady'
24
+ # APP_VERSION = [PrailRoady::VERSION::MAJOR, PrailRoady::VERSION::MINOR, PrailRoady::VERSION::PATCH]
25
+ APP_VERSION = PrailRoady::VERSION
26
+ COPYRIGHT = 'Copyright (C) 2007-2008 Javier Smaldone, 2009 Peter Hoeg, 2010-2015 Preston Lee'
27
+
28
+ options = OptionsStruct.new(app_name: APP_NAME, app_human_name: APP_HUMAN_NAME, app_version: APP_VERSION, copyright: COPYRIGHT)
29
+
30
+ options.parse ARGV
31
+
32
+ # puts "OPTS!!!"
33
+ # puts options
34
+
35
+ old_dir = Dir.pwd
36
+
37
+ Dir.chdir(options.root) if options.root != ''
38
+
39
+ case options.command
40
+ when 'models'
41
+ diagram = ModelsDiagram.new options
42
+ when 'controllers'
43
+ diagram = ControllersDiagram.new options
44
+ when 'aasm'
45
+ diagram = AasmDiagram.new(options)
46
+ else
47
+ STDERR.print "#{APP_HUMAN_NAME} v#{APP_VERSION}\n" \
48
+ "Error: You must supply a command\n" \
49
+ " (try #{APP_NAME} -h)\n\n"
50
+ exit 1
51
+ end
52
+
53
+ diagram.process
54
+ diagram.generate
55
+
56
+ Dir.chdir(old_dir)
57
+
58
+ diagram.print
data/lib/prailroady.rb ADDED
@@ -0,0 +1,5 @@
1
+ %w(version options_struct models_diagram controllers_diagram aasm_diagram).each { |f| require "prailroady/#{f}" }
2
+
3
+ module PrailRoady
4
+ require 'prailroady/railtie' if defined?(Rails)
5
+ end
@@ -0,0 +1,105 @@
1
+ # PrailRoady - 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
+ # AASM code provided by Ana Nelson (http://ananelson.com/)
8
+
9
+ require 'prailroady/app_diagram'
10
+
11
+ # Diagram for Acts As State Machine
12
+ class AasmDiagram < AppDiagram
13
+ def initialize(options = OptionsStruct.new)
14
+ # options.exclude.map! {|e| e = "app/models/" + e}
15
+ super options
16
+ @graph.diagram_type = 'Models'
17
+ # Processed habtm associations
18
+ @habtm = []
19
+ end
20
+
21
+ # Process model files
22
+ def generate
23
+ STDERR.print "Generating AASM diagram\n" if @options.verbose
24
+ get_files.each do |f|
25
+ process_class extract_class_name(f).constantize
26
+ end
27
+ end
28
+
29
+ def get_files(prefix = '')
30
+ files = !@options.specify.empty? ? Dir.glob(@options.specify) : Dir.glob(prefix + 'app/models/**/*.rb')
31
+ files += Dir.glob('vendor/plugins/**/app/models/*.rb') if @options.plugins_models
32
+ files -= Dir.glob(prefix + 'app/models/concerns/**/*.rb') unless @options.include_concerns
33
+ files -= Dir.glob(@options.exclude)
34
+ files
35
+ end
36
+
37
+ private
38
+
39
+ # Load model classes
40
+ def load_classes
41
+ disable_stdout
42
+ get_files.each { |m| require m }
43
+ enable_stdout
44
+ rescue LoadError
45
+ enable_stdout
46
+ print_error 'model classes'
47
+ raise
48
+ end # load_classes
49
+
50
+ # Process a model class
51
+ def process_class(current_class)
52
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
53
+
54
+ # Only interested in acts_as_state_machine models.
55
+ process_acts_as_state_machine_class(current_class) if current_class.respond_to?(:states)
56
+ process_aasm_class(current_class) if current_class.respond_to?(:aasm_states) || current_class.respond_to?(:aasm)
57
+ end # process_class
58
+
59
+ def process_acts_as_state_machine_class(current_class)
60
+ node_attribs = []
61
+ node_type = 'aasm'
62
+
63
+ STDERR.print "\t\tprocessing as acts_as_state_machine\n" if @options.verbose
64
+ current_class.aasm.states.each do |state_name|
65
+ node_shape = (current_class.aasm.initial_state == state_name) ? ', peripheries = 2' : ''
66
+ node_attribs << "#{current_class.name.downcase}_#{state_name} [label=#{state_name} #{node_shape}];"
67
+ end
68
+ @graph.add_node [node_type, current_class.name, node_attribs]
69
+
70
+ current_class.aasm.events.each do |event|
71
+ event_name = event.name
72
+ event.transitions.each do |transition|
73
+ @graph.add_edge [
74
+ 'event',
75
+ current_class.name.downcase + '_' + transition.from.to_s,
76
+ current_class.name.downcase + '_' + transition.to.to_s,
77
+ event_name.to_s
78
+ ]
79
+ end
80
+ end
81
+ end
82
+
83
+ def process_aasm_class(current_class)
84
+ node_attribs = []
85
+ node_type = 'aasm'
86
+
87
+ STDERR.print "\t\tprocessing as aasm\n" if @options.verbose
88
+ current_class.aasm.states.each do |state|
89
+ node_shape = (current_class.aasm.initial_state == state.name) ? ', peripheries = 2' : ''
90
+ node_attribs << "#{current_class.name.downcase}_#{state.name} [label=#{state.name} #{node_shape}];"
91
+ end
92
+ @graph.add_node [node_type, current_class.name, node_attribs]
93
+
94
+ current_class.aasm.events.each do |event|
95
+ event.transitions.each do |transition|
96
+ @graph.add_edge [
97
+ 'event',
98
+ current_class.name.downcase + '_' + transition.from.to_s,
99
+ current_class.name.downcase + '_' + transition.to.to_s,
100
+ event.name.to_s
101
+ ]
102
+ end
103
+ end
104
+ end
105
+ end # class AasmDiagram
@@ -0,0 +1,103 @@
1
+ # PrailRoady - 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 'prailroady/diagram_graph'
8
+
9
+ # Root class for PrailRoady diagrams
10
+ class AppDiagram
11
+ def initialize(options = OptionsStruct.new)
12
+ @options = options
13
+ @graph = DiagramGraph.new
14
+ @graph.show_label = @options.label
15
+ @graph.alphabetize = @options.alphabetize
16
+ end
17
+
18
+ # Print diagram
19
+ def print
20
+ if @options.output
21
+ old_stdout = STDOUT.dup
22
+ begin
23
+ STDOUT.reopen(@options.output)
24
+ rescue
25
+ STDERR.print "Error: Cannot write diagram to #{@options.output}\n\n"
26
+ exit 2
27
+ end
28
+ end
29
+
30
+ if @options.xmi
31
+ STDERR.print "Generating XMI diagram\n" if @options.verbose
32
+ STDOUT.print @graph.to_xmi
33
+ else
34
+ STDERR.print "Generating DOT graph\n" if @options.verbose
35
+ STDOUT.print @graph.to_dot
36
+ end
37
+
38
+ STDOUT.reopen(old_stdout) if @options.output
39
+ end # print
40
+
41
+ def process
42
+ load_environment
43
+ end
44
+
45
+ # get all engines
46
+ def engines
47
+ engines = []
48
+
49
+ if defined?(Rails)
50
+ engines = if Rails::Application::Railties.respond_to?(:engines)
51
+ Rails::Application::Railties.engines
52
+ else
53
+ # rails 4 way of getting engines
54
+ Rails::Engine.subclasses.map(&:instance)
55
+ end
56
+ end
57
+ engines
58
+ end
59
+
60
+ private
61
+
62
+ # Load Rails application's environment
63
+ def load_environment
64
+ STDERR.print "Loading application environment\n" if @options.verbose
65
+ begin
66
+ disable_stdout
67
+ l = File.join(Dir.pwd.to_s, @options.config_file)
68
+ require l
69
+ enable_stdout
70
+ rescue LoadError
71
+ enable_stdout
72
+ print_error 'application environment'
73
+ raise
74
+ end
75
+ STDERR.print "Loading application classes as we go\n" if @options.verbose
76
+ end
77
+
78
+ # Prevents Rails application from writing to STDOUT
79
+ def disable_stdout
80
+ @old_stdout = STDOUT.dup
81
+ # via Tomas Matousek, http://www.ruby-forum.com/topic/205887
82
+ STDOUT.reopen(::RUBY_PLATFORM =~ /djgpp|(cyg|ms|bcc)win|mingw/ ? 'NUL' : '/dev/null')
83
+ end
84
+
85
+ # Restore STDOUT
86
+ def enable_stdout
87
+ STDOUT.reopen(@old_stdout)
88
+ end
89
+
90
+ # Print error when loading Rails application
91
+ def print_error(type)
92
+ STDERR.print "Error loading #{type}.\n (Are you running " \
93
+ "#{@options.app_name} on the application's root directory?)\n\n"
94
+ end
95
+
96
+ # Extract class name from filename
97
+ def extract_class_name(filename)
98
+ # filename.split('/')[2..-1].join('/').split('.').first.camelize
99
+ # Fixed by patch from ticket #12742
100
+ # File.basename(filename).chomp(".rb").camelize
101
+ filename.split('/')[2..-1].collect(&:camelize).join('::').chomp('.rb')
102
+ end
103
+ end # class AppDiagram
@@ -0,0 +1,103 @@
1
+ # PrailRoady - 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 'prailroady/app_diagram'
8
+
9
+ # PrailRoady controllers diagram
10
+ class ControllersDiagram < AppDiagram
11
+ # as of Rails 2.3 the file is no longer application.rb but instead
12
+ # application_controller.rb
13
+ APP_CONTROLLER = File.exist?('app/controllers/application.rb') ? 'app/controllers/application.rb' : 'app/controllers/application_controller.rb'
14
+
15
+ def initialize(options = OptionsStruct.new)
16
+ # options.exclude.map! {|e| "app/controllers/" + e}
17
+ super options
18
+ @graph.diagram_type = 'Controllers'
19
+ end
20
+
21
+ # Process controller files
22
+ def generate
23
+ STDERR.print "Generating controllers diagram\n" if @options.verbose
24
+ files = get_files
25
+ # only add APP_CONTROLLER if it isn't already included from the glob above
26
+ files << APP_CONTROLLER unless files.include? APP_CONTROLLER
27
+ files.each do |f|
28
+ class_name = extract_class_name(f)
29
+ # ApplicationController's file is 'application.rb' in Rails < 2.3
30
+ class_name += 'Controller' if class_name == 'Application'
31
+ begin
32
+ process_class class_name.constantize
33
+ rescue Exception
34
+ STDERR.print "Warning: exception #{$ERROR_INFO} raised while trying to load controller class #{f}"
35
+ end
36
+ end
37
+ end # generate
38
+
39
+ def get_files(prefix = '')
40
+ files = !@options.specify.empty? ? Dir.glob(@options.specify) : Dir.glob(prefix << 'app/controllers/**/*_controller.rb')
41
+ files += engine_files if @options.engine_controllers
42
+ files -= Dir.glob(@options.exclude)
43
+ files
44
+ end
45
+
46
+ def engine_files
47
+ engines.collect { |engine| Dir.glob("#{engine.root}/app/controllers/**/*_controller.rb") }.flatten
48
+ end
49
+
50
+ def extract_class_name(filename)
51
+ filename.match(/.*\/controllers\/(.*).rb$/)[1].camelize
52
+ end
53
+
54
+ private
55
+
56
+ # Load controller classes
57
+ def load_classes
58
+ disable_stdout
59
+ # ApplicationController must be loaded first
60
+ require APP_CONTROLLER
61
+ get_files.each { |c| require "./#{c}" }
62
+ enable_stdout
63
+ rescue LoadError
64
+ enable_stdout
65
+ print_error 'controller classes'
66
+ raise
67
+ end # load_classes
68
+
69
+ # Proccess a controller class
70
+ def process_class(current_class)
71
+ STDERR.print "\tProcessing #{current_class}\n" if @options.verbose
72
+
73
+ if @options.brief
74
+ @graph.add_node ['controller-brief', current_class.name]
75
+ elsif current_class.is_a? Class
76
+ # Collect controller's methods
77
+ node_attribs = { public: [],
78
+ protected: [],
79
+ private: [] }
80
+ current_class.public_instance_methods(false).sort.each do |m|
81
+ node_attribs[:public] << m
82
+ end unless @options.hide_public
83
+ current_class.protected_instance_methods(false).sort.each do |m|
84
+ node_attribs[:protected] << m
85
+ end unless @options.hide_protected
86
+ current_class.private_instance_methods(false).sort.each do |m|
87
+ node_attribs[:private] << m
88
+ end unless @options.hide_private
89
+ @graph.add_node ['controller', current_class.name, node_attribs]
90
+ elsif @options.modules && current_class.is_a?(Module)
91
+ @graph.add_node ['module', current_class.name]
92
+ end
93
+
94
+ # Generate the inheritance edge (only for transitive subclasses of ApplicationControllers)
95
+ if @options.inheritance && (transitive_subclasses_of(ApplicationController).include? current_class)
96
+ @graph.add_edge ['is-a', current_class.superclass.name, current_class.name]
97
+ end
98
+ end # process_class
99
+
100
+ def transitive_subclasses_of(klass)
101
+ klass.subclasses | klass.subclasses.map { |subklass| transitive_subclasses_of(subklass) }.flatten
102
+ end
103
+ end # class ControllersDiagram
@@ -0,0 +1,125 @@
1
+ # PrailRoady - 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
+ # PrailRoady diagram structure
8
+ class DiagramGraph
9
+ def initialize
10
+ @diagram_type = ''
11
+ @show_label = false
12
+ @alphabetize = false
13
+ @nodes = []
14
+ @edges = []
15
+ end
16
+
17
+ def add_node(node)
18
+ @nodes << node
19
+ end
20
+
21
+ def add_edge(edge)
22
+ @edges << edge
23
+ end
24
+
25
+ attr_writer :diagram_type
26
+
27
+ attr_writer :show_label
28
+
29
+ attr_writer :alphabetize
30
+
31
+ # Generate DOT graph
32
+ def to_dot
33
+ dot_header +
34
+ @nodes.map { |n| dot_node n[0], n[1], n[2], n[3] }.join +
35
+ @edges.map { |e| dot_edge e[0], e[1], e[2], e[3] }.join +
36
+ dot_footer
37
+ end
38
+
39
+ # Generate XMI diagram (not yet implemented)
40
+ def to_xmi
41
+ STDERR.print "Sorry. XMI output not yet implemented.\n\n"
42
+ ''
43
+ end
44
+
45
+ private
46
+
47
+ # Build DOT diagram header
48
+ def dot_header
49
+ result = "@startuml\n"
50
+ result += "\tskinparam linetype ortho\n"
51
+ result += "\tskinparam packageStyle rectangle\n"
52
+ result += "\tskinparam shadowing false\n"
53
+ result += "\tskinparam class {\n"
54
+ result += "\t\tBackgroundColor White\n"
55
+ result += "\t\tBorderColor Black\n"
56
+ result += "\t\tArrowColor Black\n"
57
+ result += "\t}\n"
58
+ result += "\thide members\n" if !@show_label
59
+ result += "\thide circle\n"
60
+ result
61
+ end
62
+
63
+ # Build DOT diagram footer
64
+ def dot_footer
65
+ "\n@enduml\n"
66
+ end
67
+
68
+ # Build a DOT graph node
69
+ def dot_node(type, name, attributes = nil, custom_options = '')
70
+ options = ''
71
+ case type
72
+ when 'model'
73
+ options += attributes.sort_by { |s| @alphabetize ? s : nil }.map{|s| "\t\t#{s}"}.join("\n")
74
+ when 'model-brief'
75
+ options += ""
76
+ when 'class'
77
+ options += ""
78
+ when 'class-brief'
79
+ options += ""
80
+ when 'controller'
81
+ options += ""
82
+ options += attributes[:public].sort_by { |s| @alphabetize ? s : nil }.map{|s| "\t\t#{s}"}.join("\n")
83
+ options += attributes[:protected].sort_by { |s| @alphabetize ? s : nil }.map{|s| "\t\t#{s}"}.join("\n")
84
+ options += attributes[:private].sort_by { |s| @alphabetize ? s : nil }.map{|s| "\t\t#{s}"}.join("\n")
85
+ when 'controller-brief'
86
+ options += ""
87
+ when 'module'
88
+ options += ""
89
+ when 'aasm'
90
+ return "aasm: \\n #{attributes.join("\\n")}"
91
+ end
92
+ # options = [options, custom_options].compact.reject{|o| o.empty?}.join(', ')
93
+ attr_options = "{\n#{options}\n\t}" if @show_label && options.length > 0
94
+ return "\tclass #{quote(name)} as #{noquote(name)} #{attr_options}\n"
95
+ end # dot_node
96
+
97
+ # Build a DOT graph edge
98
+ def dot_edge(type, from, to, name = '')
99
+ ret = ""
100
+ case type
101
+ when 'one-one'
102
+ ret = "\t#{quote(from)} -- #{quote(to)}\n"
103
+ when 'one-many'
104
+ ret = "\t#{quote(from)} --|{ #{quote(to)}\n"
105
+ when 'many-many'
106
+ ret = "\t#{quote(from)} }|--|{ #{quote(to)}\n"
107
+ when 'belongs-to'
108
+ ret = "\t#{quote(from)} }|--|{ #{quote(to)}\n"
109
+ when 'is-a'
110
+ ret = "\t#{quote(from)} }|--|{ #{quote(to)}\n"
111
+ when 'event'
112
+ ret = "\t#{quote(from)} }|--|{ #{quote(to)}\n"
113
+ end
114
+ ret
115
+ end # dot_edge
116
+
117
+ # Quotes a class name
118
+ def quote(name)
119
+ ('"' + name.to_s + '"').gsub(":","")
120
+ end
121
+ def noquote(name)
122
+ name.to_s.gsub(":","")
123
+ end
124
+
125
+ end # class DiagramGraph