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.