Skip to content

Commit 5d431e6

Browse files
prabha-gitclaude
andcommitted
Add dynamic homepage generation with latest 5 blog posts
- Created generate_index.py to automatically generate homepage with 5 most recent posts - Updated GitHub Actions workflow to run generation script before build - Added PyYAML dependency to requirements-doc.txt - Homepage now shows post title, date, tags, and excerpt with links 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent ca012ea commit 5d431e6

File tree

4 files changed

+214
-5
lines changed

4 files changed

+214
-5
lines changed

.github/workflows/mkdocs.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ jobs:
2626
python -m pip install --upgrade pip
2727
pip install -r requirements-doc.txt # This file should list mkdocs and any other packages.
2828
29+
- name: Generate Index Page
30+
run: |
31+
python generate_index.py
32+
2933
- name: Build the MkDocs Site
3034
run: |
3135
mkdocs build --verbose # Build the site.

docs/index.md

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,51 @@
11
# Prabha Arivalagan
22

3-
AI Engineer
3+
AI Engineer writing about agents, LLMs, and cloud infrastructure
44

5-
5+
## Recent Writing
66

7-
- [Github](https://github.com/prabha-git)
7+
### [Building AI Agents in Google Cloud: Choose the Right Approach for Your Needs](/writing/2025/10/04/agents-in-gcp/)
8+
**October 04, 2025** • adk, agent-framework
89

9-
- [Medium Blog](https://medium.com/@prabhakaran_arivalagan)
10+
- ADK → Google-developed open-source framework for building complex multi-agent systems with maximum control and modularity - Conversational Agents (Dialogflow CX) → Omnichannel...
1011

11-
- [x / Twitter](https://twitter.com/prabhatweet)
12+
[Read more →](/writing/2025/10/04/agents-in-gcp/)
13+
14+
### [The Language of Agents - Decoding Messages in LangChain & LangGraph](/writing/2025/05/13/the-language-of-agents/)
15+
**May 13, 2025** • llm
16+
17+
Ever wondered how apps get AI to chat, follow instructions, or even use tools? A lot of the magic comes down to "messages." Think of them as the notes passed between you, the AI,...
18+
19+
[Read more →](/writing/2025/05/13/the-language-of-agents/)
20+
21+
### [Building Personal Chatbot - Part 2](/writing/2024/08/18/building-obsidian-kb-chatbot/)
22+
**August 18, 2024** • llm
23+
24+
In our previous post, we explored building a chatbot for Obsidian notes using Langchain and basic Retrieval-Augmented Generation (RAG) techniques. Today, I am sharing the...
1225

26+
[Read more →](/writing/2024/08/18/building-obsidian-kb-chatbot/)
1327

28+
### [Building an Obsidian Knowledge base Chatbot: A Journey of Iteration and Learning](/writing/2024/04/29/building-obsidian-kb-chatbot/)
29+
**April 29, 2024** • llm, obsidian-kb
1430

31+
As an avid Obsidian user, I've always been fascinated by the potential of leveraging my daily notes as a personal knowledge base. Obsidian has become my go-to tool for taking...
1532

33+
[Read more →](/writing/2024/04/29/building-obsidian-kb-chatbot/)
1634

35+
### [Quantized LLM Models](/writing/2024/04/19/quantized-llm-models/)
36+
**April 19, 2024** • llm
1737

38+
Large Language Models (LLMs) are known for their vast number of parameters, often reaching billions. For example, open-source models like Llama2 come in sizes of 7B, 13B, and 70B...
39+
40+
[Read more →](/writing/2024/04/19/quantized-llm-models/)
41+
42+
---
43+
44+
[View all posts →](/writing/)
45+
46+
## Contact
47+
48+
49+
- [Github](https://github.com/prabha-git)
50+
- [Medium Blog](https://medium.com/@prabhakaran_arivalagan)
51+
- [x / Twitter](https://twitter.com/prabhatweet)

generate_index.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate index.md with the latest 5 blog posts.
4+
This script scans all posts in docs/writing/posts/ and creates a homepage
5+
with the most recent posts including title, date, tags, and excerpt.
6+
"""
7+
8+
import os
9+
import glob
10+
import re
11+
import yaml
12+
from datetime import datetime, date
13+
14+
def extract_frontmatter_and_content(file_path):
15+
"""Extract YAML frontmatter and content from a markdown file."""
16+
with open(file_path, 'r', encoding='utf-8') as f:
17+
content = f.read()
18+
19+
# Match frontmatter between --- markers
20+
match = re.match(r'^---\s*\n(.*?)\n---\s*\n(.*)', content, re.DOTALL)
21+
if not match:
22+
return None, content
23+
24+
frontmatter_text = match.group(1)
25+
markdown_content = match.group(2)
26+
27+
try:
28+
frontmatter = yaml.safe_load(frontmatter_text)
29+
except yaml.YAMLError:
30+
return None, markdown_content
31+
32+
return frontmatter, markdown_content
33+
34+
def extract_excerpt(content, max_length=200):
35+
"""Extract an excerpt from markdown content."""
36+
# Remove markdown headers
37+
content = re.sub(r'^#+\s+.*$', '', content, flags=re.MULTILINE)
38+
# Remove markdown links but keep text
39+
content = re.sub(r'\[([^\]]+)\]\([^\)]+\)', r'\1', content)
40+
# Remove markdown formatting
41+
content = re.sub(r'[*_`]', '', content)
42+
# Remove extra whitespace
43+
content = re.sub(r'\s+', ' ', content).strip()
44+
45+
# Get first meaningful content
46+
if len(content) > max_length:
47+
content = content[:max_length].rsplit(' ', 1)[0] + '...'
48+
49+
return content
50+
51+
def parse_date(date_value):
52+
"""Parse date from various formats to datetime object."""
53+
if isinstance(date_value, datetime):
54+
return date_value
55+
if isinstance(date_value, date):
56+
# Convert date to datetime for consistent handling
57+
return datetime.combine(date_value, datetime.min.time())
58+
if isinstance(date_value, str):
59+
try:
60+
return datetime.strptime(date_value, '%Y-%m-%d')
61+
except ValueError:
62+
pass
63+
return None
64+
65+
def format_date(date_obj):
66+
"""Format datetime object to readable string."""
67+
if date_obj:
68+
return date_obj.strftime('%B %d, %Y')
69+
return ''
70+
71+
def generate_post_url(date_obj, slug):
72+
"""Generate URL for a blog post based on date and slug."""
73+
if date_obj and slug:
74+
return f"/writing/{date_obj.year}/{date_obj.month:02d}/{date_obj.day:02d}/{slug}/"
75+
return "/writing/"
76+
77+
def generate_index():
78+
"""Generate the index.md file with the latest 5 blog posts."""
79+
posts_dir = 'docs/writing/posts'
80+
post_files = glob.glob(f'{posts_dir}/*.md')
81+
82+
posts = []
83+
for file_path in post_files:
84+
frontmatter, content = extract_frontmatter_and_content(file_path)
85+
if not frontmatter:
86+
continue # Skip posts without frontmatter
87+
88+
# Skip drafts
89+
if frontmatter.get('draft') is True:
90+
continue
91+
92+
# Extract metadata
93+
date_obj = parse_date(frontmatter.get('date'))
94+
slug = frontmatter.get('slug', '')
95+
tags = frontmatter.get('tags', [])
96+
97+
# Try to get title from content (first # header)
98+
title_match = re.search(r'^#\s+(.+)$', content, re.MULTILINE)
99+
title = title_match.group(1).strip() if title_match else os.path.basename(file_path).replace('.md', '')
100+
101+
# Get excerpt
102+
excerpt = extract_excerpt(content, max_length=180)
103+
104+
posts.append({
105+
'title': title,
106+
'date': date_obj,
107+
'slug': slug,
108+
'tags': tags,
109+
'excerpt': excerpt,
110+
})
111+
112+
# Sort by date (newest first), handling None dates
113+
posts.sort(key=lambda x: x['date'] if x['date'] else datetime.min, reverse=True)
114+
115+
# Take top 5
116+
recent_posts = posts[:5]
117+
118+
# Generate index.md content
119+
index_content = """# Prabha Arivalagan
120+
121+
AI Engineer writing about agents, LLMs, and cloud infrastructure
122+
123+
## Recent Writing
124+
125+
"""
126+
127+
for post in recent_posts:
128+
date_str = format_date(post['date'])
129+
tags_str = ', '.join(post['tags']) if post['tags'] else ''
130+
post_url = generate_post_url(post['date'], post['slug'])
131+
132+
# Post title (linked)
133+
index_content += f"### [{post['title']}]({post_url})\n"
134+
135+
# Date and tags
136+
index_content += f"**{date_str}**"
137+
if tags_str:
138+
index_content += f" • {tags_str}"
139+
index_content += "\n\n"
140+
141+
# Excerpt
142+
if post['excerpt']:
143+
index_content += f"{post['excerpt']}\n\n"
144+
145+
# Read more link
146+
index_content += f"[Read more →]({post_url})\n\n"
147+
148+
# Footer with link to all posts and contact info
149+
index_content += """---
150+
151+
[View all posts →](/writing/)
152+
153+
## Contact
154+
155+
156+
- [Github](https://github.com/prabha-git)
157+
- [Medium Blog](https://medium.com/@prabhakaran_arivalagan)
158+
- [x / Twitter](https://twitter.com/prabhatweet)
159+
"""
160+
161+
# Write to docs/index.md
162+
with open('docs/index.md', 'w', encoding='utf-8') as f:
163+
f.write(index_content)
164+
165+
print(f"✅ Generated index.md with {len(recent_posts)} recent posts")
166+
for post in recent_posts:
167+
print(f" - {post['title']} ({format_date(post['date'])})")
168+
169+
if __name__ == '__main__':
170+
generate_index()

requirements-doc.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ mkdocstrings-python
55
mkdocs-minify-plugin
66
mkdocs-rss-plugin
77
mkdocs-redirects
8+
PyYAML

0 commit comments

Comments
 (0)