defmodule EarmarkAstDsl do import __MODULE__.Table, only: [make_table: 2] import __MODULE__.Atts, only: [make_atts: 1, only_atts: 1] use EarmarkAstDsl.Types @moduledoc """ EarmarkAstDsl is a toolset to generate EarmarkParser conformant AST Nodes. Its main purpose is to remove boilerplate code from Earmark and EarmarkParser tests. Documentation for `EarmarkAstDsl`. ### `tag` The most general helper is the tag function: iex(1)> tag("div", "Some content") {"div", [], ["Some content"]} Content and attributes can be provided as arrays, ... iex(2)> tag("p", ~w[Hello World], class: "hidden") {"p", [{"class", "hidden"}], ["Hello", "World"]} ... or maps: iex(3)> tag("p", ~w[Hello World], %{class: "hidden"}) {"p", [{"class", "hidden"}], ["Hello", "World"]} ### Shortcuts for `p` and `div` iex(4)> p("A para") {"p", [], ["A para"]} iex(5)> div(tag("span", "content")) {"div", [], [{"span", [], ["content"]}]} """ @spec div(content_t(), free_atts_t()) :: ast_t() def div(content \\ [], atts \\ []), do: tag("div", content, atts) @spec p(content_t(), free_atts_t()) :: ast_t() def p(content \\ [], atts \\ []), do: tag("p", content, atts) @doc """ ### Tables Tables are probably the _raison d'ĂȘtre_ ot this little lib, as their ast is quite verbose, as we will see here: iex(6)> table("one cell only") # and look at the output {"table", [], [ {"tbody", [], [ {"tr", [], [ {"td", [{"style", "text-align: left;"}], ["one cell only"]} ]} ]} ]} Now if we want a header and have some more data: iex(7)> table([~w[1-1 1-2], ~w[2-1 2-2]], head: ~w[left right]) # This is quite verbose! {"table", [], [ {"thead", [], [ {"tr", [], [ {"th", [{"style", "text-align: left;"}], ["left"]}, {"th", [{"style", "text-align: left;"}], ["right"]}, ]} ]}, {"tbody", [], [ {"tr", [], [ {"td", [{"style", "text-align: left;"}], ["1-1"]}, {"td", [{"style", "text-align: left;"}], ["1-2"]}, ]}, {"tr", [], [ {"td", [{"style", "text-align: left;"}], ["2-1"]}, {"td", [{"style", "text-align: left;"}], ["2-2"]}, ]} ]} ]} And tables can easily be aligned differently in Markdown, which makes some style helpers very useful iex(8)> table([~w[1-1 1-2], ~w[2-1 2-2]], ...(8)> head: ~w[alpha beta], ...(8)> text_aligns: ~w[right center]) {"table", [], [ {"thead", [], [ {"tr", [], [ {"th", [{"style", "text-align: right;"}], ["alpha"]}, {"th", [{"style", "text-align: center;"}], ["beta"]}, ]} ]}, {"tbody", [], [ {"tr", [], [ {"td", [{"style", "text-align: right;"}], ["1-1"]}, {"td", [{"style", "text-align: center;"}], ["1-2"]}, ]}, {"tr", [], [ {"td", [{"style", "text-align: right;"}], ["2-1"]}, {"td", [{"style", "text-align: center;"}], ["2-2"]}, ]} ]} ]} Some leeway is given for the determination of the number of columns, bear in mind that Markdown only supports regularly shaped tables with a fixed number of columns. Problems might arise when we have a table like the following | alpha | | beta *gamma* | where the first cell contains one element, but the second two, we can hint that we only want one by grouping into tuples iex(9)> table(["alpha", {"beta", tag("em", "gamma")}]) {"table", [], [ {"tbody", [], [ {"tr", [], [ {"td", [{"style", "text-align: left;"}], ["alpha"]}, ]}, {"tr", [], [ {"td", [{"style", "text-align: left;"}], ["beta", {"em", [], ["gamma"]}]} ]} ]} ]} """ @spec table(table_t(), free_atts_t()) :: ast_t() def table(rows, atts \\ []) def table(rows, atts) when is_binary(rows), do: table([rows], atts) def table(rows, atts) do tag("table", make_table(rows, atts), only_atts(atts)) end @spec tag(maybe(binary()), content_t(), free_atts_t()) :: ast_t() def tag(name, content \\ [], atts \\ []) def tag(name, nil, atts), do: tag(name, [], atts) def tag(name, content, atts) when is_binary(content) or is_tuple(content), do: tag(name, [content], atts) def tag(name, content, atts) do {to_string(name), make_atts(atts), content} end end