Chris Wiegman Chris Wiegman

Updating GitHub Profiles with WordPress and Go

For quite some time GitHub has had a neat feature that lets you customize your GitHub profile with a README file that you can use to tell visitors about yourself.

By itself the feature is pretty cool but with WordPress, and a little Go, it can get a lot better as you can easily configure it to update itself to display your latest posts or anything else from your WordPress site. In this post we’ll use WordPress’ built-in RSS feed to get our post data and update our GitHub profile daily using a small Go script and GitHub actions. Here’s how we do it.

Creating the GitHub profile repository

The first thing you’ll need is a GitHub repository with the same name as your GitHub username. In my case that means I setup the repository ChrisWiegman as that is my GitHub username. You can see that repository here.

Once you create the repository you can initialize it with a single README.md file to get started. Don’t worry about the contents, yet. We’ll get to that next.

Creating the template

The next thing you’ll need is a template file to build your README from. I put this in a directory called update in my repo but you don’t need to do it that way. In it you can start by putting your content in markdown format in a file named template.md. Here’s what mine looked like to start:

### Hi! I am Chris Wiegman

I currently lead the team building **[Faust.js](https://faustjs.org)** as an **Engineering Manager** for **[WP Engine](https://wpengine.com/)**. For the last 20 years I've lead various tech teams building everything from JavaScript frameworks, WordPress plugins, Chrome extensions, development environments and more. When not building products I enjoy teaching, mentoring an **[speaking](https://chriswiegman.com/speaking/)** and have been fortunate to do so at dozens of courses, conferences, Meetups and other events over the years. Today my work, both personal and professional, focuses on improving the development experience of WordPress developers and anyone who has ever visited a WordPress site.

My latest blog post is: **[My WordPress wish list](https://chriswiegman.com/2023/02/my-wordpress-wish-list/)**.

#### More recent posts from my blog:

* [Things I will miss from Apple](https://chriswiegman.com/2023/02/things-i-will-miss-from-apple/)
* [Design is more than visuals](https://chriswiegman.com/2023/01/design-is-more-than-visuals/)
* [Still wishing for a better blogging tool](https://chriswiegman.com/2023/01/still-wishing-for-a-better-blogging-tool/)
* [The Perfect Mastodon Client for Me](https://chriswiegman.com/2023/01/the-perfect-mastodon-client-for-me/)
* [Code, Ideas and the Future of Kana](https://chriswiegman.com/2023/01/code-ideas-and-the-future-of-kana/)

If you like my posts take a look at my site, **[chriswiegman.com](https://chriswiegman.com/)**, and subscribe to get them in your favorite feed reader via **[RSS](https://chriswiegman.com/feed/)**.

You can also find me on **[Mastodon](https://mastodon.chriswiegman.com/@chris)** and **[Pixelfed](https://pixelfed.chriswiegman.com/@chris)** or you can view **[my full resume](https://gist.github.com/ChrisWiegman/8a89d7c2aca775884ae4227ca3b5be01)**.

<sub>Last updated: February 5, 2023</sub>
Code language: Markdown (markdown)

Note that, for now, it doesn’t look like much of a template as all the content is already there. We’ll get to the rest shortly.

Processing the template file with Go

Now for the fun part. If you’re a WordPress developer this might seem a bit daunting but I think you’ll quickly find that, if you like working in PHP, you’ll love working in Go

First, install Go if you haven’t already. If you use Homebrew on Linux or Mac I recommend just using brew install go. If not, check out their installation instructions to get started.

Next, setup your normal code editor for Go. If you use VSCode this is as easy as installing a single plugin (which it wlil prompt you to do when you first open a Go file). If not, check with your editor for proper setup instructions.

Once your editor is setup and Go is installed navigate in your Terminal to where your template.md file from earlier is located and let’s initialize a Go project. This is a bit like what you might do with NPM or Composer but a lot easier. Simply run go mod init with the repo and path to your template.md file. In the below example you can see the repo as I created mine and the update folder where I’m putting my Go script.

go mod init github.com/chriswiegman/chriswiegman/updateCode language: Bash (bash)

This will create the go.mod file in your directory which is to Go what package.lock is to JavaScript.

Just like PHP or JavaScript we also have one dependency we’re going to need for this. This is gofeed, a small package to parse our WordPress (or any other) RSS feed. Go packages are easy to install. Usually you can just tell Go to go (no pun intended) and get it from it’s repository. Use the following command in your terminal.

go get github.com/mmcdole/gofeedCode language: Bash (bash)

When you run the above command it will create a go.sum file which acts like the lock files in Composer or JavaScript. Your go.mod file will also show the package and it’s dependency tree. We’re now ready to start writing our Go Script.

In your editor create a new file called main.go in the same directory we used previously. For now all we need at the top is the following package declaration. This is important in Go. All files need a package declaration and every Go app needs a main package.

package mainCode language: Go (go)

Underneath the package declaration we’ll create our main function and a couple of data types to hold the information for our template. There are two data types, as identified by the struct keyword below. templateLink which holds the Display text and the actual Link that we output in our template and templateVars which holds all the variables we are going to use in the template. In this case Date to replace the date at the bottom of the template, LatestLink for the link to the latest post and PostLinks which holds as many posts as we want to pull from RSS to populate the post list as in the template above.

Below that goes the main function which all Go apps require as their entry point. You can see all the code so far below.

package main

// templateLink is all the data we need for an individual link in our Markdown template
type templateLink struct {
	Link, Display string
}

//templateVars are all the variables we will use to build our template
type templateVars struct {
	Date       string
	LatestLink templateLink
	PostLinks  []templateLink
}

func main() {
}Code language: Go (go)

You can test this all in terminal by running go run main.go. It won’t do anything yet but you’re well on your way. Our next task is to import our RSS feed with the library we imported above.

We’ll do this by initializing the feed parser and parsing the feed as seen below. Note the error checking with err. This is a pretty normal paradigm in Go and serves to exit the app if the parser encounters and error.

package main

import (
	"log"
	"os"

	"github.com/mmcdole/gofeed"
)

// templateLink is all the data we need for an individual link in our Markdown template
type templateLink struct {
	Link, Display string
}

// templateVars are all the variables we will use to build our template
type templateVars struct {
	Date       string
	LatestLink templateLink
	PostLinks  []templateLink
}

func main() {
	fp := gofeed.NewParser()

      	// gets the feed from my own site and logs an error if it fails
	feed, err := fp.ParseURL("https://chriswiegman.com/feed/")
	if err != nil {
		log.Fatalf("error getting feed: %v", err)
		os.Exit(1)
	}
}
Code language: Go (go)

Another thing to call out here is the import at the top. Most Go dependencies will autopopulate themselves if you’ve run go get or if they’re otherwise known. In this case you’ll probably need to initialize it with the github.com/mmcdole/gofeed package and Go should take care of the rest. Also note that you won’t be able to run it here yet. Go doesn’t like you defining unused variables so it will fail because we’re not doing anything with feed yet. Let’s fix that

Our next step is to populate our templateVariables so we can use them in our template. We’ll do that by looping through the feed, first grabbing the top post for LatestLink and then the next 5 for the PostLinks. Here’s the code for that.

package main

import (
	"log"
	"os"
	"time"

	"github.com/mmcdole/gofeed"
)

// templateLink is all the data we need for an individual link in our Markdown template
type templateLink struct {
	Link, Display string
}

// templateVars are all the variables we will use to build our template
type templateVars struct {
	Date       string
	LatestLink templateLink
	PostLinks  []templateLink
}

func main() {
	fp := gofeed.NewParser()

	// gets the feed from my own site and logs an error if it fails
	feed, err := fp.ParseURL("https://chriswiegman.com/feed/")
	if err != nil {
		log.Fatalf("error getting feed: %v", err)
		os.Exit(1)
	}

	templateVars := templateVars{
		Date: time.Now().Format("January 2, 2006"), //gets and formats the current date
		LatestLink: templateLink{
			Display: feed.Items[0].Title, //calls the feed like a PHP array to populate the latest link
			Link:    feed.Items[0].Link,
		},
	}

	index := 1 // start at 1 to skip the latest link we already processed

	// Loop through the next 5 posts and add them to the PostLinks in templateVars
	for index <= 5 {
		link := templateLink{
			Display: feed.Items[index].Title,
			Link:    feed.Items[index].Link,
		}
		templateVars.PostLinks = append(templateVars.PostLinks, link)

		index++
	}
}
Code language: Go (go)

Great! We’re getting closer. Now we just need to populate our template. First, we need to load it. Fortunately Go has a standard embed library. We import the library (we have to do this import manually in most editors) and simply call go:embed and the template file in a commend before the variable we’ll save the template to. Later, when we run our app again, Go will automatically load the template file into memory so we can parse it. Here’s how we load that below. Note I’ve taken out the whole main function so we can focus on the embed.

package main

import (
	_ "embed"
	"log"
	"os"
	"time"

	"github.com/mmcdole/gofeed"
)

//go:embed template.md
var markdownTemplate string
Code language: Go (go)

The above code will save our template to the markdownTemplate variable which is now a string we can parse. We’ll parse it with a few lines of code using Go’s standard text/template package as highlighted below.

package main

import (
	_ "embed"
	"log"
	"os"
	"text/template"
	"time"

	"github.com/mmcdole/gofeed"
)

//go:embed template.md
var markdownTemplate string

// templateLink is all the data we need for an individual link in our Markdown template
type templateLink struct {
	Link, Display string
}

// templateVars are all the variables we will use to build our template
type templateVars struct {
	Date       string
	LatestLink templateLink
	PostLinks  []templateLink
}

func main() {
	fp := gofeed.NewParser()

	// gets the feed from my own site and logs an error if it fails
	feed, err := fp.ParseURL("https://chriswiegman.com/feed/")
	if err != nil {
		log.Fatalf("error getting feed: %v", err)
		os.Exit(1)
	}

	templateVars := templateVars{
		Date: time.Now().Format("January 2, 2006"), //gets and formats the current date
		LatestLink: templateLink{
			Display: feed.Items[0].Title, //calls the feed like a PHP array to populate the latest link
			Link:    feed.Items[0].Link,
		},
	}

	index := 1 // start at 1 to skip the latest link we already processed

	// Loop through the next 5 posts and add them to the PostLinks in templateVars
	for index <= 5 {
		link := templateLink{
			Display: feed.Items[index].Title,
			Link:    feed.Items[index].Link,
		}
		templateVars.PostLinks = append(templateVars.PostLinks, link)

		index++
	}

	// we'll parse the template itself to a tmpl variable
	tmpl := template.Must(template.New("chriswiegmanReadme").Parse(markdownTemplate))

	// Create the readme file again. Note my go code is in a folder called `update` so this creates it in the parent folder. Make sure yours in in the root of your repo.
	myFile, _ := os.Create("../README.md")

	// This combines the template with the templateVars
	tmpl.Execute(myFile, templateVars)
}

Code language: Go (go)

I’ve highlighted the relavent code in lines 59-66 above. if you now run go run main.go again in your terminal you should get a README.md file in the root of your repository. Great! We’re close but we’re not done yet. We’ve populated our template variables but now we need to use them in our template.

Using template variables in a Go template

Go has a very robuse text/template package that inserts variables delimited by {{ and }}. For example to call the Date from templateVars we would use {{ .Date }}. To get child variables such as from LatestPosts we just add an extra “.” such as {{ .LatestLink.Display }} and we can even use loops (see the example below) to go through all the PostLinks. Below is our final template with the lines highlighted where I’ve replaced the text in our above example with the variables we want to output.

### Hi! I am Chris Wiegman

I currently lead the team building **[Faust.js](https://faustjs.org)** as an **Engineering Manager** for **[WP Engine](https://wpengine.com/)**. For the last 20 years I've lead various tech teams building everything from JavaScript frameworks, WordPress plugins, Chrome extensions, development environments and more. When not building products I enjoy teaching, mentoring an **[speaking](https://chriswiegman.com/speaking/)** and have been fortunate to do so at dozens of courses, conferences, Meetups and other events over the years. Today my work, both personal and professional, focuses on improving the development experience of WordPress developers and anyone who has ever visited a WordPress site.

My latest blog post is: **[{{ .LatestLink.Display }}]({{ .LatestLink.Link }})**.

#### More recent posts from my blog:
{{ range $link := .PostLinks }}
* [{{ $link.Display }}]({{ $link.Link }}){{ end }}

If you like my posts take a look at my site, **[chriswiegman.com](https://chriswiegman.com/)**, and subscribe to get them in your favorite feed reader via **[RSS](https://chriswiegman.com/feed/)**.

You can also find me on **[Mastodon](https://mastodon.chriswiegman.com/@chris)** and **[Pixelfed](https://pixelfed.chriswiegman.com/@chris)** or you can view **[my full resume](https://gist.github.com/ChrisWiegman/8a89d7c2aca775884ae4227ca3b5be01)**.

<sub>Last updated: {{ .Date }}</sub>

Code language: Markdown (markdown)

Saving your template and running go run main.go again from your terminal should give you a complete template with all the variables dynamically populated instead of being hard-coded as we originally did.

So close. Now we just need to get GitHub to process it.

Updating your README daily with GitHub Actions

Our final task is to automate it all with GitHub Actions. To do this we first need to create two folders: .github in the root of your repository and, within the .github folder create a folder called workflows. Once you have these create a file called update.yml in the workflows folder and open it in your editor. At this point you should be editing the following file: .github/workflows/update.yml.

Populate your update.yml with the following content:

name: update

on:
  push:
    branches:
      - main
  schedule:
    - cron: "0 18 * * *"

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Get working copy
        uses: actions/checkout@main
        with:
          fetch-depth: 1
      - name: Update Readme
        run: |
          cd ${GITHUB_WORKSPACE}/update/
          go run main.go
      - name: Deploy
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          message: Update GitHub profile
          committer_name: GitHub Actions
          committer_email: actions@github.com
Code language: YAML (yaml)

Now let’s walk through what this all does:

First it will run this action on any push to the main branch in your repository (if you’re using a different branch name change this accordingly on line 6). It will also run this action at 18:00 (6pm) GMT every day. This is denoted with the schedule and cron configs on lines 7 and 8.

Next it will checkout your repo and run the go run main.go command we used to test our work eariler. Note the folder name on line 20. Update this accordingly if your main.go doesn’t live in an update folder under the root of your repo.

Finally it will commit the changes to your repo using a fictional “GitHub Actions” user as denoted on lines 25-28. You can change this to your own GitHub user info if you would like but that will mean GitHub will show an extra commit from you every time this runs and anyone who looks at your profile might think you’re working every single day given you’ll have at least one daily commit in your GitHub profile.

Finishing up

That’s it. Commit your work and push it yo GitHub. After a couple of minutes you’ll see the results. Don’t worry if it fails the first time as it might not have any README.md changes to make if you’ve committed everything right after generating the file (if you really want to test it clear out all the contents from README.md and then commit allowing GitHub Actions to populate it).

While it’s all a fairly simple script you should be able to edit your profile for all kinds of fancy bio or other information from here. You can see this all put together on my repo on GitHub. Feel free to reach out to me if you have issues with any of it and I’ll do my best to help if I can.

One final call-out on this is to note that my profile updates daily. This works for me because I publish every Monday, Wednesday and Friday morning. Depending on your schedule you might want to modify this or even use a webhook trigger to update yours right when you press publish in WordPress.

Credits

I do want to give credit for this post to Victoria Drake. While my own implementation has evolved considerably it was her original script that I forked to start my own profile bio.

If this works for you, please do reach out to me on Mastodon and share your own profile. I’d love to follow you there and share some of the better profiles I see.