defmodule Dependents.Tree.DotGraph do @moduledoc """ Converts a DOT graph (file `deps_tree.dot`) into a `Dependents.Tree`. Also returns the directory of a DOT graph given its path. """ alias Dependents.Tree @doc ~S""" Returns the `Dependents.Tree` of a DOT graph. A DOT graph line maps an app to a dependency. For example: ``` "noaa_observations" "noaa_observations" -> "ex_doc" [label="~> 0.22"] "ex_doc" -> "earmark_parser" [label="~> 1.4.0"] "noaa_observations" -> "io_ansi_table" [label="~> 1.0"] "noaa_observations" -> "persist_config" [label="~> 0.4"] ``` Converted into a `Dependents.Tree`, the above 5 lines become: ``` %{ # Number of local dependencies... noaa_observations: [2], io_ansi_table: [:noaa_observations], persist_config: [:noaa_observations] } ``` ## Examples iex> alias Dependents.Tree.DotGraph iex> projs_dir = "c:/Users/Ray/Documents/ex_dev/projects" iex> dir = "noaa_observations" iex> path = "#{projs_dir}/#{dir}/deps_tree.dot" iex> dirs = ["io_ansi_table", "log_reset", "persist_config", dir] iex> DotGraph.to_tree({path, dir}, dirs) %{ noaa_observations: [3], log_reset: [:noaa_observations], io_ansi_table: [:noaa_observations], persist_config: [:noaa_observations] } """ @spec to_tree({Path.t(), dir :: String.t()}, [String.t()]) :: Tree.t() def to_tree({path, dir} = _dot_graph_path_and_dir, dirs) do tree = for line <- File.stream!(path), into: %{} do with [app, dep] <- String.split(line, "->") |> Enum.map(&String.trim/1), [_full, app] <- Regex.run(~r|^"(\w+)"$|, app), true <- app == dir, [_full, dep] <- Regex.run(~r|^"(\w+)" \[.+\]$|, dep), true <- dep in dirs do {String.to_atom(dep), [String.to_atom(dir)]} else _non_matched -> {String.to_atom(dir), nil} end end # [Number of dependencies for `app`]... Map.put(tree, String.to_atom(dir), [map_size(tree) - 1]) end @doc ~S""" Returns the directory of a DOT graph (file `deps_tree.dot`) given its `path`. ## Examples iex> alias Dependents.Tree.DotGraph iex> projs_dir = "c:/Users/Ray/Documents/ex_dev/projects" iex> path1 = "#{projs_dir}/file_only_logger/deps_tree.dot" iex> path2 = "#{projs_dir}/file only logger/deps_tree.dot" iex> {DotGraph.dir(path1), DotGraph.dir(path2)} {"file_only_logger", nil} """ @spec dir(Path.t()) :: String.t() | nil def dir(path) do case Regex.run(~r|^.+/(\w+)/deps_tree.dot$|, path) do [_full, dir] -> dir # dir may contain spaces (not \w) nil -> nil end end end