Packages
earmark
1.4.13
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).
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/earmark/context.ex
defmodule Earmark.Context do
@moduledoc false
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 false
# Convenience method to append to the value list
# def append(%__MODULE__{value: value} = ctx, prep), do: %{ctx | value: [value | prep]}
def modify_value(%__MODULE__{value: value}=context, fun) do
# IO.inspect(value, label: ">>>modify_value")
nv = fun.(value) #|> IO.inspect(label: "<<<modify_value")
unless is_list(nv), do: raise "Not a list!!!\n#{inspect nv}"
%{context | value: nv}
end
@doc false
# Convenience method to prepend to the value list
def prepend(context, ast, messages \\ [])
def prepend(%__MODULE__{value: value} = ctx, prep, messages) do
# TODO: Remove me
unless is_list(value), do: raise "Not a list!!!\n#{inspect value}"
options1 = %{ctx.options | messages: Enum.uniq(ctx.options.messages ++ messages)}
_prepend(%{ctx|options: options1}, prep)
end
defp _prepend(ctxt, []), do: ctxt
defp _prepend(%{value: value}=ctxt, {:comment, _, _, _}=ct), do: %{ctxt|value: [ct|value]}
defp _prepend(%{value: value}=ctxt, tuple) when is_tuple(tuple) do
%{ctxt|value: [tuple|value] |> List.flatten}
end
defp _prepend(%{value: value}=ctxt, string) when is_binary(string), do: %{ctxt|value: [string|value] |> List.flatten}
defp _prepend(%{value: value}=ctxt, list) when is_list(list), do: %{ctxt|value: List.flatten(list ++ value)}
@doc """
Convenience method to prepend to the value list
"""
def set_value(%__MODULE__{} = ctx, value) do
# TODO: Remove me
unless is_list(value), do: raise "Not a list!!!\n#{inspect value}"
%{ctx | value: value}
end
def clear_value(%__MODULE__{} = ctx), do: %{ctx | 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() do
update_context(%Earmark.Context{})
end
def update_context(context = %Earmark.Context{options: options}) do
context = %{context | rules: rules_for(options)}
if options.smartypants do
put_in(context.options.do_smartypants, &smartypants/1)
else
put_in(context.options.do_smartypants, &noop/1)
end
end
# ( "[" .*? "]"n or anything w/o {"[", "]"}* or "]" ) *
@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