Skip to main content

lib/ex_check/manifest.ex

defmodule ExCheck.Manifest do
  @moduledoc false

  def convert_retry_to_only(opts) do
    if Keyword.get(opts, :retry) do
      only =
        opts
        |> get_failed_tools()
        |> Enum.map(&{:only, &1})
        |> case do
          [] -> [{:only, "-"}]
          only -> only
        end

      opts ++ only
    else
      opts
    end
  end

  # sobelow_skip ["Traversal.FileModule", "DOS.StringToAtom"]
  def get_failed_tools(opts) do
    manifest_path = get_path(opts)

    if File.exists?(manifest_path) do
      manifest_path
      |> File.read!()
      |> String.split("\n")
      |> Enum.map(fn
        "FAIL " <> tool -> deserialize_tool_name_without_app(tool)
        _ -> nil
      end)
      |> Enum.filter(& &1)
      |> Enum.map(&String.to_atom/1)
      |> Enum.uniq()
    else
      []
    end
  end

  defp deserialize_tool_name_without_app(name) do
    case String.split(name, "/") do
      [_app, tool] -> tool
      [tool] -> tool
    end
  end

  # sobelow_skip ["Traversal.FileModule"]
  def save(checks, opts) do
    lines = Enum.map(checks, &serialize_check/1)
    content = Enum.join(lines, "\n") <> "\n"
    path = get_path(opts)

    File.write!(path, content)
  end

  defp serialize_check({:ok, {name, _, _}, _}), do: "PASS #{serialize_tool_name(name)}"
  defp serialize_check({:error, {name, _, _}, _}), do: "FAIL #{serialize_tool_name(name)}"
  defp serialize_check({:skipped, name, _}), do: "SKIP #{serialize_tool_name(name)}"

  defp serialize_tool_name({tool, app}), do: "#{app}/#{tool}"
  defp serialize_tool_name(tool), do: tool

  @escape Enum.map(~c" [~#%&*{}\\:<>?/+|\"]", &<<&1::utf8>>)

  defp get_path(opts) do
    Keyword.get_lazy(opts, :manifest, fn ->
      app_id = File.cwd!() |> String.replace(@escape, "_") |> String.replace(~r/^_+/, "")
      Path.join([System.tmp_dir!(), "ex_check_ng-manifest-#{app_id}.txt"])
    end)
  end
end