Class: Woods::RubyAnalyzer::MermaidRenderer

Inherits:
Object
  • Object
show all
Defined in:
lib/woods/ruby_analyzer/mermaid_renderer.rb

Overview

Renders Mermaid-format diagrams from extracted units, dependency graphs, and graph analysis data.

Produces valid Mermaid markdown strings for call graphs, dependency maps, dataflow charts, and combined architecture documents.

Examples:

Rendering a call graph

renderer = MermaidRenderer.new
units = RubyAnalyzer.analyze(paths: ["lib/"])
puts renderer.render_call_graph(units)

Instance Method Summary collapse

Instance Method Details

#render_architecture(units, graph_data, analysis) ⇒ String

Render a combined architecture document with all three diagram types.

Returns a markdown document with headers and fenced Mermaid code blocks for call graph, dependency map, and dataflow diagrams, plus a summary of graph analysis findings.

Parameters:

  • units (Array<ExtractedUnit>)

    Extracted units

  • graph_data (Hash)

    Serialized dependency graph data

  • analysis (Hash)

    Graph analysis report from GraphAnalyzer#analyze

Returns:

  • (String)

    Combined markdown document



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/woods/ruby_analyzer/mermaid_renderer.rb', line 159

def render_architecture(units, graph_data, analysis)
  sections = []

  sections << '# Architecture Overview'
  sections << ''

  # Call graph
  sections << '## Call Graph'
  sections << ''
  sections << '```mermaid'
  sections << render_call_graph(units)
  sections << '```'
  sections << ''

  # Dependency map
  sections << '## Dependency Map'
  sections << ''
  sections << '```mermaid'
  sections << render_dependency_map(graph_data)
  sections << '```'
  sections << ''

  # Dataflow
  sections << '## Data Flow'
  sections << ''
  sections << '```mermaid'
  sections << render_dataflow(units)
  sections << '```'
  sections << ''

  # Analysis summary
  sections << '## Analysis Summary'
  sections << ''
  sections.concat(render_stats_section(analysis))

  sections.join("\n")
end

#render_call_graph(units) ⇒ String

Render a call graph from extracted units showing method call relationships.

Each unit with dependencies produces edges to its targets. Nodes are styled by type (class, module, method).

Parameters:

Returns:

  • (String)

    Mermaid graph TD markdown



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/woods/ruby_analyzer/mermaid_renderer.rb', line 26

def render_call_graph(units)
  lines = ['graph TD']
  return lines.join("\n") if units.nil? || units.empty?

  seen_nodes = Set.new
  seen_edges = Set.new

  units.each do |unit|
    node_id = sanitize_id(unit.identifier)
    lines << "  #{node_id}[\"#{escape_label(unit.identifier)}\"]" if seen_nodes.add?(node_id)

    (unit.dependencies || []).each do |dep|
      target = dep[:target] || dep['target']
      next unless target

      target_id = sanitize_id(target)
      lines << "  #{target_id}[\"#{escape_label(target)}\"]" if seen_nodes.add?(target_id)

      via = dep[:via] || dep['via']
      edge_key = "#{node_id}->#{target_id}"
      next unless seen_edges.add?(edge_key)

      lines << if via
                 "  #{node_id} -->|#{via}| #{target_id}"
               else
                 "  #{node_id} --> #{target_id}"
               end
    end
  end

  lines.join("\n")
end

#render_dataflow(units) ⇒ String

Render a dataflow diagram from units that have data_transformations metadata.

Shows transformation chains: which units construct, serialize, or deserialize data, with edges flowing between them.

Parameters:

  • units (Array<ExtractedUnit>)

    Units with :data_transformations metadata

Returns:

  • (String)

    Mermaid flowchart TD markdown



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/woods/ruby_analyzer/mermaid_renderer.rb', line 115

def render_dataflow(units)
  lines = ['flowchart TD']
  return lines.join("\n") if units.nil? || units.empty?

  seen_nodes = Set.new

  units.each do |unit|
    transformations = unit.[:data_transformations] || unit.['data_transformations']
    next unless transformations.is_a?(Array) && transformations.any?

    node_id = sanitize_id(unit.identifier)
    if seen_nodes.add?(node_id)
      shape = dataflow_shape(transformations)
      lines << "  #{node_id}#{shape}"
    end

    transformations.each do |t|
      receiver = t[:receiver] || t['receiver']
      next unless receiver

      receiver_id = sanitize_id(receiver)
      category = (t[:category] || t['category'])&.to_s
      method_name = t[:method] || t['method']

      lines << "  #{receiver_id}[\"#{escape_label(receiver)}\"]" if seen_nodes.add?(receiver_id)

      label = [category, method_name].compact.join(': ')
      lines << "  #{node_id} -->|#{label}| #{receiver_id}"
    end
  end

  lines.join("\n")
end

#render_dependency_map(graph_data) ⇒ String

Render a dependency map from graph data (as returned by DependencyGraph#to_h).

Shows nodes grouped by type with edges representing dependencies.

Parameters:

  • graph_data (Hash)

    Serialized graph data with :nodes and :edges keys

Returns:

  • (String)

    Mermaid graph TD markdown



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/woods/ruby_analyzer/mermaid_renderer.rb', line 65

def render_dependency_map(graph_data)
  lines = ['graph TD']
  return lines.join("\n") unless graph_data

  nodes = graph_data[:nodes] || graph_data['nodes'] || {}
  edges = graph_data[:edges] || graph_data['edges'] || {}

  return lines.join("\n") if nodes.empty?

  # Group nodes by type for subgraph rendering
  by_type = {}
  nodes.each do |identifier, meta|
    type = (meta[:type] || meta['type'])&.to_sym || :unknown
    by_type[type] ||= []
    by_type[type] << identifier
  end

  # Render subgraphs per type
  by_type.each do |type, identifiers|
    lines << "  subgraph #{type}"
    identifiers.each do |id|
      node_id = sanitize_id(id)
      lines << "    #{node_id}[\"#{escape_label(id)}\"]"
    end
    lines << '  end'
  end

  # Render edges
  seen_edges = Set.new
  edges.each do |source, targets|
    Array(targets).each do |target|
      next unless nodes.key?(target)

      edge_key = "#{sanitize_id(source)}->#{sanitize_id(target)}"
      next unless seen_edges.add?(edge_key)

      lines << "  #{sanitize_id(source)} --> #{sanitize_id(target)}"
    end
  end

  lines.join("\n")
end