Packages
earmark
1.3.4
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/context.ex
defmodule Earmark.Context do
use Earmark.Types
import Earmark.Helpers
@type t :: %__MODULE__{
options: Earmark.Options.t(),
links: map(),
rules: Keyword.t() | nil,
footnotes: map(),
value: String.t() | [String.t()]
}
defstruct options: %Earmark.Options{},
links: Map.new(),
rules: nil,
footnotes: Map.new(),
value: []
##############################################################################
# Handle adding option specific rules and processors #
##############################################################################
defp noop(text), do: text
@doc """
Convenience method to append to the value list
"""
def append(%__MODULE__{value: value} = ctx, prep), do: %{ctx | value: [value | prep]}
@doc """
Convenience method to prepend to the value list
"""
def prepend(%__MODULE__{value: value} = ctx, prep), do: %{ctx | value: [prep | value]}
@doc """
Convenience method to prepend to the value list
"""
def set_value(%__MODULE__{} = ctx, value), do: %{ctx | value: value}
@doc """
Convenience method to get a context with cleared value and messages
"""
def clear(%__MODULE__{} = ctx) do
with empty_value <- set_value(ctx, []) do
%{empty_value | options: %{empty_value.options | messages: []}}
end
end
@doc false
# this is called by the command line processor to update
# the inline-specific rules in light of any options
def update_context(context = %Earmark.Context{options: options}) do
context = %{context | rules: rules_for(options)}
context =
if options.smartypants do
put_in(context.options.do_smartypants, &smartypants/1)
else
put_in(context.options.do_smartypants, &noop/1)
end
if options.sanitize do
put_in(context.options.do_sanitize, &escape/1)
else
put_in(context.options.do_sanitize, &noop/1)
end
end
@link_text ~S{(?:\[[^]]*\]|[^][]|\])*}
# "
@href ~S{\s*<?(.*?)>?(?:\s+['"](.*?)['"])?\s*}
@code ~r{^
(`+) # $1 = Opening run of `
(.+?) # $2 = The code block
(?<!`)
\1 # Matching closer
(?!`)
}xs
defp basic_rules do
[
escape: ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>])},
# noop
url: ~r{\z\A},
tag: ~r{
^<!--[\s\S]*?--> |
^<\/?\w+(?: "[^"<]*" | # < inside an attribute is illegal, luckily
'[^'<]*' |
[^'"<>])*?>}x,
inline_ial: ~r<^\s*\{:\s*(.*?)\s*}>,
link: ~r{^!?\[(#{@link_text})\]\(#{@href}\)},
reflink: ~r{^!?\[(#{@link_text})\]\s*\[([^]]*)\]},
nolink: ~r{^!?\[((?:\[[^]]*\]|[^][])*)\]},
strong: ~r{^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)},
em: ~r{^\b_((?:__|[\s\S])+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)},
code: @code,
br: ~r<^ {2,}\n(?!\s*$)>,
text: ~r<^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)>,
# noop
strikethrough: ~r{\z\A}
]
end
defp rules_for(options) do
rule_updates =
if options.gfm do
rules = [
escape: ~r{^\\([\\`*\{\}\[\]()\#+\-.!_>~|])},
url: ~r{^(https?:\/\/[^\s<]+[^<.,:;\"\')\]\s])},
strikethrough: ~r{^~~(?=\S)([\s\S]*?\S)~~},
text: ~r{^[\s\S]+?(?=[\\<!\[_*`~]|https?://| \{2,\}\n|$)}
]
if options.breaks do
break_updates = [
br: ~r{^ *\n(?!\s*$)},
text: ~r{^[\s\S]+?(?=[\\<!\[_*`~]|https?://| *\n|$)}
]
Keyword.merge(rules, break_updates)
else
rules
end
else
if options.pedantic do
[
strong: ~r{^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)},
em: ~r{^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)}
]
else
[]
end
end
footnote = if options.footnotes, do: ~r{^\[\^(#{@link_text})\]}, else: ~r{\z\A}
rule_updates = Keyword.merge(rule_updates, footnote: footnote)
Keyword.merge(basic_rules(), rule_updates)
|> Enum.into(%{})
end
# Smartypants transformations convert quotes to the appropriate curly
# variants, and -- and ... to – and …
defp smartypants(text) do
text
|> replace(~r{--}, "—")
|> replace(~r{(^|[-—/\(\[\{"”“\s])'}, "\\1‘")
|> replace(~r{\'}, "’")
|> replace(~r{(^|[-—/\(\[\{‘\s])\"}, "\\1“")
|> replace(~r{"}, "”")
|> replace(~r{\.\.\.}, "…")
end
end
# SPDX-License-Identifier: Apache-2.0