archruby 0.1.0

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