all repos — site @ 199869b6490380d01e8c7dd4d83c131888a987fe

source for my site, found at icyphox.sh

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.