Packages

Providing a web interface to interact with mix xref graph output

Current section

Files

Jump to
ex_compile_graph lib ex_compile_graph.ex
Raw

lib/ex_compile_graph.ex

defmodule ExCompileGraph do
@moduledoc """
Module contains API to handle Web UI interactions
"""
@type manifest_path :: binary()
@type file_path :: binary()
def init() do
:ets.new(__MODULE__.Cache, [:set, :named_table, :public])
:ok
end
def get_graph() do
manifest = Mix.Project.manifest_path() <> "/compile.elixir"
graph = __MODULE__.Graph.build(manifest)
# Cache for later use
:ets.insert(__MODULE__.Cache, {:graph, graph})
graph
|> __MODULE__.Graph.summarize()
|> Enum.map(fn vertex ->
recompile = __MODULE__.Dependency.recompile_dependencies(graph, vertex.id)
# These sets should be non-overlapping
recompile_dependencies =
Enum.reduce(recompile, [], fn {reason, dependency}, acc ->
dependency
|> Enum.map(fn {file, path} ->
:ets.insert(__MODULE__.Cache, {{:dependency_path, vertex.id, file, reason}, path})
%{id: file, reason: reason}
end)
|> Enum.concat(acc)
end)
Map.put(vertex, :recompile_dependencies, recompile_dependencies)
end)
end
def get_recompile_dependency_causes(source_file, sink_file, reason) do
[{_, path}] =
:ets.lookup(__MODULE__.Cache, {:dependency_path, sink_file, source_file, reason})
get_detailed_explanation(path ++ [{:eof, sink_file}])
|> IO.inspect(label: inspect({source_file, sink_file, reason}))
end
# Expand the dependency path to a detailed explanation
# Consecutive runtime links could be collapse into one node
defp get_detailed_explanation(path) do
initial_state = %{consecutive_runtime_links: [], result: [], prev: nil}
get_detailed_explanation(path, initial_state)
end
defp get_detailed_explanation([], state), do: state.result
defp get_detailed_explanation([{:runtime, file} | tail], state) do
state =
Map.update!(state, :consecutive_runtime_links, &(&1 ++ [file]))
|> Map.put(:prev, file)
get_detailed_explanation(tail, state)
end
defp get_detailed_explanation([{type, file} | tail], state)
when type in [:exports, :compile, :eof] do
state =
if state.consecutive_runtime_links != [] do
new_entry = %{
type: :runtime,
source: hd(state.consecutive_runtime_links),
intermediates: tl(state.consecutive_runtime_links),
snippets: nil
}
Map.update!(state, :result, &(&1 ++ [new_entry]))
|> Map.put(:consecutive_runtime_links, [])
else
state
end
state =
if type != :eof do
manifest = Mix.Project.manifest_path() <> "/compile.elixir"
# It's guarantee that we always have a next file here
{_, next_file} = hd(tail)
snippets =
ExCompileGraph.Dependency.dependency_causes(%{
source_file: file,
sink_file: next_file,
manifest: manifest,
dependency_type: type
})
|> Enum.map(&extract_snippet/1)
new_entry = %{
type: type,
source: file,
intermediates: [],
snippets: snippets
}
Map.update!(state, :result, &(&1 ++ [new_entry]))
else
state
end
state = Map.put(state, :prev, file)
get_detailed_explanation(tail, state)
end
@lines_span_padding 5
defp extract_snippet(%ExCompileGraph.Dependency.Cause{origin_file: file, lines_span: lines_span}) do
# We want to add some paddings to the lines span
{from, to} = lines_span
from = max(from - @lines_span_padding, 1)
to = to + @lines_span_padding
lines =
File.stream!(file, [], :line)
|> Stream.drop(from - 1)
|> Stream.take(to - from + 1)
|> Enum.to_list()
# to may exceeds the file lines count
to = min(from + length(lines) - 1, to)
%{
content: Enum.join(lines),
lines_span: [from, to],
highlight: Tuple.to_list(lines_span)
}
end
end