I recently upgraded my Mac from MacOS 10.11 to MacOS 10.13. Nothing too exciting in that. But I also use stumpwm as my window manager for a lot of the work I do. Having upgraded MacOS, I discovered that my stumpwm installation was broken. On starting up the window manager I was briefly shown an error message with a backtrace. Unhelpfully this then flickered off and on again before I could read it.
A little bit of digging around showed that this was something to do with my mode line configuration and in particular the bit where I display the current battery level. The relevant lisp code to configure stump is:
;; Battery status (defun battery () (parse-integer (stumpwm:run-shell-command "/Users/grc/bin/grc-battery" t))) (defun battery-level-string () "Battery level indication, coloured for use in mode line" (let* ((bat-level (battery)) (color-code (bar-zone-color bat-level 60 40 20 t))) (format nil "Battery: ~A~A%" color-code bat-level))) ;; Set up the mode line (defun fmt-bat (ml) (battery-level-string)) (add-screen-mode-line-formatter #\B #'fmt-bat) (setf stumpwm:*screen-mode-line-format* (list "%w %W %B" )) (mode-line)
The work of calculating battery charge is delegated to a shell script which uses the BSD pmset power management command and an awk script to extract the remaining charge, which is present in the second space delimited field and terminated by a ‘%’ character.
# Mac laptop # Returns the amount of battery remaining on a scale of 0-100 pmset -g ps | awk '/Internal/{printf "%s", substr($2, 0, match($2, "%")-1) ;}'
Upgrading MacOS has changed the output of the pmset command from
-InternalBattery-0 91%; charging; 0:46 remaining present: true
to
-InternalBattery-0 (id=4522083) 91%; charging; 0:46 remaining present: true
Thanks to the introduction of the ID field, $2 no longer contains a ‘%’ character hence the awk no longer finds a match and my shell script prints an empty string. The lisp battery function then fails as parse-integer, called in this fashion will signal an error if called on anything but the representation of a signed integer. An empty string is not such a representation.
The trivial fix is to amend the awk script to extract the battery level from the new pmset format. Using $(NF-5) in place of $2 conveniently works with both old and new format. But having my window manager rendered inoperable by a minor change to an output format is nasty. I’d like to be able to survive an unparseable return from grc-battery. parse-integer has an optional :junk-allowed argument, causing nil to be returned rather than an error to be signalled. Reworking the stumpwm battery status pieces to handle nil we arrive at some mildly more resilient battery status code.
(defun battery () (parse-integer (stumpwm:run-shell-command "/Users/grc/bin/grc-battery" t) :junk-allowed t)) (defun battery-level-string () "Battery level indication, coloured for use in mode line" (let ((bat-level (battery)) (if bat-level (let ((color-code (bar-zone-color bat-level 60 40 20 t))) (format nil "Battery: ~A~A%" color-code bat-level))))))
Whilst this works, it’s less readable than the initial version and felt unsatisfying. I’d heard of the power of Common Lisp’s condition system, but was initially somewhat daunted. Turns out that for my simple usage it’s trivial. Leave battery and battery-level-string in their initial form and amend the mode line formatter to handle a parse-error exception:
(defun fmt-bat (ml) (handler-case (battery-level-string) (parse-error () "Battery: unknown")))
I think the moral of this story is to try to go with the flow of a language’s natural idioms, even if that takes a bit more effort to do initially. Shoehorning in approaches from other languages makes for uncomfortable code.