Recently I was toying with the somewhat new GitHub feature that allows you to create a repository with the same name as your GitHub username and host in it a README.md
file that then is displayed in your profile and visible to whoever visits your profile page. While this feature is good, it’s not too dynamic, but there are two things in our favour: a) GitHub Actions are free for public repositories and b) we can use the GitHub API to fetch information about our repositories, pull requests and more.
So I thought “why not make my GitHub profile more dynamic by adding my latest blog posts, my latest pull requests and my latest starred repositories to my profile?” and that’s what I did, in fact, you can see it here! Here’s how you can leverage the same workflow to achieve something very similar!
I wrote a small Go program that somehow I ended up calling the binary patrickdappollonio
, because it was the name of the folder where I put it (apologies if the name is too tongue-in-cheek, but you can rename the binary to anything you want if needed).
This program can fetch the following details, all using no GitHub token, so only using publicly available information:
And they all support a configurable amount of items to return, so you can choose to grab 10 posts, 7 PRs and 20 starred repos.
You need a repository with the same name as your GitHub username, and in it, a README.md
file (which can be empty to begin with). Then you need two other files:
README.md
file from the processed templateThen simply update the default settings of the program to fetch details for your profile since, by default, the program tries to fetch my information 😆
Let’s start with the Go template. It can be as minimalistic as you want, and if you’re a Go developer, chances are you know enough about Go templates to customize the heck out of it.
For example, the following Go template will work, assume it’s called template.gotmpl
:
## Welcome to my profile!
### Latest pull requests
{{- range .PullRequests }}
* [#{{ .ID }} - {{ .Title }}]({{ .URL }}) on `{{ .ProjectOrg }}`
{{- end }}
### Latest blog posts
{{- range .Articles }}
* [{{ .Title }}]({{ .Link }}) *(published on {{ .GoDate | formatDate }})*
{{- end }}
### Latest starred repositories
{{- range .StarredRepos }}
* [{{ .Name }}]({{ .URL }}) (with {{ .Stars | humanizeBigNumber }} stars)
{{- end }}
Which in my case it would render something like this, assume I configured just 2 items on each category:
# this is the name of the binary, the converted output is printed
# to stdout, so we redirect it to the README.md file:
patrickdappollonio > README.md
And checking the newly updated README.md
file, you’d see something like this:
## Welcome to my profile!
### Latest pull requests
* [#2336 - Wrap bubbletea on each command execution.](https://github.com/konstructio/kubefirst/pull/2336) on `konstructio/kubefirst`
* [#494 - Bump Go versions](https://github.com/konstructio/kubefirst-api/pull/494) on `konstructio/kubefirst-api`
### Latest blog posts
* [Cultural differences between Chile and USA (and North America)](https://www.patrickdap.com/post/cultural-differences-chile-usa/) *(published on January 21, 2024)*
* [Benchmarking is Hard: Caddy vs Nginx Edition](https://www.patrickdap.com/post/benchmarking-is-hard/) *(published on September 23, 2023)*
### Latest starred repositories
* [rust-build/rust-build.action](https://github.com/rust-build/rust-build.action) (with 370 stars)
* [PostgREST/postgrest](https://github.com/PostgREST/postgrest) (with 24.1K stars)
From here, you can customize the template to suit your needs…
There are several template functions documented in the HOWTO.md
file here which will help with things like number formatting (so instead of rendering 29348
, it can either render 29,348
or 29.3K
) or even some mathematical functions like add
, sub
and div
.
Additionally, there are also functions to help you control arrays of data (for example, if you want to operate on the first 10 records from the Pull Requests fetched), you can use take
and skip
to control the amount of records you want to process.
If you feel other functions might be useful for everyone, feel free to open an issue requesting them!
You can also add images to the template by simply storing them alongside the README.md
file and referencing them in the template.
The GitHub Workflow is quite simple: it focuses on downloading the program, then running it so it updates the README.md
file with the processed output.
The following is a GitHub Workflow that runs the program every 4 hours. Note how we’re configuring some settings at the top level env
key, and then we’re using them in the workflow steps:
name: "Update READMEs contents"
on:
schedule:
- cron: "0 */4 * * *" # every 4 hours
workflow_dispatch:
permissions:
contents: write
env:
VERSION: "0.1.15"
GITHUB_USERNAME: patrickdappollonio
RSS_FEED: "https://www.patrickdap.com/index.xml"
TEMPLATE_FILE: "template.gotmpl"
MAX_PULL_REQUESTS: 20
MAX_STARRED_REPOS: 30
jobs:
update-readme:
name: "Update README"
runs-on: ubuntu-latest
steps:
- name: "Checkout repository"
uses: actions/checkout@v4
- name: Install application to render the template
uses: supplypike/setup-bin@v4
with:
uri: "https://github.com/patrickdappollonio/patrickdappollonio/releases/download/v${{ env.VERSION }}/patrickdappollonio_linux_x86_64.tar.gz"
name: "patrickdappollonio"
version: "${{ env.VERSION }}"
- name: Configure timezone
uses: zcong1993/setup-timezone@master
with:
timezone: "America/New_York"
- name: Update README with latest information
run: |
git config user.name "GitHub Actions"
git config user.email "github-actions[bot]@users.noreply.github.com"
export MAX_PULL_REQUESTS="${{ env.MAX_PULL_REQUESTS }}"
export MAX_STARRED_REPOS="${{ env.MAX_STARRED_REPOS }}"
export GITHUB_USERNAME="${{ env.GITHUB_USERNAME }}"
export RSS_FEED="${{ env.RSS_FEED }}"
export TEMPLATE_FILE="${{ env.TEMPLATE_FILE }}"
patrickdappollonio > README.md
git add README.md || echo "No changes to add"
git commit -m "[ci skip] Updating README with latest information" || echo "No changes to commit"
git push || echo "No changes to push"
Some noteworthy things about this template:
VERSION
environment variable is used to specify the version of the program to download. You can find the latest version of the program in the releases page.GITHUB_USERNAME
environment variable is used to specify the GitHub username to fetch information from.README.md
file, so the permissions
key is used to grant those permissions with a value of contents: write
.supplypike/setup-bin@v4
is used to download the latest version of the program and cache it for future runs.zcong1993/setup-timezone@master
is used to set the timezone of the runner to America/New_York
(you can change this to your timezone).run
step is where the program is run and the README.md
file is updated.All available environment variables are documented in the HOWTO.md
file in the repository.
Another nifty feature that came out of the need of maintaining data outside of the template for easier maintenance is the ability to store data in YAML files and then reference them in the template.
Data files are YAML files in the current working directory (excluding subdirectories). The data is loaded into the template as a map of string to any. Data values are available under .Data
and the file name without extension is used as the key.
For example, if you have a collection of links you want to show in your README
, you can create a file called links.yaml
with the following content:
- name: GitHub
url: https://github.com/patrickdappollonio
- name: LinkedIn
url: https://www.linkedin.com/in/patrickdappollonio
- name: Twitter
url: https://twitter.com/marlex
Then you can reference this data in your template like this, note the access to .Data
then .links
under it, matching the links.yaml
file name:
## Links
{{ range .Data.links }}
- [{{ .name }}]({{ .url }})
{{ end }}
Which would render the following:
## Links
- [GitHub](https://github.com/patrickdappollonio)
- [LinkedIn](https://www.linkedin.com/in/patrickdappollonio)
- [Twitter](https://twitter.com/marlex)
One pet peeve I have with GitHub is that, by default, importing an image using the default Markdown format for images, that is:
![Alt text](/path/to/image.png)
Will make the image clickable: clicking on it will take you to the file in the repo where the image is stored. This is not always desirable, and you might want to make the image unclickable.
A trick I learned about a few repos that do this showed me you can actually use HTML directly in the markdown file to make the image unclickable by producing a <picture>
HTML tag with two <source>
tags: one for dark and one for light mode, with a fallback <img>
tag that will be displayed if the browser doesn’t support the <picture>
tag.
For example, converting the image from the previous example to an unclickable image would look like this:
<picture>
<source media="(prefers-color-scheme: dark)" srcset="/path/to/image.png">
<source media="(prefers-color-scheme: light)" srcset="/path/to/image.png">
<img src="/path/to/image.png" alt="Alt text">
</picture>
This is so common now that there is a template function in patrickdappollonio
that would simplify this: simply call dualimage
with one argument, two or three arguments:
The same image above would be rendered like this:
{{ dualimage "/path/to/image.png" }}
There you have it! A quick and simple Go program without dependencies that can quickly set up a very cool GitHub profile for you. If you want to see it in action, my own profile uses it here.
Feel free to request features or report bugs in the repository, and I’ll be happy to help you out!