defmodule PhoenixKitWeb.Live.Modules.Publishing.Metadata do @moduledoc """ Metadata helpers for .phk (PhoenixKit) publishing entries. Metadata is stored as a simple key-value format at the top of the file: ``` --- slug: home title: Welcome status: published published_at: 2025-10-29T18:48:00Z --- Content goes here... ``` """ @type metadata :: %{ status: String.t(), title: String.t(), description: String.t() | nil, slug: String.t(), published_at: String.t() } @doc """ Parses .phk content, extracting metadata from frontmatter and returning the content. """ @spec parse_with_content(String.t()) :: {:ok, metadata(), String.t()} | {:error, atom()} def parse_with_content(content) do case extract_frontmatter(content) do {:ok, metadata, body_content} -> {:ok, metadata, body_content} {:error, _} -> # Fallback: try old XML format for backwards compatibility metadata = extract_metadata_from_xml(content) {:ok, metadata, content} end end @doc """ Serializes metadata as YAML-style frontmatter. """ @spec serialize(metadata()) :: String.t() def serialize(metadata) do """ --- slug: #{metadata.slug} title: #{metadata.title || ""} status: #{metadata.status} published_at: #{metadata.published_at} --- """ end @doc """ Returns default metadata for a new entry. """ @spec default_metadata() :: metadata() def default_metadata do now = DateTime.utc_now() |> DateTime.truncate(:second) %{ status: "draft", title: "", description: nil, slug: "", published_at: DateTime.to_iso8601(now) } end # Extract metadata from YAML-style frontmatter defp extract_frontmatter(content) do case Regex.run(~r/^---\n(.*?)\n---\n(.*)$/s, content) do [_, frontmatter, body] -> metadata = parse_frontmatter_lines(frontmatter) {:ok, metadata, String.trim(body)} _ -> {:error, :no_frontmatter} end end defp parse_frontmatter_lines(frontmatter) do default = default_metadata() lines = frontmatter |> String.split("\n") |> Enum.map(&String.trim/1) |> Enum.reject(&(&1 == "")) metadata = Enum.reduce(lines, %{}, fn line, acc -> case String.split(line, ":", parts: 2) do [key, value] -> Map.put(acc, String.trim(key), String.trim(value)) _ -> acc end end) %{ title: Map.get(metadata, "title", default.title), status: Map.get(metadata, "status", default.status), slug: Map.get(metadata, "slug", default.slug), published_at: Map.get(metadata, "published_at", default.published_at), description: Map.get(metadata, "description") } end # Extract metadata from element attributes (legacy XML format) defp extract_metadata_from_xml(content) do default = default_metadata() # Simple regex-based extraction (for now) title = extract_attribute(content, "title") || default.title status = extract_attribute(content, "status") || default.status slug = extract_attribute(content, "slug") || default.slug published_at = extract_attribute(content, "published_at") || default.published_at description = extract_attribute(content, "description") %{ title: title, status: status, slug: slug, published_at: published_at, description: description } end defp extract_attribute(content, attr_name) do regex = ~r/]*\s#{attr_name}="([^"]*)"/ case Regex.run(regex, content) do [_, value] -> value _ -> nil end end end