diff --git a/lib/chiya_web/outline.ex b/lib/chiya_web/outline.ex index 0d88e14..7e03aa3 100644 --- a/lib/chiya_web/outline.ex +++ b/lib/chiya_web/outline.ex @@ -3,19 +3,39 @@ defmodule ChiyaWeb.Outline do @heading_regex ~r/^(#+)\s(.+)$/ def get(markdown) do - headings = - @outline_regex - |> Regex.scan(markdown, capture: :all) - - Enum.chunk_by(headings, fn h -> nil end) - end - - def level(heading) do - Regex.scan(@heading_regex, heading) - |> Enum.map(fn [_, level, heading] -> - [level_from_string(level), heading] - end) + @outline_regex + |> Regex.scan(markdown, capture: :all) + |> List.flatten() + |> Enum.map(&into_map/1) + |> into_tree([]) end defp level_from_string(string), do: String.length(string) + + defp into_map(heading) do + [[_, level, heading]] = Regex.scan(@heading_regex, heading) + %{level: level_from_string(level), text: heading, children: []} + end + + defp into_tree([], result), do: result + + defp into_tree([head | tail], result) do + level = head.level + + # Split the remaining items (tail) at the next + # same level element as the current (head) + # The first list will be the children of the head, + # the second list will be the new list + {children, new_list} = + Enum.split_while(tail, fn heading -> + level < heading.level + end) + + # add the children to the current item + head = Map.put(head, :children, children) + + # call again with new list and current item + # added to result + into_tree(new_list, result ++ [head]) + end end diff --git a/test/chiya_web/outline_test.exs b/test/chiya_web/outline_test.exs index 8722b0b..6430760 100644 --- a/test/chiya_web/outline_test.exs +++ b/test/chiya_web/outline_test.exs @@ -5,16 +5,32 @@ defmodule ChiyaWeb.OutlineTest do describe "extract_outline/1" do test "extracts headlines from markdown" do - markdown = "# Heading\nsome paragraph\n## Sub Heading\n# Second Heading" + markdown = + "# Heading\nsome paragraph\n## Sub Heading\nsome text\n## Second Sub Heading\nmore text\n# Second Heading" - assert [{1, "Heading", [{2, "Sub Heading", []}]}, {1, "Second Heading", []}] = - Outline.get(markdown) + result = [ + %{ + level: 1, + text: "Heading", + children: [ + %{level: 2, text: "Sub Heading", children: []}, + %{level: 2, text: "Second Sub Heading", children: []} + ] + }, + %{level: 1, text: "Second Heading", children: []} + ] + + assert result == Outline.get(markdown) end end - describe "outline_level/1" do - test "extracts outline level" do - assert {1, "Heading"} = Outline.level("# Heading") - end + test "extracts headlines from markdown 2" do + markdown = "## Second Level" + + result = [ + %{level: 2, text: "Second Level", children: []} + ] + + assert result == Outline.get(markdown) end end