From 12686c5914a3c80deacba1a0baef397ef5cd2123 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:06:51 +0000 Subject: [PATCH 1/2] build(deps): bump python-frontmatter from 1.1.0 to 1.3.0 Bumps [python-frontmatter](https://github.com/eyeseast/python-frontmatter) from 1.1.0 to 1.3.0. - [Release notes](https://github.com/eyeseast/python-frontmatter/releases) - [Commits](https://github.com/eyeseast/python-frontmatter/compare/v1.1.0...v1.3.0) --- updated-dependencies: - dependency-name: python-frontmatter dependency-version: 1.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index eb657fe..5b91eda 100644 --- a/uv.lock +++ b/uv.lock @@ -2090,14 +2090,14 @@ wheels = [ [[package]] name = "python-frontmatter" -version = "1.1.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/de/910fa208120314a12f9a88ea63e03707261692af782c99283f1a2c8a5e6f/python-frontmatter-1.1.0.tar.gz", hash = "sha256:7118d2bd56af9149625745c58c9b51fb67e8d1294a0c76796dafdc72c36e5f6d", size = 16256, upload-time = "2024-01-16T18:50:04.052Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e8/79cbe69864d44f3b48e70ebee0a872a7d5a4e7150c9f8577ed7a5beefff0/python_frontmatter-1.3.0.tar.gz", hash = "sha256:acc73e477a568dc2a25c9e130c6c68ae8daa8c204c8f7e813db47d6a7280dcf2", size = 8322, upload-time = "2026-05-20T19:21:44.164Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/87/3c8da047b3ec5f99511d1b4d7a5bc72d4b98751c7e78492d14dc736319c5/python_frontmatter-1.1.0-py3-none-any.whl", hash = "sha256:335465556358d9d0e6c98bbeb69b1c969f2a4a21360587b9873bfc3b213407c1", size = 9834, upload-time = "2024-01-16T18:50:00.911Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a3/17c284b4f4d8ad50f0f9ba70ad8fcc35c777aeafcdbbffdd91bbdc5ab379/python_frontmatter-1.3.0-py3-none-any.whl", hash = "sha256:9f7dd9260bec99044219159a329f64f039087f9d1a2124c9442556f2fe6f82ec", size = 10562, upload-time = "2026-05-20T19:21:43.323Z" }, ] [[package]] From 7164ef22b4aff38da6a226ffb0b70b33d5c788c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20M=C3=BCller?= Date: Wed, 26 Aug 2026 23:14:22 +0200 Subject: [PATCH 2/2] fix: require a string note title from front matter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit python-frontmatter 1.3 types Post.metadata as dict[str, Any], so mypy now rejects passing an arbitrary metadata value as NoteDetails.title. Front matter is arbitrary YAML, so a title: may parse as a number, list, or blank string — fall back to the filename unless it is a non-blank string. --- src/scribae/io_utils.py | 13 +++++++++++-- tests/unit/io_utils_test.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/scribae/io_utils.py b/src/scribae/io_utils.py index 998c5b4..afe8924 100644 --- a/src/scribae/io_utils.py +++ b/src/scribae/io_utils.py @@ -44,8 +44,9 @@ def load_note(note_path: Path, *, max_chars: int) -> NoteDetails: body = post.content.strip() truncated_body, truncated = truncate(body, max_chars) - note_title = ( - metadata.get("title") or metadata.get("name") or note_path.stem.replace("_", " ").replace("-", " ").title() + # Front matter is arbitrary YAML, so a `title:` may parse as a number, list, or blank. + note_title = _first_text(metadata.get("title"), metadata.get("name")) or ( + note_path.stem.replace("_", " ").replace("-", " ").title() ) return NoteDetails( @@ -74,4 +75,12 @@ def truncate(value: str, max_chars: int) -> tuple[str, bool]: return value[: max_chars - 1].rstrip() + " …", True +def _first_text(*candidates: object) -> str | None: + """Return the first candidate that is a non-blank string.""" + for candidate in candidates: + if isinstance(candidate, str) and candidate.strip(): + return candidate + return None + + __all__ = ["NoteDetails", "Reporter", "load_note", "truncate"] diff --git a/tests/unit/io_utils_test.py b/tests/unit/io_utils_test.py index a581f2f..6606020 100644 --- a/tests/unit/io_utils_test.py +++ b/tests/unit/io_utils_test.py @@ -110,3 +110,35 @@ def _boom(_path: Path) -> None: with pytest.raises(RuntimeError, match="unexpected parser failure"): load_note(Path("note.md"), max_chars=100) + + +class TestLoadNoteTitle: + def test_title_comes_from_frontmatter(self, tmp_path: Path) -> None: + note = tmp_path / "my-note.md" + note.write_text("---\ntitle: Real Title\n---\n\nBody\n", encoding="utf-8") + + assert load_note(note, max_chars=1000).title == "Real Title" + + def test_name_is_used_when_title_is_absent(self, tmp_path: Path) -> None: + note = tmp_path / "my-note.md" + note.write_text("---\nname: From Name\n---\n\nBody\n", encoding="utf-8") + + assert load_note(note, max_chars=1000).title == "From Name" + + def test_filename_is_used_when_frontmatter_has_no_title(self, tmp_path: Path) -> None: + note = tmp_path / "my_great-note.md" + note.write_text("Body\n", encoding="utf-8") + + assert load_note(note, max_chars=1000).title == "My Great Note" + + def test_non_string_title_falls_back_to_the_filename(self, tmp_path: Path) -> None: + note = tmp_path / "my-note.md" + note.write_text("---\ntitle: 2026\n---\n\nBody\n", encoding="utf-8") + + assert load_note(note, max_chars=1000).title == "My Note" + + def test_blank_title_falls_back_to_the_filename(self, tmp_path: Path) -> None: + note = tmp_path / "my-note.md" + note.write_text("---\ntitle: ' '\n---\n\nBody\n", encoding="utf-8") + + assert load_note(note, max_chars=1000).title == "My Note"