hyperlayer 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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +38 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/manifest.js +7 -0
  6. data/app/assets/stylesheets/application.bootstrap.scss +2 -0
  7. data/app/assets/stylesheets/hyperlayer/app.css +47 -0
  8. data/app/assets/stylesheets/hyperlayer/application.css +18774 -0
  9. data/app/assets/stylesheets/hyperlayer/prism.css +3 -0
  10. data/app/controllers/hyperlayer/application_controller.rb +4 -0
  11. data/app/controllers/hyperlayer/events_controller.rb +95 -0
  12. data/app/controllers/hyperlayer/example_groups_controller.rb +9 -0
  13. data/app/controllers/hyperlayer/paths_controller.rb +18 -0
  14. data/app/controllers/hyperlayer/runs_controller.rb +7 -0
  15. data/app/controllers/hyperlayer/specs_controller.rb +8 -0
  16. data/app/javascript/controllers/application.js +9 -0
  17. data/app/javascript/controllers/index.js +11 -0
  18. data/app/javascript/hyperlayer/application.js +7 -0
  19. data/app/javascript/hyperlayer/prism.js +8 -0
  20. data/app/models/application_record.rb +3 -0
  21. data/app/models/hyperlayer/event.rb +28 -0
  22. data/app/models/hyperlayer/example_group.rb +6 -0
  23. data/app/models/hyperlayer/path.rb +35 -0
  24. data/app/models/hyperlayer/run.rb +5 -0
  25. data/app/models/hyperlayer/spec.rb +8 -0
  26. data/app/services/build_file_overlay.rb +26 -0
  27. data/app/services/callable.rb +129 -0
  28. data/app/services/file_builder.rb +260 -0
  29. data/app/services/find_method_from_event.rb +65 -0
  30. data/app/services/find_method_in_path.rb +98 -0
  31. data/app/services/generate_buffer.rb +116 -0
  32. data/app/services/import_events.rb +56 -0
  33. data/app/services/split_methods.rb +50 -0
  34. data/app/services/test_one.rb +34 -0
  35. data/app/services/update_code.rb +111 -0
  36. data/app/views/hyperlayer/events/_code.html.erb +36 -0
  37. data/app/views/hyperlayer/events/_event.html.erb +15 -0
  38. data/app/views/hyperlayer/events/_tree-text.html.erb +22 -0
  39. data/app/views/hyperlayer/events/_tree.html.erb +15 -0
  40. data/app/views/hyperlayer/events/index.html.erb +41 -0
  41. data/app/views/hyperlayer/example_groups/index.html.erb +25 -0
  42. data/app/views/hyperlayer/paths/_path.html.erb +52 -0
  43. data/app/views/hyperlayer/paths/index.html.erb +5 -0
  44. data/app/views/hyperlayer/paths/show.html.erb +3 -0
  45. data/app/views/hyperlayer/runs/index.html.erb +23 -0
  46. data/app/views/hyperlayer/specs/index.html.erb +24 -0
  47. data/app/views/layouts/_nav.html.erb +8 -0
  48. data/app/views/layouts/hyperlayer/application.html.erb +30 -0
  49. data/app/views/layouts/hyperlayer/mailer.html.erb +13 -0
  50. data/app/views/layouts/hyperlayer/mailer.text.erb +1 -0
  51. data/config/initializers/engine_migrations.rb +3 -0
  52. data/config/routes.rb +11 -0
  53. data/db/migrate/20231009165755_create_hyperlayer_events.rb +19 -0
  54. data/db/migrate/20231009165919_create_hyperlayer_example_groups.rb +12 -0
  55. data/db/migrate/20231009165952_create_hyperlayer_runs.rb +9 -0
  56. data/db/migrate/20231009170101_create_hyperlayer_paths.rb +10 -0
  57. data/db/migrate/20231009170121_create_hyperlayer_specs.rb +12 -0
  58. data/lib/hyperlayer/engine.rb +22 -0
  59. data/lib/hyperlayer/method_tracer.rb +12 -0
  60. data/lib/hyperlayer/tracer.rb +78 -0
  61. data/lib/hyperlayer/version.rb +3 -0
  62. data/lib/hyperlayer.rb +15 -0
  63. data/lib/tasks/listen.rake +8 -0
  64. metadata +207 -0
@@ -0,0 +1,260 @@
1
+ class FileBuilder
2
+ def initialize(options = {})
3
+ @options = options
4
+
5
+ if options[:url]
6
+ parts = options[:url].split('/')
7
+
8
+ if parts.count > 1
9
+ options[:object] = parts.last.singularize
10
+ options[:class] = parts.last.singularize.capitalize
11
+ end
12
+ end
13
+ end
14
+
15
+ attr_reader :options
16
+
17
+ OPTION_KLASS_MAP = {
18
+ 'authorisation' => 'Authorisation',
19
+ 'url' => 'ClassDefinition',
20
+ 'request' => 'BuildStrongParams',
21
+ 'controller_action' => 'ControllerAction',
22
+ 'skip-csrf' => 'SkipCsrf'
23
+ }
24
+
25
+ def call
26
+ options.to_h.each_with_object(parts) do |(option, _value), output|
27
+ klass = OPTION_KLASS_MAP[option]
28
+
29
+ next unless klass
30
+
31
+ fragments = "FileBuilder::#{klass}".constantize.call(options: options)
32
+
33
+ fragments.each do |part, code|
34
+ output[part] ||= []
35
+
36
+ output[part] << code
37
+ end
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def parts
44
+ {
45
+ definition_start: [],
46
+ before_actions: [],
47
+ public_methods: [],
48
+ private_methods: [],
49
+ definition_finish: []
50
+ }
51
+ end
52
+
53
+ class Authorisation
54
+ include Callable
55
+ expects :options
56
+
57
+ def call
58
+ if options['authorisation'] == '1'
59
+ {
60
+ before_actions: authorisation
61
+ }
62
+ else
63
+ {}
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def authorisation
70
+ template.tap do |temp|
71
+ temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value|
72
+ temp.gsub!("%#{value}%", options[value])
73
+ end
74
+ end
75
+ end
76
+
77
+ def template
78
+ File.read('app/views/code/authorisation.erb')[0..-2]
79
+ end
80
+ end
81
+
82
+ class ControllerAction
83
+ include Callable
84
+ expects :options
85
+
86
+ def call
87
+ if options['controller_action'].present?
88
+ {
89
+ public_methods: code
90
+ }
91
+ else
92
+ {}
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def code
99
+ template.tap do |temp|
100
+ temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value|
101
+ temp.gsub!("%#{value}%", options[value])
102
+ end
103
+ end
104
+ end
105
+
106
+ def template
107
+ File.read("app/views/code/controller_actions/#{controller_action}.erb")[0..-2]
108
+ end
109
+
110
+ def controller_action
111
+ options['controller_action']
112
+ end
113
+ end
114
+
115
+ class TrackEvent
116
+ include Callable
117
+ expects :options
118
+
119
+ def call
120
+ if options['track_event'].present?
121
+ {
122
+ before_action: code,
123
+ private_methods: code
124
+ }
125
+ else
126
+ {}
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ def code
133
+ template.tap do |temp|
134
+ temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value|
135
+ temp.gsub!("%#{value}%", options[value])
136
+ end
137
+ end
138
+ end
139
+
140
+ def template
141
+ File.read("app/views/code/controller_actions/#{controller_action}.erb")[0..-2]
142
+ end
143
+
144
+ def controller_action
145
+ options['controller_action']
146
+ end
147
+ end
148
+
149
+ class ClassDefinition
150
+ include Callable
151
+ expects :options
152
+
153
+ def call
154
+ return {} unless options[:url].present?
155
+
156
+ {
157
+ definition_start: start,
158
+ definition_finish: finish
159
+ }
160
+ end
161
+
162
+ private
163
+
164
+ def start
165
+ levels.map.with_index(1) do |level, index|
166
+ if index == count
167
+ "class #{level.capitalize}Controller < ApplicationController"
168
+ else
169
+ "module #{level.capitalize}"
170
+ end
171
+ end
172
+ end
173
+
174
+ def levels
175
+ options[:url].split('/').select(&:present?)
176
+ end
177
+
178
+ def count
179
+ levels.count
180
+ end
181
+
182
+ def finish
183
+ levels.map { 'end' }
184
+ end
185
+ end
186
+
187
+ class SkipCsrf
188
+ include Callable
189
+ expects :options
190
+
191
+ def call
192
+ return {} unless options['skip-csrf'] == '1'
193
+
194
+ { private_methods: code }
195
+ end
196
+
197
+ private
198
+
199
+ def code
200
+ template.tap do |temp|
201
+ temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value|
202
+ temp.gsub!("%#{value}%", options[value].to_s)
203
+ end
204
+ end
205
+ end
206
+
207
+ def template
208
+ File.read('app/views/code/strong_params.erb')
209
+ end
210
+ end
211
+
212
+ class BuildStrongParams
213
+ include Callable
214
+ expects :options
215
+
216
+ def call
217
+ return {} unless options[:request].present?
218
+
219
+ options[:keys] = keys
220
+
221
+ { private_methods: code }
222
+ end
223
+
224
+ private
225
+
226
+ def code
227
+ template.tap do |temp|
228
+ temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value|
229
+ temp.gsub!("%#{value}%", options[value].to_s)
230
+ end
231
+ end
232
+ end
233
+
234
+ def template
235
+ File.read('app/views/code/strong_params.erb')
236
+ end
237
+
238
+ def keys
239
+ JSON.parse(options[:request]).keys
240
+ end
241
+ end
242
+
243
+ ############################################
244
+ # def parts
245
+ # {
246
+ # definition_start: {
247
+ # def_strong_params: {
248
+ # open: [],
249
+ # body: [],
250
+ # end: []
251
+ # }
252
+ # },
253
+ # before_actions: {},
254
+ # public_methods: {},
255
+ # private_methods: {},
256
+ # definition_finish: {}
257
+ # }
258
+ # end
259
+ ############################################
260
+ end
@@ -0,0 +1,65 @@
1
+ # require 'fast'
2
+
3
+ class FindMethodFromEvent < Parser::AST::Processor
4
+ include RuboCop::AST::Traversal
5
+
6
+ def self.call(event)
7
+ new.call(event)
8
+ end
9
+
10
+ # Have a "Exists?" method that checks to see if something exists
11
+ # If it doesn't use an Insert class and define which section it should go into
12
+ # If it does, work out the differences(?) and update them
13
+
14
+ def call(event)
15
+ code = File.readlines(event.path.path).join
16
+ source = RuboCop::ProcessedSource.new(code, 2.7)
17
+
18
+ return unless source.present?
19
+
20
+ node = FindMethod.call(
21
+ type: :def,
22
+ name: event.method.to_sym,
23
+ source: source
24
+ )
25
+
26
+ return unless node.present?
27
+
28
+ DisplayMethod
29
+ .call(node: node, code: code)
30
+ .split("\n")
31
+ end
32
+
33
+ private
34
+
35
+ class FindMethod
36
+ include Callable
37
+ expects :type, :name, :source
38
+
39
+ def call
40
+ source.ast
41
+ .each_node
42
+ .map { |n| n if n.type == type && n.method_name == name }
43
+ .compact
44
+ end
45
+ end
46
+
47
+ class DisplayMethod
48
+ include Callable
49
+ expects :code, :node
50
+
51
+ def call
52
+ code[begin_pos...end_pos]
53
+ end
54
+
55
+ private
56
+
57
+ delegate :begin_pos, :end_pos, to: :expression
58
+
59
+ def expression
60
+ node.first
61
+ .loc
62
+ .expression
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,98 @@
1
+ # require 'fast'
2
+
3
+ class FindMethodInPath < Parser::AST::Processor
4
+ include RuboCop::AST::Traversal
5
+
6
+ def self.call(path_id)
7
+ new.call(path_id)
8
+ end
9
+
10
+ # Have a "Exists?" method that checks to see if something exists
11
+ # If it doesn't use an Insert class and define which section it should go into
12
+ # If it does, work out the differences(?) and update them
13
+
14
+ def call(path_id)
15
+ path = Path.find(path_id)
16
+ code = File.readlines(path.path).join
17
+ source = RuboCop::ProcessedSource.new(code, 2.7)
18
+
19
+ path.events.call.each_with_object({}) do |event, methods|
20
+ methods[event.method.to_sym] = ''
21
+
22
+ node = FindMethod.call(
23
+ type: :def,
24
+ name: event.method.to_sym,
25
+ source: source
26
+ )
27
+
28
+ next unless node.present?
29
+
30
+ methods[event.method.to_sym] = DisplayMethod.call(node: node, code: code)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ class FindMethod
37
+ include Callable
38
+ expects :type, :name, :source
39
+
40
+ def call
41
+ binding.pry
42
+
43
+ source.ast
44
+ .each_node
45
+ .map { |n| n if n.type == type && n.method_name == name }
46
+ .compact
47
+ end
48
+ end
49
+
50
+ class AddMethod
51
+ include Callable
52
+ expects :code, :pos
53
+
54
+ def call
55
+ code.insert(pos, "def code \n inserted.here! \n end")
56
+ end
57
+ end
58
+
59
+ class Replace
60
+ include Callable
61
+ expects :code, :node, :with
62
+
63
+ def call
64
+ code[begin_pos...end_pos] = with
65
+ end
66
+
67
+ private
68
+
69
+ delegate :begin_pos, :end_pos, to: :expression
70
+
71
+ def expression
72
+ node.first
73
+ .arguments
74
+ .first
75
+ .loc
76
+ .expression
77
+ end
78
+ end
79
+
80
+ class DisplayMethod
81
+ include Callable
82
+ expects :code, :node
83
+
84
+ def call
85
+ code[begin_pos...end_pos]
86
+ end
87
+
88
+ private
89
+
90
+ delegate :begin_pos, :end_pos, to: :expression
91
+
92
+ def expression
93
+ node.first
94
+ .loc
95
+ .expression
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,116 @@
1
+ class GenerateBuffer < Parser::AST::Processor
2
+ include RuboCop::AST::Traversal
3
+ include Callable
4
+
5
+ expects :params
6
+
7
+ DEFAULT_BUFFER = <<~NODE
8
+ module Fresco
9
+ class Dev
10
+ end
11
+ end
12
+ NODE
13
+
14
+ def call
15
+ @code = DEFAULT_BUFFER
16
+ @buffer = RuboCop::ProcessedSource.new(@code, 2.7)
17
+
18
+ fields.each do |field|
19
+ @buffer = "#{self.class}::#{field.classify}"
20
+ .constantize
21
+ .call(buffer: @buffer, params: params)
22
+ end
23
+
24
+ @buffer
25
+ end
26
+
27
+ private
28
+
29
+ def fields
30
+ params.slice(:url).keys
31
+ end
32
+
33
+ class Url
34
+ include Callable
35
+ expects :buffer, :params
36
+
37
+ def call
38
+ node = FindMethod.call(
39
+ meth_name: :module,
40
+ source: buffer
41
+ )
42
+
43
+ Replace.call(
44
+ buffer: code,
45
+ node: node,
46
+ with: InitializeModuleClass.call(url: params[:url])
47
+ )
48
+ end
49
+ end
50
+
51
+ class InitializeModuleClass
52
+ include Callable
53
+ expects :url
54
+
55
+ def call
56
+ return {} unless url.present?
57
+
58
+ [start, finish].flatten.join("\n")
59
+ end
60
+
61
+ private
62
+
63
+ def start
64
+ levels.map.with_index(1) do |level, index|
65
+ if index == count
66
+ "class #{level.capitalize}Controller < ApplicationController"
67
+ else
68
+ "module #{level.capitalize}"
69
+ end
70
+ end
71
+ end
72
+
73
+ def levels
74
+ url.split('/').select(&:present?)
75
+ end
76
+
77
+ def count
78
+ levels.count
79
+ end
80
+
81
+ def finish
82
+ levels.map { 'end' }
83
+ end
84
+ end
85
+
86
+ class FindMethod
87
+ include Callable
88
+ expects :meth_name, :source
89
+
90
+ def call
91
+ source.ast
92
+ .each_node
93
+ .map { |n| n if n.type == :send && n.method_name == meth_name }
94
+ .compact
95
+ end
96
+ end
97
+
98
+ class Replace
99
+ include Callable
100
+ expects :code, :node, :with
101
+
102
+ def call
103
+ code[start...finish] = with
104
+ end
105
+
106
+ private
107
+
108
+ def start
109
+ node.first.arguments.first.loc.expression.begin_pos
110
+ end
111
+
112
+ def finish
113
+ node.first.arguments.first.loc.expression.end_pos
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,56 @@
1
+ # ["#{tp.path}:#{tp.lineno}", tp.defined_class, tp.event, tp.method_id, rv]
2
+
3
+ require 'csv'
4
+
5
+ class ImportEvents
6
+ def self.call
7
+ # Connect to a Redis server
8
+ redis = Redis.new(url: 'redis://localhost:6379')
9
+
10
+ begin
11
+ # Subscribe to a Redis channel
12
+ redis.subscribe('events') do |on|
13
+ on.message do |channel, message|
14
+ puts "Received message on channel #{channel}: #{message}"
15
+
16
+ event = Hashie::Mash.new(JSON.parse(message))
17
+
18
+ run = Hyperlayer::Run.where(process: event.spec.process).first_or_create
19
+ path = Hyperlayer::Path.where(path: event.path).first_or_create
20
+
21
+ spec_file = event.spec.tree.reverse.first
22
+ spec = run.specs.where(
23
+ location: spec_file.file_path,
24
+ description: spec_file.description, # sometimes a class, could also be a string for a feature spec,
25
+ data: spec_file.except(:file_path, :description)
26
+ ).first_or_create
27
+
28
+ example_groups = event.spec.tree.reverse[1..]
29
+ groups = example_groups.map do |example_group|
30
+ spec.example_groups.where(
31
+ location: example_group.location,
32
+ description: example_group.description,
33
+ data: example_group.except(:location, :description)
34
+ ).first_or_create
35
+ end
36
+
37
+ example_group = groups.last
38
+ example_group.events.create(
39
+ path: path,
40
+ line_number: event.line_number,
41
+ defined_class: event.defined_class,
42
+ event_type: event.event,
43
+ method: event.method_id,
44
+ return_value: event.return_value,
45
+ arguments: event.arguments,
46
+ variables: event.variables
47
+ )
48
+ end
49
+ end
50
+ rescue Redis::BaseConnectionError => error
51
+ puts "#{error}, retrying in 1s"
52
+ sleep 1
53
+ retry
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,50 @@
1
+ # require 'fast'
2
+
3
+ class SplitMethods < Parser::AST::Processor
4
+ include RuboCop::AST::Traversal
5
+
6
+ def self.call(event)
7
+ new.call(event)
8
+ end
9
+
10
+ def call(event)
11
+ raw_code = File.readlines(event.path.path)
12
+ code = raw_code.join
13
+ source = RuboCop::ProcessedSource.new(code, 3.2)
14
+
15
+ method_name = event.method.to_sym
16
+
17
+ method = source.ast
18
+ .each_node
19
+ .select do |n|
20
+ # Will need to add method name here, and the delegate param
21
+ #
22
+ n.type == :def && method_name == n.method_name # || n.type == :send && n.method_name == :delegate
23
+ end
24
+
25
+ method = method.first
26
+
27
+ DisplayMethod.call(code: code, node: method)
28
+ rescue => e
29
+ Rails.logger.info("Missing method: #{method_name}")
30
+ end
31
+
32
+ private
33
+
34
+ class DisplayMethod
35
+ include Callable
36
+ expects :code, :node
37
+
38
+ def call
39
+ code[begin_pos...end_pos]
40
+ end
41
+
42
+ private
43
+
44
+ delegate :begin_pos, :end_pos, to: :expression
45
+
46
+ def expression
47
+ node.loc.expression
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,34 @@
1
+ class TestOne
2
+ include Callable
3
+ expects :path
4
+
5
+ def call
6
+ @file = []
7
+ @raw_file = File.readlines(path.path).join
8
+
9
+ return @raw_file
10
+
11
+ path.events.where(event_type: 'return').each do |event|
12
+ href = "<a href='#'>#{event.method}</a>"
13
+
14
+ matches = @raw_file.gsub!(/#{event.method}/, href)
15
+ end
16
+
17
+ return @raw_file
18
+
19
+
20
+
21
+ @display_file = @raw_file.map.with_index(1) do |line, index|
22
+ @file << line
23
+
24
+ events = path.events.where(event_type: 'return', line_number: index)
25
+
26
+ events.map do |event|
27
+ @file << ">>> #{event.return_value.inspect} (#{event.method})"
28
+ @file << "\n"
29
+ end
30
+ end
31
+
32
+ @file
33
+ end
34
+ end