Static Readme Regeneration

Original post:

GitHub has recently introduced a “secret” feature to show a markdown template on your profile page. You may have heard about this. All you need to do is create a repo named with your username and create the file there.

When we think of a template markdown on GitHub we normally think of static content. However, in this article, I want to go a little further. I’ll tell you how to add content that is going to be updated from time to time; whether it’s about your latest tweets, your latest youtube video or your latest blog posts.

My GitHub README profile under aralroca repo

About Static Readme Regeneration (SRR)

By “Static Readme Regeneration” I mean that the file is generated by our script, and then we update the content through a bot that periodically (programmed by us) makes a re-build of the The beauty of this is in the fact that the bot only commits to the repo if has really changed. This way the content of the README can't be entirely static, it can change every day, every hour, or even every minute.

In order to do this, we’ll use a GitHub Action with a cron:

Diagram about static regeneration with GitHub Actions


I’m going to use as example what I did in my profile. It always shows the last 5 articles of my blog and updates every day (if necessary). This way I can relax because I know that when I upload a new post in my blog, the file of my profile will be automatically updated.


Let’s create a file, the .tpl format is used in template files. This file will contain all the static content of the We'll write the markdown here as if we were writing in the file.

The main difference is that we’ll add what we want to be dynamic with some interpolation symbols. This way, our script will be able to replace them with dynamic content.

Diagram about interpolation from .md.tpl to .md

Script to generate the

The script have to:

  • Read the file
  • Fetch all the posts from
  • Sort by pub_date + filter 5.
  • Write the file replacing the interpolation from to the 5 articles as markdown string.

This can be implemented in any language; JavaScript, Rust, Python, Go, C… In this case, I chose Rust, mostly because I have no experience with it and so I took the opportunity to learn a little (feel free to create an issue on the repo if you are a Rust expert and see things that could be improved).


mod create_readme;use create_readme::create_readme;fn main() {
match create_readme() {
Ok(_v) => println!(" file generated correctly"),
Err(e) => println!("Opps! there was an error: {:?}", e),


extern crate chrono;
extern crate rss;
use chrono::DateTime;
use rss::Channel;
use std::cmp::Ordering;
use std::fs;
struct FeedItem {
title: String,
link: String,
pub_date: String,
pub fn create_readme() -> std::io::Result<()> {
let tpl =
.expect("Something went wrong reading the README.tpl file");
let last_articles = get_latest_articles(); return fs::write(
tpl.replace("%{{latest_articles}}%", &last_articles),
fn get_latest_articles() -> String {
let mut posts: Vec<FeedItem> = get_blog_rss();
// Sort articles by pub_date
posts.sort_by(|a, b| {
let date_a = DateTime::parse_from_rfc2822(&a.pub_date).unwrap();
let date_b = DateTime::parse_from_rfc2822(&b.pub_date).unwrap();
if date_b < date_a {
} else if date_b > date_a {
} else {
// Filter las 5 articles + format each one as markdown list string
return posts[..5].iter().fold("".to_string(), |acc, item| {
format!("{} \n* [{}]({})", acc, item.title,
// Fetch all articles of my blog on rss.xml
fn get_blog_rss() -> Vec<FeedItem> {
let items = Channel::from_url("")
.map(|item| FeedItem {
title: item.title().unwrap().to_string(),
pub_date: item.pub_date().unwrap().to_string(),

GitHub Action with a cron

Once we have the script that builds our, we just need to generate the cron using GitHub Action.

In order to create an Action, I recommend first uploading your script to the master branch and then clicking the “Actions” tab of GitHub to create it. This way, GitHub detects the script language (Rust in our case) and creates a default yaml.

We’re going to replace some things from the default yaml in order to:

  • Schedule a cron
  • Run the script (cargo run instead of cargo build && cargo test)
  • Commit the regenerated README (only if has changes)

> .github/workflows/rust.yml

name: Ruston:  # Schedule a cron
- cron: "0 0 */1 * *" # each day at 00:00 UTC
runs-on: ubuntu-latest steps:
- uses: actions/checkout@v2
- name: Build
# Replace "cargo build" to "cargo run" to run the script
run: cargo run
# Commit the regenerated README only when it change
# (git diff --quiet && git diff --staged --quiet )
- run: |
git config aralroca
git config
git add
git diff --quiet && git diff --staged --quiet || git commit -m "[gh-action] Update README"
git push origin master


To conclude, although in most repos the README file is always static, thanks to GitHub Actions and this new feature of GitHub we can build our to always have our profile up to date with the latest updates (releases, pr, tweets, posts...).





Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Sorting in Python

Creating and running Spark Jobs in Scala on Cloud Dataproc !!!

Mutable and Immutable data types in Python

Unified Permissions Model

The status quo of crosscloud and where we are heading

On (Programming) Language Design

30 Events & 10 Websites later — My journey with GDG Kuala Lumpur

Docker Image building with Gitlab Pipeline and Kaniko

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Aral Roca Gomez

Aral Roca Gomez

More from Medium

How to sneak in a XSS exploit in 4 steps or how to detect said attempt

SvelteStorm 3 | The IDE for Svelte Developers

What to do when PostgreSQL does not use your index

How to use the LIKE Operator and Wildcard Characters in SQL