babashka

https://github.com/babashka/babashka. Also see #sci, #nbb and #babashka-circleci-builds .
mogenslund 2021-03-12T08:36:49.172600Z

Hi. Is it possible in some way to execute something like this: (.print (System/out) "Something") in babashka? I get this error message: No matching method print found taking 1 args for class http://java.io.PrintStream. I know, (print "Something") will work, but my problem is, that I need to capture print and println and at the same time write to the terminal. On the jvm it works using System/out.

borkdude 2021-03-12T09:04:22.173200Z

@mogenslund It's a bit convoluted but you can do it like this:

$ bb -e '(binding [*out* (io/writer System/out)] (println :foo))'
:foo
Please post an issue about the above and I'll try to fix it in the next release

mogenslund 2021-03-12T09:27:26.174100Z

Great. Thank you! The workaround works, but I have posted an issue anyway.

borkdude 2021-03-12T10:33:07.174300Z

It's now fixed on master

🚀 1
🚤 3
borkdude 2021-03-12T12:37:37.177600Z

There is currently something in babashka (since the latest version) that I'm not certain about if we should keep it or revert at the cost of being less compatible with Clojure. The issue is that when you use (reify ....) it will give false positives for instanceof checks for any interface supported with reify, like IFn, Seqable, etc.. E.g.:

$ bb -e '(def x (reify clojure.lang.ILookup (valAt [this k] k))) (ifn? x)'
true
$ bb -e '(def x (reify)) (prn x)'
----- Error --------------------------------------------------------------------
Type:     java.lang.Exception
Message:  Not implemented: seq
Location: <expr>:1:17

----- Context ------------------------------------------------------------------
1: (def x (reify)) (prn x)
                   ^--- Not implemented: seq
(apparently something in prn checks if something is a Seqable and then calls seq on it, probably the print-method dispatch).

borkdude 2021-03-12T12:38:08.178300Z

The reason for this strange behavior is that a reified object implements all interfaces (this is a compile time thing) and then dispatches at runtime to functions provided by the user.

borkdude 2021-03-12T12:38:27.178700Z

This was added to support "smart maps" in pathom, but I'm inclined to revert due to unexpected behavior.

borkdude 2021-03-12T12:38:44.178900Z

Here is the sci issue: https://github.com/borkdude/sci/issues/549. Feedback welcome.

2021-03-12T16:29:02.181300Z

Can someone show me how I can write a script that can accept command line arguments? e.g. I would like to do bb ./rabbit-parser.clj "some file here" My script looks like this:

(require '[clojure.string :as str])
; other methods left out for shortness.

(defn parse-log! [logfile]
  (let [log 				(slurp logfile)
        missing-heartbeats 	(get-missing-heartbeats log)
        missing-baselines 	(get-missing-baselines log)
        output 				(str "Reported Missing Heartbeats\n"
								 "===========================\n"
								 (apply str (map #(str (first %) " :: " (second %) "\n") missing-heartbeats))
								 "\n"
								 "Total: " (reduce + (map #(second %) missing-heartbeats))
								 "\n\n\n"
								 "Missing baselines on Thresholds where threshold set is Relative\n"
								 "===============================================================\n"
								 (str/join "\n" missing-baselines))
        output-file 		(str (subs logfile 0 (- (count logfile) 3)) "report.log")]
    (spit output-file output)))
	
	
(parse-log! (first args))
I just want to call parse-log! and supply a file to parse via the command line. Does this make sense as a thing to do with babashka?

2021-03-12T16:29:30.182Z

If not, i have a native built with graal-vm with a main [& args] that works 🙂

borkdude 2021-03-12T16:30:13.182400Z

@qmstuart Seems like a good use case for bb yes, depending on what the ; other methods are

2021-03-12T16:30:31.182700Z

The other 2 methods are just

(defn- get-missing-heartbeats [logfile]
  (->> (str/split-lines logfile)
       (filter #(str/includes? % "No heartbeat data found in db for"))
       (map #(str/trimr (subs % (+ (str/index-of % "for:") 5))))
       (group-by identity)
       (map #((juxt first (fn [f] (count (second f)))) %))
       (filter #(> (second %) 1))
       (sort-by first)))

(defn- get-missing-baselines [logfile]
  (->> (str/split-lines logfile)
       (filter #(str/includes? % "Heartbeat Information: "))
       (map (partial apply str))
       (map #(subs % (count "Heartbeat Information: [hbId : ")))
       (map #(take-while (fn [x] (not= x \])) %))
       (map (partial apply str))
       (distinct)))

borkdude 2021-03-12T16:30:42.183100Z

should be fine then

2021-03-12T16:30:53.183400Z

just messing with strings. I have a file full of horrible error messages i'm trying to tidy up and produce a nice report i can work with

2021-03-12T16:31:04.183700Z

i just dont know how to pass the file name from the command line

borkdude 2021-03-12T16:33:07.184Z

@qmstuart You can use *command-line-args*

2021-03-12T16:33:44.184200Z

perfect, that works !

2021-03-12T16:33:47.184400Z

thank you!

borkdude 2021-03-12T22:15:49.184900Z

CĂ©lio 2021-03-12T22:45:18.186100Z

Hello there. I’m writing this script that starts a long-running process and the intent is to keep spitting out its out and err streams until the process dies. I’m not sure what I’m doing wrong but even though my script reads the streams in their own threads the output gets dumped entirely right after the process exits. Any advice is appreciated. Here’s my script:

; File: /Users/ccidral/.bin/src/test.clj

#!/usr/bin/env bb

(ns test
  (:require [babashka.process :as p]
            [<http://clojure.java.io|clojure.java.io> :as io]))

(defn print-output
  [input]
  (future
    (try
      (with-open [reader (io/reader input)]
        (doseq [line (line-seq reader)]
          (println line)))
      (catch Exception e
        (println e)))))

(let [process (p/process '["/Users/ccidral/dev/proj/sleep-loop/sleep-loop"])]
  (print-output (:out process))
  (print-output (:err process))
  (println "Exit code:" (:exit @process)))
This is the output:
$ src/test.clj
Waiting 1
Exit code: Waiting 2
Waiting 3
Waiting 4
Waiting 5
0
This is the program started by the script. It’s a simple loop that prints some message every one second, five times.
// File: /Users/ccidral/dev/proj/sleep-loop/sleep-loop.c
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
int main()
{
    int i;
    for (i = 1; i &lt;= 5; i++) {
        printf("Waiting %d\n", i);
        sleep(1);
    }
    return 0;
}

borkdude 2021-03-12T23:06:34.186500Z

@ccidral This works like expected:

#!/usr/bin/env bb

(ns test
  (:require [babashka.process :as p]
            [<http://clojure.java.io|clojure.java.io> :as io]))

(defn print-output
  [input]
  (future
    (try
      (with-open [reader (io/reader input)]
        (doseq [line (line-seq reader)]
          (println line)))
      (catch Exception e
        (println e)))))

(let [process (p/process '["bb" "-e" "(run! (fn [_] (Thread/sleep 1000) (println :yo)) [1 2 3])"])]
  (print-output (:out process))
  (print-output (:err process))
  (println "Exit code:" (:exit @process)))

borkdude 2021-03-12T23:07:01.186800Z

Maybe you need to call flush in the c program?

borkdude 2021-03-12T23:07:20.187200Z

or your sleep is not long enough, I think it's only 1 millisecond?

CĂ©lio 2021-03-12T23:08:45.188100Z

When I run sleep-loop in my terminal, it prints out the messages every one second.

$ ./sleep-loop
Waiting 1
Waiting 2
Waiting 3
Waiting 4
Waiting 5

CĂ©lio 2021-03-12T23:09:10.188400Z

Let me try your example.

CĂ©lio 2021-03-12T23:10:18.189300Z

Your script behaves as expected.

borkdude 2021-03-12T23:10:28.189700Z

Btw, if you just want to see the output to the console, you can also use {:out :inherit}

CĂ©lio 2021-03-12T23:11:21.190100Z

Ohhhh nice

CĂ©lio 2021-03-12T23:11:57.191100Z

That works perfectly, and saves some effort. Thanks so much!

borkdude 2021-03-12T23:11:58.191200Z

Same for stderr: {:err :inherit} and for all, including stdin: {:inherit true}

❤️ 1
borkdude 2021-03-12T23:16:05.191700Z

@ccidral Btw, I fixed your C program:

// File: /Users/ccidral/dev/proj/sleep-loop/sleep-loop.c
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
int main()
{
  setbuf(stdout, NULL); // this turns off buffering!
  int i;
  for (i = 1; i &lt;= 5; i++) {
    printf("Waiting %d\n", i);
    sleep(1);
  }
  return 0;
}

borkdude 2021-03-12T23:16:27.191900Z

Found that here: https://stackoverflow.com/a/1716621/6264

CĂ©lio 2021-03-12T23:24:14.193800Z

Hmmm interesting. I first faced that issue when I tried to print the output of https://sshuttle.readthedocs.io/, which is why I wrote that test C program. I guess sshuttle also doesn’t turn off buffering.