Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental feature: Add graph for specific module #60

Merged
merged 13 commits into from
Jun 17, 2024
Prev Previous commit
Next Next commit
fix: broken graph
  • Loading branch information
alpaca-tc committed Jun 7, 2024
commit 00384aa88591a1dea22ccbd42bd493415632aece
90 changes: 51 additions & 39 deletions lib/diver_down/web/definition_to_dot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ def render_only_modules
end

# Remove duplicated prefix modules
# from [["A"], ["A", "B"]] to [["A", "B"]]
# from [["A"], ["A", "B"], ["A", "C"], ["D"]] to { "A" => { "B" => {}, "C" => {} }, "D" => {} }
uniq_modules = [*dependency_map.keys, *dependency_map.values.map(&:keys).flatten(1)].uniq
uniq_modules.reject! do |modules|
modules.empty? ||
Expand Down Expand Up @@ -268,60 +268,50 @@ def render_only_modules
end

def render_sources
# Hash{ modules => sources }
# Hash{ Array<String> => Array<DiverDown::Definition::Source> }
by_modules = definition.sources.group_by do |source|
metadata.source(source.source_name).modules
end

# Remove duplicated prefix modules
# from [["A"], ["A", "B"]] to [["A", "B"]]
uniq_modules = by_modules.keys.uniq
uniq_modules = uniq_modules.reject do |modules|
uniq_modules.any? { _1[0..modules.size - 1] == modules && _1.length > modules.size }
# Render subgraph for each module and its sources second
nested_modules = array_to_hash(by_modules.keys.uniq.sort)
render_nested_modules_sources(by_modules, nested_modules)

# Render dependencies last
definition.sources.sort_by(&:source_name).each do |source|
insert_dependencies(source)
end
end

uniq_modules.each do |full_modules|
# Render module and source
if full_modules.empty?
sources = by_modules[full_modules].sort_by(&:source_name)
def render_nested_modules_sources(by_modules, nested_modules, prefix = [])
nested_modules.each do |module_name, next_nested_modules|
module_names = prefix + [module_name].compact
sources = (by_modules[module_names] || []).sort_by(&:source_name)

if module_name.nil?
sources.each do |source|
insert_source(source)
io.puts build_source_node(source)
end
else
buf = swap_io do
indexes = (0..(full_modules.length - 1)).to_a

chain_yield(indexes) do |index, next_proc|
module_names = full_modules[0..index]
module_name = module_names[-1]

io.puts %(subgraph "#{escape_quote(module_label(module_names))}" {)
io.indented do
io.puts %(id="#{@dot_metadata_store.issue_modules_id(module_names)}")
io.puts %(label="#{escape_quote(module_name)}")

sources = (by_modules[module_names] || []).sort_by(&:source_name)
sources.each do |source|
insert_source(source)
end

next_proc&.call
end
io.puts '}'
io.puts %(subgraph "#{escape_quote(module_label(module_names))}" {)
io.indented do
io.puts %(id="#{@dot_metadata_store.issue_modules_id(module_names)}")
io.puts %(label="#{escape_quote(module_name)}")

sources.each do |source|
io.puts build_source_node(source)
end
end

io.write buf.string
render_nested_modules_sources(by_modules, next_nested_modules, module_names)
end
io.puts '}'
end
end

definition.sources.sort_by(&:source_name).each do |source|
insert_dependencies(source)
end
end

def insert_source(source)
io.puts %("#{escape_quote(source.source_name)}" #{build_attributes(label: source.source_name, id: @dot_metadata_store.issue_source_id(source))})
def build_source_node(source)
%("#{escape_quote(source.source_name)}" #{build_attributes(label: source.source_name, id: @dot_metadata_store.issue_source_id(source))})
end

def insert_dependencies(source)
Expand Down Expand Up @@ -420,6 +410,28 @@ def module_label(*modules)
def escape_quote(string)
string.to_s.gsub(/"/, '\"')
end

# from [["A"], ["A", "B"], ["A", "C"], ["D"]] to { "A" => { "B" => {}, "C" => {} }, "D" => {} }
def array_to_hash(array)
hash = {}
array.each do |sub_array|
current_hash = hash

if sub_array.empty?
current_hash[nil] = {}
else
sub_array.each_with_index do |element, index|
if index == sub_array.length - 1
current_hash[element] = {}
else
current_hash[element] ||= {}
current_hash = current_hash[element]
end
end
end
end
hash
end
end
end
end
90 changes: 90 additions & 0 deletions spec/diver_down/web/definition_to_dot_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,96 @@ def build_definition(title: 'title', sources: [])
)
end

it 'returns digraph given multiple modules' do
definition = build_definition(
sources: [
{
source_name: 'a.rb',
dependencies: [
{
source_name: 'b.rb',
method_ids: [
{
name: 'call_b',
context: 'class',
paths: [],
},
],
}, {
source_name: 'c.rb',
method_ids: [
{
name: 'call_c',
context: 'class',
paths: [],
},
],
}, {
source_name: 'd.rb',
method_ids: [
{
name: 'call_d',
context: 'class',
paths: [],
},
],
},
],
}, {
source_name: 'b.rb',
}, {
source_name: 'c.rb',
}, {
source_name: 'd.rb',
}, {
source_name: 'e.rb',
}, {
source_name: 'f.rb',
},
]
)

metadata.source('a.rb').modules = ['A']
metadata.source('b.rb').modules = ['B']
metadata.source('c.rb').modules = ['B']
metadata.source('d.rb').modules = ['B', 'C']
metadata.source('e.rb').modules = ['B', 'D']
metadata.source('f.rb').modules = []
metadata.source('unknown.rb').modules = ['Unknown']

instance = described_class.new(definition, metadata)

expect(instance.to_s).to eq(<<~DOT)
strict digraph "title" {
"f.rb" [label="f.rb" id="graph_1"]
subgraph "cluster_A" {
id="graph_2"
label="A"
"a.rb" [label="a.rb" id="graph_3"]
}
subgraph "cluster_B" {
id="graph_4"
label="B"
"b.rb" [label="b.rb" id="graph_5"]
"c.rb" [label="c.rb" id="graph_6"]
subgraph "cluster_B::C" {
id="graph_7"
label="C"
"d.rb" [label="d.rb" id="graph_8"]
}
subgraph "cluster_B::D" {
id="graph_9"
label="D"
"e.rb" [label="e.rb" id="graph_10"]
}
}
"a.rb" -> "b.rb" [id="graph_11"]
"a.rb" -> "c.rb" [id="graph_12"]
"a.rb" -> "d.rb" [id="graph_13"]
}
DOT
end

it 'returns compound digraph if compound = true' do
definition = build_definition(
sources: [
Expand Down