From 7f15fb3cca5e1517cc0f61998668ea195026ab17 Mon Sep 17 00:00:00 2001 From: Alex Castillo Date: Tue, 8 Sep 2026 20:03:23 -0400 Subject: [PATCH] Ignore a leading thematic break that isn't really frontmatter `/\A---\s*(.*?)\s*---\s*(.*)/m` also matches a post whose body opens with a `---` thematic break and has another `---` further down. `YAML.safe_load` then returns a string (or an array) rather than a mapping, and `Separator#frontmatter` calls `.each` on it: Perron::Resource::Separator.new("---\nintro\n---\nbody").frontmatter # => NoMethodError: undefined method 'each' for an instance of String Only treat the fenced block as frontmatter when it parses to a `Hash` (or to `nil`, i.e. empty frontmatter); otherwise the whole document is content. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01GtYgwYNfJ3wHrw9xrooVoB --- lib/perron/resource/separator.rb | 19 ++++++++++++++----- test/perron/resource/separator_test.rb | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/perron/resource/separator.rb b/lib/perron/resource/separator.rb index afa241c..263e60a 100644 --- a/lib/perron/resource/separator.rb +++ b/lib/perron/resource/separator.rb @@ -19,12 +19,21 @@ def frontmatter def parsed(content) if content =~ /\A---\s*(.*?)\s*---\s*(.*)/m - @frontmatter = YAML.safe_load($1, permitted_classes: [Date, Time]) || {} - @content = $2.strip - else - @frontmatter = {} - @content = content + parsed_yaml = YAML.safe_load($1, permitted_classes: [Date, Time]) + + # A `---` at the start of the body (a thematic break) followed by + # another `---` also matches, but parses to a string or an array + # rather than a mapping. Only treat it as frontmatter when it is one. + if parsed_yaml.nil? || parsed_yaml.is_a?(Hash) + @frontmatter = parsed_yaml || {} + @content = $2.strip + + return + end end + + @frontmatter = {} + @content = content end end end diff --git a/test/perron/resource/separator_test.rb b/test/perron/resource/separator_test.rb index a4280ad..db52c4a 100644 --- a/test/perron/resource/separator_test.rb +++ b/test/perron/resource/separator_test.rb @@ -60,6 +60,30 @@ def test_parses_yaml_list_syntax assert_equal ["alice", "bob"], separator.frontmatter.authors end + def test_treats_a_leading_thematic_break_as_content_not_frontmatter + content = <<~CONTENT + --- + + An intro paragraph. + + --- + + The rest of the post. + CONTENT + separator = Perron::Resource::Separator.new(content) + + assert_equal content, separator.content + assert_empty separator.frontmatter.to_h + end + + def test_ignores_a_scalar_between_the_fences + content = "---\nJust a sentence, not a mapping\n---\nBody\n" + separator = Perron::Resource::Separator.new(content) + + assert_equal content, separator.content + assert_empty separator.frontmatter.to_h + end + def test_parses_mixed_types_in_frontmatter content = <<~CONTENT ---