Fork me on GitHub
#babashka
<
2021-03-12
>
mogenslund08:03:49

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.

borkdude09:03:22

@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

mogenslund09:03:26

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

borkdude10:03:07

It's now fixed on master

🚤 9
🚀 3
borkdude12:03:37

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).

borkdude12:03:08

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.

borkdude12:03:27

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

borkdude12:03:44

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

Stuart16:03:02

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?

Stuart16:03:30

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

borkdude16:03:13

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

Stuart16:03:31

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)))

borkdude16:03:42

should be fine then

Stuart16:03:53

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

Stuart16:03:04

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

borkdude16:03:07

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

Stuart16:03:44

perfect, that works !

Stuart16:03:47

thank you!

Célio22:03:18

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]
            [ :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 <stdio.h>
#include <unistd.h>
int main()
{
    int i;
    for (i = 1; i <= 5; i++) {
        printf("Waiting %d\n", i);
        sleep(1);
    }
    return 0;
}

borkdude23:03:34

@ccidral This works like expected:

#!/usr/bin/env bb

(ns test
  (:require [babashka.process :as p]
            [ :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)))

borkdude23:03:01

Maybe you need to call flush in the c program?

borkdude23:03:20

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

Célio23:03:45

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élio23:03:10

Let me try your example.

Célio23:03:18

Your script behaves as expected.

borkdude23:03:28

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

Célio23:03:21

Ohhhh nice

Célio23:03:57

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

borkdude23:03:58

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

❤️ 3
borkdude23:03:05

@ccidral Btw, I fixed your C program:

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

Célio23:03:14

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.