fix: parser errors
This commit is contained in:
@@ -102,13 +102,18 @@ defmodule BDS.Frontmatter do
|
|||||||
|
|
||||||
defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do
|
defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do
|
||||||
trimmed = String.trim(value)
|
trimmed = String.trim(value)
|
||||||
|
parsed = parse_generic_scalar(trimmed)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
timestamp_key?(key) ->
|
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 ->
|
true ->
|
||||||
parse_generic_scalar(trimmed)
|
parsed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -120,6 +125,7 @@ defmodule BDS.Frontmatter do
|
|||||||
|
|
||||||
defp parse_generic_scalar("true"), do: true
|
defp parse_generic_scalar("true"), do: true
|
||||||
defp parse_generic_scalar("false"), do: false
|
defp parse_generic_scalar("false"), do: false
|
||||||
|
defp parse_generic_scalar("[]"), do: []
|
||||||
|
|
||||||
defp parse_generic_scalar(value) do
|
defp parse_generic_scalar(value) do
|
||||||
if Regex.match?(~r/^-?\d+$/, value) do
|
if Regex.match?(~r/^-?\d+$/, value) do
|
||||||
@@ -137,6 +143,14 @@ defmodule BDS.Frontmatter do
|
|||||||
|> String.replace("\\\\", "\\")
|
|> String.replace("\\\\", "\\")
|
||||||
end
|
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 parse_string(value), do: value
|
||||||
|
|
||||||
defp serialize_scalar(_key, value) when is_boolean(value) do
|
defp serialize_scalar(_key, value) when is_boolean(value) do
|
||||||
|
|||||||
@@ -79,10 +79,18 @@ defmodule BDS.Sidecar do
|
|||||||
|
|
||||||
defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do
|
defp parse_scalar(key, value) when is_binary(key) and is_binary(value) do
|
||||||
trimmed = String.trim(value)
|
trimmed = String.trim(value)
|
||||||
|
parsed = parse_generic_scalar(trimmed)
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
timestamp_key?(key) -> Persistence.parse_timestamp(trimmed) || parse_generic_scalar(trimmed)
|
timestamp_key?(key) ->
|
||||||
true -> parse_generic_scalar(trimmed)
|
if is_binary(parsed) do
|
||||||
|
Persistence.parse_timestamp(parsed) || parsed
|
||||||
|
else
|
||||||
|
parsed
|
||||||
|
end
|
||||||
|
|
||||||
|
true ->
|
||||||
|
parsed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -94,6 +102,7 @@ defmodule BDS.Sidecar do
|
|||||||
|
|
||||||
defp parse_generic_scalar("true"), do: true
|
defp parse_generic_scalar("true"), do: true
|
||||||
defp parse_generic_scalar("false"), do: false
|
defp parse_generic_scalar("false"), do: false
|
||||||
|
defp parse_generic_scalar("[]"), do: []
|
||||||
|
|
||||||
defp parse_generic_scalar(value) do
|
defp parse_generic_scalar(value) do
|
||||||
if Regex.match?(~r/^-?\d+$/, value) do
|
if Regex.match?(~r/^-?\d+$/, value) do
|
||||||
@@ -111,6 +120,14 @@ defmodule BDS.Sidecar do
|
|||||||
|> String.replace("\\\\", "\\")
|
|> String.replace("\\\\", "\\")
|
||||||
end
|
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 parse_string(value), do: value
|
||||||
|
|
||||||
defp serialize_scalar(_key, value) when is_boolean(value) do
|
defp serialize_scalar(_key, value) when is_boolean(value) do
|
||||||
|
|||||||
@@ -200,6 +200,46 @@ defmodule BDS.MediaTest do
|
|||||||
end)
|
end)
|
||||||
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", %{
|
test "import_media generates the four thumbnail files in bucketed thumbnail paths", %{
|
||||||
project: project,
|
project: project,
|
||||||
temp_dir: temp_dir
|
temp_dir: temp_dir
|
||||||
|
|||||||
@@ -384,6 +384,51 @@ defmodule BDS.PostsTest do
|
|||||||
assert translation.content == nil
|
assert translation.content == nil
|
||||||
end
|
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
|
defp errors_on(changeset) do
|
||||||
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
|
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
|
||||||
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
|
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
|
||||||
|
|||||||
Reference in New Issue
Block a user