python-substack 0.1.17__tar.gz → 0.1.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {python_substack-0.1.17 → python_substack-0.1.18}/PKG-INFO +1 -1
- {python_substack-0.1.17 → python_substack-0.1.18}/pyproject.toml +1 -1
- {python_substack-0.1.17 → python_substack-0.1.18}/substack/post.py +49 -21
- {python_substack-0.1.17 → python_substack-0.1.18}/LICENSE +0 -0
- {python_substack-0.1.17 → python_substack-0.1.18}/README.md +0 -0
- {python_substack-0.1.17 → python_substack-0.1.18}/substack/__init__.py +0 -0
- {python_substack-0.1.17 → python_substack-0.1.18}/substack/api.py +0 -0
- {python_substack-0.1.17 → python_substack-0.1.18}/substack/exceptions.py +0 -0
|
@@ -39,40 +39,41 @@ def parse_inline(text: str) -> List[Dict]:
|
|
|
39
39
|
tokens = []
|
|
40
40
|
# Process text character by character to handle nested formatting
|
|
41
41
|
# We'll use regex to find all markdown patterns, then process them in order
|
|
42
|
-
|
|
42
|
+
|
|
43
43
|
# Find all markdown patterns: links, bold, italic
|
|
44
44
|
# Pattern order: links first (to avoid conflicts), then bold, then italic
|
|
45
45
|
link_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
|
|
46
46
|
bold_pattern = r'\*\*([^*]+)\*\*'
|
|
47
47
|
italic_pattern = r'(?<!\*)\*([^*]+)\*(?!\*)' # Not preceded or followed by *
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
# Find all matches with their positions
|
|
50
50
|
matches = []
|
|
51
51
|
for match in re.finditer(link_pattern, text):
|
|
52
52
|
# Skip if it's an image link (starts with ![)
|
|
53
|
-
|
|
53
|
+
# But do NOT skip normal links at position 0.
|
|
54
|
+
if match.start() == 0 or text[match.start()-1:match.start()+1] != "![":
|
|
54
55
|
matches.append((match.start(), match.end(), "link", match.group(1), match.group(2)))
|
|
55
|
-
|
|
56
|
+
|
|
56
57
|
for match in re.finditer(bold_pattern, text):
|
|
57
58
|
# Check if this range is already covered by a link
|
|
58
59
|
if not any(start <= match.start() < end for start, end, _, _, _ in matches):
|
|
59
60
|
matches.append((match.start(), match.end(), "bold", match.group(1), None))
|
|
60
|
-
|
|
61
|
+
|
|
61
62
|
for match in re.finditer(italic_pattern, text):
|
|
62
63
|
# Check if this range is already covered by a link or bold
|
|
63
64
|
if not any(start <= match.start() < end for start, end, _, _, _ in matches):
|
|
64
65
|
matches.append((match.start(), match.end(), "italic", match.group(1), None))
|
|
65
|
-
|
|
66
|
+
|
|
66
67
|
# Sort matches by position
|
|
67
68
|
matches.sort(key=lambda x: x[0])
|
|
68
|
-
|
|
69
|
+
|
|
69
70
|
# Build tokens
|
|
70
71
|
last_pos = 0
|
|
71
72
|
for start, end, match_type, content, url in matches:
|
|
72
73
|
# Add text before this match
|
|
73
74
|
if start > last_pos:
|
|
74
75
|
tokens.append({"content": text[last_pos:start]})
|
|
75
|
-
|
|
76
|
+
|
|
76
77
|
# Add the formatted content
|
|
77
78
|
if match_type == "link":
|
|
78
79
|
tokens.append({
|
|
@@ -89,16 +90,16 @@ def parse_inline(text: str) -> List[Dict]:
|
|
|
89
90
|
"content": content,
|
|
90
91
|
"marks": [{"type": "em"}]
|
|
91
92
|
})
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
last_pos = end
|
|
94
|
-
|
|
95
|
+
|
|
95
96
|
# Add remaining text
|
|
96
97
|
if last_pos < len(text):
|
|
97
98
|
tokens.append({"content": text[last_pos:]})
|
|
98
|
-
|
|
99
|
+
|
|
99
100
|
# Filter out empty tokens
|
|
100
101
|
tokens = [t for t in tokens if t.get("content")]
|
|
101
|
-
|
|
102
|
+
|
|
102
103
|
return tokens
|
|
103
104
|
|
|
104
105
|
|
|
@@ -351,7 +352,7 @@ class Post:
|
|
|
351
352
|
for mark in marks:
|
|
352
353
|
new_mark = {"type": mark.get("type")}
|
|
353
354
|
if mark.get("type") == "link":
|
|
354
|
-
href = mark.get("href")
|
|
355
|
+
href = mark.get("href") or mark.get("attrs", {}).get("href")
|
|
355
356
|
new_mark.update({"attrs": {"href": href}})
|
|
356
357
|
content_marks.append(new_mark)
|
|
357
358
|
content["marks"] = content_marks
|
|
@@ -572,7 +573,7 @@ class Post:
|
|
|
572
573
|
alt_text = linked_image_match.group(1)
|
|
573
574
|
image_url = linked_image_match.group(2)
|
|
574
575
|
link_url = linked_image_match.group(3)
|
|
575
|
-
|
|
576
|
+
|
|
576
577
|
# Adjust image URL if it starts with a slash
|
|
577
578
|
image_url = image_url[1:] if image_url.startswith("/") else image_url
|
|
578
579
|
|
|
@@ -613,22 +614,49 @@ class Post:
|
|
|
613
614
|
# Process paragraphs or bullet lists
|
|
614
615
|
else:
|
|
615
616
|
if "\n" in text_content:
|
|
616
|
-
# Process each line
|
|
617
|
+
# Process each line, grouping consecutive bullets
|
|
618
|
+
# into a single bullet_list node
|
|
619
|
+
pending_bullets: List[List[Dict]] = []
|
|
620
|
+
|
|
621
|
+
def flush_bullets():
|
|
622
|
+
if not pending_bullets:
|
|
623
|
+
return
|
|
624
|
+
list_items = []
|
|
625
|
+
for bullet_nodes in pending_bullets:
|
|
626
|
+
list_items.append({
|
|
627
|
+
"type": "list_item",
|
|
628
|
+
"content": [{"type": "paragraph", "content": bullet_nodes}],
|
|
629
|
+
})
|
|
630
|
+
self.draft_body["content"].append(
|
|
631
|
+
{"type": "bullet_list", "content": list_items}
|
|
632
|
+
)
|
|
633
|
+
pending_bullets.clear()
|
|
634
|
+
|
|
617
635
|
for line in text_content.split("\n"):
|
|
618
636
|
line = line.strip()
|
|
619
637
|
if not line:
|
|
638
|
+
flush_bullets()
|
|
620
639
|
continue
|
|
621
|
-
|
|
640
|
+
|
|
641
|
+
# Check for bullet marker
|
|
642
|
+
bullet_text = None
|
|
622
643
|
if line.startswith("* "):
|
|
623
|
-
|
|
644
|
+
bullet_text = line[2:].strip()
|
|
624
645
|
elif line.startswith("- "):
|
|
625
|
-
|
|
646
|
+
bullet_text = line[2:].strip()
|
|
626
647
|
elif line.startswith("*") and not line.startswith("**"):
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
if
|
|
648
|
+
bullet_text = line[1:].strip()
|
|
649
|
+
|
|
650
|
+
if bullet_text is not None:
|
|
651
|
+
tokens = parse_inline(bullet_text)
|
|
652
|
+
if tokens:
|
|
653
|
+
pending_bullets.append(tokens)
|
|
654
|
+
else:
|
|
655
|
+
flush_bullets()
|
|
630
656
|
tokens = parse_inline(line)
|
|
631
657
|
self.add({"type": "paragraph", "content": tokens})
|
|
658
|
+
|
|
659
|
+
flush_bullets()
|
|
632
660
|
else:
|
|
633
661
|
# Single paragraph
|
|
634
662
|
tokens = parse_inline(text_content)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|