archruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +32 -0
  6. data/Rakefile +1 -0
  7. data/archruby.gemspec +28 -0
  8. data/bin/archruby +20 -0
  9. data/bin/constraints_breaks.yml +29 -0
  10. data/constraints_breaks.yml +22 -0
  11. data/lib/archruby.rb +47 -0
  12. data/lib/archruby/architecture/architecture.rb +83 -0
  13. data/lib/archruby/architecture/config_definition.rb +66 -0
  14. data/lib/archruby/architecture/constraint_break.rb +22 -0
  15. data/lib/archruby/architecture/dependency.rb +11 -0
  16. data/lib/archruby/architecture/file_content.rb +24 -0
  17. data/lib/archruby/architecture/module_definition.rb +218 -0
  18. data/lib/archruby/architecture/parser.rb +58 -0
  19. data/lib/archruby/presenters/graph.rb +132 -0
  20. data/lib/archruby/presenters/text.rb +14 -0
  21. data/lib/archruby/presenters/yaml.rb +28 -0
  22. data/lib/archruby/ruby/core_library.rb +26 -0
  23. data/lib/archruby/ruby/parser.rb +409 -0
  24. data/lib/archruby/ruby/std_library.rb +22 -0
  25. data/lib/archruby/ruby/var_propagation.rb +46 -0
  26. data/lib/archruby/version.rb +3 -0
  27. data/spec/architecture/architecture_spec.rb +48 -0
  28. data/spec/architecture/config_definition_spec.rb +20 -0
  29. data/spec/architecture/constraint_break_spec.rb +22 -0
  30. data/spec/architecture/file_content_spec.rb +12 -0
  31. data/spec/architecture/module_definition_spec.rb +70 -0
  32. data/spec/architecture/parser_spec.rb +11 -0
  33. data/spec/archruby_spec.rb +10 -0
  34. data/spec/dummy_app/.gitignore +16 -0
  35. data/spec/dummy_app/Gemfile +45 -0
  36. data/spec/dummy_app/README.rdoc +28 -0
  37. data/spec/dummy_app/Rakefile +6 -0
  38. data/spec/dummy_app/app/assets/images/.keep +0 -0
  39. data/spec/dummy_app/app/assets/javascripts/application.js +16 -0
  40. data/spec/dummy_app/app/assets/stylesheets/application.css +13 -0
  41. data/spec/dummy_app/app/controllers/application_controller.rb +17 -0
  42. data/spec/dummy_app/app/controllers/concerns/.keep +0 -0
  43. data/spec/dummy_app/app/helpers/application_helper.rb +2 -0
  44. data/spec/dummy_app/app/mailers/.keep +0 -0
  45. data/spec/dummy_app/app/models/.keep +0 -0
  46. data/spec/dummy_app/app/models/concerns/.keep +0 -0
  47. data/spec/dummy_app/app/models/module_test.rb +5 -0
  48. data/spec/dummy_app/app/models/teste.rb +11 -0
  49. data/spec/dummy_app/app/models/user.rb +9 -0
  50. data/spec/dummy_app/app/views/layouts/application.html.erb +14 -0
  51. data/spec/dummy_app/app/views/view_test.rb +5 -0
  52. data/spec/dummy_app/bin/bundle +3 -0
  53. data/spec/dummy_app/bin/rails +4 -0
  54. data/spec/dummy_app/bin/rake +4 -0
  55. data/spec/dummy_app/config.ru +4 -0
  56. data/spec/dummy_app/config/application.rb +23 -0
  57. data/spec/dummy_app/config/boot.rb +4 -0
  58. data/spec/dummy_app/config/database.yml +25 -0
  59. data/spec/dummy_app/config/environment.rb +5 -0
  60. data/spec/dummy_app/config/environments/development.rb +29 -0
  61. data/spec/dummy_app/config/environments/production.rb +80 -0
  62. data/spec/dummy_app/config/environments/test.rb +36 -0
  63. data/spec/dummy_app/config/initializers/backtrace_silencers.rb +7 -0
  64. data/spec/dummy_app/config/initializers/filter_parameter_logging.rb +4 -0
  65. data/spec/dummy_app/config/initializers/inflections.rb +16 -0
  66. data/spec/dummy_app/config/initializers/mime_types.rb +5 -0
  67. data/spec/dummy_app/config/initializers/secret_token.rb +12 -0
  68. data/spec/dummy_app/config/initializers/session_store.rb +3 -0
  69. data/spec/dummy_app/config/initializers/wrap_parameters.rb +14 -0
  70. data/spec/dummy_app/config/locales/en.yml +23 -0
  71. data/spec/dummy_app/config/routes.rb +56 -0
  72. data/spec/dummy_app/db/seeds.rb +7 -0
  73. data/spec/dummy_app/lib/assets/.keep +0 -0
  74. data/spec/dummy_app/lib/fetch_facebook_info.rb +7 -0
  75. data/spec/dummy_app/lib/integracao_twitter.rb +5 -0
  76. data/spec/dummy_app/lib/tasks/.keep +0 -0
  77. data/spec/dummy_app/log/.keep +0 -0
  78. data/spec/dummy_app/public/404.html +58 -0
  79. data/spec/dummy_app/public/422.html +58 -0
  80. data/spec/dummy_app/public/500.html +57 -0
  81. data/spec/dummy_app/public/favicon.ico +0 -0
  82. data/spec/dummy_app/public/robots.txt +5 -0
  83. data/spec/dummy_app/test/controllers/.keep +0 -0
  84. data/spec/dummy_app/test/fixtures/.keep +0 -0
  85. data/spec/dummy_app/test/helpers/.keep +0 -0
  86. data/spec/dummy_app/test/integration/.keep +0 -0
  87. data/spec/dummy_app/test/mailers/.keep +0 -0
  88. data/spec/dummy_app/test/models/.keep +0 -0
  89. data/spec/dummy_app/test/test_helper.rb +15 -0
  90. data/spec/dummy_app/vendor/assets/javascripts/.keep +0 -0
  91. data/spec/dummy_app/vendor/assets/stylesheets/.keep +0 -0
  92. data/spec/fixtures/new_arch_definition.yml +32 -0
  93. data/spec/fixtures/ruby_example.rb +74 -0
  94. data/spec/ruby/parser_spec.rb +45 -0
  95. data/spec/ruby/var_propagation_spec.rb +22 -0
  96. data/spec/spec_helper.rb +1 -0
  97. metadata +295 -0
@@ -0,0 +1,218 @@
1
+ module Archruby
2
+ module Architecture
3
+
4
+ class ModuleDefinition
5
+
6
+ ALLOWED_CONSTRAINTS = ['required', 'allowed', 'forbidden']
7
+
8
+ attr_reader :name, :allowed_modules, :required_modules, :forbidden_modules,
9
+ :dependencies, :classes_and_dependencies
10
+
11
+ attr_accessor :classes
12
+
13
+ def initialize config_definition, base_directory
14
+ @config_definition = config_definition
15
+ @name = @config_definition.module_name
16
+ @allowed_modules = @config_definition.allowed_modules
17
+ @required_modules = @config_definition.required_modules
18
+ @forbidden_modules = @config_definition.forbidden_modules
19
+ @base_directory = base_directory
20
+ @files_and_contents = []
21
+ @classes = []
22
+ @dependencies = []
23
+ @classes_and_dependencies = []
24
+ extract_content_of_files
25
+ extract_dependencies
26
+ end
27
+
28
+ def extract_content_of_files file_extractor = Archruby::Architecture::FileContent
29
+ return if !@classes.empty?
30
+ file_extractor = file_extractor.new(@base_directory)
31
+ @config_definition.files.each do |file|
32
+ file_content = file_extractor.all_content_from_directory file
33
+ @files_and_contents << file_content
34
+ end
35
+ end
36
+
37
+ def extract_dependencies ruby_parser = Archruby::Ruby::Parser
38
+ return if !@classes.empty?
39
+ @files_and_contents.each do |file_and_content|
40
+ file_and_content.each do |file_name, content|
41
+ parser = ruby_parser.new content
42
+ @classes << parser.classes
43
+ @dependencies << parser.dependencies
44
+ @classes_and_dependencies << parser.classes_and_dependencies
45
+ end
46
+ end
47
+ @classes << @config_definition.gems
48
+ @classes.flatten!
49
+ @dependencies.flatten!
50
+ end
51
+
52
+ def is_mine? class_name
53
+ splited_class_name = class_name.split('::')
54
+ first_class_name = splited_class_name.first
55
+ is_mine = false
56
+ if first_class_name.empty?
57
+ #pocurando por um match exato de dependencia
58
+ first_name = splited_class_name[1]
59
+ splited_class_name.shift # retirando o elemento ''
60
+ class_name = splited_class_name.join("::")
61
+ @classes.each do |klass|
62
+ #TODO Arrumar isso com uma expressao regular
63
+ if klass.include?(class_name) && klass.size == class_name.size
64
+ is_mine = true
65
+ break
66
+ end
67
+ end
68
+ if !is_mine && !@config_definition.gems.empty?
69
+ @classes.each do |klass|
70
+ #TODO Arrumar isso com uma expressao regular
71
+ if klass.include?(first_name) && klass.size == first_name.size
72
+ is_mine = true
73
+ break
74
+ end
75
+ end
76
+ end
77
+ end
78
+ if !is_mine
79
+ # procurando por acesso a classe que possa ser desse modulo
80
+ class_name = splited_class_name.join("::")
81
+ included_separator = class_name.include?("::")
82
+ @classes.each do |klass|
83
+ #TODO Arrumar isso com uma expressao regular
84
+ if included_separator
85
+ if klass.include?(class_name)
86
+ is_mine = true
87
+ break
88
+ end
89
+ else
90
+ if klass.include?(class_name) && klass.size == class_name.size
91
+ is_mine = true
92
+ break
93
+ end
94
+ end
95
+ end
96
+ end
97
+ if !is_mine
98
+ # procurando por GEM
99
+ @classes.each do |klass|
100
+ #TODO Arrumar isso com uma expressao regular
101
+ if klass.include?(first_class_name) && klass.size == first_class_name.size
102
+ is_mine = true
103
+ break
104
+ end
105
+ end
106
+ end
107
+
108
+ return is_mine
109
+ end
110
+
111
+ def is_external?
112
+ !@config_definition.gems.empty?
113
+ end
114
+
115
+ def is_empty?
116
+ @classes.empty?
117
+ end
118
+
119
+ def verify_constraints architecture
120
+ required_breaks = verify_required architecture
121
+ forbidden_breaks = verify_forbidden architecture
122
+ allowed_breaks = verify_allowed architecture
123
+ all_constraints_breaks = [required_breaks, forbidden_breaks, allowed_breaks].flatten
124
+ all_constraints_breaks.delete(nil)
125
+ all_constraints_breaks
126
+ end
127
+
128
+ # Verifica todas as classes do modulo
129
+ # Cada uma deve, de alguma forma, depender dos modulos que estao listados como required
130
+ def verify_required architecture
131
+ return if @config_definition.required_modules.empty?
132
+ breaks = []
133
+ @classes_and_dependencies.each_with_index do |class_and_depencies, index|
134
+ if class_and_depencies.empty?
135
+ breaks << Archruby::Architecture::ConstraintBreak.new(
136
+ :type => 'absence',
137
+ :module_origin => self.name,
138
+ :module_target => @config_definition.required_modules.first,
139
+ :class_origin => @classes[index],
140
+ :msg => "not implement a required module"
141
+ )
142
+ next
143
+ end
144
+ class_and_depencies.each do |class_name, dependencies|
145
+ dependency_module_names = []
146
+ dependencies.each do |dependency|
147
+ module_name = architecture.module_name(dependency.class_name)
148
+ dependency_module_names << module_name
149
+ end
150
+ @config_definition.required_modules.each do |required_module|
151
+ if !dependency_module_names.include?(required_module)
152
+ breaks << Archruby::Architecture::ConstraintBreak.new(
153
+ :type => 'absence',
154
+ :module_origin => self.name,
155
+ :module_target => required_module,
156
+ :class_origin => class_name,
157
+ :msg => "not implement a required module"
158
+ )
159
+ end
160
+ end
161
+ end
162
+ end
163
+ breaks
164
+ end
165
+
166
+ def verify_forbidden architecture
167
+ return if @config_definition.forbidden_modules.empty?
168
+ breaks = []
169
+ @classes_and_dependencies.each do |class_and_depencies|
170
+ class_and_depencies.each do |class_name, dependencies|
171
+ dependencies.each do |dependency|
172
+ module_name = architecture.module_name(dependency.class_name)
173
+ next if architecture.is_ruby_internals? module_name
174
+ if @config_definition.forbidden_modules.include? module_name
175
+ next if /[A-Z]_+[A-Z]/.match(dependency.class_name)
176
+ breaks << Archruby::Architecture::ConstraintBreak.new(
177
+ :type => 'divergence',
178
+ :class_origin => class_name,
179
+ :line_origin => dependency.line_number,
180
+ :class_target => dependency.class_name,
181
+ :module_origin => self.name,
182
+ :module_target => module_name,
183
+ :msg => "accessing a module which is forbidden")
184
+ end
185
+ end
186
+ end
187
+ end
188
+ breaks
189
+ end
190
+
191
+ def verify_allowed architecture
192
+ return if @config_definition.allowed_modules.empty?
193
+ breaks = []
194
+ @classes_and_dependencies.each do |class_and_depencies|
195
+ class_and_depencies.each do |class_name, dependencies|
196
+ dependencies.each do |dependency|
197
+ module_name = architecture.module_name(dependency.class_name)
198
+ next if architecture.is_ruby_internals? module_name
199
+ if module_name != self.name && !@config_definition.allowed_modules.include?(module_name)
200
+ next if /[A-Z]_+[A-Z]/.match(dependency.class_name)
201
+ breaks << Archruby::Architecture::ConstraintBreak.new(
202
+ :type => 'divergence',
203
+ :class_origin => class_name,
204
+ :line_origin => dependency.line_number,
205
+ :class_target => dependency.class_name,
206
+ :module_origin => self.name,
207
+ :module_target => module_name,
208
+ :msg => "module #{self.name} is not allowed to depend on module #{module_name}")
209
+ end
210
+ end
211
+ end
212
+ end
213
+ breaks
214
+ end
215
+
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,58 @@
1
+ require 'yaml'
2
+
3
+ module Archruby
4
+ module Architecture
5
+
6
+ class Parser
7
+ attr_reader :modules
8
+
9
+ def initialize config_file_path, base_path
10
+ @config_file = config_file_path
11
+ @base_path = base_path
12
+ @modules = []
13
+ parse
14
+ ruby_std_lib_module
15
+ ruby_core_module
16
+ unknow_module
17
+ end
18
+
19
+ def parse
20
+ parsed_yaml = yaml_parser.load_file @config_file
21
+ parsed_yaml.each do |module_name, definitions|
22
+ begin
23
+ config_definition = Archruby::Architecture::ConfigDefinition.new module_name, definitions
24
+ module_definition = Archruby::Architecture::ModuleDefinition.new config_definition, @base_path
25
+ rescue ArchChecker::MultipleConstraints => e
26
+ STDOUT.puts "The config file is not right: #{e.msg} | err_code: #{e.status_code} | module_definition: #{module_name}"
27
+ exit(e.status_code)
28
+ end
29
+ @modules << module_definition
30
+ end
31
+ end
32
+
33
+ def yaml_parser
34
+ YAML
35
+ end
36
+
37
+ def ruby_std_lib_module
38
+ config_definition_std_lib = Archruby::Architecture::ConfigDefinition.new Archruby::Ruby::STD_LIB_NAME, {"gems"=>"", "files"=>"", "allowed"=>"", "forbidden" => ""}
39
+ module_definiton_std_lib = Archruby::Architecture::ModuleDefinition.new(config_definition_std_lib, @base_path)
40
+ module_definiton_std_lib.classes = Archruby::Ruby::STD_LIBRARY_CLASSES
41
+ @modules << module_definiton_std_lib
42
+ end
43
+
44
+ def ruby_core_module
45
+ config_definition_core = Archruby::Architecture::ConfigDefinition.new Archruby::Ruby::CORE_LIB_NAME, {"gems"=>"", "files"=>"", "allowed"=>"", "forbidden" => ""}
46
+ module_definiton_core = Archruby::Architecture::ModuleDefinition.new(config_definition_core, @base_path)
47
+ module_definiton_core.classes = Archruby::Ruby::CORE_LIBRARY_CLASSES
48
+ @modules << module_definiton_core
49
+ end
50
+
51
+ def unknow_module
52
+ config_definition_unknow = Archruby::Architecture::ConfigDefinition.new "unknown", {"gems"=>"unknown", "files"=>"", "allowed"=>"", "forbidden" => ""}
53
+ @modules << Archruby::Architecture::ModuleDefinition.new(config_definition_unknow, @base_path)
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,132 @@
1
+ require 'graphviz'
2
+
3
+ module Archruby
4
+ module Presenters
5
+
6
+ class Graph
7
+
8
+ def render architecture
9
+ modules = architecture.modules
10
+
11
+ g = GraphViz.new(:G)
12
+
13
+ g.edge[:color] = "black"
14
+ g.edge[:style] = "filled"
15
+ g.edge[:label] = ""
16
+
17
+ internal = g.subgraph("internal", "label" => "Internal", "color" => "black")
18
+
19
+ external = g.subgraph("external", "label" => "External", "color" => "black", "rank" => "same")
20
+
21
+ nodes = {}
22
+
23
+ internal_nodes = []
24
+ external_nodes = []
25
+ modules.each do |module_definiton|
26
+ next if module_definiton.name == Archruby::Ruby::STD_LIB_NAME || module_definiton.name == Archruby::Ruby::CORE_LIB_NAME
27
+ if module_definiton.is_external?
28
+ nodes[module_definiton.name] = external.add_nodes(module_definiton.name, "shape" => "parallelogram", "color" => "gray60", "style" => "filled")
29
+ external_nodes << nodes[module_definiton.name]
30
+ else
31
+ if module_definiton.is_empty?
32
+ nodes[module_definiton.name] = internal.add_nodes("#{module_definiton.name}\n [empty]", "color" => "gray74", "shape" => "rectangle")
33
+ else
34
+ nodes[module_definiton.name] = internal.add_nodes(module_definiton.name, "color" => "gray92", "style" => "filled", "shape" => "rectangle")
35
+ end
36
+ internal_nodes << nodes[module_definiton.name]
37
+ end
38
+ end
39
+
40
+ edges = {}
41
+ edges_objs = []
42
+
43
+ modules.each do |module_definiton|
44
+ module_name = module_definiton.name
45
+ node_origin = nodes[module_name]
46
+ edges[module_name] ||= {}
47
+ edges[module_name][:edges] ||= []
48
+
49
+ module_definiton.dependencies.each do |class_name|
50
+ module_dest = architecture.module_name class_name
51
+ next if module_dest == Archruby::Ruby::STD_LIB_NAME || module_dest == Archruby::Ruby::CORE_LIB_NAME
52
+ how_many_access = architecture.how_many_access_to module_name, module_dest
53
+ if !edges[module_name][:edges].include?(module_dest) && module_dest != module_name
54
+ edges[module_name][:edges] << module_dest
55
+ node_dest = nodes[module_dest]
56
+ edges_objs << internal.add_edges(node_origin, node_dest, :headlabel => how_many_access, :minlen => 2)
57
+ end
58
+ end
59
+ end
60
+
61
+ constraints_breaks = architecture.constraints_breaks
62
+ constraints_breaks.each_with_index do |constraint_break, index|
63
+ module_origin = constraint_break.module_origin
64
+ module_target = constraint_break.module_target
65
+ contraint_type = constraint_break.type
66
+ node_origin = nodes[module_origin]
67
+ node_dest = nodes[module_target]
68
+ node_found = false
69
+ edges_objs.each do |edge|
70
+ if edge.node_one == module_origin && edge.node_two == module_target
71
+ if contraint_type == Archruby::Architecture::ConstraintBreak::ABSENSE
72
+ edge.set do |e|
73
+ e.headlabel = "X (##{architecture.how_many_break(module_origin, module_target, Archruby::Architecture::ConstraintBreak::ABSENSE)})"
74
+ e.color = "red"
75
+ e.style = "bold"
76
+ e.minlen = 2
77
+ end
78
+ else
79
+ edge.set do |e|
80
+ e.headlabel = "! (##{architecture.how_many_break(module_origin, module_target, Archruby::Architecture::ConstraintBreak::DIVERGENCE)})"
81
+ e.color = "orange"
82
+ e.style = "bold"
83
+ e.minlen = 2
84
+ end
85
+ end
86
+ node_found = true
87
+ break
88
+ end
89
+ end
90
+
91
+ if !node_found
92
+ if contraint_type == Archruby::Architecture::ConstraintBreak::ABSENSE
93
+ break_count = architecture.how_many_break(module_origin, module_target, Archruby::Architecture::ConstraintBreak::ABSENSE)
94
+ edges_objs << g.add_edges(node_origin, node_dest, :color => 'red', :headlabel => "X (##{break_count})", 'style' => 'bold', :minlen => 2)
95
+ else
96
+ break_count = architecture.how_many_break(module_origin, module_target, Archruby::Architecture::ConstraintBreak::DIVERGENCE)
97
+ edges_objs << g.add_edges(node_origin, node_dest, :color => 'red', :headlabel => "! (##{break_count})", 'style' => 'bold', :minlen => 2)
98
+ end
99
+ end
100
+ end
101
+
102
+ modules.each do |module_definiton|
103
+ next if module_definiton.name == Archruby::Ruby::STD_LIB_NAME || module_definiton.name == Archruby::Ruby::CORE_LIB_NAME
104
+ module_origin = module_definiton.is_empty? ? "#{module_definiton.name}\n [empty]" : module_definiton.name
105
+ node_origin = nodes[module_origin]
106
+ #puts module_definiton.name.inspect
107
+ #puts module_definiton.classes.inspect
108
+ #puts module_definiton.dependencies.inspect
109
+ #puts module_definiton.classes_and_dependencies.inspect
110
+ #puts
111
+ module_definiton.allowed_modules.each do |allowed_module_name|
112
+ module_target = allowed_module_name
113
+ node_dest = nodes[allowed_module_name]
114
+ edge_found = false
115
+ edges_objs.each do |edge|
116
+ if edge.node_one == module_origin && edge.node_two == module_target
117
+ edge_found = true
118
+ break
119
+ end
120
+ end
121
+ if !edge_found
122
+ internal.add_edges(node_origin, node_dest, :color => 'gray74', :label => "[none]", :minlen => 2)
123
+ end
124
+ end
125
+ end
126
+
127
+ g.output( :png => "architecture.png" )
128
+ end
129
+
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,14 @@
1
+ module ArchChecker
2
+ module Presenters
3
+ class Text
4
+ def render constraint_breaks
5
+ file = File.new('violations.txt', 'w')
6
+ constraint_breaks.each do |constraint_break|
7
+ constraint_break.each do |violations|
8
+ file.puts "#{violations.keys.first} #{violations[violations.keys.first]}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ require 'yaml'
2
+
3
+ module Archruby
4
+ module Presenters
5
+
6
+ class Yaml
7
+ def render architecture
8
+ constraints_breaks = architecture.constraints_breaks
9
+ file = File.new('constraints_breaks.yml', 'w')
10
+ contraints = []
11
+ constraints_breaks.each do |constraint_break|
12
+ constraint = {}
13
+ constraint[constraint_break.type] = {}
14
+ constraint[constraint_break.type]['class_origin'] = constraint_break.class_origin
15
+ constraint[constraint_break.type]['line_origin'] = constraint_break.line_origin
16
+ constraint[constraint_break.type]['class_target'] = constraint_break.class_target
17
+ constraint[constraint_break.type]['module_origin'] = constraint_break.module_origin
18
+ constraint[constraint_break.type]['module_target'] = constraint_break.module_target
19
+ constraint[constraint_break.type]['message'] = constraint_break.msg
20
+ contraints << constraint
21
+ end
22
+ file.write contraints.to_yaml
23
+ file.close
24
+ end
25
+ end
26
+
27
+ end
28
+ end