A Metal Subgenre Sample Platter
Catchy hooks, dynamic melodies, energetic vocals accompanied by consistent concepts to their albums
A very high-energy fusion of black metal, punk, and rock
Combines sampling, classical music, and EDM into chaotic but beautiful compositions
Unique band with hardcore and punk roots
Story driven black metal mixed with some striking symphonic elements creating a very beautiful and heavy wave of sound
A departure from their previous sound but builds on their strengths with catchy cleans accented by heavy moments
Quite possibly the most abrasive band on this list, it's not meant to be comfortable you endure it and feel fortunate to have made it through
A hauntingly tragic sound that adds some lofi energy to the typical 11/10 black metal genre.
Much angsty and yet much iconic
This page was served by a Clojure script running through Babashka. This gives us the functional programming paradigm we love with the accessibility of PHP!
Your browser is requesting this file from the server. The server, running Apache, is processing it as a cgi-bin script. The script's contents is piped as input to the static Babashka binary, which then interprets the code, runs the program, and outputs the printed HTML. The Apache web server then returns the HTML as a response which the browser pieces together into the page you are seeing now.
metal.clj
Initial setup code. Pulls in libraries and adds source directories to source path. Might be able to make a generic bash script for this but works for now.
#!/bin/env /home1/<username>/bin/bb
(ns cgi.metal
(:require
[babashka.classpath :refer [add-classpath]]
[babashka.fs :as fs]
[babashka.pods :as pods]
[clojure.java.shell :refer [sh]]
[clojure.string :as s]
[hiccup2.core :refer [html]]))
;; Dynamic Libs
(def LIB-DIR "/home1/<username>/lib/")
(def CWD
(if-let [filename (System/getenv "SCRIPT_FILENAME")]
(str (fs/parent filename))
(System/getenv "PWD")))
(defn lib
"
Create an absolute path to a jar file in sibling lib directory
Takes a string filename like \"honeysql.jar\"
Returns a string like \"/path/to/dir/lib/honeysql.jar\".
"
[path]
(str LIB-DIR path))
;; Add jars and current directory to classpath to import library code
(add-classpath (s/join ":" [CWD
(lib "gaka.jar")
(lib "honeysql.jar")]))
(pods/load-pod (lib "pod-babashka-postgresql"))
;; Require our main page code
;; Must be placed here after updating the class path
(require
'[metal.core :as metal])
;; CGI scripts must print headers then body
(println "Content-type:text/html\r\n")
(println (str (html metal/content)))
(System/exit 0)
metal/core.clj
The example code. Fetches each band from the database and displays them in HTML using the hiccup library that comes with Babashka 2.8.
(ns metal.core
(:require
[clojure.string :as s]
[hiccup.util :refer [raw-string]]
[honeysql.core :as sql]
[pod.babashka.postgresql :as pg]
[gaka.core :refer [css]]
[metal.guide :as guide]
[metal.style :refer [rems]]))
;; Load secrets for the db
(def secrets (read-string (slurp "prod.secret.edn")))
;; Connect to the database
(def db {:dbtype "postgresql"
:host "localhost"
:dbname (:db/name secrets)
:user (:db/user secrets)
:password (:db/password secrets)
:port 5432})
;; Fetch bands from the database
(def bands (pg/execute! db (sql/format {:select [:*]
:from [:metal_bands]
:order-by [[:rank :desc]]})))
(defn embed-url
"Transforms a public youtube URL to the embedded URL"
[yt-url]
(as-> yt-url $
(s/split $ #"=")
(drop 1 $)
(s/join "" $)
(str "https://youtube.com/embed/" $)))
(defn popularity
[pop-rank]
(str (s/join "" (repeat pop-rank "★"))
(s/join "" (repeat (- 5 pop-rank) "☆"))))
(def style
(css
[:body
{:padding 0
:margin 0
:font-family :sans-serif
:background-color "#E5E5E5"}]
[:h1
{:font-size (rems 32)}]
[:h2
{:font-size (rems 24)
:font-family "\"Metal Mania\", sans-serif"}]
[:h3
{:font-size (rems 20)}]
[:h4
{:font-size (rems 18)}]
[:.example
{:padding "2rem"
:text-align :center
:margin-bottom "4rem"}]
[:.cards
{:list-style "none"
:display "flex"
:flex-flow "row wrap"
:align-items "stretch"
:justify-content "space-evenly"
:margin "0 auto"
:padding "0"
:max-width "1100px"}]
[:.cards__item
{:background-color "#FFF"
:flex "0 0 320px"
:box-sizing "border-box"
:position "relative"
:margin "1rem"
:box-shadow "0 0 10px 0 rgba(0, 0, 0, 0.2)"}]
[:.card
{:box-sizing :border-box
:width "320px"
:display "block"
:position :relative
:text-align :left
:border-bottom-left-radius "5px"
:border-bottom-right-radius "5px"}]
[:.rank
{:position :absolute
:right "-16px"
:top "-16px"
:z-index 100
:border-radius "50%"
:width "32px"
:height "32px"
:background "#fff"
:line-height "32px"
:text-align :center
:font-size (rems 14)
:font-style :italic
:color "#666"}]
[:.card__body
{:padding "1rem"}]
[:.detail
{:margin-bottom "1rem"}]
[:.label
{:display "block"
:font-weight "bold"}]
[:.comment
{:font-size (rems 14)
:line-height 1.4}]))
(def example
[:section.example
[:h1 "Running Clojure as a CGI-bin Script Example"]
[:h2 "Example Project"]
[:p "A Metal Subgenre Sample Platter"]
[:ul.cards
(for [band bands]
[:li.cards__item
[:div.card
[:span.rank
(inc (- (count bands) (:metal_bands/rank band)))]
[:iframe
{:width "320"
:height "180"
:src (embed-url (:metal_bands/music_video band))
:frameborder "0"
:allow "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
:allowfullscreen true}]
[:div.card__body
[:h3 (:metal_bands/name band)]
[:div.detail
[:span.label "Genre"]
[:span (:metal_bands/genre band)]]
[:div.detail
[:span.label "Popularity"]
[:span (popularity (:metal_bands/popularity band))]]
[:div.detail
[:span.label "Recommended Album"]
[:span
{:style {:font-style "italic"}}
(:metal_bands/recommended_album band)]]
[:p.comment
(:metal_bands/comment band)]]]])]])
(def content
[:html
[:head
[:title "Live Clojure CGI Script Example"]
[:meta {:charset "UTF-8"}]
[:link {:rel "preconnect"
:href "https://fonts.gstatic.com"}]
[:link {:rel "stylesheet"
:href "https://fonts.googleapis.com/css2?family=Metal+Mania&family=Sriracha&display=swap"}]
[:link {:rel "stylesheet"
:href "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/atelier-cave-dark.min.css"}]
[:script
{:src "https://kit.fontawesome.com/8c366f1e4e.js"
:crossorigin "anonymous"}]
[:style (raw-string style)]
[:style (raw-string guide/styles)]]
[:body
[:div#page
example
guide/content]
[:script {:src "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"}]
[:script {:src "//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/languages/clojure.min.js"}]
[:script "hljs.initHighlightingOnLoad();"]]])
Writing code is more than just a language's features and popularity. It's how it all comes together, it's how it feels to do the day-to-day tasks, and the thought and care put into that is what makes Clojure such a fun language to work with. The forms just flow, I'm not buried in docs, and the libraries make use of common data structures that are often inherently compatible with each other. Now hacking together a webapp can leverage Clojure even on the cheapest of hosting providers if they allow cgi-scripts!
Probably not!
Check out my article to explain the process and rationale at
https://eccentric-j.com/blog/clojure-like-its-php.html
Check out this example repo on github at
https://github.com/eccentric-j/clj-cgi-example
A huge amount of credit goes to Taco for figuring out tools like Clojure, Babashka, and cgi-scripts can be combined. Also thanks to rushsteve1 for making the problem approachable, borkdude for all your work on babashka and support for running it on a shared host, and didibus who helped me debug the first test script.