import gleam/list import gleam/option.{type Option, None, Some} import gleam/string pub type Error { Empty ParseError(msg: String) } /// The frontmatter content with format-specific variants. pub type Frontmatter { TOML(content: String) JSON(content: String) YAML(content: String) } /// The Entire Document split between frontmatter and content. pub type Document { Document(frontmatter: Option(Frontmatter), content: String) } fn grab_frontmatter( input: List(String), frontmatter: List(String), format_constructor: fn(String) -> Frontmatter, delimiter: String, ) -> Result(Document, Error) { case input { [] -> { Error(ParseError("expected ending delimitor")) } [line, ..rest] if line == delimiter -> { let final_content = case delimiter { "}" -> string.join(list.append(frontmatter, ["}"]), with: "\n") _ -> string.join(frontmatter, with: "\n") } Ok(Document( Some(format_constructor(final_content)), string.join(rest, with: "\n"), )) } [line, ..rest] -> grab_frontmatter( rest, list.append(frontmatter, [line]), format_constructor, delimiter, ) } } /// Parse an input string and grab the frontmatter if it exists. pub fn parse(in: String) -> Result(Document, Error) { let lines = in |> string.split("\n") case lines { [] -> { Error(Empty) } ["+++", ..rest] -> { grab_frontmatter(rest, [], TOML, "+++") } ["{", ..rest] -> { grab_frontmatter(rest, ["{"], JSON, "}") } ["---", ..rest] -> { grab_frontmatter(rest, [], YAML, "---") } rest -> { Ok(Document(None, string.join(rest, with: "\n"))) } } }