Thursday, April 5, 2018

Simple cowsay in Clojure

A simple version of cowsay in Clojure.
(ns fortune
"Fortune fairy."
(:require [clojure.pprint :as pprn]
[clojure.string :as str])
(:import [java.util.concurrent ThreadLocalRandom]))

(def tale ["I'll walk where my own nature would be leading: It vexes me to choose another guide."
"Every leaf speaks bliss to me, fluttering from the autumn tree."
"I see heaven's glories shine and faith shines equal."
"I have to remind myself to breathe -- almost to remind my heart to beat!"
"I’ve dreamt in my life dreams that have stayed with me ever after, and changed my ideas: they’ve gone through and through me, like wine through water, and altered the colour of my mind."])

(defn gen-random [lb ub]
(-> (ThreadLocalRandom/current)
(.nextInt lb ub)))

(defn gen-rand-txt []
(nth tale (gen-random 0 (count tale))))

(def cowsay-body
" \\ ^__^
\\ (oo)\\_______
(__)\\ )\\/\\
||----w |
|| ||")

(defn cowsay-hr [width]
(println (pprn/cl-format nil "+ ~v@<~d~> +" width (apply str (repeat width "-")))))

(defn cowsay-txt-fmt [msg width]
(println (pprn/cl-format nil "| ~v@<~d~> |" width (str/trim msg))))

(defn cowsay
(let [txt (gen-rand-txt)]
(cowsay txt 0 21 21 (count txt))))
(let [txt (if (empty? msg) (gen-rand-txt) msg)]
(cowsay txt 0 21 21 (count txt))))
([msg width]
(cowsay msg 0 width width (count msg)))
([msg start end width len]
(when (= start 0) (cowsay-hr width))
(<= end len) (do
(cowsay-txt-fmt (subs msg start end) width)
(recur msg (+ start width) (+ end width) width len))
(< start end) (do
(cowsay-txt-fmt (subs msg start len) width)
(cowsay-hr width)
(println cowsay-body)))))
The format specifiers in cl-format is very expressive. Since this is a simple version, it just left aligns by characters.
boot.user=> (load-file "fortune.clj")

boot.user=> (require '[fortune :as fortune])

boot.user=> (fortune/cowsay)
+ --------------------- +
| I see heaven's glorie |
| s shine and faith shi |
| nes equal. |
+ --------------------- +
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

boot.user=> (f/cowsay (first f/tale) 0 13 13 (count (first f/tale)))
+ ------------- +
| I'll walk whe |
| re my own nat |
| ure would be |
| leading: It v |
| exes me to ch |
| oose another |
| guide. |
+ ------------- +
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||

Boot Task
We can further use this as a boot task. Let's say we placed this in scripts under the project root and we add the below snippet to build.boot.
(def generic-pod (future (pod/make-pod (core/get-env))))

(deftask cowsay
[m msg VAL str "Message to print"]
(merge-env! :source-paths #{"scripts"})
(pod/with-call-in @generic-pod (fortune/cowsay ~msg))
This can be run as boot cowsay -m "Every leaf speaks bliss to me, fluttering from the autumn tree.".