defmodule Mix.Tasks.Compile.Apidoc do use Mix.Task @moduledoc File.read!("README.md") @shortdoc "Create documentation for RESTful web APIs" @default_node_bin "node" @default_output_dir Path.join(~w"priv static apidoc") @phx_version_gt_13_regex ~r/(1\.[3-]\.\d+|[2-]\.\d+\.\d+)/ @doc false def run(_) do config = Mix.Project.config[:apidoc] unless config do Mix.raise "Please specify an apidoc config in your mix.exs" end cond do config == [] -> Mix.Shell.IO.info "Omitting apidoc generation for #{Mix.env} environment." is_list(config) && not Keyword.keyword?(config) -> for c <- config, do: run_for_config(c) true -> run_for_config(config) end end defp run_for_config(config) do unless Keyword.keyword?(config) do Mix.raise "The apidoc config in your mix.exs must be a keyword list " <> "or list of keyword lists." end node_bin = config[:node_bin] || @default_node_bin apidoc_bin = get_apidoc_bin(config) input_dir = get_input_dir(config) output_dir = config[:output_dir] || @default_output_dir extra_args = config[:extra_args] || [] config_json = config |> Enum.into(%{}) |> Map.drop(~w(node_bin apidoc_bin input_dir output_dir extra_args)a) |> Poison.encode! build_dir = Mix.Project.build_path File.mkdir_p! build_dir apidoc_json = build_dir |> Path.join("apidoc.json") |> File.open!([:write, :utf8]) IO.write apidoc_json, config_json File.close apidoc_json params = ["-i", input_dir, "-o", output_dir, "-c", build_dir] ++ extra_args api_title = config[:title] || config[:name] || "api" run_apidoc(node_bin, apidoc_bin, params, api_title) end defp run_apidoc(node_bin, apidoc_bin, params, api_title) do if File.exists?(apidoc_bin) do exec_apidoc(node_bin, apidoc_bin, params, api_title) else apidoc_global = System.find_executable(apidoc_bin) if apidoc_global |> is_nil do Mix.raise "Could not find apidoc executable '#{apidoc_bin}'. " <> "Run 'npm install' or set the 'apidoc_bin' config parameter " <> "in your 'mix.exs' to a different apidoc executable." else exec_apidoc(node_bin, apidoc_global, params, api_title) end end end defp exec_apidoc(node_bin, apidoc_bin, params, api_title) do node_bin |> System.cmd([apidoc_bin | params]) |> _handle_result(api_title) end defp _handle_result({_, 0}, api_title) do Mix.Shell.IO.info "Generated apidoc for #{api_title}." end defp _handle_result(_error, api_title) do Mix.raise "apidoc responded with an error for #{api_title}." end defp get_apidoc_bin(config) do phx_ver_req = get_phx_version() cond do config[:apidoc_bin] != nil -> config[:apidoc_bin] phx_ver_req != nil && Regex.run(@phx_version_gt_13_regex, phx_ver_req) -> Path.join([System.cwd()] ++ ~w"assets node_modules apidoc bin apidoc") true -> Path.join([System.cwd()] ++ ~w"node_modules apidoc bin apidoc") end end defp get_input_dir(config) do phx_ver_req = get_phx_version() app_name = Mix.Project.config[:app] |> Atom.to_string cond do config[:input_dir] != nil -> config[:input_dir] phx_ver_req != nil && Regex.run(@phx_version_gt_13_regex, phx_ver_req) -> Path.join(~w"lib #{app_name}_web controllers") true -> Path.join(~w"web controllers") end end defp get_phx_version, do: Application.spec(:phoenix, :vsn) |> to_string end