diff --git a/lib/ex_doc.ex b/lib/ex_doc.ex index fbe26f60f..320733c02 100644 --- a/lib/ex_doc.ex +++ b/lib/ex_doc.ex @@ -3,6 +3,8 @@ defmodule ExDoc do Elixir Documentation System. ExDoc produces documentation for Elixir projects """ + alias ExDoc.Markdown + defmodule Config do @moduledoc """ Configuration structure that holds all the available options for ExDoc @@ -39,6 +41,7 @@ defmodule ExDoc do language: @default.language, logo: nil, main: nil, + markdown_processor: nil, output: @default.output, project: nil, retriever: @default.retriever, @@ -67,6 +70,7 @@ defmodule ExDoc do language: String.t, logo: nil | Path.t, main: nil | String.t, + markdown_processor: nil | atom, output: nil | Path.t, project: nil | String.t, retriever: :atom, @@ -102,6 +106,12 @@ defmodule ExDoc do # Builds configuration by merging `options`, and normalizing the options. @spec build_config(String.t, String.t, Keyword.t) :: ExDoc.Config.t defp build_config(project, vsn, options) do + # The app environment has priority over the mix config + processor_from_env = Markdown.get_markdown_processor_from_app_env() + if !processor_from_env && options[:markdown_processor] do + Markdown.set_markdown_processor(options[:markdown_processor]) + end + # After this, we have no use for the :markdown processor option options = normalize_options(options) preconfig = %Config{ project: project, diff --git a/lib/ex_doc/markdown.ex b/lib/ex_doc/markdown.ex index f747ab2c6..8030e3787 100644 --- a/lib/ex_doc/markdown.ex +++ b/lib/ex_doc/markdown.ex @@ -47,13 +47,23 @@ defmodule ExDoc.Markdown do bin end + @doc false + def get_markdown_processor_from_app_env do + Application.get_env(:ex_doc, @markdown_processor_key) + end + + @doc false + def set_markdown_processor(processor) do + Application.put_env(:ex_doc, @markdown_processor_key, processor) + end + defp get_markdown_processor do case Application.fetch_env(:ex_doc, @markdown_processor_key) do {:ok, processor} -> processor :error -> processor = find_markdown_processor() || raise_no_markdown_processor() - Application.put_env(:ex_doc, @markdown_processor_key, processor) + set_markdown_processor(processor) processor end end diff --git a/lib/ex_doc/markdown/mocks.ex b/lib/ex_doc/markdown/mocks.ex new file mode 100644 index 000000000..0c7fac1d0 --- /dev/null +++ b/lib/ex_doc/markdown/mocks.ex @@ -0,0 +1,18 @@ +defmodule ExDoc.Markdown.MockMarkdownProcessor.MockMarkdownProcessorError do + @moduledoc false + + defexception message: "You're using a mock markdown processor" + end + +defmodule ExDoc.Markdown.MockMarkdownProcessor do + @moduledoc false + + # To be used in only in testing + + def to_html(_, _) do + # When we get an exception, we know this processor's been used + # This seems to be the easiest way to ensure we're choosing the right processor + # without instrumenting the code + raise ExDoc.Markdown.MockMarkdownProcessor.MockMarkdownProcessorError + end +end diff --git a/test/ex_doc/markdown_processor_option_test.exs b/test/ex_doc/markdown_processor_option_test.exs new file mode 100644 index 000000000..f5a9d707c --- /dev/null +++ b/test/ex_doc/markdown_processor_option_test.exs @@ -0,0 +1,84 @@ +defmodule ExDoc.MarkdownProcessorOptionTest do + use ExUnit.Case + + alias ExDoc.Markdown.MockMarkdownProcessor + alias ExDoc.Markdown.MockMarkdownProcessor.MockMarkdownProcessorError + + setup do + File.rm_rf(output_dir()) + File.mkdir_p!(output_dir()) + end + + defp output_dir do + Path.expand("../../tmp/html", __DIR__) + end + + defp beam_dir do + Path.expand("../../tmp/beam", __DIR__) + end + + defp doc_config_markdown_processor_not_set do + [project: "Elixir", + version: "1.0.1", + formatter: "html", + assets: "test/tmp/html_assets", + output: output_dir(), + source_root: beam_dir(), + source_beam: beam_dir(), + logo: "test/fixtures/elixir.png", + extras: ["test/fixtures/README.md"]] + end + + defp doc_config_markdown_processor_set do + # MockMarkdownProcessor will raise an exception if used to process files + # This exception will be our way to tell that someting's not right. + Keyword.put(doc_config_markdown_processor_not_set(), + :markdown_processor, + MockMarkdownProcessor) + end + + def generate_docs(config) do + # A wrapper around the child process, so that we can use assert_raise below. + # Propagates the MockMarkdownProcessorError exception from the chid process. + # Raises a MockMarkdownProcessorError if the exception causes the child process to exit. + Process.flag(:trap_exit, true) + try do + ExDoc.generate_docs(config[:project], config[:version], config) + catch + # Raise the exception so that it can be captured by assert_raise + :exit, {{%MockMarkdownProcessorError{} = exception, _}, _} -> raise exception + # The purpose of these tests is to detect if the right markdown processor is used. + _ -> :ok + end + end + + test "app env not set, markdown_processor option set" do + Application.delete_env(:ex_doc, :markdown_processor) + # ExDoc.Markdown should respect our choice and run the mock processor + assert_raise MockMarkdownProcessorError, fn -> + generate_docs doc_config_markdown_processor_set() + end + end + + test "app env not set, markdown_processor option not set" do + Application.delete_env(:ex_doc, :markdown_processor) + generate_docs doc_config_markdown_processor_not_set() + end + + test "app env set, markdown_processor option set" do + Application.put_env(:ex_doc, :markdown_processor, MockMarkdownProcessor) + # It's been given two mock processors + assert_raise MockMarkdownProcessorError, fn -> + generate_docs doc_config_markdown_processor_set() + end + end + + test "app env set, markdown_processor option not set" do + Application.put_env(:ex_doc, :markdown_processor, MockMarkdownProcessor) + # The app env has priority + assert_raise MockMarkdownProcessorError, fn -> + generate_docs doc_config_markdown_processor_not_set() + end + end + +end