Packages
earmark
1.2.2
1.5.0-pre1
retired
1.5.0-pre
retired
1.4.49
retired
1.4.48
retired
1.4.47
retired
1.4.46
retired
1.4.45
retired
1.4.44
retired
1.4.43
retired
1.4.42
retired
1.4.41
retired
1.4.40
retired
1.4.39
retired
1.4.38
retired
1.4.37
retired
1.4.36
retired
1.4.35
retired
1.4.34
retired
1.4.33
retired
1.4.32
retired
1.4.31
retired
1.4.30
retired
1.4.29
retired
1.4.28
retired
1.4.27
retired
1.4.26
retired
1.4.25
retired
1.4.24
retired
1.4.23
retired
1.4.22
retired
1.4.21
retired
1.4.20
retired
1.4.19
retired
1.4.18
retired
1.4.17
retired
1.4.16
retired
1.4.16-pre2
retired
1.4.16-pre1
retired
1.4.16-pre
retired
1.4.15
retired
1.4.14
retired
1.4.13
retired
1.4.12
retired
1.4.10
retired
1.4.9
retired
1.4.8
retired
1.4.7
retired
1.4.6
retired
1.4.5
retired
1.4.4
retired
1.4.3
retired
1.4.2
retired
1.4.1
retired
1.4.0
retired
1.3.6
retired
1.3.5
retired
1.3.4
retired
1.3.3
retired
1.3.2
retired
1.3.1
retired
1.3.0
retired
1.2.6
retired
1.2.5
retired
1.2.4
retired
1.2.3
retired
1.2.2
retired
1.2.1
retired
1.2.0
retired
1.1.1
retired
1.1.0
retired
1.0.3
retired
1.0.2
retired
1.0.1
retired
1.0.0
retired
0.2.1
retired
0.2.0
retired
0.1.19
retired
0.1.18
retired
0.1.17
retired
0.1.16
retired
0.1.15
retired
0.1.14
retired
0.1.13
retired
0.1.12
retired
0.1.10
retired
0.1.9
retired
0.1.8
retired
0.1.7
retired
0.1.6
retired
0.1.5
retired
0.1.4
retired
0.1.3
retired
0.1.2
retired
0.1.1
retired
0.1.0
retired
Earmark is a pure-Elixir Markdown converter. It is intended to be used as a library (just call Earmark.as_html), but can also be used as a command-line tool (run mix escript.build first). Output generation is pluggable.
Retired package: Deprecated - Earmark is no longer maintained. Migrate to a replacement, for example MDEx (https://hex.pm/packages/mdex).
Current section
Files
Jump to
Current section
Files
lib/earmark/helpers/lookahead_helpers.ex
defmodule Earmark.Helpers.LookaheadHelpers do
use Earmark.Types
alias Earmark.Line
import Earmark.Helpers.LineHelpers
import Earmark.Helpers.LeexHelpers
@doc """
Indicates if the _numbered_line_ passed in leaves an inline code block open.
If so returns a tuple whre the first element is the opening sequence of backticks,
and the second the linenumber of the _numbered_line_
Otherwise `{nil, 0}` is returned
"""
@spec opens_inline_code(numbered_line) :: inline_code_continuation
def opens_inline_code( %{line: line, lnb: lnb} ) do
case tokenize(line, with: :string_lexer) |> has_still_opening_backtix(nil) do
nil -> {nil, 0}
{_, btx} -> {btx, lnb}
end
end
@doc """
returns false if and only if the line closes a pending inline code
*without* opening a new one.
The opening backtix are passed in as second parameter.
If the function does not return false it returns the (new or original)
opening backtix
"""
# (#{},{_,_}) -> {_,_}
@spec still_inline_code(numbered_line, inline_code_continuation) :: inline_code_continuation
def still_inline_code( %{line: line, lnb: lnb}, old = {pending, _pending_lnb} ) do
case tokenize(line, with: :string_lexer) |> has_still_opening_backtix({:old, pending}) do
nil -> {nil, 0}
{:new, btx} -> {btx, lnb}
{:old, _ } -> old
end
end
# A tokenized line {:verabtim, text} | {:backtix, ['``+]} is analyzed for
# if it is closed (-> nil), not closed (-> {:old, btx}) or reopened (-> {:new, btx})
# concerning backtix
defp has_still_opening_backtix(tokens, opened_so_far)
defp has_still_opening_backtix([], opened_so_far), do: opened_so_far
defp has_still_opening_backtix([{:verbatim,_}|rest], opened_so_far), do: has_still_opening_backtix(rest, opened_so_far)
defp has_still_opening_backtix([{:backtix,btx}|rest], nil), do: has_still_opening_backtix(rest, {:new, btx})
defp has_still_opening_backtix([{:backtix,btx}|rest], opened_so_far={_, pending}) do
if btx == pending do
has_still_opening_backtix(rest, nil)
else
has_still_opening_backtix(rest, opened_so_far)
end
end
#######################################################################################
# read_list_lines
#######################################################################################
@spec read_list_lines( Line.ts, inline_code_continuation, number ) :: {boolean, Line.ts, Line.ts, number, number}
@doc """
Called to slurp in the lines for a list item.
basically, we allow indents and blank lines, and
we allow text lines only after an indent (and initially)
We also slurp in lines that are inside a multiline inline
code block as indicated by `pending`.
"""
def read_list_lines( lines, {pending, pending_lnb}, initial_indent ) do
_read_list_lines(lines, [], %{pending: pending, pending_lnb: pending_lnb, min_indent: nil, initial_indent: initial_indent})
end
@type read_list_info :: %{pending: maybe(String.t), pending_lnb: number, initial_indent: number, min_indent: maybe(number)}
@spec _read_list_lines(Line.ts, Line.ts, read_list_info) :: {boolean, Line.ts, Line.ts, number}
# List items with initial_indent + 2
defp _read_list_lines([ line = %Line.ListItem{initial_indent: li_indent} | rest ], result,
params=%{pending: nil, initial_indent: initial_indent, min_indent: min_indent})
when li_indent > initial_indent + 1 do
with {pending1, pending_lnb1} = opens_inline_code(line),
min_indent1 = new_min_indent(min_indent, 2), do:
_read_list_lines(rest, [ line | result ], %{params | pending: pending1, pending_lnb: pending_lnb1, min_indent: min_indent1})
end
# List items with same indent than last one
defp _read_list_lines([ line = %Line.ListItem{initial_indent: li_indent} | rest ], res=[%Line.ListItem{initial_indent: old_indent} | _],
params=%{pending: nil})
when li_indent == old_indent do
with {pending1, pending_lnb1} = opens_inline_code(line), do:
_read_list_lines(rest, [ line | res ], %{params | pending: pending1, pending_lnb: pending_lnb1, min_indent: li_indent})
end
# text immediately after the start
defp _read_list_lines([ line = %Line.Text{} | rest ], [], params=%{pending: nil}) do
_read_list_lines(rest, [ line ], _opens_inline_code(line, params))
end
# table line immediately after the start
defp _read_list_lines([ line = %Line.TableLine{} | rest ], [], params=%{pending: nil}) do
_read_list_lines(rest, [ line ], _opens_inline_code(line, params))
end
# text immediately after another text line
defp _read_list_lines([ line = %Line.Text{} | rest ], result =[ %Line.Text{} | _], params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# table line immediately after another text line
defp _read_list_lines([ line = %Line.TableLine{} | rest ], result =[ %Line.Text{} | _], params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# text immediately after a table line
defp _read_list_lines([ line = %Line.Text{} | rest ], result =[ %Line.TableLine{} | _], params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# table line immediately after another table line
defp _read_list_lines([ line = %Line.TableLine{} | rest ], result =[ %Line.TableLine{} | _], params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# text immediately after an indent
defp _read_list_lines([ line = %Line.Text{} | rest ], result =[ %Line.Indent{} | _], params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# table line immediately after an indent
defp _read_list_lines([ line = %Line.TableLine{} | rest ], result =[ %Line.Indent{} | _], params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# Always allow blank lines and indents, and text or table lines with at least
# two spaces
defp _read_list_lines([ line = %Line.Blank{} | rest ], result, params=%{pending: nil}) do
_read_list_lines(rest, [ line | result ], params)
end
defp _read_list_lines([ line = %Line.Indent{level: indent_level} | rest ], result,
params=%{pending: nil, min_indent: min_indent}) do
with min_indent1 = new_min_indent(min_indent, indent_level * 4), do:
_read_list_lines(rest, [ line | result ], %{params | min_indent: min_indent1})
end
defp _read_list_lines([ line = %Line.Text{line: <<" ", _ :: binary>>} | rest ],
result, params=%{pending: nil})
do
_read_list_lines(rest, [ line | result ], _opens_inline_code(line, params))
end
# no match, must be done
defp _read_list_lines(lines, result, %{pending: nil, min_indent: min_indent}) do
{ trailing_blanks, rest } = Enum.split_while(result, &blank?/1)
spaced = length(trailing_blanks) > 0
{ spaced, Enum.reverse(rest), lines, 0, min_indent }
end
# Only now we match for list lines inside an open multiline inline code block
defp _read_list_lines([line|rest], result, params = %{pending: pending, pending_lnb: pending_lnb}) do
with {pending1, pending_lnb1} = still_inline_code(line, {pending, pending_lnb}), do:
_read_list_lines(rest, [%{line|inside_code: true} | result], %{params | pending: pending1, pending_lnb: pending_lnb1})
end
# Running into EOI insise an open multiline inline code block
defp _read_list_lines([], result, params = %{pending_lnb: pending_lnb, min_indent: min_indent}) do
{ spaced, rest, lines, _, _ } =_read_list_lines( [], result, %{params | pending: nil} )
{ spaced, rest, lines, pending_lnb, min_indent }
end
defp new_min_indent(nil, new_min_indent), do: new_min_indent
defp new_min_indent(old_min_indent, new_min_indent) when old_min_indent <= new_min_indent, do: old_min_indent
defp new_min_indent(_, new_min_indent), do: new_min_indent
# Convenience wrapper around `opens_inline_code` into a map
defp _opens_inline_code( line, params ) do
with {pending, pending_lnb} = opens_inline_code(line), do:
%{ params | pending: pending, pending_lnb: pending_lnb }
end
end