Packages
**The extended, configurable markdown-like syntax parser, that produces an AST.** Supports the full set of `markdown`, plus extensions (custom markup with a bit of elixir code to handle parsing.) The AST produced is understandable by [`XmlBuilder`](https://github.com/joshnuss/xml_builder).
Current section
Files
Jump to
Current section
Files
lib/markright.ex
defmodule Markright do
@moduledoc """
Custom syntax `Markdown`-like text processor.
"""
@behaviour Markright.Parser
alias Markright.Continuation, as: Plume
@doc """
Main application helper: call
`Markright.to_ast(input, fn {ast, tail} -> IO.inspect(ast) end)`
to transform the markright into the AST, optionally being called back
on every subsequent transform.
## Examples
iex> Markright.to_ast("Plain string.")
{:article, %{}, [{:p, %{}, "Plain string."}]}
iex> input = "plain *bold* rest!"
iex> Markright.to_ast(input)
{:article, %{}, [{:p, %{}, ["plain ", {:strong, %{}, "bold"}, " rest!"]}]}
iex> input = "plain *bold1* _italic_ *bold2* rest!"
iex> Markright.to_ast(input)
{:article, %{},
[{:p, %{}, ["plain ", {:strong, %{}, "bold1"}, " ", {:em, %{}, "italic"}, " ",
{:strong, %{}, "bold2"}, " rest!"]}]}
iex> input = "plainplainplain *bold1bold1bold1* and *bold21bold21bold21 _italicitalicitalic_ bold22bold22bold22* rest!"
iex> Markright.to_ast(input)
{:article, %{},
[{:p, %{}, ["plainplainplain ", {:strong, %{}, "bold1bold1bold1"}, " and ",
{:strong, %{},
["bold21bold21bold21 ", {:em, %{}, "italicitalicitalic"},
" bold22bold22bold22"]}, " rest!"]}]}
iex> input = "_Please ~use~ love **`Markright`** since it is *great*_!"
iex> Markright.to_ast(input)
{:article, %{},
[{:p, %{}, [
{:em, %{},
["Please ", {:strike, %{}, "use"}, " love ",
{:b, %{}, [{:code, %{}, "Markright"}]}, " since it is ",
{:strong, %{}, "great"}]}, "!"]}]}
iex> input = "Escaped /*asterisk"
iex> Markright.to_ast(input)
{:article, %{}, [{:p, %{}, "Escaped *asterisk"}]}
iex> input = "Escaped \\\\*asterisk 2"
iex> Markright.to_ast(input)
{:article, %{}, [{:p, %{}, "Escaped *asterisk 2"}]}
"""
def to_ast(input, fun_or_collector \\ nil, opts \\ [])
def to_ast(input, fun, opts)
when is_binary(input) and (is_nil(fun) or is_function(fun)) and is_list(opts) do
mod =
case opts[:preset] do
nil -> Markright.Utils.to_preset_module(:article)
mod when is_atom(mod) -> Markright.Utils.to_preset_module(mod)
{mod, nil} -> Markright.Utils.to_preset_module(mod)
{mod, params} -> Markright.Utils.to_preset_module(mod, params)
end
with plume <- %Plume{fun: fun},
%Plume{ast: ast} <-
apply(
mod,
:to_ast,
[input, plume, Keyword.put_new(opts, :syntax, apply(mod, :syntax, []))]
),
do: ast
end
def to_ast(input, collector, opts)
when is_binary(input) and is_atom(collector) and is_list(opts) do
apply(collector, :start_link, [])
fun = apply(collector, :on_ast_callback, [])
ast = to_ast(input, fun, opts)
collected = apply(collector, :ast_collected, [])
afterwards = apply(collector, :afterwards, [collected])
{ast, afterwards}
after
apply(collector, :stop, [])
end
# IO.inspect Markright.Utils.all_of("Markright"), label: "★★★"
# TODO: smth like @before_compile
[Markright.Presets.Article, Markright.Presets.Empty]
|> Enum.each(fn mod ->
# Module.put_attribute(__MODULE__, :mod, mod)
def unquote(:"#{Markright.Utils.atomic_module_name(mod)}!")(input, fun \\ nil, opts \\ []) do
module_opts = opts[:module_opts]
params =
opts
|> Keyword.delete(:module_opts)
|> Enum.into(%{})
opts = [{:preset, {unquote(mod), module_opts}}, {:params, params}]
to_ast(input, fun, opts)
end
end)
@doc """
Most shame part of this package: here we use `Regex` because, you know, fuck Windows.
@fixme
"""
def to_ast_safe_input(input, fun \\ nil, opts \\ []) do
to_ast(Regex.replace(~r/\r\n|\r/, input, "\n"), fun, opts)
end
##############################################################################
def main(args) do
args |> parse_args |> process
end
def process([]) do
IO.puts("Usage: markright --file=filename.md")
end
def process(options) do
if File.exists?(options[:file]) do
result =
options[:file]
|> File.read!()
|> to_ast
|> XmlBuilder.generate()
# result = if options[:squeeze], do: result |> String.split |> Enum.join(" "), else: result
if options[:silent], do: result, else: IO.puts(result)
else
IO.puts("Unable to read file #{options[:file]}")
end
end
defp parse_args(args) do
{options, _, _} =
OptionParser.parse(args,
switches: [file: :string]
)
options
end
end