Packages
earmark
1.3.0
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/scanner.ex
defmodule Earmark.Scanner do
import Earmark.Helpers.StringHelpers, only: [behead: 2]
@backtix_rgx ~r/\A(`+)(.*)/
@blockquote_rgx ~r/\A>(?!\S)/
@code_fence_rgx ~r/\A(\s*)~~~/
@headline_rgx ~r/\A(\#{1,6})(\s+)(.*)/
# @id_close_rgx ~r/\[(.*?)\](?!:)/
@id_open_rgx ~r/\A(\s{0,3})\[(.*?)\]:\s+(.*)\z/
@indent_rgx ~r/\A\s{4,}/
@list_item_rgx ~r/\A(\s{0,3})(\d+\.|\*|-)\s+/
@ruler_rgx ~r/\A \s{0,3} (?:([-_*])\s?)(?:\1\s?){2,} \z/x
@under_l1_head_rgx ~r/\A=+\s*\z/
@under_l2_head_rgx ~r/\A-{1,2}\s*\z/
@text_rgx ~r/(?:[^`]|\\`)*/
defmodule Backtix, do: defstruct count: 1
defmodule Blockquote, do: defstruct []
defmodule CodeFence, do: defstruct []
defmodule Headline, do: defstruct level: 1..6
defmodule IdClose, do: defstruct id: "content of [...]"
defmodule IdOpen, do: defstruct id: "content of [...]", href: "word after ]:\\s+"
defmodule Indent, do: defstruct count: 4
defmodule ListItem, do: defstruct type: :ul_ol, bullet: "* or - or empty"
defmodule RulerFat, do: defstruct []
defmodule RulerMedium, do: defstruct []
defmodule RulerThin, do: defstruct []
defmodule Text, do: defstruct content: ""
defmodule UnderHeadline, do: defstruct level: 1..2
@type token :: %Backtix{} | %Blockquote{} | %CodeFence{} | %Headline{} | %IdClose{} | %IdOpen{} | %Indent{} | %ListItem{} | %RulerFat{} | %RulerMedium{} | %RulerThin{} | %Text{} | %UnderHeadline{}
@type tokens :: list(token)
@type t_continuation :: {token, String.t, boolean()}
@doc """
Scans a line into a list of tokens
"""
def scan_line line do
scan_line_into_tokens( line, [], true )
|> Enum.reverse
end
# Empty Line
defp scan_line_into_tokens "", [], _beg do
[]
end
# Line consumed
defp scan_line_into_tokens( "", tokens, _beg), do: tokens
# Line not consumed
defp scan_line_into_tokens line, tokens, beg do
{token, rest, still_at_beg} = scan_next_token( line, beg )
scan_line_into_tokens( rest, [token|tokens], still_at_beg )
end
defp scan_next_token line, beg_of_line
defp scan_next_token line, true do
cond do
Regex.run( @blockquote_rgx, line ) ->
{%Blockquote{}, behead(line, 1), false}
matches = Regex.run( @list_item_rgx, line) ->
[content, ws, bullet] = matches
prefixed_with_ws(line, ws) ||
{make_list_item(bullet), behead(line,content), false}
matches = Regex.run( @id_open_rgx, line ) ->
[_content, ws, id, rest ] = matches
prefixed_with_ws(line, ws) ||
{%IdOpen{id: id}, rest, false}
_matches = Regex.run( @under_l1_head_rgx, line ) ->
{%UnderHeadline{level: 1}, "", false}
_matches = Regex.run( @under_l2_head_rgx, line ) ->
{%UnderHeadline{level: 2}, "", false}
matches = Regex.run( @code_fence_rgx, line ) ->
[_line, ws] = matches
prefixed_with_ws(line, ws) ||
{%CodeFence{}, behead(line, 3), false}
matches = Regex.run( @indent_rgx, line ) ->
count = String.length(hd matches)
{%Indent{count: count}, behead(line, count), false}
matches = Regex.run( @headline_rgx, line ) ->
[_line, levelstr, _ws, rest] = matches
{%Headline{level: String.length(levelstr)}, rest, false}
matches = Regex.run( @ruler_rgx, line ) ->
[_content, type] = matches
{make_ruler_from(type), "", false}
true ->
scan_next_token( line, false )
end
end
defp scan_next_token line, false do
scan_token_not_at_beg( line )
|> Tuple.append( false )
end
defp scan_token_not_at_beg line do
cond do
matches = Regex.run( @backtix_rgx, line ) ->
[_line, backtix, rest] = matches
{%Backtix{count: String.length(backtix)}, rest}
# matches = Regex.run( @id_close_rgx, line ) ->
# [text, id, does_open] = matches
# {%IdDef{id: id, type:
# (if does_open == "", do: "close", else: "open")
# }, behead(line, text)}
matches = Regex.run( @text_rgx, line ) ->
text = hd matches
{%Text{content: text}, behead(line, text)}
true -> {}
end
end
defp make_ruler_from type do
case type do
"*" -> %RulerFat{}
"_" -> %RulerMedium{}
"-" -> %RulerThin{}
end
end
defp make_list_item bullet do
case bullet do
"*" -> %ListItem{type: :ul, bullet: "*"}
"-" -> %ListItem{type: :ul, bullet: "-"}
_ -> %ListItem{type: :ol, bullet: ""}
end
end
defp prefixed_with_ws line, ws do
if ws == "" do
false
else
rest = behead( line, ws )
{%Text{content: ws}, rest, true}
end
end
end
# SPDX-License-Identifier: Apache-2.0