From 6702532fc9b5753781fd3d48bc5d1194c7897b26 Mon Sep 17 00:00:00 2001 From: Chili Palmer Date: Sat, 25 Apr 2026 13:17:40 +0200 Subject: [PATCH] fix: parser errors --- lib/bds/frontmatter.ex | 18 +++++++++++++++-- lib/bds/sidecar.ex | 21 +++++++++++++++++-- test/bds/media_test.exs | 40 ++++++++++++++++++++++++++++++++++++ test/bds/posts_test.exs | 45 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) diff --git a/lib/bds/frontmatter.ex b/lib/bds/frontmatter.ex index 24ce7af..72c7c33 100644 --- a/lib/bds/frontmatter.ex +++ b/lib/bds/frontmatter.ex @@ -102,13 +102,18 @@ defmodule BDS.Frontmatter do defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do trimmed = String.trim(value) + parsed = parse_generic_scalar(trimmed) cond do timestamp_key?(key) -> - Persistence.parse_timestamp(trimmed) || parse_generic_scalar(trimmed) + if is_binary(parsed) do + Persistence.parse_timestamp(parsed) || parsed + else + parsed + end true -> - parse_generic_scalar(trimmed) + parsed end end @@ -120,6 +125,7 @@ defmodule BDS.Frontmatter do defp parse_generic_scalar("true"), do: true defp parse_generic_scalar("false"), do: false + defp parse_generic_scalar("[]"), do: [] defp parse_generic_scalar(value) do if Regex.match?(~r/^-?\d+$/, value) do @@ -137,6 +143,14 @@ defmodule BDS.Frontmatter do |> String.replace("\\\\", "\\") end + defp parse_string("'" <> rest) do + rest + |> String.trim_trailing("'") + |> String.replace("\\n", "\n") + |> String.replace("\\'", "'") + |> String.replace("\\\\", "\\") + end + defp parse_string(value), do: value defp serialize_scalar(_key, value) when is_boolean(value) do diff --git a/lib/bds/sidecar.ex b/lib/bds/sidecar.ex index b52400b..40ac09f 100644 --- a/lib/bds/sidecar.ex +++ b/lib/bds/sidecar.ex @@ -79,10 +79,18 @@ defmodule BDS.Sidecar do defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do trimmed = String.trim(value) + parsed = parse_generic_scalar(trimmed) cond do - timestamp_key?(key) -> Persistence.parse_timestamp(trimmed) || parse_generic_scalar(trimmed) - true -> parse_generic_scalar(trimmed) + timestamp_key?(key) -> + if is_binary(parsed) do + Persistence.parse_timestamp(parsed) || parsed + else + parsed + end + + true -> + parsed end end @@ -94,6 +102,7 @@ defmodule BDS.Sidecar do defp parse_generic_scalar("true"), do: true defp parse_generic_scalar("false"), do: false + defp parse_generic_scalar("[]"), do: [] defp parse_generic_scalar(value) do if Regex.match?(~r/^-?\d+$/, value) do @@ -111,6 +120,14 @@ defmodule BDS.Sidecar do |> String.replace("\\\\", "\\") end + defp parse_string("'" <> rest) do + rest + |> String.trim_trailing("'") + |> String.replace("\\n", "\n") + |> String.replace("\\'", "'") + |> String.replace("\\\\", "\\") + end + defp parse_string(value), do: value defp serialize_scalar(_key, value) when is_boolean(value) do diff --git a/test/bds/media_test.exs b/test/bds/media_test.exs index 3037452..580ba9b 100644 --- a/test/bds/media_test.exs +++ b/test/bds/media_test.exs @@ -200,6 +200,46 @@ defmodule BDS.MediaTest do end) end + test "rebuild_media_from_files parses inline empty tag arrays", %{ + project: project, + temp_dir: temp_dir + } do + media_dir = Path.join([temp_dir, "media", "2002", "11"]) + File.mkdir_p!(media_dir) + + binary_path = Path.join(media_dir, "muehle.jpg") + sidecar_path = binary_path <> ".meta" + + File.write!(binary_path, tiny_jpeg_binary()) + + File.write!( + sidecar_path, + [ + "id: 23c69669-678d-4b0b-bb10-c4c31bd427d1", + "originalName: muehle.jpg", + "mimeType: image/jpeg", + "size: #{byte_size(tiny_jpeg_binary())}", + "width: 3", + "height: 2", + "title: Beitrag ohne Titel", + "alt: Ein grüner Mühlenbau mit Windmühlen in einem goldgelben Feld", + "caption: Ein friedlicher Blick auf eine alte Mühle und die Weizenernte unter einem strahlend blauen Himmel.", + "createdAt: '2002-11-17T00:00:00.000Z'", + "updatedAt: '2026-03-18T15:31:10.146Z'", + "tags: []", + "" + ] + |> Enum.join("\n") + ) + + assert {:ok, [media]} = BDS.Media.rebuild_media_from_files(project.id) + assert media.id == "23c69669-678d-4b0b-bb10-c4c31bd427d1" + assert media.original_name == "muehle.jpg" + assert media.tags == [] + assert media.created_at == 1_037_491_200_000 + assert media.updated_at == 1_773_847_870_146 + end + test "import_media generates the four thumbnail files in bucketed thumbnail paths", %{ project: project, temp_dir: temp_dir diff --git a/test/bds/posts_test.exs b/test/bds/posts_test.exs index d43d86b..3ecd008 100644 --- a/test/bds/posts_test.exs +++ b/test/bds/posts_test.exs @@ -384,6 +384,51 @@ defmodule BDS.PostsTest do assert translation.content == nil end + test "rebuild_posts_from_files parses quoted canonical timestamps and inline empty tag arrays" do + temp_dir = + Path.join(System.tmp_dir!(), "bds-post-rebuild-quoted-#{System.unique_integer([:positive])}") + + File.mkdir_p!(temp_dir) + on_exit(fn -> File.rm_rf(temp_dir) end) + + assert {:ok, project} = + BDS.Projects.create_project(%{name: "Quoted Rebuild", data_path: temp_dir}) + + posts_dir = Path.join([BDS.Projects.project_data_dir(project), "posts", "2002", "11"]) + File.mkdir_p!(posts_dir) + + File.write!( + Path.join(posts_dir, "p10.md"), + [ + "---", + "id: 1c1ecaeb-c94c-446a-9291-e946cb991637", + "title: Chimera", + "slug: p10", + "status: published", + "language: de", + "createdAt: '2002-11-05T12:00:00.000Z'", + "updatedAt: '2026-03-04T14:11:28.000Z'", + "publishedAt: '2002-11-05T12:00:00.000Z'", + "tags: []", + "categories:", + " - article", + "---", + "Quelle", + "" + ] + |> Enum.join("\n") + ) + + assert {:ok, [post]} = BDS.Posts.rebuild_posts_from_files(project.id) + assert post.id == "1c1ecaeb-c94c-446a-9291-e946cb991637" + assert post.slug == "p10" + assert post.tags == [] + assert post.categories == ["article"] + assert post.created_at == 1_036_497_600_000 + assert post.updated_at == 1_772_633_488_000 + assert post.published_at == 1_036_497_600_000 + end + defp errors_on(changeset) do Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> Regex.replace(~r"%{(\w+)}", message, fn _, key ->