diver_down 0.0.1.alpha1
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.
- 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
|