From adf4dc694fa71bbf09029d9b7a7481b971277225 Mon Sep 17 00:00:00 2001 From: alpaca-tc Date: Thu, 9 May 2024 17:33:01 +0900 Subject: [PATCH] source and dependency should be render within subgraph The source and dependency are rendered under the appropriate subgraph. There was a problem that Graphviz could not be rendered if the same subgraph was rendered multiple times. --- lib/diver_down/web/definition_to_dot.rb | 91 +++++++++++-------- spec/diver_down/web/definition_to_dot_spec.rb | 70 ++++++++++++-- 2 files changed, 115 insertions(+), 46 deletions(-) diff --git a/lib/diver_down/web/definition_to_dot.rb b/lib/diver_down/web/definition_to_dot.rb index 8e09a1f..25cc822 100644 --- a/lib/diver_down/web/definition_to_dot.rb +++ b/lib/diver_down/web/definition_to_dot.rb @@ -169,9 +169,7 @@ def to_s if @only_module render_only_modules else - definition.sources.sort_by(&:source_name).each do - insert_source(_1) - end + render_sources end end io.puts '}' @@ -268,13 +266,62 @@ def render_only_modules end end - def insert_source(source) - if module_store.get(source.source_name).empty? - io.puts %("#{source.source_name}" #{build_attributes(label: source.source_name, id: @metadata_store.issue_source_id(source))}) - else - insert_modules(source) + def render_sources + by_modules = definition.sources.group_by do |source| + module_store.get(source.source_name) + 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 } end + uniq_modules.each do |full_modules| + # Render module and source + if full_modules.empty? + sources = by_modules[full_modules].sort_by(&:source_name) + + sources.each do |source| + insert_source(source) + insert_dependencies(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 "#{module_label(module_names)}" {) + io.indented do + io.puts %(id="#{@metadata_store.issue_modules_id(module_names)}") + io.puts %(label="#{module_name}") + + sources = (by_modules[module_names] || []).sort_by(&:source_name) + sources.each do |source| + insert_source(source) + insert_dependencies(source) + end + + next_proc&.call + end + io.puts '}' + end + end + + io.write buf.string + end + end + end + + def insert_source(source) + io.puts %("#{source.source_name}" #{build_attributes(label: source.source_name, id: @metadata_store.issue_source_id(source))}) + end + + def insert_dependencies(source) source.dependencies.each do attributes = {} ltail = module_label(*module_store.get(source.source_name)) @@ -313,34 +360,6 @@ def insert_source(source) end end - def insert_modules(source) - buf = swap_io do - all_module_names = module_store.get(source.source_name) - indexes = (0..(all_module_names.length - 1)).to_a - - chain_yield(indexes) do |index, next_proc| - module_names = all_module_names[0..index] - module_name = module_names[-1] - - io.puts %(subgraph "#{module_label(module_names)}" {) - io.indented do - io.puts %(id="#{@metadata_store.issue_modules_id(module_names)}") - io.puts %(label="#{module_name}") - - if next_proc - next_proc.call - else - # last. equals indexes[-1] == index - io.puts %("#{source.source_name}" #{build_attributes(label: source.source_name, id: @metadata_store.issue_source_id(source))}) - end - end - io.puts '}' - end - end - - io.write buf.string - end - def chain_yield(values, &block) *head, tail = values diff --git a/spec/diver_down/web/definition_to_dot_spec.rb b/spec/diver_down/web/definition_to_dot_spec.rb index e6a3668..df962f0 100644 --- a/spec/diver_down/web/definition_to_dot_spec.rb +++ b/spec/diver_down/web/definition_to_dot_spec.rb @@ -206,16 +206,12 @@ def build_definition(title: 'title', sources: []) id="graph_1" label="A" "a.rb" [label="a.rb" id="graph_2"] + "a.rb" -> "b.rb" [id="graph_3" ltail="cluster_A" lhead="cluster_B" minlen="3"] } - "a.rb" -> "b.rb" [id="graph_3" ltail="cluster_A" lhead="cluster_B" minlen="3"] subgraph "cluster_B" { id="graph_4" label="B" "b.rb" [label="b.rb" id="graph_5"] - } - subgraph "cluster_B" { - id="graph_4" - label="B" "c.rb" [label="c.rb" id="graph_6"] } } @@ -323,16 +319,12 @@ def build_definition(title: 'title', sources: []) id="graph_1" label="A" "a.rb" [label="a.rb" id="graph_2"] + "a.rb" -> "b.rb" [id="graph_3" ltail="cluster_A" lhead="cluster_B" minlen="3"] } - "a.rb" -> "b.rb" [id="graph_3" ltail="cluster_A" lhead="cluster_B" minlen="3"] subgraph "cluster_B" { id="graph_4" label="B" "b.rb" [label="b.rb" id="graph_5"] - } - subgraph "cluster_B" { - id="graph_4" - label="B" "c.rb" [label="c.rb" id="graph_6"] } } @@ -535,6 +527,64 @@ def build_definition(title: 'title', sources: []) ] ) end + + it 'returns concentrate digraph with multiple modules if concentrate = true' do + definition = build_definition( + sources: [ + { + source_name: 'a.rb', + }, + { + source_name: 'b.rb', + }, + { + source_name: 'c.rb', + }, + { + source_name: 'd.rb', + }, + ] + ) + + module_store.set('b.rb', ['A']) + module_store.set('c.rb', ['A', 'C']) + module_store.set('d.rb', ['B']) + + instance = described_class.new(definition, module_store, concentrate: true) + expect(instance.to_s).to eq(<<~DOT) + strict digraph "title" { + concentrate=true + "a.rb" [label="a.rb" id="graph_1"] + subgraph "cluster_A" { + id="graph_2" + label="A" + "b.rb" [label="b.rb" id="graph_3"] + subgraph "cluster_A::C" { + id="graph_4" + label="C" + "c.rb" [label="c.rb" id="graph_5"] + } + } + subgraph "cluster_B" { + id="graph_6" + label="B" + "d.rb" [label="d.rb" id="graph_7"] + } + } + DOT + + expect(instance.metadata).to eq( + [ + { id: 'graph_1', type: 'source', source_name: 'a.rb', modules: [] }, + { id: 'graph_2', type: 'module', modules: [{ module_name: 'A' }] }, + { id: 'graph_3', type: 'source', source_name: 'b.rb', modules: [{ module_name: 'A' }] }, + { id: 'graph_4', type: 'module', modules: [{ module_name: 'A' }, { module_name: 'C' }] }, + { id: 'graph_5', type: 'source', source_name: 'c.rb', modules: [{ module_name: 'A' }, { module_name: 'C' }] }, + { id: 'graph_6', type: 'module', modules: [{ module_name: 'B' }] }, + { id: 'graph_7', type: 'source', source_name: 'd.rb', modules: [{ module_name: 'B' }] }, + ] + ) + end end end end