Skip to content

Commit 2d29b65

Browse files
committed
Merge develop: 블로그 truncate 배치 개선 [skip-notion]
2 parents 1f262b2 + 18160be commit 2d29b65

2 files changed

Lines changed: 96 additions & 10 deletions

File tree

scripts/notion_to_md.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,28 @@ def save_sync_map(mapping):
675675
json.dump(mapping, f, ensure_ascii=False, indent=2)
676676

677677

678+
def _insert_truncate_marker(body):
679+
"""블로그 excerpt 경계용 <!--truncate--> 삽입.
680+
681+
선행 제목(#...)은 excerpt에 남기고, 첫 실제 콘텐츠 블록이 끝나는 지점 뒤에
682+
마커를 넣는다. 콘텐츠 블록이 없으면(제목/빈 줄만) 원본을 그대로 반환한다.
683+
section-break 구분선을 excerpt 경계로 오인하지 않는다.
684+
"""
685+
if "<!--truncate-->" in body:
686+
return body
687+
lines = body.split("\n")
688+
i = 0
689+
while i < len(lines) and (lines[i].strip() == "" or re.match(r"#{1,6}\s", lines[i])):
690+
i += 1
691+
if i >= len(lines):
692+
return body
693+
j = i
694+
while j < len(lines) and lines[j].strip() != "":
695+
j += 1
696+
head = "\n".join(lines[:j])
697+
tail = "\n".join(lines[j:]).lstrip("\n")
698+
return head + "\n\n<!--truncate-->\n\n" + tail
699+
678700

679701
def save_doc_page(page, position, existing_map, parent_slug=None, is_parent=False):
680702
"""Notion 페이지를 Markdown 파일로 저장한다.
@@ -763,16 +785,8 @@ def save_doc_page(page, position, existing_map, parent_slug=None, is_parent=Fals
763785
safe_title = title.replace('"', '\\"')
764786

765787
if BLOG_MODE:
766-
# truncate 마커 삽입: Notion 구분선 우선, 없으면 첫 단락 뒤에 자동 삽입
767-
if "\n---\n" in body:
768-
body = body.replace("\n---\n", "\n\n<!--truncate-->\n\n", 1)
769-
else:
770-
stripped = body.lstrip("\n")
771-
match = re.search(r"\n\n", stripped)
772-
if match:
773-
offset = len(body) - len(stripped)
774-
pos = offset + match.end()
775-
body = body[:pos] + "<!--truncate-->\n\n" + body[pos:]
788+
# truncate 마커 삽입: 선행 제목은 excerpt에 남기고 첫 콘텐츠 블록 뒤에 삽입
789+
body = _insert_truncate_marker(body)
776790

777791
authors_list = read_multi_select(props, NOTION_PROPERTY_AUTHORS)
778792
if not authors_list:

scripts/tests/test_notion_to_md.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,3 +759,75 @@ def test_link_preview_empty_url(self):
759759
}
760760
result = self._render(block)
761761
assert result == ""
762+
763+
764+
# ── 블로그 truncate 마커 배치 (_insert_truncate_marker) ──────────────────────
765+
766+
class TestInsertTruncateMarker:
767+
"""BLOG_MODE excerpt 경계용 <!--truncate--> 삽입 규칙.
768+
769+
규칙: 선행 제목(#...)은 excerpt에 남기고, 첫 실제 콘텐츠 블록 뒤에 삽입.
770+
"""
771+
772+
def _split(self, body):
773+
result = n._insert_truncate_marker(body)
774+
assert result.count("<!--truncate-->") == 1
775+
excerpt, rest = result.split("<!--truncate-->", 1)
776+
return excerpt, rest
777+
778+
def test_leading_heading_then_paragraph(self):
779+
# 05번 글 케이스: 빈 제목 뒤 첫 문단이 excerpt에 포함되어야 함
780+
body = "### **개요**\n\n개발 과정에서 달마다 글을 작성하기로 했다.\n\n다음 문단.\n"
781+
excerpt, rest = self._split(body)
782+
assert "### **개요**" in excerpt
783+
assert "개발 과정에서 달마다 글을 작성하기로 했다." in excerpt
784+
assert "다음 문단." not in excerpt
785+
assert "다음 문단." in rest
786+
787+
def test_leading_list_block(self):
788+
# 06번 글 케이스: 선행 제목 없이 불릿 리스트가 첫 콘텐츠 → 리스트 전체가 excerpt
789+
body = "- 프로젝트 소개\n- Qwen3.6 VLM 적용기\n\n### 프로젝트 소개\n본문\n"
790+
excerpt, rest = self._split(body)
791+
assert "- 프로젝트 소개" in excerpt
792+
assert "- Qwen3.6 VLM 적용기" in excerpt
793+
assert "### 프로젝트 소개" not in excerpt
794+
assert "### 프로젝트 소개" in rest
795+
796+
def test_multiple_leading_headings(self):
797+
body = "# 제목\n\n## 소제목\n\n첫 문단.\n\n둘째 문단.\n"
798+
excerpt, rest = self._split(body)
799+
assert "# 제목" in excerpt
800+
assert "## 소제목" in excerpt
801+
assert "첫 문단." in excerpt
802+
assert "둘째 문단." not in excerpt
803+
804+
def test_paragraph_first_no_heading(self):
805+
body = "첫 문단.\n\n둘째 문단.\n"
806+
excerpt, rest = self._split(body)
807+
assert "첫 문단." in excerpt
808+
assert "둘째 문단." not in excerpt
809+
810+
def test_image_as_first_content_block(self):
811+
body = "### 개요\n\n![image](/img/x.png)\n\n본문 텍스트.\n"
812+
excerpt, rest = self._split(body)
813+
assert "### 개요" in excerpt
814+
assert "![image](/img/x.png)" in excerpt
815+
assert "본문 텍스트." not in excerpt
816+
817+
def test_divider_not_treated_as_boundary(self):
818+
# section-break 구분선이 첫 문단보다 뒤에 있으면 excerpt는 첫 문단까지만
819+
body = "### 개요\n\n첫 문단.\n\n---\n\n### 다음 섹션\n내용\n"
820+
excerpt, rest = self._split(body)
821+
assert "첫 문단." in excerpt
822+
assert "다음 섹션" not in excerpt
823+
824+
def test_already_has_truncate_unchanged(self):
825+
body = "### 개요\n\n<!--truncate-->\n\n본문\n"
826+
assert n._insert_truncate_marker(body) == body
827+
828+
def test_only_headings_no_content(self):
829+
# 콘텐츠 블록이 없으면 마커 삽입하지 않음
830+
body = "### 개요\n\n### 다른 제목\n"
831+
result = n._insert_truncate_marker(body)
832+
assert "<!--truncate-->" not in result
833+
assert result == body

0 commit comments

Comments
 (0)