Adding blog posts to your GitHub README with Python
Published: 2020-08-12 20:42:34

This post is edited from my [original blog post](https://kewbish.github.io/blog/posts/200802/). Find it, and other posts by me on [kewbish.github.io/blog](https://kewbish.github.io/blog/).

# Introduction
I'm working on the web track of CS50 at the moment, and I'm having a lot of fun, actually. In the meantime, I thought I'd take some time to investigate GitHub's new profile feature, and take a dive into GitHub Actions.

# Yes, we have READMEs now.
I'm a bit late to the game, but hey, if you want these posts as I write them, [here's my blog](https://kewbish.github.io/blog).

For a couple days, my [Dev.to](https://dev.to/kewbish), Reddit, *and* dev Discord feeds were inundated with *the shiny new GitHub profile README feature*. All you need to know is that if you create a repo named your GitHub username (i.e. `kewbish/kewbish`) and make a README, it'll show on your profile, like so:

![Hey, a cool README. ~~Yes, it's mine~~.](https://i.imgur.com/13Rd9gJ.png)  
*Hey, a cool README. ~~Yes, it's mine~~.*

I'm not going to go through writing the copy / main text of the README much. After writing the first version, I started seeing lots of cool READMEs on Dev.to. Apparently, there's even an [awesome list](https://github.com/abhisheknaiidu/awesome-github-profile-readme) now. Why am I not surprised?

Anyhow, after reading through *too many* 'top 8 GitHub README' lists, I found [SimonW's](https://github.com/simonw) featured quite often, and I really liked the self-updating blog posts / TIL sections. So, I decided to implement a similar, albeit simpler version on my own README.

# RSS with Hugo
[Skip over this bit](#scripting-with-python) if you're not using Hugo - I'm just going over some changes to Hugo's default RSS that you can definitely ignore.

Hugo comes with a RSS template built in, so I had an RSS feed before I even knew I had one. However, you can also [customize it](https://gohugo.io/templates/rss/) just like all the other default layouts. [This](https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/_default/rss.xml) is the default template Hugo ships with - here are the changes I made.

- Changing the description (line 18):
```xml
<description>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
```
This is pretty self-explanatory, just changed it to:
```xml
<description>Latest Yours, Kewbish posts</description>
```

- Changing the date format (line 32):
```xml
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
```
I prefer a cleaner date format (02 Jan 2006) instead of *all* this time info, so I changed this to:
```xml
<pubDate>{{ .Date.Format "02 Jan 2006" | safeHTML }}</pubDate>
```

- Move from summary to description (line 35):
```xml
<description>{{ .Summary | html }}</description>
```
I wanted to use my descriptions instead of the first couple lines, so I used this:
```xml
<description>{{ .Description | html }}</description>
```

These are all just personal preference, but it makes the README bit a little more consistent with the actual blog.

# Scripting with Python
The README update script is only 18 lines of Python, and uses the [feedparser](https://github.com/kurtmckee/feedparser) library to, well, parse the RSS feed.

Of course, let's start with installing and importing the library with `pip install feedparser` and:
```py
from feedparser import parse
```

Next, we're going to get all our feed entries.
```py
feed = parse("https://kewbish.github.io/blog/index.xml").entries
latest = [
    f"""- [{feed[i].title}]({feed[i].link})  \n{feed[i].description} - {feed[i].published}"""
    for i in range(3)]
```
`feed` contains all the entries of your RSS feed (you're going to want to change the URL to something other than my blog URL, obviously - try using your dev.to feed!). Then, we create a new list to store the first three entries, formatted as a two-line bullet point. The first line will have a link to the post and the title, and the second a description and publishing date. You can definitely play around with this, it's just plain markdown, and this is just how I decided to format my README.

```py
farr = []
with open("README.md", "r", encoding='utf8') as x:
    for line in x:
        if line.strip() == "<!--bp-->":
            break
        farr.append(line)
```
We then open the README file and read each line into an array if it isn't this specific HTML comment. At this point, you might want to go back to your README and add the `<!--bp-->` comment at the end. (If you want it somewhere in the middle, you're going to have to modify the code by adding a new array and reading into that array after the comment is encountered, probably by setting a boolean value somewhere.)

```py
with open("README.md", "w", encoding='utf8') as x:
    x.writelines(farr)
    x.write("<!--bp-->\n")
    li = [x.write(i + "\n") for i in latest]
```
And finally, we open the README, this time in write mode, and write all the lines back. Then, we rewrite our comment line, and then our `latest` list, which will be the list of formatted blog posts. (Again, if you want your widget somewhere in the middle of your README, you're going to have to write the new array you created after the blog post lines.)

The full script can be found [on my GitHub](https://github.com/kewbish/kewbish/blob/master/get_post.py).

You're also going to want to create a `requirements.txt` file with `feedparser` in it, so go ahead and do that. 

# Creating a GitHub Action
> Note: [SimonW's blog post](https://simonwillison.net/2020/Jul/10/self-updating-profile-readme/) was super helpful in figuring this out - much of my code was created after looking through theirs!

Now that we have our script and requirements, let's make our Action. There's a little Actions button on the main page of your repository, so click that and create a new workflow. Choose the 'by yourself' option, which will spit out a long YAML file. We're going to rewrite the file, so go ahead and delete it.

```yml
name: Add newest YK

on:
  workflow_dispatch:
  schedule:
    - cron: '0 */6 * * *'
```
First, we start with our Action name. Pretty self explanatory, call it whatever you want. Next, we have our `on` triggers. These define when our Action will run. `workflow_dispatch` lets me trigger one manually, and `schedule` uses familiar cron syntax. (In case you're wondering, this runs the Action every 6 hours. I highly recommend [crontab.guru](https://crontab.guru/) for figuring this out. GitHub does have a built-in tooltip though, so that can be helpful.)

```yml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - name: Check out repo
      uses: actions/checkout@v2
```
Every Action also has a set of jobs that you can run on it. The `build` and `runs-on` line are pretty standard, just defining your Action to be run on the latest version of Ubuntu. Then, we have a set of steps, which are each individual tasks that can then run commands for us. Our first step will be checking out the repo. This is also pretty standard, as we just use one of GitHub's premade Actions.

```yml
- name: Set up Python
    uses: actions/setup-python@v2
    with:
    python-version: 3.8
- name: pip caches
    uses: actions/cache@v2
    with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
        ${{ runner.os }}-pip-
```
This part sets up Python, using another premade Action, and sets the default Python version. Next, we set up the pip cache so we won't have to download the dependencies each time. More information about this part can be found [on the GitHub site](https://docs.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions).

```yml
- name: Install Python dependencies
    run: |
    python -m pip install -r requirements.txt
```
Here, we run one command to install the requirements from the `requirements.txt` file - here, just feedparser.

```yml
- name: Update README
    env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    run: |-
    python get_post.py
    cat README.md
```
With this, we get the GitHub secret that auto-generated when using GitHub Actions, and then run the script we created earlier. Then, we `cat` this to README.md. Now, in our Action, our README will have updated (or not - more on that with the next block.)

```yml
- name: Commit and push if changed
    run: |-
        git diff
        git config --global user.email "yourskewbot@notarealdomain.com"
        git config --global user.name "YoursKewbot"
        git add -A
        git commit -m "Update blog posts" || exit 0
        git push
```
We `diff` the two files. If they've changed, then we set a configuration for our committer bot. Here, I've just set it to some random information - this is what'll end up in Git history and in GitHub's contribution bar at the top of your repo. Then, as we normally do when committing code, we add all the files, commit them, and push them back to the repository. At this point, our README will have changed live.

See the full Action [on my GitHub](https://github.com/kewbish/kewbish/blob/master/.github/workflows/rss.yml).

# Conclusion
Now, every 6 hours, our Action will run and update our profile README. Hopefully, this was a good introduction to GitHub Actions, and now, you have a shiny new updating README! This was a really fun learning experience for me as well - now, I can be part of the cool GitHub Actions-powered README squad!

What are some other creative RSS-based README's you've seen?