sqzass.git

sqzass

~ A static site generator written in Rust
git clone https://github.com/debuman/sqzass.git

src/search.rs (2787B)

      1 //! 검색 색인 — 언어마다 JSON 한 벌.
      2 //!
      3 //! **부분 문자열로 찾는다. 형태소 분석도, 단어 색인도 쓰지 않는다.**
      4 //! 한국어는 명사에 조사가 붙어 어절이 되고 합성어를 붙여 쓰기 때문에, 단어 단위로
      5 //! 쪼갠 색인은 `검색엔진최적화` 안의 `최적화`를 영영 못 찾는다. 형태소 분석기를
      6 //! 붙이면 이번엔 사전에 없는 외래어가 뭉개진다(`템플릿` → `템플`+`릿`) — 기술 문서엔
      7 //! 치명적이다. 원문을 그대로 두고 부분 문자열로 훑으면 둘 다 걸린다.
      8 //!
      9 //! 대가는 색인이 본문 전체라는 것이다. 문서 사이트 규모에서 이건 문제가 아니고,
     10 //! 브라우저는 첫 검색 때 한 번만 받는다.
     11 //!
     12 //! 파일명에 해시를 붙이지 않는다. 색인은 `<link>`나 `<script>`가 아니라 JS가 런타임에
     13 //! 가져가는 데이터고, 게다가 색인 내용은 렌더된 페이지에서 나오는데 템플릿은 렌더
     14 //! 전에 에셋 URL을 조회한다 — 해시를 붙이면 순환이 된다. 잠깐 옛 색인이 뜨는 건
     15 //! 검색 결과 몇 줄의 문제라 그 대가를 치를 이유가 없다.
     16 
     17 use serde::Serialize;
     18 
     19 /// 색인 한 행. 키를 한 글자로 줄인 건 취향이 아니라 크기다 — 행마다 반복되므로
     20 /// `"title"` 대신 `"t"`를 쓰면 수백 페이지에서 수십 KB가 빠진다.
     21 #[derive(Debug, Clone, Serialize)]
     22 pub struct Entry {
     23     /// 제목
     24     pub t: String,
     25     /// 설명. 없으면 통째로 뺀다.
     26     #[serde(skip_serializing_if = "String::is_empty")]
     27     pub d: String,
     28     /// URL
     29     pub u: String,
     30     /// 소속 섹션 제목. 최상위 페이지엔 없다.
     31     #[serde(skip_serializing_if = "String::is_empty")]
     32     pub s: String,
     33     /// 본문 평문
     34     pub c: String,
     35 }
     36 
     37 /// 해당 언어 색인의 출력 경로. 클라이언트가 `<html lang>`으로 같은 이름을 만든다.
     38 pub fn path(language: &str) -> String {
     39     format!("search-{language}.json")
     40 }
     41 
     42 #[cfg(test)]
     43 mod tests {
     44     use super::*;
     45 
     46     #[test]
     47     fn path_is_per_language() {
     48         assert_eq!(path("ko"), "search-ko.json");
     49         assert_eq!(path("en"), "search-en.json");
     50     }
     51 
     52     #[test]
     53     fn empty_optional_fields_are_omitted() {
     54         let e = Entry {
     55             t: "설치".into(),
     56             d: String::new(),
     57             u: "/ko/start/installation/".into(),
     58             s: String::new(),
     59             c: "소스에서 빌드합니다".into(),
     60         };
     61         let json = serde_json::to_string(&e).unwrap();
     62         assert!(!json.contains("\"d\""), "빈 설명이 나갔다: {json}");
     63         assert!(!json.contains("\"s\""), "빈 섹션이 나갔다: {json}");
     64         assert!(json.contains("\"t\":\"설치\""), "실제: {json}");
     65     }
     66 }