Skip to content

Commit 9b2a9e0

Browse files
committed
fix: keep --- inside a front matter value when a build sets a page option
When setting an option such as `bread-crumbs`, `toc` or `body-classes` in a user guide, blog index or blended homepage, the build split the page at the first two occurrences of `---` anywhere. A front matter value containing `---`, such as `title: A---B`, ended the block early and corrupted the page. The block now ends at the first line that starts with `---`, matching the build's front matter reader. The page's other lines keep their text. Other places that edit front matter by splitting on `---` are unchanged. The Quarto project diff against main matches in all 40 cases. Across the pages in `docs/` and the rendered Gauntlet sites, the old and new edits differ only on one rendered page whose front matter quotes `# ---`.
1 parent c41becf commit 9b2a9e0

2 files changed

Lines changed: 53 additions & 25 deletions

File tree

‎great_docs/_yaml.py‎

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -203,21 +203,43 @@ def locate_front_matter(content: str) -> FrontMatterBlock | None:
203203
The front matter, or `None` when the page does not open with a closed
204204
block.
205205
"""
206-
opening = FRONT_MATTER_OPENING.match(content)
207-
if opening is None:
208-
return None
209-
closing = content.find("\n---", opening.end() - 1)
210-
if closing == -1:
206+
span = _front_matter_span(content)
207+
if span is None:
211208
return None
209+
start, closing = span
212210
try:
213-
parsed = parse_yaml(content[opening.end() : closing + 1])
211+
parsed = parse_yaml(content[start:closing])
214212
except ValueError:
215213
parsed = None
216214
mapping = YamlMapping(parsed) if isinstance(parsed, dict) else YamlMapping()
217-
line_end = content.find("\n", closing + 4)
215+
line_end = content.find("\n", closing + 3)
218216
return FrontMatterBlock(mapping, len(content) if line_end == -1 else line_end + 1)
219217

220218

219+
def _front_matter_span(content: str) -> tuple[int, int] | None:
220+
"""
221+
Locate the YAML text in a page's opening front matter
222+
223+
Parameters
224+
----------
225+
content
226+
The page source.
227+
228+
Returns
229+
-------
230+
:
231+
The start and end offsets of the YAML text, excluding the opening and closing
232+
delimiter lines, or `None` when the page does not open with a closed block.
233+
"""
234+
opening = FRONT_MATTER_OPENING.match(content)
235+
if opening is None:
236+
return None
237+
closing = content.find("\n---", opening.end() - 1)
238+
if closing == -1:
239+
return None
240+
return opening.end(), closing + 1
241+
242+
221243
def split_front_matter(content: str) -> tuple[YamlMapping, str]:
222244
"""
223245
Separate a page's front matter from its body
@@ -267,21 +289,19 @@ def set_front_matter_option(content: str, key: str, value: bool | int | str) ->
267289
yaml_value = f'"{value}"'
268290
else:
269291
yaml_value = str(value)
270-
271-
if content.startswith("---"):
272-
parts = content.split("---", 2)
273-
if len(parts) >= 3:
274-
frontmatter = parts[1]
275-
rest = parts[2]
276-
277-
key_pattern = rf"^{re.escape(key)}:\s*.*$"
278-
if re.search(key_pattern, frontmatter, re.MULTILINE):
279-
frontmatter = re.sub(
280-
key_pattern, f"{key}: {yaml_value}", frontmatter, flags=re.MULTILINE
281-
)
282-
else:
283-
frontmatter = frontmatter.rstrip() + f"\n{key}: {yaml_value}\n"
284-
285-
return f"---{frontmatter}---{rest}"
286-
287-
return f"---\n{key}: {yaml_value}\n---\n\n{content}"
292+
line = f"{key}: {yaml_value}"
293+
294+
span = _front_matter_span(content)
295+
if span is None:
296+
return f"---\n{line}\n---\n\n{content}"
297+
298+
start, closing = span
299+
front_matter = content[start:closing]
300+
key_pattern = re.compile(rf"^{re.escape(key)}:\s*.*$", re.MULTILINE)
301+
if key_pattern.search(front_matter):
302+
front_matter = key_pattern.sub(lambda _: line, front_matter)
303+
elif front_matter.strip():
304+
front_matter = f"{front_matter.rstrip()}\n{line}\n"
305+
else:
306+
front_matter = f"{line}\n"
307+
return content[:start] + front_matter + content[closing:]

‎tests/test_yaml.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,11 @@ def test_locates_nothing_without_a_closed_block():
143143
)
144144
def test_sets_a_front_matter_option(content: str, key: str, value: bool | int | str, expected: str):
145145
assert set_front_matter_option(content, key, value) == expected
146+
147+
148+
def test_setting_an_option_keeps_dashes_inside_a_value():
149+
content = "---\ntitle: A---B\n---\nBody\n"
150+
151+
result = set_front_matter_option(content, "toc", False)
152+
153+
assert result == "---\ntitle: A---B\ntoc: false\n---\nBody\n"

0 commit comments

Comments
 (0)