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: "<< 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 (?])}, # noop url: ~r{\z\A}, tag: ~r{ ^ | ^<\/?\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]+?(?=[\\, # 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]+?(?=[\\ 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