babashka

floscr 2025-05-07T12:48:36.993539Z

Can I capture output and also keep printing to *out* with babashka? I've got a CLI that prints to stderr something like a progress bar and then the output (dont ask why 😄 ) So I want to still be able to interactively keep printing the progress bar, but process the output as a string at the end.

borkdude 2025-05-07T12:51:16.902149Z

do you mean you want to print the same output to stderr and stdout?

floscr 2025-05-07T13:06:05.517789Z

Here's an example I've got this cli

(ns cli)

(binding [*out* *err*]
  (println "0% Progress ..."))

(Thread/sleep 1000)

(binding [*out* *err*]
  (println "Final output"))
And I consume it like this
(ns test
  (:require
   [babashka.process :as bp]))

(->> (bp/shell {:out :string
                :err :out} "bb ./cli.clj")
     (:out)
     (println "Output"))
But, this means I'm waiting for the CLI to be finished. But I want to to keep printing the output from cli.clj and process the printed output after its finished

borkdude 2025-05-07T13:07:43.730219Z

the issue here is buffering of the outputstream

borkdude 2025-05-07T13:08:05.938549Z

I think

borkdude 2025-05-07T13:08:45.374349Z

what you could do is capture the output yourself in bb.cli and do whatever you want with it

borkdude 2025-05-07T13:08:52.268149Z

let me fetch an example

floscr 2025-05-07T13:13:36.244859Z

Ok I think with that info about the outputstream buffer I've got it

(ns test
  (:require
   [babashka.process :as bp])
  (:import
   [java.io BufferedReader InputStreamReader]))

(let [content (atom [])]
  (with-open [err (:err (bp/process "bb ./cli.clj"))
              reader (BufferedReader. (InputStreamReader. err))]
    (loop [line (.readLine reader)]
      (when line
        (println "Error:" line)
        (swap! content conj line)
        (recur (.readLine reader)))))
  @content)

floscr 2025-05-07T13:14:20.227699Z

Is there an easier way for this?

borkdude 2025-05-07T13:16:44.020339Z

not yet. I've been playing around with ideas. https://github.com/babashka/process/issues/54#issuecomment-2423819646

borkdude 2025-05-07T13:17:39.913429Z

so the proposed solution in the branch discussed in the comment is to add a :out-line-fn and :err-line-fn option. Perhaps we could also just pass a function to :out and :err

borkdude 2025-05-07T13:17:59.306209Z

If you like that option, leave a comment in the issue

floscr 2025-05-07T13:24:03.318419Z

Nice will do thumbsup_all It's quite interesting, as I have to remove the clear screen instructions as well in the final result, otherwise the final print is offset weirdly

(ns test
  (:require
   [babashka.process :as bp]
   [clojure.string :as str])
  (:import
   [ BufferedReader InputStreamReader]))

(defn strip-ansi [s]
  (str/replace s #"\u001b\[[0-9;]*[JHm]" ""))

(let [content (atom [])]
  (with-open [err (:err (bp/process "bb ./cli.clj"))
              reader (BufferedReader. (InputStreamReader. err))]
    (loop [line (.readLine reader)]
      (when line
        (let [clean-line (strip-ansi line)]
          (println line)
          (when (not (str/blank? clean-line))
            (swap! content conj clean-line)))
        (recur (.readLine reader))))
    (println "FINAL CONTENT" @content)))
(ns cli)

(defn print-progress! [progress]
  (print "\u001b[2J") ; clear screen
  (print "\u001b[H")  ; move cursor to home position
  (println progress "% Progress ..."))


(binding [*out* *err*]
  (print-progress! 0)
  (Thread/sleep 1000)
  (print-progress! 50)
  (Thread/sleep 1000)
  (print-progress! 100))

(binding [*out* *err*]
  (println "Final output"))

borkdude 2025-05-07T13:24:42.846319Z

interesting

floscr 2025-05-07T16:20:08.783059Z

What do you think about adding BreakIterator to babashka java classes? Useful to get "composed" character length of strings

(ns app
  (:import [java.text BreakIterator]))

(defn count-characters
  [^String text]
  (let [it (BreakIterator/getCharacterInstance)]
    (.setText it text)
    (loop [count 0]
      (if (= (.next it) BreakIterator/DONE)
        count
        (recur (inc count))))))

(comment
  (count-characters ":flag-ca:") ;; => 1
  (count ":flag-ca:") ;; => 4
  nil)
edit: Slacks keeps replacing the emojis 😬 I'm using "🇨🇦" in the comment block

borkdude 2025-05-07T17:29:40.808729Z

yes

borkdude 2025-05-07T17:49:31.289339Z

https://github.com/babashka/babashka/pull/1817

🌟 2
floscr 2025-05-07T17:52:02.889949Z

Sweet! ❤️

borkdude 2025-05-07T17:59:38.378149Z

you can install the new version with:

bash <(curl )  --dev-build --dir /tmp
once CI on master finishes (should be within 10 minutes or so)