Skip to content

Commit 56a1c07

Browse files
Bring in auto readme
1 parent cbd01ac commit 56a1c07

File tree

3 files changed

+285
-0
lines changed

3 files changed

+285
-0
lines changed

.github/scripts/update_posts.py

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Update README.md with the latest posts from passo.uno RSS feed and GitHub activity.
4+
"""
5+
6+
import feedparser
7+
import re
8+
import requests
9+
import os
10+
from datetime import datetime, timezone
11+
12+
RSS_FEED_URL = "https://chrischinchilla.com/rss.xml"
13+
README_PATH = "README.md"
14+
MAX_POSTS = 5
15+
MAX_ACTIVITY = 5
16+
GITHUB_USERNAME = "chrischinchilla"
17+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") # Optional, improves rate limits
18+
EXCLUDED_REPOS = ["chrischinchilla"] # Repos to exclude from activity feed
19+
20+
# Markers to identify where to insert content
21+
POSTS_START_MARKER = "<!-- BLOG-POSTS:START -->"
22+
POSTS_END_MARKER = "<!-- BLOG-POSTS:END -->"
23+
ACTIVITY_START_MARKER = "<!-- GITHUB-ACTIVITY:START -->"
24+
ACTIVITY_END_MARKER = "<!-- GITHUB-ACTIVITY:END -->"
25+
26+
27+
def fetch_latest_posts(feed_url, max_posts):
28+
"""Fetch the latest posts from the RSS feed."""
29+
feed = feedparser.parse(feed_url)
30+
31+
if feed.bozo:
32+
print(f"Warning: Feed parsing encountered issues: {feed.bozo_exception}")
33+
34+
posts = []
35+
for entry in feed.entries[:max_posts]:
36+
title = entry.get("title", "Untitled")
37+
link = entry.get("link", "")
38+
pub_date = entry.get("published", "")
39+
40+
# Parse and format the date
41+
try:
42+
date_obj = datetime.strptime(pub_date, "%a, %d %b %Y %H:%M:%S %z")
43+
formatted_date = date_obj.strftime("%B %d, %Y")
44+
except (ValueError, TypeError):
45+
formatted_date = pub_date
46+
47+
posts.append({
48+
"title": title,
49+
"link": link,
50+
"date": formatted_date
51+
})
52+
53+
return posts
54+
55+
56+
def fetch_github_activity(username, max_items):
57+
"""Fetch recent commits and releases from non-fork repos."""
58+
try:
59+
# Prepare headers with token if available
60+
headers = {}
61+
if GITHUB_TOKEN:
62+
headers['Authorization'] = f'token {GITHUB_TOKEN}'
63+
print("Using authenticated GitHub API requests")
64+
65+
# Get user's repos (non-fork), sorted by most recently updated
66+
repos_url = f"https://api.github.com/users/{username}/repos?per_page=100&type=owner&sort=updated"
67+
repos_response = requests.get(repos_url, headers=headers)
68+
repos_response.raise_for_status()
69+
repos = [r for r in repos_response.json()
70+
if not r.get('fork', False) and r['name'] not in EXCLUDED_REPOS]
71+
72+
print(f"Found {len(repos)} non-fork repositories (excluding {len(EXCLUDED_REPOS)} repos)")
73+
74+
activities = []
75+
76+
# Fetch recent commits from each repo
77+
for repo in repos[:20]: # Limit to first 20 repos to avoid rate limits
78+
repo_name = repo['name']
79+
repo_url = repo['html_url']
80+
81+
# Get commits
82+
commits_url = f"https://api.github.com/repos/{username}/{repo_name}/commits?per_page=3"
83+
commits_response = requests.get(commits_url, headers=headers)
84+
85+
if commits_response.status_code == 200:
86+
commits = commits_response.json()
87+
for commit in commits:
88+
commit_data = commit.get('commit', {})
89+
commit_msg = commit_data.get('message', '').split('\n')[0] # First line only
90+
commit_date = commit_data.get('author', {}).get('date', '')
91+
commit_sha = commit.get('sha', '')[:7]
92+
commit_url = commit.get('html_url', '')
93+
94+
if commit_date:
95+
try:
96+
date_obj = datetime.strptime(commit_date, "%Y-%m-%dT%H:%M:%SZ")
97+
date_obj = date_obj.replace(tzinfo=timezone.utc)
98+
except ValueError:
99+
continue
100+
101+
activities.append({
102+
'type': 'commit',
103+
'repo': repo_name,
104+
'repo_url': repo_url,
105+
'message': commit_msg,
106+
'sha': commit_sha,
107+
'url': commit_url,
108+
'date': date_obj
109+
})
110+
111+
# Get releases
112+
releases_url = f"https://api.github.com/repos/{username}/{repo_name}/releases?per_page=3"
113+
releases_response = requests.get(releases_url, headers=headers)
114+
115+
if releases_response.status_code == 200:
116+
releases = releases_response.json()
117+
for release in releases:
118+
release_name = release.get('name') or release.get('tag_name', 'Release')
119+
release_date = release.get('published_at', '')
120+
release_url = release.get('html_url', '')
121+
122+
if release_date:
123+
try:
124+
date_obj = datetime.strptime(release_date, "%Y-%m-%dT%H:%M:%SZ")
125+
date_obj = date_obj.replace(tzinfo=timezone.utc)
126+
except ValueError:
127+
continue
128+
129+
activities.append({
130+
'type': 'release',
131+
'repo': repo_name,
132+
'repo_url': repo_url,
133+
'name': release_name,
134+
'url': release_url,
135+
'date': date_obj
136+
})
137+
138+
# Sort by date and return most recent
139+
activities.sort(key=lambda x: x['date'], reverse=True)
140+
return activities[:max_items]
141+
142+
except Exception as e:
143+
print(f"Error fetching GitHub activity: {e}")
144+
return []
145+
146+
147+
def generate_posts_markdown(posts):
148+
"""Generate markdown for the posts section."""
149+
lines = [POSTS_START_MARKER]
150+
151+
for post in posts:
152+
lines.append(f"- [{post['title']}]({post['link']}) - {post['date']}")
153+
154+
lines.append(POSTS_END_MARKER)
155+
return "\n".join(lines)
156+
157+
158+
def generate_activity_markdown(activities):
159+
"""Generate markdown for GitHub activity."""
160+
if not activities:
161+
return f"{ACTIVITY_START_MARKER}\n*No recent activity*\n{ACTIVITY_END_MARKER}"
162+
163+
lines = [ACTIVITY_START_MARKER]
164+
165+
for activity in activities:
166+
formatted_date = activity['date'].strftime("%B %d, %Y")
167+
168+
if activity['type'] == 'commit':
169+
lines.append(f"- **[{activity['repo']}]({activity['repo_url']})**: [{activity['sha']}]({activity['url']}) - {activity['message']} ({formatted_date})")
170+
elif activity['type'] == 'release':
171+
lines.append(f"- **[{activity['repo']}]({activity['repo_url']})**: Released [{activity['name']}]({activity['url']}) ({formatted_date})")
172+
173+
lines.append(ACTIVITY_END_MARKER)
174+
return "\n".join(lines)
175+
176+
177+
def update_readme_section(content, start_marker, end_marker, new_content_md):
178+
"""Update a specific section in the README between markers."""
179+
if start_marker in content and end_marker in content:
180+
pattern = f"{re.escape(start_marker)}.*?{re.escape(end_marker)}"
181+
return re.sub(pattern, new_content_md, content, flags=re.DOTALL)
182+
return content
183+
184+
185+
def update_readme(readme_path, posts_markdown, activity_markdown):
186+
"""Update the README file with the latest posts and GitHub activity."""
187+
with open(readme_path, "r", encoding="utf-8") as f:
188+
content = f.read()
189+
190+
# Update posts section
191+
content = update_readme_section(content, POSTS_START_MARKER, POSTS_END_MARKER, posts_markdown)
192+
193+
# Update activity section
194+
content = update_readme_section(content, ACTIVITY_START_MARKER, ACTIVITY_END_MARKER, activity_markdown)
195+
196+
with open(readme_path, "w", encoding="utf-8") as f:
197+
f.write(content)
198+
199+
print(f"✓ README updated successfully")
200+
201+
202+
def main():
203+
"""Main function to fetch posts, GitHub activity, and update README."""
204+
# Fetch blog posts
205+
print(f"Fetching latest {MAX_POSTS} posts from {RSS_FEED_URL}...")
206+
posts = fetch_latest_posts(RSS_FEED_URL, MAX_POSTS)
207+
208+
if not posts:
209+
print("No posts found in feed")
210+
return
211+
212+
print(f"✓ Found {len(posts)} posts")
213+
214+
# Fetch GitHub activity
215+
print(f"Fetching GitHub activity for {GITHUB_USERNAME}...")
216+
activities = fetch_github_activity(GITHUB_USERNAME, MAX_ACTIVITY)
217+
print(f"✓ Found {len(activities)} recent activities")
218+
219+
# Generate markdown
220+
posts_markdown = generate_posts_markdown(posts)
221+
activity_markdown = generate_activity_markdown(activities)
222+
223+
# Update README
224+
update_readme(README_PATH, posts_markdown, activity_markdown)
225+
226+
print("✓ Done!")
227+
228+
229+
if __name__ == "__main__":
230+
main()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Update README with Latest Posts
2+
3+
on:
4+
schedule:
5+
# Run daily at 00:00 UTC
6+
- cron: '0 0 * * *'
7+
workflow_dispatch: # Allow manual trigger
8+
9+
jobs:
10+
update-readme:
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: write
14+
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: '3.11'
23+
24+
- name: Install dependencies
25+
run: |
26+
pip install feedparser requests
27+
28+
- name: Update README with latest posts
29+
env:
30+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
run: python .github/scripts/update_posts.py
32+
33+
- name: Commit and push if changed
34+
run: |
35+
git config --global user.name 'github-actions[bot]'
36+
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
37+
git add README.md
38+
if git diff --staged --quiet; then
39+
echo "No changes to commit"
40+
else
41+
git commit -m "Update README with latest posts"
42+
git push
43+
fi

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ I am Chris, a Technical writer, developer experience aficionado, blogger, podcas
44

55
I am active in many communities such as Write the Docs, The Good Docs project, Vale, and more. If I find an issue in your documentation I will submit a PR ;)
66

7+
## Latest blog posts
8+
9+
<!-- BLOG-POSTS:START -->
10+
<!-- BLOG-POSTS:END -->
11+
12+
## My personal projects
13+
14+
<!-- GITHUB-ACTIVITY:START -->
15+
<!-- GITHUB-ACTIVITY:END -->
16+
17+
## Support and connect with me
18+
719
- Read, watch, listen to, and play with my creations: https://chrischinchilla.com
820
- Join my Discord server: https://discord.gg/s3UFwFejKT
921
- Support me using the GitHub links, or [find more options on this webpage](https://chrischinchilla.com/support/)

0 commit comments

Comments
 (0)