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.
do you mean you want to print the same output to stderr and stdout?
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 finishedthe issue here is buffering of the outputstream
I think
what you could do is capture the output yourself in bb.cli and do whatever you want with it
let me fetch an example
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)Is there an easier way for this?
not yet. I've been playing around with ideas. https://github.com/babashka/process/issues/54#issuecomment-2423819646
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
If you like that option, leave a comment in the issue
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"))interesting
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 blockyes
Sweet! ❤️
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)