Skip to content

Commit

Permalink
Fixes: #236
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertDober committed Jul 29, 2019
1 parent 0512273 commit 8895db6
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 121 deletions.
11 changes: 6 additions & 5 deletions lib/earmark/html_renderer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule Earmark.HtmlRenderer do
import Earmark.Inline, only: [convert: 3]
import Earmark.Helpers, only: [escape: 2]
import Earmark.Helpers.HtmlHelpers
import Earmark.Message, only: [add_messages_from: 2, add_messages: 2, get_messages: 1]
import Earmark.Message, only: [add_messages_from: 2, add_message: 2, add_messages: 2, get_messages: 1]
import Earmark.Context, only: [append: 2, set_value: 2]
import Earmark.Options, only: [get_mapper: 1]

Expand Down Expand Up @@ -190,11 +190,12 @@ defmodule Earmark.HtmlRenderer do
# Plugins #
###########

defp render_block(%Block.Plugin{lines: lines, handler: handler}, context) do
defp render_block(%Block.Plugin{lines: [{_, lnb}|_]=lines, handler: handler}, context) do
context1 = add_message(context, {:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", lnb})
case handler.as_html(lines) do
html when is_list(html) -> {context, html}
{html, errors} -> {add_messages(context, errors), html}
html -> {context, [html]}
html when is_list(html) -> {context1, html}
{html, errors} -> {add_messages(context1, errors), html}
html -> {context1, [html]}
end
end

Expand Down
113 changes: 2 additions & 111 deletions lib/earmark/plugin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,126 +3,17 @@ defmodule Earmark.Plugin do
alias Earmark.Options

@moduledoc """
Plugins are modules that implement a render function. Right now that is `as_html`.
### Plugin API
#### Plugin Registration
When invoking `Earmark.as_html(some_md, options)` we can register plugins inside the `plugins` map, where
each plugin is a value pointed to by the prefix triggering it.
Prefixes are appended to `"$$"` and lines starting by that string will be rendered by the registered plugin.
`%Earmark.Options{plugins: %{"" => CommentPlugin}}` would trigger the `CommentPlugin` for each block of
lines prefixed by `$$`, while `%Earmark.Options{plugins: %{"cp" => CommentPlugin}}` would do the same for
blocks of lines prefixed by `$$cp`.
Please see the documentation of `Plugin.define` for a convenience function that helps creating the necessary
`Earmark.Options` structs for the usage of plugins.
#### Plugin Invocation
`as_html` (or other render functions in the future) is invoked with a list of pairs containing the text
and line number of the lines in the block. As an example, if our plugin was registered with the default prefix
of `""` and the markdown to be converted was:
# Plugin output ahead
$$ line one
$$
$$ line two
`as_html` would be invoked as follows:
as_html([{"line one", 2}, {"", 3}, {"line two", 4})
#### Plugin Output
Earmark's render function will invoke the plugin's render function as explained above. It can then integrate the
return value of the function into the generated rendering output if it complies to the following criteria.
1. It returns a string
1. It returns a list of strings
1. It returns a pair of lists containing a list of strings and a list of error/warning tuples.
Where the tuples are of the form `{:error | :warning, line_number, descriptive_text}`
#### A complete example
iex> defmodule MyPlug do
...> def as_html(lines) do
...> # to demonstrate the three possible return values
...> case render(lines) do
...> {[line], []} -> line
...> {lines, []} -> lines
...> tuple -> tuple
...> end
...> end
...>
...> defp render(lines) do
...> Enum.map(lines, &render_line/1) |> Enum.split_with(&ok?/1)
...> end
...>
...> defp render_line({"", _}), do: "<hr/>"
...> defp render_line({"line one", _}), do: "<p>first line</p>\\n"
...> defp render_line({line, lnb}), do: {:error, lnb, line}
...>
...> defp ok?({_, _, _}), do: false
...> defp ok?(_), do: true
...> end
...>
...> lines = [
...> "# Plugin Ahead",
...> "$$ line one",
...> "$$",
...> "$$ line two",
...> ]
...> Earmark.as_html(lines, Earmark.Plugin.define(MyPlug))
{:error, "<h1>Plugin Ahead</h1>\\n<p>first line</p>\\n<hr/>", [{ :error, 4, "line two"}]}
#### Plugins, reusing Earmark
As long as you avoid endless recursion there is absolutely no problem to call `Earmark.as_html` in your plugin, consider the following
example in which the plugin will parse markdown and render html verbatim (which is stupid, that is what Earmark already does for you,
but just to demonstrate the possibilities):
iex> defmodule Again do
...> def as_html(lines) do
...> text_lines = Enum.map(lines, fn {str, _} -> str end)
...> {_, html, errors} = Earmark.as_html(text_lines)
...> { Enum.join([html | text_lines]), errors }
...> end
...> end
...> lines = [
...> "$$a * one",
...> "$$a * two",
...> ]
...> Earmark.as_html(lines, Earmark.Plugin.define({Again, "a"}))
{:ok, "<ul>\\n<li>one\\n</li>\\n<li>two\\n</li>\\n</ul>\\n* one* two", []}
DEPRECATED!!!
"""

@doc """
adds the definition of one or more plugins to `Earmark.Options`.
If the plugin is defined with the default prefix and no other options are needed
one can use the one parameter form:
iex> Earmark.Plugin.define(Earmark) # not a legal plugin of course
%Earmark.Options{plugins: %{"" => Earmark}}
More then one plugin can be defined, as long as all prefixes differ
iex> defmodule P1, do: nil
...> defmodule P2, do: nil
...> Earmark.Plugin.define([ Earmark, {P1, "p1"}, {P2, "p2"} ])
%Earmark.Options{plugins: %{"" => Earmark, "p1" => Unit.PluginTest.P1, "p2" => Unit.PluginTest.P2}}
DEPRECATED!!!
"""
def define(plugin_defs)
def define(plugin_defs), do: define(%Options{}, plugin_defs)


def define(options, plugin_defs)

def define(options, plugins) when is_list(plugins) do
Enum.reduce(plugins, options, fn plugin, acc -> define(acc, plugin) end)
end
Expand Down
17 changes: 12 additions & 5 deletions test/regressions/i106_plugin_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ defmodule Regressions.I106PluginTest do
"""
test "the comment plugin" do
assert as_html(@comment_md, %Options{plugins: %{"" => CommentPlugin}}) ==
{:ok, "<!-- this is a comment -->\n", [] } end
{:ok, "<!-- this is a comment -->\n", [{:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 1}] } end
@comments_md """
$$c comment one
$$c comment two
Expand All @@ -22,7 +22,10 @@ defmodule Regressions.I106PluginTest do
"""
test "more lines" do
assert as_html(@comments_md, Plugin.define({CommentPlugin, "c"})) ==
{:error, "<!-- comment one\ncomment two -->\n<!-- comment three -->\n", [{ :warning, 3, "lines for undefined plugin prefix \"unregistered\" ignored (3..3)"}]}
{:error, "<!-- comment one\ncomment two -->\n<!-- comment three -->\n",
[{ :warning, 3, "lines for undefined plugin prefix \"unregistered\" ignored (3..3)"},
{:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 1},
{:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 4}]}
end

@more_md """
Expand All @@ -33,7 +36,9 @@ defmodule Regressions.I106PluginTest do
"""
test "even more lines" do
assert as_html(@more_md, Plugin.define(%Options{}, [CommentPlugin])) ==
{:error, "<p>line one</p>\n<!-- comment one -->\n<p>line two</p>\n", [{ :warning, 3, "lines for undefined plugin prefix \"c\" ignored (3..3)"}]}
{:error, "<p>line one</p>\n<!-- comment one -->\n<p>line two</p>\n",
[{ :warning, 3, "lines for undefined plugin prefix \"c\" ignored (3..3)"},
{:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 2}]}
end

@mix_errors """
Expand All @@ -45,7 +50,8 @@ defmodule Regressions.I106PluginTest do
assert as_html(@mix_errors, Plugin.define(%Options{}, CommentPlugin)) ==
{:error, "<!-- comment -->\n<p></p>\n",[
{ :warning, 1, "lines for undefined plugin prefix \"unregistered\" ignored (1..1)"},
{ :warning, 3, "Unexpected line ="}]}
{ :warning, 3, "Unexpected line ="},
{:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 2}]}
end

@error_plugin """
Expand All @@ -60,7 +66,8 @@ defmodule Regressions.I106PluginTest do
assert as_html(@error_plugin, options) ==
{:error, "<!-- comment -->\n<p>data</p>\n<strong>correct</strong>", [
{:error, 4, "that is incorrect" },
{:warning, 5, "lines for undefined plugin prefix \"undef\" ignored (5..5)"}]}
{:warning, 5, "lines for undefined plugin prefix \"undef\" ignored (5..5)"},
{:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 1}, {:deprecation, "DEPRECATED: Plugins will be removed in Earmark 1.4", 3}]}
end
end

Expand Down

0 comments on commit 8895db6

Please sign in to comment.