>> Problem

The Elisp library prodigy.el helps you manage your external processes; in particular, it allows you to listen to the process log which allows you to execute commands on certain states. Although we have start-process and set-process-filter, this library wraps those lower level constructs into a more manageable object with hooks and properties.

One common feature when using processes is to be notified when it is ready or something happens. For example, I use offlineimap with gnus to get my mail where I want to be notified when there is one or jekyll to make this blog where I want to told when the blog is built successfully or not. I could use the simple message construct but to be more flexible I use the library alert.el.

That notifying library features more than just using message, it can use libnotify, growl and more importantly the fringe. A fringe alert uses set-face-background to change the color of the buffer sides and removes it on action. You can try this one liner to understand better:

(set-face-background 'fringe "red")

So when there is a process event, I want a fringe alert. I have no snippet to give here only an outline on how I went about it. For this post, I will use jekyll as an example.

>> Using prodigy

To define a jekyll process with prodigy, here is how you will go about it:

  :name "Gnus-Offlineimap"      ;; Service name or id
  :cwd "~/Fakespace/fnlog.io"  ;; Working directory when the service is called

  :command "jekyll"             ;; The jekyll command
  :args (list                      ;; Command arguments
         "--ports" (number-to-string 34000))
  :port 34000                   ;; Optional port property
  :kill-signal 'sigkill         ;; Kill signal to send when closing

  :on-output #'ignore           ;; The output listener we are interested in

This is more or less how I setup my blog process service but I use the tag system to make it a more reusable. To emphasize convenience, I would use this library instead of handling it with start-process, with-temp-buffer and default-directory. To get a better picture, you can see the new service with a special tabulated and process buffer.


What we are interested in with the logs are two lines that contain the string/text:

  • Server running to indicate the blog is ready
  • ...done in to indicate the blog is built and should be refreshed

The :on-output allows us to tap into this which takes a function with a plist argument that has :service and :output properties. In our context of listenting to the following lines:

(defun fn/prodigy-jekyll-state-change-listener (&rest args) ;;defun* can also work
  (lexical-let* ((output (plist-get args :output)) ;; Get the arguments
      (service (plist-get args :service)))
    (pcase output
      ((pred (string-match-p "Server running"))
       (prodigy-set-status service 'ready) ;; Manually set the status
       ;; Trigger ready alert
      ((pred (string-match-p "Error:"))
       ;; Trigger error alert
      ((pred (string-match-p "...done"))
       ;; Trigger done built alert
      (_ ;; Do nothing otherwise

;; Redefining the service
  ;; Previous definition
  :on-output 'fn/prodigy-jekyll-state-change-listener

If you don't have string-match-p since you are using an earlier version, it can be shivved like so:

(unless (fboundp 'string-match-p)
  (defsubst string-match-p (regexp string &optional start)
Same as `string-match' except this function does not change the match data.
Taken from `subr-x'"
    (let ((inhibit-changing-match-data t))
      (string-match regexp string start))))

Aside from using pcase, it is pretty much just listening to the output. As an aside, listening to the output is pretty much determined by the process itself; so some awkward log state management might be present for other services. For example, there might be a start line, then followed by either an error or process line, finally an end line; in that scenario, one has to keep track if there is an error or not to determine at the end line if there is an error instead of notifying at each one. Logging and state machines, some nuances when dealing with listening to logs.

We move on to using alert.

>> Using alert

Using alert is pretty straightforward, the first argument is the message and the rest are some options. I encourage you to check it out but for our case, we will be focused on using the fringe type:

(alert "Hello Alert")         ;; Default alert with message

(alert "Hello Fringe Alert"   ;; Using a fringe alert
       :severity 'trivial
       :style 'fringe)

If you use this snippet, you will get a purple fringe alert which comes from alert-severity-colors.

((urgent . "red")
 (high . "orange")
 (moderate . "yellow")
 (normal . "green")
 (low . "blue")
 (trivial . "purple"))

Weirdly, the severity handles the color. Either way, there isn't many colors to work with, so if you want to add one like Rebecca Purple or Lavender. You have to add to do the following:

(add-to-list 'alert-log-severity-functions
             (cons 'lavender #'alert--log-trace))

(add-to-list 'alert-severity-colors
             (cons 'lavender "#b378d3"))

(alert "My lavender alert"
       :style 'fringe
       :severity 'lavender)

This is not so bad but when you have several services that uses fringe alerts or have to create one, you might want a function that does this. Here is my version of that function after looking at the source:

(require 'cl-lib)

(defun* fn/alert-color (message &rest args &key color &allow-other-keys)
  "A custom alert that focuses on defining a fringe with COLOR key
with a hex value."
  ;; Step 01: Create hex color symbol
  (lexical-let* ((hex-color (replace-regexp-in-string "#" "" color))
      (hex-symbol-name ;; Prepend alert-color-- to the hex code
       (format "alert-color--%s" hex-color))
      (hex-symbol      ;; Check if the symbol exists or default
       (or (intern-soft hex-symbol-name)
          (intern hex-symbol-name))))

    ;; Step 02: Add the symbol pair to the lists
    (unless (cdr (assoc hex-symbol alert-log-severity-functions))
      (add-to-list 'alert-log-severity-functions
                   (cons hex-symbol #'alert--log-trace)))

    (unless (cdr (assoc hex-symbol alert-severity-colors))
      (add-to-list 'alert-severity-colors (cons hex-symbol color)))

    ;; Step 03: Handle an extra :color property that
    ;;          defaults :style and :severity
    (lexical-let ((color-properties
          :style 'fringe
          :severity hex-symbol))
         (cl-reduce ;; Just removes :color from the property list
          (lambda (val props)
            (if (equal :color val)
                (cdr props)
              (cons val props)))
          :from-end t
          :initial-value (list))))
      (apply #'alert
          (list message)

Although a little long, this encapsulates the symbol creation, adding to the lists and handling a :color property. It does not handle more fine grained severity options and it might not property decorate around alert but it does the job. You can see it in action here:

(fn/alert-color "Here is my colored alert"
                :color "#123456")

In the interest of being canonical with alert, I would have defined a custom style but I could not get a custom property or argument like :color to be used; I could have reused :severity but it doesn't fit the context. So I had to create a decorator for a custom color property despite the ease of doing the following:

;; Copied from alert-fringe-
(defun fn/alert-flash-notify (info)
  (set-face-background 'fringe (plist-get info :color)))

(defun fn/alert-flash-restore (info)
  (copy-face 'alert-saved-fringe-face 'fringe))

(alert-define-style 'fn/flash
                    :title "Change the fringe color"
                    :notifier #'fn/alert-flash-notify
                    :remover #'fn/alert-flash-restore)

;; If it did work...
(alert "Here is a simpler alert."
       :style 'fn/flash
       :color "#123456")

So now we can get a colored fringe alert without handling the symbols and lists.

>> Combining alert and prodigy

Now that we have the two we can easily merge them in the following snippet:

(defun fn/prodigy-jekyll-state-change-listener (&rest args) ;;defun* can also work
  (lexical-let* ((output (plist-get args :output)) ;; Get the arguments
      (service (plist-get args :service)))
    (pcase output
      ((pred (string-match-p "Server running"))
       (prodigy-set-status service 'ready) ;; Manually set the status
       (fn/alert-color "Blog is up and running"
                       :color "#01dddd"))
      ((pred (string-match-p "Error:"))
       (fn/alert-color "Blog has a build error. Check out the logs."
                       :color "#fd8a5e"))
      ((pred (string-match-p "...done"))
       (fn/alert-color "Blog has successfully been built."
                       :color "#e0e300"))
      (_ ;; Do nothing otherwise

With that, everything should be in order; however, what I did instead is created a hook so that other actions aside might be taken based on its state. As an example, I check the blog's output using w3m and when the blog is built, I want to reload all the web buffers pertaining to my blog. This snippet does it:

(defun fn/prodigy-jekyll-state-w3m-reload (service state)
  (lexical-let ((service-name (plist-get service :name)))
    (pcase state ;; My hook takes a service and state argument
          (lambda (w3m-buffer)
              (lexical-let* ((url (with-current-buffer w3m-buffer w3m-current-url))
                  (parts (w3m-parse-http-url url))
                  (host (elt parts 1))
                  (port (elt parts 2)))
                (when (and (= port 34000)
                         (string= host ""))
                  (with-current-buffer w3m-buffer
      (_ nil))))

;; The hook I created, modified for this post
(add-hook 'fn/prodigy-jekyll-state-change-hook

That's the thing about :on-output, you can't add to it like a hook so you have to make one. Using the output as a hook, I have an alert and auto-reload mechanism.

Lastly, if you forgot the meaning of the color, it would be nice if there was also a message to back it up. By default, using this style of the alert produces no minibuffer message to appear which is appropriate. However, I would like to be reminded what it means with this snippet:

(defun fn/alert-fringe-notify-message (info)
  "Log `fringe' style with `message'.
     This is to support `fn/alert-color' if the color flash needs
     a reminder."
  (message (plist-get info :message)))

(advice-add 'alert-fringe-notify :after #'fn/alert-fringe-notify-message)

Or you could add that to fn/alert-color if advicing is overkill.

>> Conclusion

Now that we have candied alerts for our services, what else can be done with these libraries?

  • Service dependencies
  • Log alerts to file

If more services such as react-native come and go, we can now easily creates alerts for it.