list_parser.ex (7525B)
1 defmodule EarmarkParser.Parser.ListParser do 2 alias EarmarkParser.{Block, Line, Options} 3 alias EarmarkParser.Parser.ListInfo 4 5 import EarmarkParser.Helpers.StringHelpers, only: [behead: 2] 6 import EarmarkParser.Message, only: [add_message: 2] 7 import ListInfo 8 9 @moduledoc false 10 11 @not_pending {nil, 0} 12 13 def parse_list(lines, result, options \\ %Options{}) do 14 {items, rest, options1} = _parse_list_items_init(lines, [], options) 15 list = _make_list(items, _empty_list(items) ) 16 {[list|result], rest, options1} 17 end 18 19 defp _parse_list_items_init([item|rest], list_items, options) do 20 options1 = %{options|line: item.lnb} 21 _parse_list_items_start(rest, _make_and_prepend_list_item(item, list_items), new(item, options1)) 22 end 23 24 defp _parse_list_items_spaced(input, items, list_info) 25 defp _parse_list_items_spaced(input, items, %{pending: @not_pending}=list_info) do 26 _parse_list_items_spaced_np(input, items, list_info) 27 end 28 defp _parse_list_items_spaced(input, items, list_info) do 29 _parse_list_items_spaced_pdg(input, items, list_info) 30 end 31 32 defp _parse_list_items_spaced_np([%Line.Blank{}|rest], items, list_info) do 33 list_info1 = %{list_info|lines: [""|list_info.lines], options: %{list_info.options|line: list_info.options.line + 1}} 34 _parse_list_items_spaced_np(rest, items, list_info1) 35 end 36 defp _parse_list_items_spaced_np([%Line.Ruler{}|_]=lines, items, list_info) do 37 _finish_list_items(lines, items, false, list_info) 38 end 39 defp _parse_list_items_spaced_np([%Line.ListItem{indent: ii}=item|_]=input, list_items, %{width: w}=list_info) 40 when ii < w do 41 if _starts_list?(item, list_items) do 42 _finish_list_items(input, list_items, false, list_info) 43 else 44 {items1, options1} = _finish_list_item(list_items, false, _loose(list_info)) 45 _parse_list_items_init(input, items1, options1) 46 end 47 end 48 defp _parse_list_items_spaced_np([%Line.Indent{indent: ii}=item|rest], list_items, %{width: w}=list_info) 49 when ii >= w do 50 indented = _behead_spaces(item.line, w) 51 _parse_list_items_spaced(rest, list_items, update_list_info(list_info, indented, item, true)) 52 end 53 defp _parse_list_items_spaced_np([%Line.ListItem{}=line|rest], items, list_info) do 54 indented = _behead_spaces(line.line, list_info.width) 55 _parse_list_items_start(rest, items, update_list_info(list_info, indented, line)) 56 end 57 # BUG: Still do not know how much to indent here??? 58 defp _parse_list_items_spaced_np([%{indent: indent, line: str_line}=line|rest], items, %{width: width}=list_info) when 59 indent >= width 60 do 61 _parse_list_items_spaced(rest, items, update_list_info(list_info, behead(str_line, width), line, true)) 62 end 63 defp _parse_list_items_spaced_np(input, items, list_info) do 64 _finish_list_items(input ,items, false, list_info) 65 end 66 67 defp _parse_list_items_spaced_pdg(input, items, list_info) 68 defp _parse_list_items_spaced_pdg([], items, %{pending: {pending, lnb}}=list_info) do 69 options1 = 70 add_message(list_info.options, {:warning, lnb, "Closing unclosed backquotes #{pending} at end of input"}) 71 _finish_list_items([], items, false, %{list_info| options: options1}) 72 end 73 defp _parse_list_items_spaced_pdg([line|rest], items, list_info) do 74 indented = _behead_spaces(line.line, list_info.width) 75 _parse_list_items_spaced(rest, items, update_list_info(list_info, indented, line, true)) 76 end 77 78 79 defp _parse_list_items_start(input, list_items, list_info) 80 defp _parse_list_items_start(input, list_items, %{pending: @not_pending}=list_info) do 81 _parse_list_items_start_np(input, list_items, list_info) 82 end 83 defp _parse_list_items_start(input, list_items, list_info) do 84 _parse_list_items_start_pdg(input, list_items, list_info) 85 end 86 87 defp _parse_list_items_start_np(input, list_items, list_info) 88 defp _parse_list_items_start_np([%Line.Blank{}|input], items, list_info) do 89 _parse_list_items_spaced(input, items, prepend_line(list_info, "")) 90 end 91 defp _parse_list_items_start_np([], list_items, list_info) do 92 _finish_list_items([], list_items, true, list_info) 93 end 94 defp _parse_list_items_start_np([%Line.Ruler{}|_]=input, list_items, list_info) do 95 _finish_list_items(input, list_items, true, list_info) 96 end 97 defp _parse_list_items_start_np([%Line.Heading{}|_]=input, list_items, list_info) do 98 _finish_list_items(input, list_items, true, list_info) 99 end 100 defp _parse_list_items_start_np([%Line.ListItem{indent: ii}=item|_]=input, list_items, %{width: w}=list_info) 101 when ii < w do 102 if _starts_list?(item, list_items) do 103 _finish_list_items(input, list_items, true, list_info) 104 else 105 {items1, options1} = _finish_list_item(list_items, true, list_info) 106 _parse_list_items_init(input, items1, options1) 107 end 108 end 109 # Slurp in everything else before a first blank line 110 defp _parse_list_items_start_np([%{line: str_line}=line|rest], items, list_info) do 111 indented = _behead_spaces(str_line, list_info.width) 112 _parse_list_items_start(rest, items, update_list_info(list_info, indented, line)) 113 end 114 115 defp _parse_list_items_start_pdg(input, items, list_info) 116 defp _parse_list_items_start_pdg([], items, list_info) do 117 _finish_list_items([], items, true, list_info) 118 end 119 defp _parse_list_items_start_pdg([line|rest], items, list_info) do 120 _parse_list_items_start(rest, items, update_list_info(list_info, line.line, line)) 121 end 122 123 defp _behead_spaces(str, len) do 124 Regex.replace(~r/\A\s{1,#{len}}/, str, "") 125 end 126 127 # INLINE CANDIDATE 128 defp _empty_list([%Block.ListItem{loose?: loose?, type: type}|_]) do 129 %Block.List{loose?: loose?, type: type} 130 end 131 132 @start_number_rgx ~r{\A0*(\d+)\.} 133 defp _extract_start(%{bullet: bullet}) do 134 case Regex.run(@start_number_rgx, bullet) do 135 nil -> "" 136 [_, "1"] -> "" 137 [_, start] -> ~s{ start="#{start}"} 138 end 139 end 140 141 defp _finish_list_item([%Block.ListItem{}=item|items], _at_start?, list_info) do 142 {blocks, _, _, options1} = list_info.lines 143 |> Enum.reverse 144 |> EarmarkParser.Parser.parse(%{list_info.options|line: item.lnb}, :list) 145 loose1? = _already_loose?(items) || list_info.loose? 146 {[%{item | blocks: blocks, loose?: loose1?}|items], options1} 147 end 148 149 defp _finish_list_items(input, items, at_start?, list_info) do 150 {items1, options1} = _finish_list_item(items, at_start?, list_info) 151 {items1, input, options1} 152 end 153 154 defp _make_and_prepend_list_item(%Line.ListItem{bullet: bullet, lnb: lnb, type: type}, list_items) do 155 [%Block.ListItem{bullet: bullet, lnb: lnb, spaced?: false, type: type}|list_items] 156 end 157 158 defp _make_list(items, list) 159 defp _make_list([%Block.ListItem{bullet: bullet, lnb: lnb}=item], %Block.List{loose?: loose?}=list) do 160 %{list | blocks: [%{item | loose?: loose?}|list.blocks], 161 bullet: bullet, 162 lnb: lnb, 163 start: _extract_start(item)} 164 end 165 defp _make_list([%Block.ListItem{}=item|rest], %Block.List{loose?: loose?}=list) do 166 _make_list(rest, %{list | blocks: [%{item | loose?: loose?}|list.blocks]}) 167 end 168 169 defp _already_loose?(items) 170 defp _already_loose?([]), do: false 171 defp _already_loose?([%{loose?: loose?}|_]), do: loose? 172 173 defp _loose(list_info), do: %{list_info|loose?: true} 174 175 defp _starts_list?(%{bullet: bullet1}, [%Block.ListItem{bullet: bullet2}|_]) do 176 String.last(bullet1) != String.last(bullet2) 177 end 178 179 end 180 # SPDX-License-Identifier: Apache-2.0