all repos — site @ master

source for my site, found at icyphox.sh

pages/blog/hacky-scripts.md (view raw)

  1---
  2template:
  3title: Hacky scripts
  4subtitle: The most fun way to learn to code
  5date: 2019-10-24
  6slug: hacky-scripts
  7---
  8
  9As a CS student, I see a lot of people around me doing courses online
 10to learn to code. Don't get me wrong -- it probably works for some.
 11Everyone learns differently. But that's only going to get you so far.
 12Great you know the syntax, you can solve some competitive programming
 13problems, but that's not quite enough, is it? The actual learning comes
 14from _applying_ it in solving _actual_ problems -- not made up ones.
 15(_inb4 some seething CP bro comes at me_)
 16
 17Now, what's an actual problem? Some might define it as real world
 18problems that people out there face, and solving it probably requires
 19building a product. This is what you see in hackathons, generally.
 20
 21If you ask me, however, I like to define it as problems that _you_ yourself
 22face. This could be anything. Heck, it might not even be a "problem". It
 23could just be an itch that you want to scratch. And this is where
 24**hacky scripts** come in. Unclear? Let me illustrate with a few
 25examples.
 26
 27## Now playing status in my bar
 28
 29If you weren't aware already -- I rice my desktop. A lot. And a part of
 30this cohesive experience I try to create involves a status bar up at the
 31top of my screen, showing the time, date, volume and battery statuses etc.
 32
 33So here's the "problem". I wanted to have my currently playing song
 34(Spotify), show up on my bar. How did I approach this? A few ideas
 35popped up in my head:
 36
 37- Send `playerctl`'s STDOUT into my bar
 38- Write a Python script to query Spotify's API
 39- Write a Python/shell script to query Last.fm's API
 40
 41The first approach bombed instantly. `playerctl` didn't recognize my
 42Spotify client and whined about some `dbus` issues to top it off.
 43I spent a while in that rabbit hole but eventually gave up.
 44
 45My next avenue was the Spotify Web API. One look at the [docs](https://developer.spotify.com/documentation/web-api/) and
 46I realize that I'll have to make _more_ than one request to fetch the
 47artist and track details. Nope, I need this to work fast.
 48
 49Last resort -- Last.fm's API. Spolier alert, this worked. Also, arguably
 50the best choice, since it shows the track status regardless of where
 51the music is being played. Here's the script in its entirety:
 52
 53```shell
 54#!/usr/bin/env bash
 55# now playing
 56# requires the last.fm API key
 57
 58source ~/.lastfm    # `export API_KEY="<key>"`
 59fg="$(xres color15)"
 60light="$(xres color8)"
 61
 62USER="icyphox"
 63URL="http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks"
 64URL+="&user=$USER&api_key=$API_KEY&format=json&limit=1&nowplaying=true"
 65NOTPLAYING=" "    # I like to have it show nothing
 66RES=$(curl -s $URL)
 67NOWPLAYING=$(jq '.recenttracks.track[0]."@attr".nowplaying' <<< "$RES" | tr -d '"')
 68
 69
 70if [[ "$NOWPLAYING" = "true" ]]
 71then
 72	TRACK=$(jq '.recenttracks.track[0].name' <<< "$RES" | tr -d '"')
 73	ARTIST=$(jq '.recenttracks.track[0].artist."#text"' <<< "$RES" | tr -d '"')
 74	echo -ne "%{F$light}$TRACK %{F$fg}by $ARTIST"
 75else
 76	echo -ne "$NOTPLAYING"
 77fi
 78```
 79
 80The `source` command is used to fetch the API key which I store at
 81`~/.lastfm`. The `fg` and `light` variables can be ignored, they're only
 82for coloring output on my bar. The rest is fairly trivial and just
 83involves JSON parsing with [`jq`](https://stedolan.github.io/jq/).
 84That's it! It's so small, but I learnt a ton. For those curious, here's
 85what it looks like running:
 86
 87![](https://cdn.icyphox.sh/orGJ9.png)
 88
 89## Update latest post on the index page
 90
 91This pertains to this very blog that you're reading. I wanted a quick
 92way to update the "latest post" section in the home page and the
 93[blog](/blog) listing, with a link to the latest post. This would require
 94editing the Markdown [source](https://github.com/icyphox/site/tree/master/pages)
 95of both pages.
 96
 97This was a very
 98interesting challenge to me, primarily because it requires in-place
 99editing of the file, not just appending. Sure, I could've come up with
100some `sed` one-liner, but that didn't seem very fun. Also I hate
101regexes. Did a lot of research (read: Googling) on in-place editing of
102files in Python, sorting lists of files by modification time etc. and
103this is what I ended up on, ultimately:
104
105```python
106#!/usr/bin/env python3
107
108from markdown2 import markdown_path
109import os
110import fileinput
111import sys
112
113# change our cwd
114os.chdir("bin")
115
116blog = "../pages/blog/"
117
118# get the most recently created file
119def getrecent(path):
120    files = [path + f for f in os.listdir(blog) if f not in ["_index.md", "feed.xml"]]
121    files.sort(key=os.path.getmtime, reverse=True)
122    return files[0]
123
124# adding an entry to the markdown table
125def update_index(s):
126    path = "../pages/_index.md"
127    with open(path, "r") as f:
128        md = f.readlines()
129    ruler = md.index("|  --  | --: |\n")
130    md[ruler + 1] = s + "\n"
131
132    with open(path, "w") as f:
133        f.writelines(md)
134
135# editing the md source in-place
136def update_blog(s):
137    path = "../pages/blog/_index.md"
138    s = s + "\n"
139    for l in fileinput.FileInput(path, inplace=1):
140        if "--:" in l:
141            l = l.replace(l, l + s)
142        print(l, end=""),
143
144
145# fetch title and date
146meta = markdown_path(getrecent(blog), extras=["metadata"]).metadata
147fname = os.path.basename(os.path.splitext(getrecent(blog))[0])
148url = "/blog/" + fname
149line = f"| [{meta['title']}]({url}) | `{meta['date']}` |"
150
151update_index(line)
152update_blog(line)
153```
154
155I'm going to skip explaining this one out, but in essence, it's **one
156massive hack**. And in the end, that's my point exactly. It's very
157hacky, but the sheer amount I learnt by writing this ~50
158line script can't be taught anywhere.
159
160This was partially how
161[vite](https://github.com/icyphox/vite) was born. It was originally
162intended to be a script to build my site, but grew into a full-blown
163Python package. I could've just 
164used an off-the-shelf static site generator
165given that there are [so many](https://staticgen.com) of them, but
166I chose to write one myself.
167
168And that just about sums up what I wanted to say. The best and most fun
169way to learn to code -- write hacky scripts. You heard it here.