all repos — site @ 375ab30691a16e60f514c33e48ae7f97f86b313a

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