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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-substack
3
- Version: 0.1.17
3
+ Version: 0.1.18
4
4
  Summary: A Python wrapper around the Substack API.
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "python-substack"
3
- version = "0.1.17"
3
+ version = "0.1.18"
4
4
  description = "A Python wrapper around the Substack API."
5
5
  authors = ["Paolo Mazza <mazzapaolo2019@gmail.com>"]
6
6
  license = "MIT"
@@ -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
- if match.start() > 0 and text[match.start()-1:match.start()+1] != "![":
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 separately (for bullet lists)
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
- # Remove bullet marker if present
640
+
641
+ # Check for bullet marker
642
+ bullet_text = None
622
643
  if line.startswith("* "):
623
- line = line[2:].strip()
644
+ bullet_text = line[2:].strip()
624
645
  elif line.startswith("- "):
625
- line = line[2:].strip()
646
+ bullet_text = line[2:].strip()
626
647
  elif line.startswith("*") and not line.startswith("**"):
627
- line = line[1:].strip()
628
-
629
- if line:
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)