defmodule Dependents.Tree.Proxy do @moduledoc """ Creates a `Dependents.Tree` of all local apps. Also converts a `Dependents.Tree` into table maps. """ alias Dependents.Tree.DotGraph alias Dependents.Tree @doc """ Creates a `Dependents.Tree` of all local apps. ## Examples iex> alias Dependents.Tree.Proxy iex> tree = Proxy.new() iex> %{log_reset: [n1 | deps1], io_ansi_table: [n2 | deps2]} = tree iex> Enum.all?(deps1, &is_atom/1) and Enum.all?(deps2, &is_atom/1) and ...> is_integer(n1) and is_integer(n2) true """ @spec new :: Tree.t() def new do paths = Path.wildcard("#{Tree.projects_dir()}/*/deps_tree.dot") dirs = Enum.map(paths, &DotGraph.dir/1) Enum.zip(paths, dirs) |> Enum.reject(fn {_path, dir} -> is_nil(dir) end) |> Enum.map(&Task.async(DotGraph, :to_tree, [&1, dirs])) |> Enum.map(&Task.await/1) |> Enum.reduce(%{}, fn tree, acc_tree -> Map.merge(acc_tree, tree, fn _app, deps, [dep] -> [dep | deps] end) end) # Number of dependencies will be first element... |> Map.new(fn {app, deps} -> {app, Enum.sort(deps)} end) end @doc """ Converts a `Dependents.Tree` into table maps. ## Examples iex> alias Dependents.Tree.Proxy iex> tree = %{ ...> io_ansi_table: [3, :noaa_observations, :github_issues], ...> map_sorter: [4, :io_ansi_table] ...> } iex> ranks = %{io_ansi_table: 27, map_sorter: 25} iex> maps = Proxy.to_maps(tree, ranks) iex> [ ...> %{ ...> app: :io_ansi_table, chunk: 1, ...> dependent_1: :noaa_observations, ...> dependent_2: :github_issues, ...> dependent_3: nil, ...> dependent_4: nil, ...> dcys: 3, deps: 2, hex: "Y", rank: 27, ver: ver_27 ...> }, ...> %{ ...> app: :map_sorter, chunk: 1, ...> dependent_1: :io_ansi_table, ...> dependent_2: nil, ...> dependent_3: nil, ...> dependent_4: nil, ...> dcys: 4, deps: 1, hex: "Y", rank: 25, ver: ver_25 ...> } ...> ] = maps iex> is_binary(ver_27) and is_binary(ver_25) true """ @spec to_maps(Tree.t(), Tree.ranks()) :: [Tree.table_map()] def to_maps(tree, ranks) do for {app, [dcys | deps]} <- tree do chunk_deps(deps) |> Enum.with_index(1) |> Enum.map(fn {[dep1, dep2, dep3, dep4], index} -> _table_map = %{ rank: ranks[app], chunk: index, ver: ver(app) |> zap_dup(index), hex: hex?(app) |> zap_dup(index), dcys: zap_dup(dcys, index), deps: length(deps) |> zap_dup(index), app: zap_dup(app, index), dependent_1: dep1, dependent_2: dep2, dependent_3: dep3, dependent_4: dep4 } end) end |> List.flatten() end ## Private functions @spec ver(Tree.app()) :: String.t() defp ver(app) do [_full, major, minor, patch] = Regex.run(~r|version: "(\d+)\.(\d+)\.(\d+)"|, mix_text(app)) "#{major}.#{minor}.#{patch}" end @spec hex?(Tree.app()) :: boolean defp hex?(app) do !!Regex.run(~r|package: \w+|, mix_text(app)) end @spec mix_text(Tree.app()) :: String.t() defp mix_text(app), do: File.read!("#{Tree.projects_dir()}/#{app}/mix.exs") @spec chunk_deps([Tree.dep()]) :: [[Tree.dep() | nil]] defp chunk_deps([]), do: [[nil, nil, nil, nil]] defp chunk_deps(deps), do: Enum.chunk_every(deps, 4, 4, [nil, nil, nil]) @spec zap_dup(any, non_neg_integer) :: any | nil defp zap_dup(true, index) do zap_dup("Y", index) end defp zap_dup(false, index) do zap_dup("n", index) end defp zap_dup(value, index) do if index == 1, do: value, else: nil end end