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