sqzass.git

sqzass

~ A static site generator written in Rust
git clone https://github.com/debuman/sqzass.git
commit cf22d8f72b9e3848a3eb24a220bd52813ee018b5
parent 03a1dc5a15e671a0939db725d9165d2770313f38
Author: sqzer <110329244+sqzer-x@users.noreply.github.com>
Date: Fri, 31 Jul 2026 22:17:23 +0900

없는 page_template도 빌드를 멈춘다

front matter `template`이 없는 이름을 가리키면 에러인데, 섹션의
`page_template`은 조용히 `page.html`로 떨어졌다. `templates.has(pt)`가
조건절에 섞여 있어서 참이 아니면 그냥 다음 단계로 흘렀다.

두 자리는 같은 규칙이어야 한다. CLAUDE.md가 불변식으로 적어 둔 것도 "없는
템플릿은 에러"다. 조용히 떨어지면 섹션 하나가 통째로 잘못된 템플릿으로
렌더되고, 빌드는 성공했다고 말한다. 알아채는 자리가 빌드가 아니라 브라우저다.

이걸 문서에 예외로 적는 선택지도 있었지만, 예외를 적는 건 불변식을 하나
무르는 것이라 코드를 불변식에 맞췄다. 에러 문구는 `template` 쪽과 같은 모양으로
없는 이름과 쓸 수 있는 템플릿 목록을 짚는다.

doctor는 `if let Ok(name)`으로 받으므로 영향이 없다 — 이제 그 페이지를 미사용
템플릿 집계에서 빼는데, 애초에 렌더되지 않을 페이지다.

Diffstat:
Msrc/lib.rs | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 54 insertion(+), 2 deletion(-)

diff --git a/src/lib.rs b/src/lib.rs
@@ -658,11 +658,22 @@ pub(crate) fn select_template(page: &Page, site: &Site, templates: &Templates) -
         return Ok("section.html".into());
     }
 
+    // 없는 이름은 front matter `template`과 똑같이 에러다. 조용히 page.html로
+    // 떨어뜨리면 섹션 하나가 통째로 잘못된 템플릿으로 렌더되고, 그걸 알아채는
+    // 자리는 빌드가 아니라 브라우저다.
     if !page.is_section
         && let Some(pt) = site.page_template_for(page)
-        && templates.has(pt)
     {
-        return Ok(pt.to_string());
+        if templates.has(pt) {
+            return Ok(pt.to_string());
+        }
+        anyhow::bail!(
+            "{}: 섹션이 page_template = \"{}\"를 가리키는데 그런 템플릿이 없습니다.\n\
+             사용 가능한 템플릿: {}",
+            page.source.display(),
+            pt,
+            templates.names().join(", ")
+        );
     }
 
     let candidate = if page.is_section {
@@ -783,3 +794,44 @@ mod search_toggle_tests {
         let _ = std::fs::remove_dir_all(&dir);
     }
 }
+
+#[cfg(test)]
+mod template_selection_tests {
+    use super::*;
+
+    /// 없는 `page_template`은 front matter `template`과 똑같이 빌드를 멈춰야 한다.
+    /// 조용히 `page.html`로 떨어지면 섹션 하나가 통째로 잘못된 템플릿으로 나가고,
+    /// 빌드는 성공했다고 말한다.
+    #[test]
+    fn a_page_template_naming_nothing_stops_the_build() {
+        let dir = std::env::temp_dir().join(format!("sqzass-pt-{}", std::process::id()));
+        let _ = std::fs::remove_dir_all(&dir);
+        init::init(&dir).unwrap();
+        std::fs::create_dir_all(dir.join("content/blog")).unwrap();
+        std::fs::write(
+            dir.join("content/blog/_index.md"),
+            "+++\ntitle = \"Blog\"\npage_template = \"nope.html\"\n+++\n",
+        )
+        .unwrap();
+        std::fs::write(
+            dir.join("content/blog/post.md"),
+            "+++\ntitle = \"Post\"\n+++\n",
+        )
+        .unwrap();
+
+        let err = build_to_memory(&BuildOptions {
+            input: dir.clone(),
+            output: None,
+            drafts: false,
+            base_url: None,
+            profile: false,
+        })
+        .unwrap_err();
+        let msg = format!("{err:#}");
+        assert!(
+            msg.contains("nope.html"),
+            "에러가 없는 이름을 짚어야 한다: {msg}"
+        );
+        let _ = std::fs::remove_dir_all(&dir);
+    }
+}