97 lines
2.4 KiB
Elixir
97 lines
2.4 KiB
Elixir
defmodule BDS.Frontmatter do
|
|
@moduledoc false
|
|
|
|
@list_item_prefix " - "
|
|
|
|
def serialize_document(fields, body) when is_list(fields) do
|
|
frontmatter =
|
|
fields
|
|
|> Enum.flat_map(&serialize_field/1)
|
|
|> Enum.join("\n")
|
|
|
|
["---", frontmatter, "---", body || "", ""]
|
|
|> Enum.join("\n")
|
|
end
|
|
|
|
def parse_document(contents) when is_binary(contents) do
|
|
case String.split(contents, "\n---\n", parts: 2) do
|
|
[frontmatter_with_marker, body] ->
|
|
frontmatter = String.replace_prefix(frontmatter_with_marker, "---\n", "")
|
|
|
|
{:ok,
|
|
%{
|
|
fields: parse_frontmatter(frontmatter),
|
|
body: String.trim_trailing(body, "\n")
|
|
}}
|
|
|
|
_parts ->
|
|
{:error, :invalid_frontmatter}
|
|
end
|
|
end
|
|
|
|
defp serialize_field({_key, nil}), do: []
|
|
defp serialize_field({_key, ""}), do: []
|
|
defp serialize_field({_key, false}), do: []
|
|
|
|
defp serialize_field({key, true}) do
|
|
["#{key}: true"]
|
|
end
|
|
|
|
defp serialize_field({key, values}) when is_list(values) do
|
|
["#{key}:" | Enum.map(values, &" - #{&1}")]
|
|
end
|
|
|
|
defp serialize_field({key, value}) do
|
|
["#{key}: #{value}"]
|
|
end
|
|
|
|
defp parse_frontmatter(frontmatter) do
|
|
frontmatter
|
|
|> String.split("\n", trim: true)
|
|
|> parse_lines(%{})
|
|
end
|
|
|
|
defp parse_lines([], acc), do: acc
|
|
|
|
defp parse_lines([line | rest], acc) do
|
|
cond do
|
|
String.starts_with?(line, @list_item_prefix) ->
|
|
parse_lines(rest, acc)
|
|
|
|
String.ends_with?(line, ":") ->
|
|
key = String.trim_trailing(line, ":")
|
|
{items, remaining} = take_list_items(rest, [])
|
|
parse_lines(remaining, Map.put(acc, key, Enum.reverse(items)))
|
|
|
|
String.contains?(line, ": ") ->
|
|
[key, raw_value] = String.split(line, ": ", parts: 2)
|
|
parse_lines(rest, Map.put(acc, key, parse_scalar(raw_value)))
|
|
|
|
true ->
|
|
parse_lines(rest, acc)
|
|
end
|
|
end
|
|
|
|
defp take_list_items([line | rest], items) do
|
|
if String.starts_with?(line, @list_item_prefix) do
|
|
value = line |> String.replace_prefix(@list_item_prefix, "") |> parse_scalar()
|
|
take_list_items(rest, [value | items])
|
|
else
|
|
{items, [line | rest]}
|
|
end
|
|
end
|
|
|
|
defp take_list_items([], items), do: {items, []}
|
|
|
|
defp parse_scalar("true"), do: true
|
|
defp parse_scalar("false"), do: false
|
|
|
|
defp parse_scalar(value) do
|
|
if Regex.match?(~r/^-?\d+$/, value) do
|
|
String.to_integer(value)
|
|
else
|
|
value
|
|
end
|
|
end
|
|
end
|