diver_down 0.0.1.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +132 -0
- data/exe/diver_down_web +55 -0
- data/lib/diver_down/definition/dependency.rb +107 -0
- data/lib/diver_down/definition/method_id.rb +83 -0
- data/lib/diver_down/definition/source.rb +90 -0
- data/lib/diver_down/definition.rb +112 -0
- data/lib/diver_down/helper.rb +81 -0
- data/lib/diver_down/trace/call_stack.rb +45 -0
- data/lib/diver_down/trace/ignored_method_ids.rb +136 -0
- data/lib/diver_down/trace/module_set/array_module_set.rb +31 -0
- data/lib/diver_down/trace/module_set/const_source_location_module_set.rb +28 -0
- data/lib/diver_down/trace/module_set.rb +78 -0
- data/lib/diver_down/trace/redefine_ruby_methods.rb +64 -0
- data/lib/diver_down/trace/tracer/session.rb +121 -0
- data/lib/diver_down/trace/tracer.rb +96 -0
- data/lib/diver_down/trace.rb +27 -0
- data/lib/diver_down/version.rb +5 -0
- data/lib/diver_down/web/action.rb +344 -0
- data/lib/diver_down/web/bit_id.rb +41 -0
- data/lib/diver_down/web/definition_enumerator.rb +54 -0
- data/lib/diver_down/web/definition_loader.rb +37 -0
- data/lib/diver_down/web/definition_store.rb +89 -0
- data/lib/diver_down/web/definition_to_dot.rb +399 -0
- data/lib/diver_down/web/dev_server_middleware.rb +72 -0
- data/lib/diver_down/web/indented_string_io.rb +59 -0
- data/lib/diver_down/web/module_store.rb +59 -0
- data/lib/diver_down/web.rb +101 -0
- data/lib/diver_down-trace.rb +4 -0
- data/lib/diver_down-web.rb +4 -0
- data/lib/diver_down.rb +14 -0
- data/web/assets/CjLq7LhZ.css +1 -0
- data/web/assets/bundle.js +978 -0
- data/web/index.html +13 -0
- metadata +122 -0
@@ -0,0 +1,344 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module DiverDown
|
6
|
+
class Web
|
7
|
+
class Action
|
8
|
+
Pagination = Data.define(
|
9
|
+
:current_page,
|
10
|
+
:total_pages,
|
11
|
+
:total_count,
|
12
|
+
:per
|
13
|
+
)
|
14
|
+
|
15
|
+
# @param store [DiverDown::Definition::Store]
|
16
|
+
# @param module_store [DiverDown::Web::ModuleStore]
|
17
|
+
# @param request [Rack::Request]
|
18
|
+
def initialize(store:, module_store:, request:)
|
19
|
+
@store = store
|
20
|
+
@module_store = module_store
|
21
|
+
@request = request
|
22
|
+
end
|
23
|
+
|
24
|
+
# GET /api/sources.json
|
25
|
+
def sources
|
26
|
+
source_names = Set.new
|
27
|
+
|
28
|
+
# rubocop:disable Style/HashEachMethods
|
29
|
+
@store.each do |_, definition|
|
30
|
+
definition.sources.each do |source|
|
31
|
+
source_names.add(source.source_name)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# rubocop:enable Style/HashEachMethods
|
35
|
+
|
36
|
+
json(
|
37
|
+
sources: source_names.sort.map do |source_name|
|
38
|
+
{
|
39
|
+
source_name:,
|
40
|
+
}
|
41
|
+
end
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# GET /api/modules.json
|
46
|
+
def modules
|
47
|
+
# Hash{ DiverDown::Definition::Modulee => Set<Integer> }
|
48
|
+
module_set = Set.new
|
49
|
+
|
50
|
+
# rubocop:disable Style/HashEachMethods
|
51
|
+
@store.each do |_, definition|
|
52
|
+
definition.sources.each do |source|
|
53
|
+
modules = @module_store.get(source.source_name)
|
54
|
+
module_set.add(modules) unless modules.empty?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# rubocop:enable Style/HashEachMethods
|
58
|
+
|
59
|
+
json(
|
60
|
+
modules: module_set.sort.map do
|
61
|
+
_1.map do |module_name|
|
62
|
+
{
|
63
|
+
module_name:,
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
# GET /api/modules/:module_name.json
|
71
|
+
# @param module_names [Array<String>]
|
72
|
+
def module(module_names)
|
73
|
+
# Hash{ DiverDown::Definition::Modulee => Set<Integer> }
|
74
|
+
related_definition_store_ids = Set.new
|
75
|
+
source_names = Set.new
|
76
|
+
|
77
|
+
# rubocop:disable Style/HashEachMethods
|
78
|
+
@store.each do |_, definition|
|
79
|
+
definition.sources.each do |source|
|
80
|
+
source_module_names = @module_store.get(source.source_name)
|
81
|
+
|
82
|
+
next unless source_module_names[0..module_names.size - 1] == module_names
|
83
|
+
|
84
|
+
source_names.add(source.source_name)
|
85
|
+
related_definition_store_ids.add(definition.store_id)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
# rubocop:enable Style/HashEachMethods
|
89
|
+
|
90
|
+
if related_definition_store_ids.empty?
|
91
|
+
return not_found
|
92
|
+
end
|
93
|
+
|
94
|
+
related_definitions = related_definition_store_ids.map { @store.get(_1) }
|
95
|
+
|
96
|
+
json(
|
97
|
+
modules: module_names.map do
|
98
|
+
{
|
99
|
+
module_name: _1,
|
100
|
+
}
|
101
|
+
end,
|
102
|
+
sources: source_names.sort.map do |source_name|
|
103
|
+
{
|
104
|
+
source_name:,
|
105
|
+
}
|
106
|
+
end,
|
107
|
+
related_definitions: related_definitions.map do |definition|
|
108
|
+
{
|
109
|
+
id: definition.store_id,
|
110
|
+
title: definition.title,
|
111
|
+
}
|
112
|
+
end
|
113
|
+
)
|
114
|
+
end
|
115
|
+
|
116
|
+
# GET /api/definitions.json
|
117
|
+
#
|
118
|
+
# @param page [Integer]
|
119
|
+
# @param per [Integer]
|
120
|
+
# @param title [String]
|
121
|
+
# @param source [String]
|
122
|
+
def definitions(page:, per:, title:, source:)
|
123
|
+
definition_enumerator = DiverDown::Web::DefinitionEnumerator.new(@store, title:, source:)
|
124
|
+
definitions, pagination = paginate(definition_enumerator, page, per)
|
125
|
+
|
126
|
+
json(
|
127
|
+
definitions: definitions.map do |definition|
|
128
|
+
{
|
129
|
+
id: definition.store_id,
|
130
|
+
definition_group: definition.definition_group,
|
131
|
+
title: definition.title,
|
132
|
+
}
|
133
|
+
end,
|
134
|
+
pagination: pagination.to_h
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
# GET /api/initialization_status.json
|
139
|
+
def initialization_status(total)
|
140
|
+
json(
|
141
|
+
total:,
|
142
|
+
loaded: @store.size
|
143
|
+
)
|
144
|
+
end
|
145
|
+
|
146
|
+
# GET /api/pid.json
|
147
|
+
def pid
|
148
|
+
json(
|
149
|
+
pid: Process.pid
|
150
|
+
)
|
151
|
+
end
|
152
|
+
|
153
|
+
# GET /api/definitions/:bit_id.json
|
154
|
+
#
|
155
|
+
# @param bit_id [Integer]
|
156
|
+
# @param compound [Boolean]
|
157
|
+
# @param concentrate [Boolean]
|
158
|
+
# @param only_module [Boolean]
|
159
|
+
def combine_definitions(bit_id, compound, concentrate, only_module)
|
160
|
+
ids = DiverDown::Web::BitId.bit_id_to_ids(bit_id)
|
161
|
+
|
162
|
+
valid_ids = ids.select do
|
163
|
+
@store.key?(_1)
|
164
|
+
end
|
165
|
+
|
166
|
+
definition, titles = case valid_ids.length
|
167
|
+
when 0
|
168
|
+
return not_found
|
169
|
+
when 1
|
170
|
+
definition = @store.get(ids[0])
|
171
|
+
[definition, [definition.title]]
|
172
|
+
else
|
173
|
+
definitions = valid_ids.map { @store.get(_1) }
|
174
|
+
definition = DiverDown::Definition.combine(definition_group: nil, title: 'combined', definitions:)
|
175
|
+
[definition, definitions.map(&:title)]
|
176
|
+
end
|
177
|
+
|
178
|
+
if definition
|
179
|
+
definition_to_dot = DiverDown::Web::DefinitionToDot.new(definition, @module_store, compound:, concentrate:, only_module:)
|
180
|
+
|
181
|
+
json(
|
182
|
+
titles:,
|
183
|
+
bit_id: DiverDown::Web::BitId.ids_to_bit_id(valid_ids).to_s,
|
184
|
+
dot: definition_to_dot.to_s,
|
185
|
+
dot_metadata: definition_to_dot.metadata,
|
186
|
+
sources: definition.sources.map do
|
187
|
+
{
|
188
|
+
source_name: _1.source_name,
|
189
|
+
modules: @module_store.get(_1.source_name).map do |module_name|
|
190
|
+
{ module_name: }
|
191
|
+
end,
|
192
|
+
}
|
193
|
+
end
|
194
|
+
)
|
195
|
+
else
|
196
|
+
not_found
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# GET /api/sources/:source_name.json
|
201
|
+
#
|
202
|
+
# @param source_name [String]
|
203
|
+
def source(source_name)
|
204
|
+
found_sources = []
|
205
|
+
related_definitions = []
|
206
|
+
reverse_dependencies = Hash.new { |h, dependency_source_name| h[dependency_source_name] = DiverDown::Definition::Dependency.new(source_name: dependency_source_name) }
|
207
|
+
|
208
|
+
@store.each do |id, definition|
|
209
|
+
found_source = nil
|
210
|
+
|
211
|
+
definition.sources.each do |definition_source|
|
212
|
+
found_source = definition_source if definition_source.source_name == source_name
|
213
|
+
|
214
|
+
found_reverse_dependencies = definition_source.dependencies.select do |dependency|
|
215
|
+
dependency.source_name == source_name
|
216
|
+
end
|
217
|
+
|
218
|
+
found_reverse_dependencies.each do |dependency|
|
219
|
+
reverse_dependency = reverse_dependencies[dependency.source_name]
|
220
|
+
|
221
|
+
dependency.method_ids.each do |method_id|
|
222
|
+
reverse_method_id = reverse_dependency.find_or_build_method_id(name: method_id.name, context: method_id.context)
|
223
|
+
reverse_method_id.paths.merge(method_id.paths)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
if found_source
|
229
|
+
found_sources << found_source
|
230
|
+
related_definitions << [id, definition]
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
return not_found if related_definitions.empty?
|
235
|
+
|
236
|
+
module_names = if found_sources.empty?
|
237
|
+
[]
|
238
|
+
else
|
239
|
+
source = DiverDown::Definition::Source.combine(*found_sources)
|
240
|
+
@module_store.get(source.source_name)
|
241
|
+
end
|
242
|
+
|
243
|
+
json(
|
244
|
+
source_name:,
|
245
|
+
modules: module_names.map do
|
246
|
+
{ module_name: _1 }
|
247
|
+
end,
|
248
|
+
related_definitions: related_definitions.map do |id, definition|
|
249
|
+
{
|
250
|
+
id:,
|
251
|
+
title: definition.title,
|
252
|
+
}
|
253
|
+
end,
|
254
|
+
reverse_dependencies: reverse_dependencies.values.map { |reverse_dependency|
|
255
|
+
{
|
256
|
+
source_name: reverse_dependency.source_name,
|
257
|
+
method_ids: reverse_dependency.method_ids.sort.map do |method_id|
|
258
|
+
{
|
259
|
+
context: method_id.context,
|
260
|
+
name: method_id.name,
|
261
|
+
paths: method_id.paths.sort,
|
262
|
+
}
|
263
|
+
end,
|
264
|
+
}
|
265
|
+
}
|
266
|
+
)
|
267
|
+
end
|
268
|
+
|
269
|
+
# POST /api/sources/:source_name/modules.json
|
270
|
+
#
|
271
|
+
# @param source_name [String]
|
272
|
+
# @param modules [Array<String>]
|
273
|
+
def set_modules(source_name, modules)
|
274
|
+
found_source = @store.any? do |_, definition|
|
275
|
+
definition.sources.any? do |source|
|
276
|
+
source.source_name == source_name
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
if found_source
|
281
|
+
@module_store.set(source_name, modules)
|
282
|
+
@module_store.flush
|
283
|
+
|
284
|
+
json({})
|
285
|
+
else
|
286
|
+
not_found
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
# @return [Array[Integer, Hash, Array]]
|
291
|
+
def not_found
|
292
|
+
[404, { 'content-type' => 'text/plain' }, ['not found']]
|
293
|
+
end
|
294
|
+
|
295
|
+
private
|
296
|
+
|
297
|
+
attr_reader :request, :store
|
298
|
+
|
299
|
+
def build_method_id_key(dependency, method_id)
|
300
|
+
"#{dependency.source_name}-#{method_id.context}-#{method_id.name}"
|
301
|
+
end
|
302
|
+
|
303
|
+
def paginate(enumerator, page, per)
|
304
|
+
start_index = (page - 1) * per
|
305
|
+
end_index = start_index + per - 1
|
306
|
+
|
307
|
+
items = []
|
308
|
+
enumerator.each_with_index do |item, index|
|
309
|
+
next if index < start_index
|
310
|
+
break if index > end_index
|
311
|
+
|
312
|
+
items.push(item)
|
313
|
+
end
|
314
|
+
|
315
|
+
total_count = enumerator.size
|
316
|
+
|
317
|
+
[
|
318
|
+
items,
|
319
|
+
Pagination.new(
|
320
|
+
current_page: page,
|
321
|
+
total_pages: [(total_count.to_f / per).ceil, 1].max,
|
322
|
+
total_count:,
|
323
|
+
per:
|
324
|
+
),
|
325
|
+
]
|
326
|
+
end
|
327
|
+
|
328
|
+
def fetch_definition(ids)
|
329
|
+
case ids.length
|
330
|
+
when 0
|
331
|
+
nil
|
332
|
+
when 1
|
333
|
+
@store.get(ids[0])
|
334
|
+
else
|
335
|
+
combine_ids_definitions(ids)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def json(data)
|
340
|
+
[200, { 'content-type' => 'application/json' }, [data.to_json]]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiverDown
|
4
|
+
class Web
|
5
|
+
# Use bitflag for a lightweight representation of a list of bound definitions on a URL.
|
6
|
+
# Each definition is related with a unique bitflag, and when merging, the bitflag is calculated with `OR` to represent the merged definitions in a lightweight and fast.
|
7
|
+
module BitId
|
8
|
+
class << self
|
9
|
+
# @param ids [Array<Integer>]
|
10
|
+
# @return [Integer]
|
11
|
+
def ids_to_bit_id(ids)
|
12
|
+
ids.inject(0) { _1 | id_to_bit_id(_2) }
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param id [Integer]
|
16
|
+
# @return [Array<Integer>]
|
17
|
+
def bit_id_to_ids(bit_id)
|
18
|
+
ids = []
|
19
|
+
shift = 0
|
20
|
+
while bit_id.positive?
|
21
|
+
if (bit_id & 1) == 1
|
22
|
+
ids.push(shift + 1)
|
23
|
+
end
|
24
|
+
|
25
|
+
bit_id >>= 1
|
26
|
+
shift += 1
|
27
|
+
end
|
28
|
+
ids
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @param id [Integer]
|
34
|
+
# @return [Integer]
|
35
|
+
def id_to_bit_id(id)
|
36
|
+
1 << (id - 1)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiverDown
|
4
|
+
class Web
|
5
|
+
class DefinitionEnumerator
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
# @param store [DiverDown::Definition::Store]
|
9
|
+
# @param query [String]
|
10
|
+
def initialize(store, title: '', source: '')
|
11
|
+
@store = store
|
12
|
+
@title = title
|
13
|
+
@source = source
|
14
|
+
end
|
15
|
+
|
16
|
+
# @yield [parent_bit_id, bit_id, definition]
|
17
|
+
def each
|
18
|
+
return enum_for(__method__) unless block_given?
|
19
|
+
|
20
|
+
definition_groups = @store.definition_groups
|
21
|
+
definition_groups.each do |definition_group|
|
22
|
+
definitions = @store.filter_by_definition_group(definition_group)
|
23
|
+
definitions.each do
|
24
|
+
next unless match_definition?(_1)
|
25
|
+
|
26
|
+
yield(_1)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Integer]
|
32
|
+
def size
|
33
|
+
@store.size
|
34
|
+
end
|
35
|
+
alias length size
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def match_definition?(definition)
|
40
|
+
matched = true
|
41
|
+
|
42
|
+
unless @title.empty?
|
43
|
+
matched &&= definition.title.include?(@title)
|
44
|
+
end
|
45
|
+
|
46
|
+
unless @source.empty?
|
47
|
+
matched &&= definition.sources.any? { _1.source_name.include?(@source) }
|
48
|
+
end
|
49
|
+
|
50
|
+
matched
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiverDown
|
4
|
+
class Web
|
5
|
+
class DefinitionLoader
|
6
|
+
# @param path [String]
|
7
|
+
def load_file(path)
|
8
|
+
hash = case File.extname(path)
|
9
|
+
when '.yaml', '.yml'
|
10
|
+
from_yaml(path)
|
11
|
+
when '.msgpack'
|
12
|
+
from_msgpack(path)
|
13
|
+
when '.json'
|
14
|
+
from_json(path)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Unsupported file type: #{path}"
|
17
|
+
end
|
18
|
+
|
19
|
+
DiverDown::Definition.from_hash(hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def from_json(path)
|
25
|
+
JSON.parse(File.read(path))
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_yaml(path)
|
29
|
+
YAML.load_file(path)
|
30
|
+
end
|
31
|
+
|
32
|
+
def from_msgpack(path)
|
33
|
+
MessagePack.unpack(File.binread(path))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DiverDown
|
4
|
+
class Web
|
5
|
+
class DefinitionStore
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
attr_reader :bit_id
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
# Hash{ Integer(unique bit flag) => DiverDown::Definition }
|
12
|
+
@definitions = []
|
13
|
+
@definition_group_store = Hash.new { |h, k| h[k] = [] }
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param id [Integer]
|
17
|
+
# @raise [KeyError] if the id is not found
|
18
|
+
# @return [DiverDown::Definition]
|
19
|
+
def get(id)
|
20
|
+
index = id - 1
|
21
|
+
|
22
|
+
raise(KeyError, "id not found: #{id}") if id <= 0 || @definitions.size < id
|
23
|
+
|
24
|
+
@definitions.fetch(index)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @param definitions [Array<DiverDown::Definition>]
|
28
|
+
# @return [Array<Integer>] ids of the definitions
|
29
|
+
def set(*definitions)
|
30
|
+
definitions.map do
|
31
|
+
raise(ArgumentError, 'definition already set') if _1.store_id
|
32
|
+
|
33
|
+
_1.store_id = @definitions.size + 1
|
34
|
+
|
35
|
+
@definitions.push(_1)
|
36
|
+
@definition_group_store[_1.definition_group] << _1
|
37
|
+
|
38
|
+
_1.store_id
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @return [Array<String, nil>]
|
43
|
+
def definition_groups
|
44
|
+
keys = @definition_group_store.keys
|
45
|
+
|
46
|
+
# Sort keys with nil at the end
|
47
|
+
with_nil = keys.include?(nil)
|
48
|
+
keys.delete(nil) if with_nil
|
49
|
+
keys.sort!
|
50
|
+
keys.push(nil) if with_nil
|
51
|
+
|
52
|
+
keys
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param definition_group [String, nil]
|
56
|
+
# @return [Array<DiverDown::Definition>]
|
57
|
+
def filter_by_definition_group(definition_group)
|
58
|
+
@definition_group_store.fetch(definition_group, [])
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param id [Integer]
|
62
|
+
# @return [Boolean]
|
63
|
+
def key?(id)
|
64
|
+
id.positive? && id <= @definitions.size
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Integer]
|
68
|
+
def length
|
69
|
+
@definitions.length
|
70
|
+
end
|
71
|
+
alias size length
|
72
|
+
|
73
|
+
# @return [Boolean]
|
74
|
+
def empty?
|
75
|
+
@definitions.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
# @yield [DiverDown::Definition]
|
79
|
+
def each
|
80
|
+
return enum_for(__method__) unless block_given?
|
81
|
+
|
82
|
+
# NOTE: To allow values to be rewritten during #each, duplicate the value through #to_a.
|
83
|
+
@definitions.each.with_index(1) do |definition, id|
|
84
|
+
yield(id, definition)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|