Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

elisp parse output of asynchronous shell command

I have a simple elisp interactive function that I use to launch a Clojure repl.

(defun boot-repl ()
  (interactive)
  (shell-command "boot repl wait &"))

It opens an *Async Shell Command* buffer, and after a while the following text appears :

nREPL server started on port 59795 on host 127.0.0.1 - nrepl://127.0.0.1:59795 Implicit target dir is deprecated, please use the target task instead. Set BOOT_EMIT_TARGET=no to disable implicit target dir.

I would like to monitor the output of this command to be able to parse the port ("59795" in this example). Even just the first line (in the case without warnings) would be alright.

This way I could be able to use another command to connect to the Clojure REPL waiting for me.

I cannot use shell-command-to-string as the command does not return and it blocks emacs forever (boot repl wait is supposed to last for my whole programming session, possibly more).

There may be something easy to do with cider also, but I haven't found it.

So, how do I parse the result of an asynchronous bash command in Elisp ? Alternatively, how can I set-up Cider to launch this REPL for my and connect to it ?

like image 949
nha Avatar asked Mar 24 '26 21:03

nha


2 Answers

To answer the question directly, you can definitely parse the output of an asyncronous shell command, using start-process and set-process-filter:

(let ((proc (start-process "find" "find" "find" 
                  (expand-file-name "~") "-name" "*el")))
     (set-process-filter proc (lambda (proc line)
                    (message "process output: %s" line))))

(Docs for filter function)

However, note that line above is not necessarily a line, and may include multiple lines or broken lines. Your filter is called whenever the process or emacs decides to flush some ouput:

... /home/user/gopath/src/github.com/gongo/json-reformat/test/json-reformat-test.el /home/user/gopath/src/github.com/gongo/json-reformat/test/test- process output: helper.el

In your case, this could mean that your port number might be broken into two separate process-filter calls.

To fix this, we can introduce a line-buffering and line-splitting wrapper, which calls your filter for each process output line:

(defun process-filter-line-buffer (real-filter)
 (let ((cum-string-sym (gensym "proc-filter-buff"))
       (newline (string-to-char "\n"))
       (string-indexof (lambda (string char start)
             (loop for i from start below (length string)
                   thereis (and (eq char (aref string i))
                        i)))))

   (set cum-string-sym "")
   `(lambda (proc string)
      (setf string (concat ,cum-string-sym string))
      (let ((start 0) new-start)
    (while (setf new-start
            (funcall ,string-indexof string ,newline start))

      ;;does not include newline
      (funcall ,real-filter proc (substring string start new-start))

      (setf start (1+ new-start)));;past newline

    (setf ,cum-string-sym (substring string start))))))

Then, you can safely expect your lines to be whole:

(let* ((test-output "\nREPL server started on port 59795 on host 127.0.0.1 - \nrepl://127.0.0.1:59795 Implicit target dir is deprecated, please use the target task instead. Set BOOT_EMIT_TARGET=no to disable implicit target dir.")
     (proc (start-process "echo-test" "echo-test" "echo" test-output)))
 (set-process-filter proc (process-filter-line-buffer
               (lambda (proc line)
                 (when (string-match
                    "REPL server started on port \\([0-9]+\\)"
                    line)
                   (let ((port (match-string 1 line)))
                 ;;take whatever action here with PORT
                 (message "port found: %s" port)))))))

Finally, I'm not familiar with cider but this kind of low-level work probably belongs in an inferior-type mode and has probably already been solved.

like image 100
ealfonso Avatar answered Mar 26 '26 12:03

ealfonso


shell-command

allows to name optional output- and error-buffers. Than the error should appear inside the latter and not clutter the output any more.

like image 21
Andreas Röhler Avatar answered Mar 26 '26 13:03

Andreas Röhler



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!