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.