-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathblog.py
More file actions
138 lines (105 loc) · 3.53 KB
/
blog.py
File metadata and controls
138 lines (105 loc) · 3.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
"""CLI tool to scaffold new blog posts for the PySV website."""
import re
import sys
from datetime import date
from pathlib import Path
CONTENT_DIR = Path(__file__).parent / "content" / "blog"
# Canonical field order matches models/blog-post.ini with `body` moved to
# the end. Only fields declared in the model are emitted here — keeping the
# scaffold aligned with the rest of content/blog/.
TEMPLATE_DE = """\
title: {title}
---
pub_date: {pub_date}
---
teaser_image: preview.jpg
---
teaser_text:
Kurze Zusammenfassung des Beitrags (1-2 Sätze). Wird in der Blog-Übersicht angezeigt.
---
cta: Weiterlesen
---
show_on_homepage: False
---
highlighted: False
---
body:
#### Überschrift
Hier den Inhalt des Blogposts schreiben. Markdown wird unterstützt.
Um Bilder einzufügen, lege die Bilddateien in diesen Ordner und verweise so darauf:

"""
TEMPLATE_EN = """\
title: {title}
---
pub_date: {pub_date}
---
teaser_image: preview.jpg
---
teaser_text:
Short summary of the post (1-2 sentences). Shown in the blog overview.
---
cta: Read more
---
show_on_homepage: False
---
highlighted: False
---
body:
#### Heading
Write your blog post content here. Markdown is supported.
To include images, place the image files in this directory and reference them like this:

"""
def slugify(text: str) -> str:
"""Convert text to a URL-friendly slug."""
slug = text.lower()
slug = re.sub(r"[^a-z0-9]+", "-", slug)
return slug.strip("-")
def prompt(label: str, default: str = "") -> str:
"""Prompt the user for input with an optional default."""
suffix = f" [{default}]" if default else ""
value = input(f"{label}{suffix}: ").strip()
return value or default
def add() -> None:
"""Interactive flow to create a new blog post."""
print("\n── New Blog Post ──\n")
title = prompt("Title (e.g. 'PyCon DE 2026')")
if not title:
print("Error: title is required.")
sys.exit(1)
today = date.today().isoformat()
pub_date = prompt("Publication date (YYYY-MM-DD)", default=today)
try:
year = date.fromisoformat(pub_date).year
except ValueError:
print(f"Error: '{pub_date}' is not a valid date (expected YYYY-MM-DD).")
sys.exit(1)
slug = f"{year}-{slugify(title)}"
post_dir = CONTENT_DIR / slug
print(f"\n── Summary ──")
print(f" Title: {title}")
print(f" Date: {pub_date}")
print(f" Directory: content/blog/{slug}/")
print(f" Files: contents.lr (DE), contents+en.lr (EN)\n")
confirm = input("Create this blog post? [Y/n] ").strip().lower()
if confirm and confirm != "y":
print("Cancelled.")
sys.exit(0)
post_dir.mkdir(parents=True, exist_ok=False)
fields = {"title": title, "pub_date": pub_date}
(post_dir / "contents.lr").write_text(TEMPLATE_DE.format(**fields))
(post_dir / "contents+en.lr").write_text(TEMPLATE_EN.format(**fields))
print(f"\nBlog post created at: content/blog/{slug}/")
print("\nNext steps:")
print(f" 1. Edit content/blog/{slug}/contents.lr (German)")
print(f" 2. Edit content/blog/{slug}/contents+en.lr (English)")
print(f" 3. Add a preview.jpg image to the directory")
print(f" 4. Run 'make run' and check http://localhost:5001/blog/{slug}/")
def main() -> None:
if len(sys.argv) < 2 or sys.argv[1] != "add":
print("Usage: uv run python blog.py add")
sys.exit(1)
add()
if __name__ == "__main__":
main()