refactor(notion-writer): make markdown_to_notion_blocks reentrant
Split the entry function into a public reentrant entry that accepts either string or List[str], and a private _parse_lines engine that container detectors will recurse into. No behavior change for existing callers; all 16 parser tests still pass. Prep for Phase 3c: callout/toggle/columns/page-mention block coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import re
|
||||
import json
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from typing import Optional, List, Dict, Any
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from notion_client import Client
|
||||
@@ -72,32 +72,36 @@ def _split_table_row(line: str) -> List[str]:
|
||||
return [cell.strip() for cell in stripped.split('|')]
|
||||
|
||||
|
||||
def markdown_to_notion_blocks(markdown_text: str) -> List[Dict[str, Any]]:
|
||||
"""Convert markdown text to Notion block objects."""
|
||||
blocks = []
|
||||
lines = markdown_text.split('\n')
|
||||
i = 0
|
||||
def markdown_to_notion_blocks(content: Union[str, List[str]]) -> List[Dict[str, Any]]:
|
||||
"""Convert markdown text (or pre-split lines) to Notion block objects.
|
||||
|
||||
Reentrant: container detectors recursively call _parse_lines on inner content.
|
||||
"""
|
||||
if isinstance(content, str):
|
||||
lines = content.split('\n')
|
||||
else:
|
||||
lines = list(content)
|
||||
return _parse_lines(lines)
|
||||
|
||||
|
||||
def _parse_lines(lines: List[str]) -> List[Dict[str, Any]]:
|
||||
"""Walk lines and emit Notion blocks. Called recursively by container detectors."""
|
||||
blocks = []
|
||||
i = 0
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# Skip empty lines
|
||||
if not line.strip():
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Table (header row + separator + body rows)
|
||||
if _is_table_row(line) and i + 1 < len(lines) and TABLE_SEPARATOR_RE.match(lines[i + 1]):
|
||||
header_cells = _split_table_row(line)
|
||||
i += 2 # skip separator
|
||||
i += 2
|
||||
body_rows: List[List[str]] = []
|
||||
while i < len(lines) and _is_table_row(lines[i]):
|
||||
body_rows.append(_split_table_row(lines[i]))
|
||||
i += 1
|
||||
blocks.append(create_table_block(header_cells, body_rows))
|
||||
continue
|
||||
|
||||
# Headers
|
||||
if line.startswith('######'):
|
||||
blocks.append(create_heading_block(line[6:].strip(), 3))
|
||||
elif line.startswith('#####'):
|
||||
@@ -110,8 +114,6 @@ def markdown_to_notion_blocks(markdown_text: str) -> List[Dict[str, Any]]:
|
||||
blocks.append(create_heading_block(line[2:].strip(), 2))
|
||||
elif line.startswith('#'):
|
||||
blocks.append(create_heading_block(line[1:].strip(), 1))
|
||||
|
||||
# Code blocks
|
||||
elif line.startswith('```'):
|
||||
language = line[3:].strip() or 'plain text'
|
||||
code_lines = []
|
||||
@@ -120,40 +122,26 @@ def markdown_to_notion_blocks(markdown_text: str) -> List[Dict[str, Any]]:
|
||||
code_lines.append(lines[i])
|
||||
i += 1
|
||||
blocks.append(create_code_block('\n'.join(code_lines), language))
|
||||
|
||||
# Checkbox / Todo (must come before generic bullet match)
|
||||
elif line.strip().startswith('- [ ]'):
|
||||
text = line.strip()[5:].strip()
|
||||
blocks.append(create_todo_block(text, False))
|
||||
elif line.strip().startswith('- [x]') or line.strip().startswith('- [X]'):
|
||||
text = line.strip()[5:].strip()
|
||||
blocks.append(create_todo_block(text, True))
|
||||
|
||||
# Bullet list
|
||||
elif line.strip().startswith('- ') or line.strip().startswith('* '):
|
||||
text = line.strip()[2:]
|
||||
blocks.append(create_bulleted_list_block(text))
|
||||
|
||||
# Numbered list
|
||||
elif re.match(r'^\d+\.\s', line.strip()):
|
||||
text = re.sub(r'^\d+\.\s', '', line.strip())
|
||||
blocks.append(create_numbered_list_block(text))
|
||||
|
||||
# Blockquote
|
||||
elif line.startswith('>'):
|
||||
text = line[1:].strip()
|
||||
blocks.append(create_quote_block(text))
|
||||
|
||||
# Horizontal rule
|
||||
elif line.strip() in ['---', '***', '___']:
|
||||
blocks.append(create_divider_block())
|
||||
|
||||
# Regular paragraph
|
||||
else:
|
||||
blocks.append(create_paragraph_block(line))
|
||||
|
||||
i += 1
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user