context.ex (3991B)
1 defmodule EarmarkParser.Context do 2 @moduledoc false 3 alias EarmarkParser.Options 4 5 @type t :: %__MODULE__{ 6 options: EarmarkParser.Options.t(), 7 links: map(), 8 footnotes: map(), 9 referenced_footnote_ids: MapSet.t(String.t()), 10 value: String.t() | [String.t()] 11 } 12 13 defstruct options: %EarmarkParser.Options{}, 14 links: Map.new(), 15 rules: nil, 16 footnotes: Map.new(), 17 referenced_footnote_ids: MapSet.new([]), 18 value: [] 19 20 ############################################################################## 21 # Handle adding option specific rules and processors # 22 ############################################################################## 23 24 @doc false 25 def modify_value(%__MODULE__{value: value} = context, fun) do 26 nv = fun.(value) 27 %{context | value: nv} 28 end 29 30 @doc false 31 def prepend(context1, ast_or_context, context2_or_nil \\ nil) 32 33 def prepend(%__MODULE__{} = context1, %__MODULE__{} = context2, nil) do 34 context1 35 |> _merge_contexts(context2) 36 |> _prepend(context2.value) 37 end 38 39 def prepend(%__MODULE__{} = context1, ast, nil) do 40 context1 41 |> _prepend(ast) 42 end 43 44 def prepend(%__MODULE__{} = context1, ast, %__MODULE__{} = context2) do 45 context1 46 |> _merge_contexts(context2) 47 |> _prepend(ast) 48 end 49 50 defp _merge_contexts( 51 %__MODULE__{referenced_footnote_ids: orig} = context1, 52 %__MODULE__{referenced_footnote_ids: new} = context2 53 ) do 54 context_ = _merge_messages(context1, context2) 55 %{context_| referenced_footnote_ids: MapSet.union(orig, new)} 56 end 57 58 defp _merge_messages(context, context_or_messages) 59 defp _merge_messages(context, %__MODULE__{options: %Options{messages: messages}}) do 60 _merge_messages(context, messages) 61 end 62 defp _merge_messages(context, messages) do 63 %{context | options: %{context.options|messages: MapSet.union(context.options.messages, messages)}} 64 end 65 66 67 defp _prepend(ctxt, []), do: ctxt 68 69 defp _prepend(%{value: value} = ctxt, {:comment, _, _, _} = ct), 70 do: %{ctxt | value: [ct | value]} 71 72 defp _prepend(%{value: value} = ctxt, tuple) when is_tuple(tuple) do 73 %{ctxt | value: [tuple | value] |> List.flatten()} 74 end 75 76 defp _prepend(%{value: value} = ctxt, list) when is_list(list), 77 do: %{ctxt | value: List.flatten(list ++ value)} 78 79 @doc """ 80 Convenience method to prepend to the value list 81 """ 82 def set_value(%__MODULE__{} = ctx, value) do 83 %{ctx | value: value} 84 end 85 86 def clear_value(%__MODULE__{} = ctx), do: %{ctx | value: []} 87 88 # this is called by the command line processor to update 89 # the inline-specific rules in light of any options 90 def update_context(context = %EarmarkParser.Context{options: options}) do 91 %{context | rules: rules_for(options)} 92 end 93 94 # ( "[" .*? "]"n or anything w/o {"[", "]"}* or "]" ) * 95 @link_text ~S{(?:\[[^]]*\]|[^][]|\])*} 96 # " 97 # @href ~S{\s*<?(.*?)>?(?:\s+['"](.*?)['"])?\s*} 98 99 defp basic_rules do 100 [ 101 br: ~r<^ {2,}\n(?!\s*$)>, 102 text: ~r<^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)> 103 ] 104 end 105 106 defp rules_for(options) do 107 subsup = 108 if options.sub_sup do 109 "~^" 110 else 111 "" 112 end 113 rule_updates = 114 if options.gfm do 115 rules = [ 116 text: ~r{^[\s\S]+?(?=~~|[\\<!\[_*`#{subsup}]|https?://| \{2,\}\n|$)} 117 ] 118 119 if options.breaks do 120 break_updates = [ 121 br: ~r{^ *\n(?!\s*$)}, 122 text: ~r{^[\s\S]+?(?=[\\<!\[_*`#{subsup}]|https?://| *\n|$)} 123 ] 124 125 Keyword.merge(rules, break_updates) 126 else 127 rules 128 end 129 else 130 [] 131 end 132 133 footnote = if options.footnotes, do: ~r{^\[\^(#{@link_text})\]}, else: ~r{\z\A} 134 rule_updates = Keyword.merge(rule_updates, footnote: footnote) 135 136 Keyword.merge(basic_rules(), rule_updates) 137 |> Enum.into(%{}) 138 end 139 end 140 141 # SPDX-License-Identifier: Apache-2.0