Updating README in Github personal repository

How to automate update README.md in Github

Github introduce new feature for repository that have special name as your nickname on Github. If you add README.md there they display content in profile header.

I see first on twitter where my friend mention this. I like it and make similar version. Later I see this tweet about automate content of readme.

I’m not big friend with python and I need something else there that I make my own version in Golang using Github Actions.

First I created template for readme and use go to proceed template. I read RSS/Atom from my blog to update readme and I used just Golang standard library. Output is just printed to stdout.

package main

import (
	"encoding/xml"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"text/template"
)

// Item type for RSS
type Item struct {
	Title   string `xml:"title"`
	Link    string `xml:"link"`
	Desc    string `xml:"description"`
	GUID    string `xml:"guid"`
	PubDate string `xml:"pubDate"`
}

// Channel type for RSS
type Channel struct {
	Title string `xml:"title"`
	Link  string `xml:"link"`
	Desc  string `xml:"description"`
	Items []Item `xml:"item"`
}

// Rss type for RSS as root
type Rss struct {
	Channel Channel `xml:"channel"`
}

func readFeed(url string) []string {
	resp, err := http.Get(url)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	rss := Rss{}

	decoder := xml.NewDecoder(resp.Body)
	err = decoder.Decode(&rss)
	if err != nil {
		log.Fatal(err)
	}

	output := []string{}
	for _, item := range rss.Channel.Items {
		output = append(output, fmt.Sprintf("[%s](%s)\n", item.Title, item.Link))
	}
	return output
}

func main() {
	content, err := ioutil.ReadFile("README.md.template")
	if err != nil {
		log.Fatal(err)
	}

	text := string(content)
	blogURL := os.Getenv("BLOG_URL")
	rssItems := readFeed(blogURL)
	data := struct {
		Title string
		Items []string
	}{
		Title: "Last blog posts",
		Items: rssItems,
	}

	t, err := template.New("readme").Parse(text)
	if err != nil {
		log.Fatal(err)
	}
	err = t.Execute(os.Stdout, data)
	if err != nil {
		log.Fatal(err)
	}
}

For automating this I created workflow file .github/workflows/build.yml.

name: Build README

on:
  push:
  workflow_dispatch:
  schedule:
    - cron: "0 4 * * *"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Check out repo
        uses: actions/checkout@v2
      - name: Set up Go
        uses: actions/setup-go@v2
        with:
          go-version: "^1.14.3"
      - run: go version

      - name: Update README
        env:
          BLOG_URL: ${{ secrets.BLOG_URL }}
        run: |-
          go run main.go > README.md
          cat README.md          
      - name: Commit and push if README changed
        run: |-
          git diff
          git config --global user.email "readme-bot@github.com"
          git config --global user.name "README-bot"
          git diff --quiet || (git add README.md && git commit -m "Updated README")
          git push          

I run cron daily, you can change as you need it. I run my program go run main.go and stdout is forward into file and commit into repository if diff exists. Github Actions manage access into repository and you don’t need add ssh key or something else. The blog URL is defined in repository <repo>/settings/secrets with name BLOG_URL, but can be directly set in this yaml file too as normal environment variable.

Ladislav Prskavec
Software Engineer and SRE