Packages
earmark
1.0.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/line.ex
defmodule Earmark.Line do
alias Earmark.Helpers
@moduledoc """
Give a line of text, return its context-free type. Not for external consumption
"""
# This is the re that matches the ridiculous "[id]: url title" syntax
@id_title_part ~S"""
(?|
" (.*) " # in quotes
| ' (.*) ' #
| \( (.*) \) # in parens
)
"""
@id_title_part_re ~r[^\s*#{@id_title_part}\s*$]x
@id_re ~r'''
^\s{0,3} # leading spaces
\[([^\]]*)\]: # [someid]:
\s+
(?|
< (\S+) > # url in <>s
| (\S+) # or without
)
(?:
\s+ # optional title
#{@id_title_part}
)?
\s*
$
'''x
@void_tags ~w{area br hr img wbr}
@void_tag_rgx ~r'''
^<( #{Enum.join(@void_tags, "|")} )
.*?
>
'''x
#'
defmodule Blank, do: defstruct lnb: 0, line: "", content: "", inside_code: false
defmodule Ruler, do: defstruct lnb: 0, line: "", type: "- or * or _", inside_code: false
defmodule Heading, do: defstruct lnb: 0, line: "", level: 1, content: "inline text", inside_code: false
defmodule BlockQuote, do: defstruct lnb: 0, line: "", content: "text", inside_code: false
defmodule Indent, do: defstruct lnb: 0, line: "", level: 0, content: "text", inside_code: false
defmodule Fence, do: defstruct lnb: 0, line: "", delimiter: "~ or `", language: nil , inside_code: false
defmodule HtmlOpenTag, do: defstruct lnb: 0, line: "", tag: "", content: "", inside_code: false
defmodule HtmlCloseTag, do: defstruct lnb: 0, line: "", tag: "<... to eol", inside_code: false
defmodule HtmlComment, do: defstruct lnb: 0, line: "", complete: true, inside_code: false
defmodule HtmlOneLine, do: defstruct lnb: 0, line: "", tag: "", content: "", inside_code: false
defmodule IdDef, do: defstruct lnb: 0, line: "", id: nil, url: nil, title: nil, inside_code: false
defmodule FnDef, do: defstruct lnb: 0, line: "", id: nil, content: "text", inside_code: false
defmodule ListItem, do: defstruct lnb: 0, type: :ul, line: "",
bullet: "* or -", content: "text",
initial_indent: 0, inside_code: false
defmodule SetextUnderlineHeading,
do: defstruct lnb: 0, line: "", level: 1, inside_code: false, inside_code: false
defmodule TableLine, do: defstruct lnb: 0, line: "", content: "", columns: 0, inside_code: false
defmodule Ial, do: defstruct lnb: 0, line: "", attrs: "", inside_code: false
defmodule Text, do: defstruct lnb: 0, line: "", content: "", inside_code: false
@type t :: %Blank{} | %Ruler{} | %Heading{} | %BlockQuote{} | %Indent{} | %Fence{} | %HtmlOpenTag{} | %HtmlCloseTag{} | %HtmlComment{} | %HtmlOneLine{} | %IdDef{} | %FnDef{} | %ListItem{} | %SetextUnderlineHeading{} | %TableLine{} | %Ial{} | %Text{}
@type ts :: list(t)
@doc false
# We want to add the original source line into every
# line we generate. We also need to expand tabs before
# proceeding
# (_,atom() | tuple() | #{},_) -> ['Elixir.B']
@spec scan_lines( list(String.t), %Earmark.Options{}, boolean ) :: ts
def scan_lines lines, options \\ %Earmark.Options{}, recursive \\ false
def scan_lines lines, options, recursive do
lines_with_count( lines, options.line - 1)
|> Earmark.pmap( fn (line) -> type_of(line, options, recursive) end)
end
defp lines_with_count lines, offset do
Enum.zip lines, offset..(offset+Enum.count(lines))
end
def type_of(line, recursive)
when is_boolean(recursive), do: type_of(line, %Earmark.Options{}, recursive)
def type_of({line, lnb}, options = %Earmark.Options{}, recursive) do
line = line |> Helpers.expand_tabs |> Helpers.remove_line_ending
%{ _type_of(line, options, recursive) | line: line, lnb: lnb }
end
@doc false
# Used by the block parser to test if a line following an IdDef
# is a possible title
def matches_id_title(content) do
case Regex.run(@id_title_part_re, content) do
[ _, title ] -> title
_ -> nil
end
end
defp _type_of(line, options=%Earmark.Options{}, recursive) do
cond do
line =~ ~r/^\s*$/ ->
%Blank{}
line =~ ~r/^ \s{0,3} ( <! (?: -- .*? -- \s* )+ > ) $/x && !recursive ->
%HtmlComment{complete: true}
line =~ ~r/^ \s{0,3} ( <!-- .*? ) $/x && !recursive ->
%HtmlComment{complete: false}
line =~ ~r/^ \s{0,3} (?:-\s?){3,} $/x ->
%Ruler{type: "-" }
line =~ ~r/^ \s{0,3} (?:\*\s?){3,} $/x ->
%Ruler{type: "*" }
line =~ ~r/^ \s{0,3} (?:_\s?){3,} $/x ->
%Ruler{type: "_" }
match = Regex.run(~R/^(#{1,6})\s+(?|([^#]+)#*$|(.*))/u, line) ->
[ _, level, heading ] = match
%Heading{level: String.length(level), content: String.strip(heading) }
match = Regex.run(~r/^>(?|(\s*)$|\s(.*))/, line) ->
[ _, quote ] = match
%BlockQuote{content: quote }
match = Regex.run(~r/^((?:\s\s\s\s)+)(.*)/, line) ->
[ _, spaces, code ] = match
%Indent{level: div(String.length(spaces), 4), content: code }
match = Regex.run(~r/^\s*(```|~~~)\s*([\w\-]*)\s*$/u, line) ->
[ _, fence, language ] = match
%Fence{delimiter: fence, language: language}
# (match = Regex.run(~r{^<hr(\s|>|/).*}, line)) && !recursive ->
# %HtmlOneLine{tag: "hr", content: line}
(match = Regex.run(@void_tag_rgx, line)) && !recursive ->
[ _, tag ] = match
%HtmlOneLine{tag: tag, content: line}
(match = Regex.run(~r{^<([-\w]+?)(?:\s.*)?>.*</\1>}, line)) && !recursive ->
[ _, tag ] = match
%HtmlOneLine{tag: tag, content: line}
(match = Regex.run(~r/^<([-\w]+?)(?:\s.*)?>/, line)) && !recursive ->
[ _, tag ] = match
%HtmlOpenTag{tag: tag, content: line}
(match = Regex.run(~r/^<\/([-\w]+?)>/, line)) && !recursive ->
[ _, tag ] = match
%HtmlCloseTag{tag: tag}
match = Regex.run(@id_re, line) ->
[ _, id, url | title ] = match
title = if(length(title) == 0, do: "", else: hd(title))
%IdDef{id: id, url: url, title: title }
match = options.footnotes && Regex.run(~r/^\[\^([^\s\]]+)\]:\s+(.*)/, line) ->
[ _, id, first_line ] = match
%FnDef{id: id, content: first_line }
match = Regex.run(~r/^(\s{0,3})([-*+])\s+(.*)/, line) ->
[ _, leading, bullet, text ] = match
%ListItem{type: :ul,
bullet: bullet,
content: text,
initial_indent: String.length(leading) }
match = Regex.run(~r/^(\s{0,3})(\d+\.)\s+(.*)/, line) ->
[ _, leading, bullet, text ] = match
%ListItem{type: :ol,
bullet: bullet,
content: text,
initial_indent: String.length(leading) }
match = Regex.run(~r/^ \s{0,3} \| (?: [^|]+ \|)+ \s* $ /x, line) ->
[ body ] = match
body = body
|> String.strip
|> String.strip(?|)
columns = split_table_columns(body)
%TableLine{content: line, columns: columns}
line =~ ~r/ \s \| \s /x ->
columns = split_table_columns(line)
%TableLine{content: line, columns: columns}
match = Regex.run(~r/^(=|-)+\s*$/, line) ->
[ _, type ] = match
level = if(String.starts_with?(type, "="), do: 1, else: 2)
%SetextUnderlineHeading{level: level }
match = Regex.run(~r<^\s{0,3}{:\s*([^}]+)}\s*$>, line) ->
[ _, ial ] = match
%Ial{attrs: String.strip(ial)}
true ->
%Text{content: line }
end
end
defp split_table_columns(line) do
line
|> String.split(~r{(?<!\\)\|})
|> Enum.map(&String.strip/1)
|> Enum.map(fn col -> Regex.replace(~r{\\\|}, col, "|") end)
end
end