diver_down 0.0.1.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +132 -0
  5. data/exe/diver_down_web +55 -0
  6. data/lib/diver_down/definition/dependency.rb +107 -0
  7. data/lib/diver_down/definition/method_id.rb +83 -0
  8. data/lib/diver_down/definition/source.rb +90 -0
  9. data/lib/diver_down/definition.rb +112 -0
  10. data/lib/diver_down/helper.rb +81 -0
  11. data/lib/diver_down/trace/call_stack.rb +45 -0
  12. data/lib/diver_down/trace/ignored_method_ids.rb +136 -0
  13. data/lib/diver_down/trace/module_set/array_module_set.rb +31 -0
  14. data/lib/diver_down/trace/module_set/const_source_location_module_set.rb +28 -0
  15. data/lib/diver_down/trace/module_set.rb +78 -0
  16. data/lib/diver_down/trace/redefine_ruby_methods.rb +64 -0
  17. data/lib/diver_down/trace/tracer/session.rb +121 -0
  18. data/lib/diver_down/trace/tracer.rb +96 -0
  19. data/lib/diver_down/trace.rb +27 -0
  20. data/lib/diver_down/version.rb +5 -0
  21. data/lib/diver_down/web/action.rb +344 -0
  22. data/lib/diver_down/web/bit_id.rb +41 -0
  23. data/lib/diver_down/web/definition_enumerator.rb +54 -0
  24. data/lib/diver_down/web/definition_loader.rb +37 -0
  25. data/lib/diver_down/web/definition_store.rb +89 -0
  26. data/lib/diver_down/web/definition_to_dot.rb +399 -0
  27. data/lib/diver_down/web/dev_server_middleware.rb +72 -0
  28. data/lib/diver_down/web/indented_string_io.rb +59 -0
  29. data/lib/diver_down/web/module_store.rb +59 -0
  30. data/lib/diver_down/web.rb +101 -0
  31. data/lib/diver_down-trace.rb +4 -0
  32. data/lib/diver_down-web.rb +4 -0
  33. data/lib/diver_down.rb +14 -0
  34. data/web/assets/CjLq7LhZ.css +1 -0
  35. data/web/assets/bundle.js +978 -0
  36. data/web/index.html +13 -0
  37. metadata +122 -0
@@ -0,0 +1,399 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'cgi'
5
+
6
+ module DiverDown
7
+ class Web
8
+ class DefinitionToDot
9
+ ATTRIBUTE_DELIMITER = ' '
10
+ MODULE_DELIMITER = '::'
11
+
12
+ # Between modules is prominently distanced
13
+ MODULE_MINLEN = 3
14
+
15
+ class MetadataStore
16
+ Metadata = Data.define(:id, :type, :data, :module_store) do
17
+ # @return [Hash]
18
+ def to_h
19
+ case type
20
+ when :source
21
+ source_to_h
22
+ when :dependency
23
+ dependency_to_h
24
+ when :module
25
+ module_to_h
26
+ else
27
+ raise NotImplementedError, "not implemented yet #{type}"
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def source_to_h
34
+ modules = module_store.get(data.source_name).map do
35
+ {
36
+ module_name: _1,
37
+ }
38
+ end
39
+
40
+ {
41
+ id:,
42
+ type: 'source',
43
+ source_name: data.source_name,
44
+ modules:,
45
+ }
46
+ end
47
+
48
+ def dependency_to_h
49
+ {
50
+ id:,
51
+ type: 'dependency',
52
+ dependencies: data.map do |dependency|
53
+ {
54
+ source_name: dependency.source_name,
55
+ method_ids: dependency.method_ids.sort.map do
56
+ {
57
+ name: _1.name,
58
+ context: _1.context,
59
+ }
60
+ end,
61
+ }
62
+ end,
63
+ }
64
+ end
65
+
66
+ def module_to_h
67
+ {
68
+ id:,
69
+ type: 'module',
70
+ modules: data.map do
71
+ {
72
+ module_name: _1,
73
+ }
74
+ end,
75
+ }
76
+ end
77
+ end
78
+
79
+ def initialize(module_store)
80
+ @prefix = 'graph_'
81
+ @module_store = module_store
82
+
83
+ # Hash{ id => Metadata }
84
+ @to_h = {}
85
+ end
86
+
87
+ # @param type [Symbol]
88
+ # @param record [DiverDown::Definition::Source]
89
+ # @return [String]
90
+ def issue_source_id(source)
91
+ build_metadata_and_return_id(:source, source)
92
+ end
93
+
94
+ # @param dependency [DiverDown::Definition::Dependency]
95
+ # @return [String]
96
+ def issue_dependency_id(dependency)
97
+ build_metadata_and_return_id(:dependency, [dependency])
98
+ end
99
+
100
+ # @param module_names [Array<String>]
101
+ # @return [String]
102
+ def issue_modules_id(module_names)
103
+ issued_metadata = @to_h.values.find { _1.type == :module && _1.data == module_names }
104
+
105
+ if issued_metadata
106
+ issued_metadata.id
107
+ else
108
+ build_metadata_and_return_id(:module, module_names)
109
+ end
110
+ end
111
+
112
+ # @param id [String]
113
+ # @param dependency [DiverDown::Definition::Dependency]
114
+ def append_dependency(id, dependency)
115
+ metadata = @to_h.fetch(id)
116
+ dependencies = metadata.data
117
+ combined_dependencies = DiverDown::Definition::Dependency.combine(*dependencies, dependency)
118
+ metadata.data.replace(combined_dependencies)
119
+ end
120
+
121
+ # @return [Array<Hash>]
122
+ def to_a
123
+ @to_h.values.map(&:to_h)
124
+ end
125
+
126
+ private
127
+
128
+ def build_metadata_and_return_id(type, data)
129
+ id = "#{@prefix}#{length + 1}"
130
+ metadata = Metadata.new(id:, type:, data:, module_store: @module_store)
131
+ @to_h[id] = metadata
132
+
133
+ id
134
+ end
135
+
136
+ def length
137
+ @to_h.length
138
+ end
139
+ end
140
+
141
+ # @param definition [DiverDown::Definition]
142
+ # @param module_store [DiverDown::ModuleStore]
143
+ # @param compound [Boolean]
144
+ # @param concentrate [Boolean] https://graphviz.org/docs/attrs/concentrate/
145
+ def initialize(definition, module_store, compound: false, concentrate: false, only_module: false)
146
+ @definition = definition
147
+ @module_store = module_store
148
+ @io = DiverDown::Web::IndentedStringIo.new
149
+ @indent = 0
150
+ @compound = compound || only_module # When only-module is enabled, dependencies between modules are displayed as compound.
151
+ @compound_map = Hash.new { |h, k| h[k] = {} } # Hash{ ltail => Hash{ lhead => issued id } }
152
+ @concentrate = concentrate
153
+ @only_module = only_module
154
+ @metadata_store = MetadataStore.new(module_store)
155
+ end
156
+
157
+ # @return [Array<Hash>]
158
+ def metadata
159
+ @metadata_store.to_a
160
+ end
161
+
162
+ # @return [String]
163
+ def to_s
164
+ io.puts %(strict digraph "#{definition.title}" {)
165
+ io.indented do
166
+ io.puts('compound=true') if @compound
167
+ io.puts('concentrate=true') if @concentrate
168
+
169
+ if @only_module
170
+ render_only_modules
171
+ else
172
+ definition.sources.sort_by(&:source_name).each do
173
+ insert_source(_1)
174
+ end
175
+ end
176
+ end
177
+ io.puts '}'
178
+ io.string
179
+ end
180
+
181
+ private
182
+
183
+ attr_reader :definition, :module_store, :io
184
+
185
+ def render_only_modules
186
+ # Hash{ from_module => { to_module => Array<DiverDown::Definition::Dependency> } }
187
+ dependency_map = Hash.new { |h, k| h[k] = Hash.new { |hi, ki| hi[ki] = [] } }
188
+
189
+ definition.sources.sort_by(&:source_name).each do |source|
190
+ source_modules = module_store.get(source.source_name)
191
+ next if source_modules.empty?
192
+
193
+ source.dependencies.each do |dependency|
194
+ dependency_modules = module_store.get(dependency.source_name)
195
+ next if dependency_modules.empty?
196
+
197
+ dependency_map[source_modules][dependency_modules].push(dependency)
198
+ end
199
+ end
200
+
201
+ # Remove duplicated prefix modules
202
+ # from [["A"], ["A", "B"]] to [["A", "B"]]
203
+ uniq_modules = [*dependency_map.keys, *dependency_map.values.map(&:keys).flatten(1)].uniq
204
+ uniq_modules.reject! do |modules|
205
+ modules.empty? ||
206
+ uniq_modules.any? { _1[0..modules.size - 1] == modules && _1.length > modules.size }
207
+ end
208
+
209
+ uniq_modules.each do |specific_module_names|
210
+ buf = swap_io do
211
+ indexes = (0..(specific_module_names.length - 1)).to_a
212
+
213
+ chain_yield(indexes) do |index, next_proc|
214
+ module_names = specific_module_names[0..index]
215
+ module_name = specific_module_names[index]
216
+
217
+ io.puts %(subgraph "#{module_label(module_names)}" {)
218
+ io.indented do
219
+ io.puts %(id="#{@metadata_store.issue_modules_id(module_names)}")
220
+ io.puts %(label="#{module_name}")
221
+ io.puts %("#{module_name}" #{build_attributes(label: module_name, id: @metadata_store.issue_modules_id(module_names))})
222
+
223
+ next_proc&.call
224
+ end
225
+ io.puts '}'
226
+ end
227
+ end
228
+
229
+ io.write buf.string
230
+ end
231
+
232
+ dependency_map.each do |from_modules, h|
233
+ h.each do |to_modules, all_dependencies|
234
+ # Do not render standalone source
235
+ # Do not render self-dependency
236
+ next if from_modules.empty? || to_modules.empty? || from_modules == to_modules
237
+
238
+ dependencies = DiverDown::Definition::Dependency.combine(*all_dependencies)
239
+
240
+ dependencies.each do
241
+ attributes = {}
242
+ ltail = module_label(*from_modules)
243
+ lhead = module_label(*to_modules)
244
+
245
+ # Already rendered dependencies between modules
246
+ # Add the dependency to the edge of the compound
247
+ if @compound_map[ltail].include?(lhead)
248
+ compound_id = @compound_map[ltail][lhead]
249
+ @metadata_store.append_dependency(compound_id, _1)
250
+ next
251
+ end
252
+
253
+ compound_id = @metadata_store.issue_dependency_id(_1)
254
+ @compound_map[ltail][lhead] = compound_id
255
+
256
+ attributes.merge!(
257
+ id: compound_id,
258
+ ltail:,
259
+ lhead:,
260
+ minlen: MODULE_MINLEN
261
+ )
262
+
263
+ io.write(%("#{from_modules[-1]}" -> "#{to_modules[-1]}"))
264
+ io.write(%( #{build_attributes(**attributes)}), indent: false) unless attributes.empty?
265
+ io.write("\n")
266
+ end
267
+ end
268
+ end
269
+ end
270
+
271
+ def insert_source(source)
272
+ if module_store.get(source.source_name).empty?
273
+ io.puts %("#{source.source_name}" #{build_attributes(label: source.source_name, id: @metadata_store.issue_source_id(source))})
274
+ else
275
+ insert_modules(source)
276
+ end
277
+
278
+ source.dependencies.each do
279
+ attributes = {}
280
+ ltail = module_label(*module_store.get(source.source_name))
281
+ lhead = module_label(*module_store.get(_1.source_name))
282
+
283
+ if @compound && (ltail || lhead)
284
+ # Rendering of dependencies between modules is done only once
285
+ between_modules = ltail != lhead
286
+
287
+ # Already rendered dependencies between modules
288
+ # Add the dependency to the edge of the compound
289
+ if between_modules && @compound_map[ltail].include?(lhead)
290
+ compound_id = @compound_map[ltail][lhead]
291
+ @metadata_store.append_dependency(compound_id, _1)
292
+ next
293
+ end
294
+
295
+ compound_id = @metadata_store.issue_dependency_id(_1)
296
+ @compound_map[ltail][lhead] = compound_id
297
+
298
+ attributes.merge!(
299
+ id: compound_id,
300
+ ltail:,
301
+ lhead:,
302
+ minlen: MODULE_MINLEN
303
+ )
304
+ else
305
+ attributes.merge!(
306
+ id: @metadata_store.issue_dependency_id(_1)
307
+ )
308
+ end
309
+
310
+ io.write(%("#{source.source_name}" -> "#{_1.source_name}"))
311
+ io.write(%( #{build_attributes(**attributes)}), indent: false) unless attributes.empty?
312
+ io.write("\n")
313
+ end
314
+ end
315
+
316
+ def insert_modules(source)
317
+ buf = swap_io do
318
+ all_module_names = module_store.get(source.source_name)
319
+ indexes = (0..(all_module_names.length - 1)).to_a
320
+
321
+ chain_yield(indexes) do |index, next_proc|
322
+ module_names = all_module_names[0..index]
323
+ module_name = module_names[-1]
324
+
325
+ io.puts %(subgraph "#{module_label(module_names)}" {)
326
+ io.indented do
327
+ io.puts %(id="#{@metadata_store.issue_modules_id(module_names)}")
328
+ io.puts %(label="#{module_name}")
329
+
330
+ if next_proc
331
+ next_proc.call
332
+ else
333
+ # last. equals indexes[-1] == index
334
+ io.puts %("#{source.source_name}" #{build_attributes(label: source.source_name, id: @metadata_store.issue_source_id(source))})
335
+ end
336
+ end
337
+ io.puts '}'
338
+ end
339
+ end
340
+
341
+ io.write buf.string
342
+ end
343
+
344
+ def chain_yield(values, &block)
345
+ *head, tail = values
346
+
347
+ last_proc = proc do
348
+ block.call(tail, nil)
349
+ end
350
+
351
+ chain_proc = head.inject(last_proc) do |next_proc, value|
352
+ proc do
353
+ block.call(value, next_proc)
354
+ end
355
+ end
356
+
357
+ chain_proc.call
358
+ end
359
+
360
+ # rubocop:disable Lint/UnderscorePrefixedVariableName
361
+ # attrsの参考 https://qiita.com/rubytomato@github/items/51779135bc4b77c8c20d
362
+ def build_attributes(_wrap: '[]', **attrs)
363
+ attrs = attrs.reject { _2.nil? || _2 == '' }
364
+ return if attrs.empty?
365
+
366
+ attrs_str = attrs.map { %(#{_1}="#{_2}") }.join(ATTRIBUTE_DELIMITER)
367
+
368
+ if _wrap
369
+ "#{_wrap[0]}#{attrs_str}#{_wrap[1]}"
370
+ else
371
+ attrs_str
372
+ end
373
+ end
374
+ # rubocop:enable Lint/UnderscorePrefixedVariableName
375
+
376
+ def increase_indent
377
+ @indent += 1
378
+ yield
379
+ ensure
380
+ @indent -= 1
381
+ end
382
+
383
+ def swap_io
384
+ old_io = @io
385
+ @io = IndentedStringIo.new
386
+ yield
387
+ @io
388
+ ensure
389
+ @io = old_io
390
+ end
391
+
392
+ def module_label(*modules)
393
+ return if modules.empty?
394
+
395
+ "cluster_#{modules.join(MODULE_DELIMITER)}"
396
+ end
397
+ end
398
+ end
399
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack/proxy'
4
+ require 'websocket/driver'
5
+ require 'eventmachine'
6
+
7
+ module DiverDown
8
+ class Web
9
+ # For vite
10
+ class DevServerMiddleware
11
+ class HttpProxy < ::Rack::Proxy
12
+ def initialize(_app = nil, host:, port:)
13
+ @host = host
14
+ @port = port
15
+
16
+ super(nil, backend: "http://#{@host}:#{@port}", proxy_host: @host, proxy_port: @port, proxy_scheme: 'http')
17
+ end
18
+ end
19
+
20
+ class WebSocketProxy
21
+ attr_reader :env, :url
22
+
23
+ def initialize(env, host:, port:)
24
+ @env = env
25
+ @url = "ws://#{host}:#{port}#{env['REQUEST_URI']}"
26
+ @driver = WebSocket::Driver.rack(self)
27
+
28
+ env['rack.hijack'].call
29
+ @io = env['rack.hijack_io']
30
+
31
+ EM.attach(@io, Reader) { |conn| conn.driver = @driver }
32
+
33
+ @driver.start
34
+ end
35
+
36
+ # @param string [String]
37
+ def write(string)
38
+ @io.write(string)
39
+ end
40
+
41
+ module Reader
42
+ attr_writer :driver
43
+
44
+ # @param string [String]
45
+ def receive_data(string)
46
+ @driver.parse(string)
47
+ end
48
+ end
49
+ end
50
+
51
+ def initialize(app, host:, port:)
52
+ @app = app
53
+ @host = host
54
+ @port = port
55
+ @http_proxy = HttpProxy.new(@app, host: @host, port: @port)
56
+ end
57
+
58
+ # @param env [Hash]
59
+ def call(env)
60
+ request = Rack::Request.new(env)
61
+
62
+ if WebSocket::Driver.websocket?(env)
63
+ WebSocketProxy.new(env, host: @host, port: @port)
64
+ elsif request.path.start_with?('/api')
65
+ @app.call(env)
66
+ else
67
+ @http_proxy.call(env)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stringio'
4
+ require 'forwardable'
5
+
6
+ module DiverDown
7
+ class Web
8
+ class IndentedStringIo
9
+ extend ::Forwardable
10
+
11
+ def_delegators :@io, :rewind, :string
12
+
13
+ attr_accessor :indent
14
+
15
+ # @param tab [String]
16
+ def initialize(tab: ' ')
17
+ @io = StringIO.new
18
+ @indent = 0
19
+ @tab = tab
20
+ end
21
+
22
+ # @param contents [Array<String>]
23
+ # @param indent [Boolean] Enable or disable indentation
24
+ # @return [void]
25
+ def write(*contents, indent: true)
26
+ indent_string = if indent
27
+ @tab * @indent
28
+ else
29
+ ''
30
+ end
31
+
32
+ string = contents.join
33
+ lines = string.lines
34
+ lines.each do |line|
35
+ if line == "\n"
36
+ @io.write "\n"
37
+ else
38
+ @io.write "#{indent_string}#{line}"
39
+ end
40
+ end
41
+ end
42
+
43
+ # @param content [String]
44
+ # @return [void]
45
+ def puts(*contents, indent: true)
46
+ write("#{contents.join("\n")}\n", indent:)
47
+ nil
48
+ end
49
+
50
+ # increase the indent level for the block
51
+ def indented
52
+ @indent += 1
53
+ yield
54
+ ensure
55
+ @indent -= 1
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+
5
+ module DiverDown
6
+ class Web
7
+ class ModuleStore
8
+ BLANK_ARRAY = [].freeze
9
+ BLANK_RE = /\A\s*\z/
10
+
11
+ private_constant(:BLANK_RE)
12
+
13
+ def initialize(path)
14
+ @path = path
15
+ @store = load
16
+ end
17
+
18
+ # @param source_name [String]
19
+ # @param module_names [Array<String>]
20
+ def set(source_name, module_names)
21
+ @store[source_name] = module_names.dup.reject do
22
+ BLANK_RE.match?(_1)
23
+ end.freeze
24
+ end
25
+
26
+ # @param source_name [String]
27
+ # @return [Array<Module>]
28
+ def get(source_name)
29
+ @store[source_name] || BLANK_ARRAY
30
+ end
31
+
32
+ # @return [Hash]
33
+ def to_h
34
+ @store.dup
35
+ end
36
+
37
+ # Write store to file
38
+ # @return [void]
39
+ def flush
40
+ File.write(@path, to_h.to_yaml)
41
+ end
42
+
43
+ private
44
+
45
+ def load
46
+ store = {}
47
+
48
+ begin
49
+ loaded = YAML.load_file(@path)
50
+ store.merge!(loaded) if loaded
51
+ rescue StandardError
52
+ # Ignore error
53
+ end
54
+
55
+ store
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rack'
4
+ require 'yaml'
5
+
6
+ module DiverDown
7
+ class Web
8
+ WEB_DIR = File.expand_path('../../web', __dir__)
9
+
10
+ require 'diver_down/web/action'
11
+ require 'diver_down/web/definition_to_dot'
12
+ require 'diver_down/web/definition_enumerator'
13
+ require 'diver_down/web/bit_id'
14
+ require 'diver_down/web/module_store'
15
+ require 'diver_down/web/indented_string_io'
16
+ require 'diver_down/web/definition_store'
17
+ require 'diver_down/web/definition_loader'
18
+
19
+ # For development
20
+ autoload :DevServerMiddleware, 'diver_down/web/dev_server_middleware'
21
+
22
+ # @param definition_dir [String]
23
+ # @param module_store [DiverDown::ModuleStore]
24
+ # @param store [DiverDown::Web::DefinitionStore]
25
+ def initialize(definition_dir:, module_store:, store: DiverDown::Web::DefinitionStore.new)
26
+ @store = store
27
+ @module_store = module_store
28
+ @files_server = Rack::Files.new(File.join(WEB_DIR))
29
+
30
+ definition_files = ::Dir["#{definition_dir}/**/*.{yml,yaml,msgpack,json}"].sort
31
+ @total_definition_files_size = definition_files.size
32
+
33
+ load_definition_files_on_thread(definition_files)
34
+ end
35
+
36
+ # @param env [Hash]
37
+ # @return [Array[Integer, Hash, Array]]
38
+ def call(env)
39
+ request = Rack::Request.new(env)
40
+ action = DiverDown::Web::Action.new(store: @store, module_store: @module_store, request:)
41
+
42
+ case [request.request_method, request.path]
43
+ in ['GET', %r{\A/api/definitions\.json\z}]
44
+ action.definitions(
45
+ page: request.params['page']&.to_i || 1,
46
+ per: request.params['per']&.to_i || 100,
47
+ title: request.params['title'] || '',
48
+ source: request.params['source'] || ''
49
+ )
50
+ in ['GET', %r{\A/api/sources\.json\z}]
51
+ action.sources
52
+ in ['GET', %r{\A/api/modules\.json\z}]
53
+ action.modules
54
+ in ['GET', %r{\A/api/modules/(?<module_names>.+)\.json\z}]
55
+ module_names = Regexp.last_match[:module_names].split('/')
56
+ action.module(module_names)
57
+ in ['GET', %r{\A/api/definitions/(?<bit_id>\d+)\.json\z}]
58
+ bit_id = Regexp.last_match[:bit_id].to_i
59
+ compound = request.params['compound'] == '1'
60
+ concentrate = request.params['concentrate'] == '1'
61
+ only_module = request.params['only_module'] == '1'
62
+ action.combine_definitions(bit_id, compound, concentrate, only_module)
63
+ in ['GET', %r{\A/api/sources/(?<source>[^/]+)\.json\z}]
64
+ source = Regexp.last_match[:source]
65
+ action.source(source)
66
+ in ['POST', %r{\A/api/sources/(?<source>[^/]+)/modules.json\z}]
67
+ source = Regexp.last_match[:source]
68
+ modules = request.params['modules'] || []
69
+ action.set_modules(source, modules)
70
+ in ['GET', %r{\A/api/pid\.json\z}]
71
+ action.pid
72
+ in ['GET', %r{\A/api/initialization_status\.json\z}]
73
+ action.initialization_status(@total_definition_files_size)
74
+ in ['GET', %r{\A/assets/}]
75
+ @files_server.call(env)
76
+ in ['GET', /\.json\z/], ['POST', /\.json\z/]
77
+ action.not_found
78
+ else
79
+ @files_server.call(env.merge('PATH_INFO' => '/index.html'))
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def load_definition_files_on_thread(definition_files)
86
+ definition_loader = DiverDown::Web::DefinitionLoader.new
87
+
88
+ Thread.new do
89
+ loop do
90
+ break if definition_files.empty?
91
+
92
+ definition_file = definition_files.shift
93
+ definition = definition_loader.load_file(definition_file)
94
+
95
+ # No needed to synchronize because this is executed on a single thread.
96
+ @store.set(definition)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diver_down'
4
+ require 'diver_down/trace'
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diver_down'
4
+ require 'diver_down/web'