Current section
Files
Jump to
Current section
Files
lib/ui/arch_map_rendering/plantuml_renderer.ex
defmodule CleanMixer.UI.ArchMapRendering.PlantUMLRenderer do
alias CleanMixer.ArchMap
alias CleanMixer.ArchMap.Component
alias CleanMixer.ArchMap.Dependency
alias CleanMixer.Metrics.MetricsMap
alias CleanMixer.Metrics.ComponentMetrics.FanIn
alias CleanMixer.Metrics.ComponentMetrics.FanOut
alias CleanMixer.Metrics.ComponentMetrics.Instability
alias CleanMixer.Metrics.ComponentMetrics.Stability
alias CleanMixer.Metrics.ComponentMetrics.Abstractness
alias CleanMixer.Metrics.ComponentMetrics.Distance
alias CleanMixer.Metrics.ComponentMetrics.PublicFiles
alias CleanMixer.Metrics.ComponentMetrics.AbstractIn
alias CleanMixer.Metrics.ComponentMetrics.AbstractOut
alias CleanMixer.Metrics.DependencyMetrics.Usage
@spec render(ArchMap.t(), MetricsMap.t(Component.t()), MetricsMap.t(Dependency.t()), map) :: String.t()
def render(arch_map, component_metrics, dependency_metrics, params) do
[
"@startuml",
"skinparam backgroundColor<<dep>> Grey",
"skinparam legend {\n FontSize 20\n }",
"legend bottom left\n #{legend(params)} \n endlegend",
format_components(arch_map.components, group_tag(params), component_metrics),
Enum.map(arch_map.dependencies, &format_dependency(&1, component_metrics, dependency_metrics, params)),
"@enduml"
]
|> List.flatten()
|> Enum.join("\n")
end
defp group_tag(%{group: true}), do: :group
defp group_tag(_params), do: :group_tag_undefined
defp format_components(components, group_tag, component_metrics) do
components
|> Enum.group_by(&get_in(&1.meta, [:tags, group_tag]))
|> Enum.map(&format_component_group(&1, component_metrics))
end
defp format_component_group({_group_name = nil, components}, component_metrics) do
Enum.map(components, &format_component(&1, component_metrics))
end
defp format_component_group({group_name, components}, component_metrics) do
[
~s(package "#{group_name}" {),
Enum.map(components, &format_component(&1, component_metrics)),
"}"
]
end
defp legend(params) do
main_legend = [
"I = Instability = out / (in + out)",
"S = Stability = 1 - I",
"A = Abstractness = behaviours / total_modules",
"D = Distance = |A+I-1|",
"Tf = Total files",
"Pf = Public files",
"Ain = Abstract in",
"Aout = Abstract out"
]
full_legend =
if params[:verbose] do
[main_legend, "U = Usage = UsedFiles / Pf"]
else
main_legend
end
full_legend |> List.flatten() |> Enum.join("\n")
end
defp format_component(comp, metrics_map) do
"rectangle #{sanitize(comp.name)} #{colour(comp)} [ =#{comp.name} \n\n #{component_desc(comp, metrics_map)} ]"
end
defp colour(comp) do
if get_in(comp.meta, [:tags, :dep]) do
"#LightGrey"
else
""
end
end
defp component_desc(comp, metrics_map) do
metrics = MetricsMap.metrics_of(metrics_map, comp)
fan_in = metrics[FanIn]
fan_out = metrics[FanOut]
stability = format_metric(metrics[Stability])
instability = format_metric(metrics[Instability])
abstractness = format_metric(metrics[Abstractness])
distance = format_metric(metrics[Distance])
distance_sigmas = format_metric(MetricsMap.sigmas_count(metrics_map, comp, Distance))
total_files = Enum.count(comp.files)
public_files = metrics[PublicFiles]
abstract_in = metrics[AbstractIn]
abstract_out = metrics[AbstractOut]
"In=#{fan_in} Out=#{fan_out} S=#{stability} (I=#{instability}) \n" <>
"A=#{abstractness} D=#{distance} (#{distance_sigmas}σ) \n" <>
"Tf=#{total_files} Pf=#{public_files} Ain=#{abstract_in} Aout=#{abstract_out}"
end
defp format_dependency(%Dependency{} = dep, component_metrics, dependency_metrics, params) do
link = "[#{sanitize(dep.source.name)}] -[#{link_style(dep, component_metrics)}]-> [#{sanitize(dep.target.name)}]"
if params[:verbose] do
usage = dependency_metrics |> MetricsMap.metric(dep, Usage) |> format_metric()
link <> " : " <> "U=#{usage}"
else
link
end
end
defp link_style(%Dependency{} = dep, component_metrics) do
source_stability = MetricsMap.metric(component_metrics, dep.source, Stability)
target_stability = MetricsMap.metric(component_metrics, dep.target, Stability)
if source_stability > target_stability do
"#red,bold"
else
"#black"
end
end
defp format_metric(value) do
Float.round(value, 2)
end
defp sanitize(name) do
name
|> String.replace(" ", "_")
|> String.replace("/", "_")
|> String.replace("-", "_")
end
end