diff --git a/custom-skills/32-notion-writer/code/scripts/notion_writer.py b/custom-skills/32-notion-writer/code/scripts/notion_writer.py index 4bad01c..15211f2 100644 --- a/custom-skills/32-notion-writer/code/scripts/notion_writer.py +++ b/custom-skills/32-notion-writer/code/scripts/notion_writer.py @@ -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