Fork me on GitHub
#babashka
<
2023-03-10
>
borkdude12:03:31

So far the go macro was "mocked" in bb for compatibility and fell back to real threads. In this commit I improve on this "mocked" situation by leveraging virtual threads for go blocks: https://github.com/babashka/babashka/compare/master...go-macro-uses-virtual-threads That makes the following example work in bb whereas previously it ran out of OS threads:

(require '[clojure.core.async :as async])

(def n 100000)

(let [begin (System/currentTimeMillis)
      state (atom 0)]
  (dotimes [_ n]
    (async/go (Thread/sleep 1000) ;; bad, you shouldn't block in a JVM go block, but what's a better way to force bb to go out of OS threads?
              (swap! state inc)))
  (while (< @state n))
  (println "Spawned" n "go blocks and finished in" (- (System/currentTimeMillis) begin) "ms"))

jeroenvandijk12:03:11

Nice! But a question about the example, you would normally not call Thread/sleep in a go block?

borkdude12:03:37

probably not, but this is just for force bb to go out of threads :)

jeroenvandijk12:03:48

haha i see. Good job!

borkdude12:03:54

if you have a better example, please provide one :)

jeroenvandijk12:03:30

I haven’t played with core.async for a long time so would be a challenge. Just remembered that you can’t block in a go block 😅

borkdude12:03:38

yeah, this is true

borkdude12:03:11

but for bb this is no longer the case 😂

jeroenvandijk12:03:30

I was mostly wondering if this would work in Clojure hehe. Nice it works in bb 👏

borkdude12:03:07

yeah, gotta find a better example that currently works in clojure but fails in bb

borkdude12:03:47

in the book I had this example: https://book.babashka.org/#core_async but it still works for me at high numbers or it takes ages in both clj and bb

jeroenvandijk12:03:26

I’ll think about it too

jeroenvandijk12:03:47

Is core.async/timeout implemented? Something like (<! (timeout 1000)) maybe?

borkdude12:03:40

by replacing (Thread/sleep 1000) with (async/<! (async/timeout 1000)) it works better in clj, still crashes with the old bb but in the new bb I'm getting:

java.lang.AssertionError: Assert failed: No more than 1024 pending takes are allowed on a single channel.

borkdude12:03:32

I think I could just replace timeout with a virtual thread implementation too

borkdude13:03:07

it might be good to find out why this error is happening though

jeroenvandijk13:03:42

Yeah I recognize that error from the JVM. I think 1024 is the maximum of the channel’s queue so you are going over the limit of the go blocks internal channel?

jeroenvandijk13:03:31

Maybe use (dotimes [i 1000] (<!! ). The blocking one

jeroenvandijk13:03:57

I hope 1024 is still enough to prove your point

jeroenvandijk13:03:07

Sorry not (<!! ..) but (>!! ..) to insert in a channel

jeroenvandijk13:03:16

But I guess this completely changes the example hmmm

borkdude13:03:36

I don't get why this error gets triggered

borkdude13:03:44

with the bb example

jeroenvandijk13:03:17

Not sure exactly about the bb implementation, but in jvm. The go never blocks. So you are doing N=100000 puts on an internal go block channel (I believe) and the maximum is 1024

jeroenvandijk13:03:41

I cannot explain it by reading the core.async code (I tried), but I can easily reproduce it in bb

(def c (async/chan))
(dotimes [i 1025] (async/go (async/>! c i)))
Exception in thread "async-thread-macro-1025" java.lang.AssertionError: Assert failed: No more than 1024 pending puts are allowed on a single channel. Consider using a windowed buffer.
(< (.size puts) impl/MAX-QUEUE-SIZE)
	at clojure.core.async.impl.channels.ManyToManyChannel.put_BANG_(channels.clj:157)
	at clojure.core.async$fn__17563.invokeStatic(async.clj:174)
	at clojure.core.async$fn__17563.invoke(async.clj:166)
	at sci.lang.Var.invoke(lang.cljc:202)
	at sci.impl.analyzer$return_call$reify__4482.eval(analyzer.cljc:1402)

jeroenvandijk14:03:20

I think one problem is that 1024 is still ok for normal threads. So you run into the limits of core.async itself. Starting a million threads is an issue in my bb though:

(require '[clojure.core.async :as async])

(def n (* 1000 1024))

(println n "Threads")

(def *cnt (atom 0))

(def start (System/currentTimeMillis))

(def threads (doall (for [i (range n)]
  (let [t (Thread. (fn [] (swap! *cnt inc)))]
    (.start t)
    t))))

(println "Elapsed" (- (System/currentTimeMillis) start))
(println "cnt" @*cnt)

(System/exit 0)
>
time bb async.clj
1024000 Threads
Elapsed 48743
cnt 1024000
bb async.clj  34.36s user 47.61s system 167% cpu 48.860 total

borkdude16:03:49

So this is a small repro:

(require '[clojure.core.async :as async])

(def n 2000)

(let [begin (System/currentTimeMillis)
      state (atom 0)]
  (dotimes [_ n]
    (async/go
      (async/<!! (async/timeout 1))
      (swap! state inc))
    #_(async/<! (async/timeout 1000)) #_(async/go (async/<! (async/timeout 1000))
              #_(swap! state inc)))
  (while (< @state n))
  (println "Spawned" @state "go blocks and finished in" (- (System/currentTimeMillis) begin) "ms"))

borkdude16:03:15

when changing async/go to async/thread the error goes away. also the error only occurs in the native image, not in a JVM that runs bb

borkdude16:03:28

async/go creates a virtual thread

jeroenvandijk16:03:02

(go (<!! …) should be (go (<! …)) maybe. Otherwise it is similar to Thread/sleep

borkdude16:03:22

yeah, in this case (bb) it doesn't matter since they both map to the blocking option

jeroenvandijk16:03:24

I’m not sure what a good example is 😅 But you clearly did something special 🙂

jeroenvandijk16:03:56

I was also thinking maybe that 1024 queue size is related to the thread limit in core.async itself. With virtual threads maybe https://github.com/clojure/core.async/search?q=1024 can be increased to something higher (https://github.com/clojure/core.async/search?q=1024)

borkdude16:03:36

There is something odd with the timeout channel:

(def hashes (atom #{}))

(let [begin (System/currentTimeMillis)
      state (atom 0)]
  (dotimes [_ n]
    (async/go
      (let [chan (async/timeout 1)]
        (swap! hashes conj chan)
        (async/<! chan))
      (swap! state inc)))
  (while (< @state n))
  (println "Spawned" @state "go blocks and finished in" (- (System/currentTimeMillis) begin) "ms")
  (prn (count @hashes)))

borkdude16:03:54

It is as if they re-used:

$ clojure -J--enable-preview /tmp/go.bb
WARNING: Implicit use of clojure.main with options is deprecated, use -M
Spawned 2000 go blocks and finished in 41 ms
36

borkdude16:03:12

so that would explain multiple processes reading the same channel at the same time

borkdude16:03:37

I would assume the timeout channel being unique each time, but that doesn't seem to be the case

jeroenvandijk16:03:21

hmm yeah indeed

borkdude16:03:09

I almost feared there was a concurrency issue with SCI and virtual threads, but I read the source here:

(or (when (and me (< (.getKey me) (+ timeout TIMEOUT_RESOLUTION_MS)))
          (.channel ^TimeoutQueueEntry (.getValue me)))
        (let [timeout-channel (channels/chan nil)
              timeout-entry (TimeoutQueueEntry. timeout-channel timeout)]
          (.put timeouts-map timeout timeout-entry)
          (.put timeouts-queue timeout-entry)
          timeout-channel))

borkdude16:03:31

it buffers the same timeout channels if they are within 10ms difference...

borkdude16:03:02

so perhaps this behavior can also be reproduced with normal JVM + clojure... let's see

borkdude16:03:49

Yep, JVM repro:

(require '[clojure.core.async :as async])

(def ^java.util.concurrent.Executor virtual-executor
  (java.util.concurrent.Executors/newVirtualThreadPerTaskExecutor))

(def n 200000)

(let [chans (atom #{})
      begin (System/currentTimeMillis)
      state (atom 0)]
  (dotimes [_ n]
    (.execute virtual-executor
              (fn []
                (let [chan (async/timeout 1)]
                  (swap! chans conj chan)
                  (async/<!! chan))
                (swap! state inc))))
  (while (< @state n))
  (println "Spawned" @state "go blocks and finished in" (- (System/currentTimeMillis) begin) "ms")
  (prn (count @chans)))

borkdude16:03:32

Now that I identified the issue I'm more confident that I can continue with virtual threads. I'll just make a virtual thread version of timeout

jeroenvandijk16:03:18

hmm weird not sure if I understand how the number of puts is related to the caching of the timeout channel :thinking_face:

borkdude16:03:41

number of puts?

jeroenvandijk16:03:00

oh wait now it’s number of takes. Never mind

borkdude16:03:18

yeah, multiple threads are trying to read from the same channel

jeroenvandijk16:03:53

Ah ok I was misunderstanding the issue the whole time 🙈

jeroenvandijk16:03:59

sorry, was thinking it was about puts

borkdude16:03:06

(defn timeout [ms]
  (if virtual-executor
    (let [chan (async/chan nil)]
      (.submit virtual-executor (fn []
                                  (Thread/sleep ms)
                                  (async/close! chan)))
      chan)
    (async/timeout ms)))

👍 2
borkdude16:03:07

Maybe I'll end up with a virtual-enabled fork of core.async :P

👍 2
jeroenvandijk16:03:46

Yeah I think this pending take is an issue for when you have many threads.. I was trying something myself, but ran into it again

(require '[clojure.core.async :as a])

(def n 100000)

(def *cnt (atom 0))

(def ch (a/chan 100000000))

(dotimes [i n]
  (a/thread
    (loop []
      (let [v (a/<!! ch)]
        (swap! *cnt inc)
        (Thread/sleep 10)))))

(dotimes [i n]
  (a/>!! ch n))

(Thread/sleep 1000)

(println "COUNTED" @*cnt " and n " n)

borkdude16:03:14

yes, this is expected, since you read from the same channel

borkdude16:03:39

but I didn't expect calls like (async/timeout 1000) returning identical channels (re-used channels) sometimes

jeroenvandijk16:03:27

Yeah true. It feels like it’s impossible to proof your improvement this way hehe

borkdude17:03:43

$ bb /tmp/go.bb
Spawned 200000 go blocks and finished in 5572 ms
200000
🎉

jeroenvandijk17:03:58

Did you already find an upper limit of the number of virtual threads?

borkdude17:03:20

Spawned 2000000 go blocks and finished in 7857 ms

👀 2
borkdude17:03:39

$ bb /tmp/go.bb
Spawned 20000000 go blocks and finished in 160668 ms
20M... I guess I could go on ;)

🎉 2
teodorlu13:03:29

Hi! The docstring for babashka.cli/auto-coerce says: > starts with :, it is coerced as a keyword (through parse-keyword) I'm seeing this:

(babashka.cli/auto-coerce ":remote-reference")
;; => ":remote-reference"

(type (babashka.cli/auto-coerce ":remote-reference"))
;; => java.lang.String
Is this expected behavior?
(System/getProperty "babashka.version")
;; => "1.2.174"
;;
;; org.babashka/cli {:mvn/version "0.6.48"}

borkdude13:03:31

this however does work:

user=> (babashka.cli/parse-args [":remote"])
{:opts {:remote true}}
Can you find out why? I'm going out for a walk, brb

teodorlu13:03:55

I'll give it a shot 🙂

teodorlu13:03:33

(don't worry about getting back to the computer, this isn't time sensitive for me at all)

borkdude13:03:26

Better example:

user=> (babashka.cli/parse-args [":remote" ":always"])
{:opts {:remote :always}}

👍 2
Bob B13:03:43

it looks like the regex in auto-coerce only matches if everything after the colon is letters/digits: <https://github.com/babashka/cli/blob/52e7eaa3755043a0db7638133d96e4e7e55c2eba/src/babashka/cli.cljc#L97>

2
Bob B13:03:56

user=> (cli/auto-coerce ":abc")
:abc ; keyword
user=> (cli/auto-coerce ":abc-def")
":abc-def" ; string

2
teodorlu13:03:28

matches what I'm seeing ☝️

borkdude13:03:39

so the regex has to be adapted

borkdude13:03:16

why did I do this regex again...?

borkdude13:03:31

there was a reason for it. maybe a test will fail if you remove it

teodorlu13:03:29

so perhaps you're avoiding things with spaces in them? ./mycli --title ":1 This is a title."

borkdude13:03:17

yeah, that was it I think

borkdude13:03:58

also numerics as the first character aren't valid

borkdude13:03:59

but it's mostly about spaces. feel free to change the regex

borkdude13:03:56

also it should accept:

borkdude13:03:58

user=> (babashka.cli/parse-args [":remote" ":a/b"])
{:opts {:remote ":a/b"}}

👍 2
teodorlu13:03:05

Should :a/b/c be allowed?

borkdude13:03:45

that's not valid but we can let that slide

borkdude13:03:18

maybe just checking for whitespace makes sense

teodorlu13:03:47

Some options:

;; single slash:
;; 
;;   [a-zA-Z][a-zA-Z0-9]+(/[a-zA-Z0-9-]+)?
;;
;; multiple slashes:
;; 
;;   [a-zA-Z][a-zA-Z0-9/]+
;;
;; no whitespace:
;;
;;   (not (str/includes? s " "))

teodorlu13:03:55

I feel like being a bit defensive on the auto-coercion and only supporting things that really are wanted to be keywords is a good thing.

borkdude13:03:58

I think also a tab and newline etc, regex has \s for that

2
borkdude13:03:43

let's do this: it has to start with an alphabetic character, and then followed by alphanumerics, _, - or /

borkdude13:03:52

and we won't do validation about the number of /

borkdude13:03:08

also a literal . must be supported

teodorlu13:03:30

Sounds good to me -- I'll put together a PR.

pesterhazy13:03:56

Looking into this now, I've got a simple wrapper for cognitect.test-runner working: https://gist.github.com/pesterhazy/ec1dedd5c83ae47d981488f743042ca8 It looks at throwables and checks if they have (= :sci/error (:type e)). This works fine, but not always. Sometimes I get exception that aren't wrapped in a sci-provided ex-info https://clojurians.slack.com/archives/CLX41ASCS/p1678016771527859?thread_ts=1677844683.592549&amp;cid=CLX41ASCS

pesterhazy13:03:32

Let's say I have test.clj and test_test.clj (which is the corresponding test) Then if I (throw (Exception. "boom")) in the deftest in test_test.clj, I get a wrapped error However, if I throw one level down in test.cj, the error is not wrapped

borkdude13:03:42

That works, but what about doing this at a deeper level, in bb's clojure.test implementation?

borkdude13:03:13

and if that works, another layer deeper, in clojure stacktrace

pesterhazy13:03:24

Yeah that's the next step, I just wanted to make it work reliably

pesterhazy13:03:32

Which it isn't yet

pesterhazy13:03:50

As I sometimes get unwrapped errors like

#error {
 :cause "boom"
 :via
 [{:type java.lang.Exception
   :message "boom"
   :at [java.lang.reflect.Constructor newInstanceWithCaller "Constructor.java" 500]}]
 :trace
 [[java.lang.reflect.Constructor newInstanceWithCaller "Constructor.java" 500]
  [java.lang.reflect.Constructor newInstance "Constructor.java" 484]
  [sci.impl.Reflector invokeConstructor "Reflector.java" 310]
  [sci.impl.interop$invoke_constructor invokeStatic "interop.cljc" 78]
  [sci.impl.analyzer$analyze_new$reify__4304 eval "analyzer.cljc" 1084]
  [sci.impl.analyzer$analyze_throw$reify__4264 eval "analyzer.cljc" 968]
  [sci.impl.analyzer$return_do$reify__3927 eval "analyzer.cljc" 124]
  [sci.impl.fns$fun$arity_1__1166 invoke "fns.cljc" 107]
  [sci.lang.Var invoke "lang.cljc" 200]

pesterhazy13:03:16

Basically I'm using it in my work project to iron out errors before (hopefully) moving it to clojure.stacktrace

borkdude13:03:33

However, if I throw one level down in test.cj, the error is not wrapped
The solution here is to catch it with:
(try ... (catch ^:sci/error Exception e))

borkdude13:03:50

this way SCI is signaled to produce an ex-info with the stacktrace

borkdude13:03:10

The reason it doesn't always do this is that not each exception can carry metadata

pesterhazy13:03:12

That's interesting. Bad news for me because I'm not controlling the try/catch here. I think it's here https://github.com/babashka/babashka/blob/master/src/babashka/impl/clojure/test.clj#L540

borkdude13:03:32

This is only the case for code that is executed within SCI, i.e. interpreted code

borkdude13:03:47

the code you are pointing at isn't run inside SCI but as regular clojure code

borkdude13:03:20

oh but because it's a macro, it is run inside SCI

borkdude13:03:28

I stand corrected by myself ;)

borkdude13:03:41

we can of course change that

pesterhazy13:03:37

But I think it's try-expr

borkdude13:03:02

you can fix this in userland by overriding the defmethod or using alter-var-root

borkdude13:03:30

(for now I mean, as part of the experiment)

pesterhazy13:03:32

Which defmethod do you mean? I'm not following

borkdude14:03:13

you can use alter-var-root on try-expr

pesterhazy14:03:23

on a macro?! wow

borkdude14:03:30

yeah of course :)

borkdude14:03:47

but hmm, not sure if this will be seen in SCI though

borkdude14:03:11

yes, I think so because try-expr is called through other macro-expansions

pesterhazy14:03:10

20: (alter-var-root clojure.test/try-expr (constantly try-expr))
    ^--- Can't take value of a macro: #'clojure.test/try-expr

borkdude14:03:26

(constantly @#'try-expr)

pesterhazy14:03:17

(alter-var-root #'clojure.test/try-expr (constantly @#'try-expr))

pesterhazy14:03:11

Ok this runs

(defmacro try-expr
  "Used by the 'is' macro to catch unexpected exceptions.
  You don't call this."
  {:added "1.1"}
  [msg form]
  `(try ~(clojure.test/assert-expr msg form)
        (catch ^:sci/error Exception t#
          (clojure.test/do-report {:type :error, :message ~msg,
                                   :expected '~form, :actual t#}))))

(alter-var-root #'clojure.test/try-expr (constantly @#'try-expr))

pesterhazy14:03:21

But I'm still seeing an unwrapped exception

pesterhazy14:03:29

Doesn't see to make a difference

pesterhazy14:03:01

The exception is caught in my monkey-patched try-expr but it's not wrapped

{:type java.lang.Exception
   :message "boom"
   :at [java.lang.reflect.Constructor newInstanceWithCaller "Constructor.java" 500]}

borkdude14:03:52

this is because in macro you should probably write: ~(with-meta 'Exception {:sci/error true}) or so

borkdude14:03:40

user=> (meta ^:sci/error 'Exception)
nil

borkdude14:03:46

user=> (meta (with-meta 'Exception {:sci/error true}))
#:sci{:error true}

pesterhazy14:03:20

That worked! I'm not a macro pro

pesterhazy14:03:29

Would it make sense as a next step to upstream those changes into babashka.impl.clojure.test?

pesterhazy14:03:40

OK, I have to pick up my daughter now. Maybe I'll have some time next week In the meantime the gist is there if people want to try it

👍 2
Daniel Gerson14:03:51

About to make use of babashka/json given code that needs to be run from bb and clj , hence:

Caused by: clojure.lang.ExceptionInfo: defrecord/deftype currently only support protocol implementations, found: java.util.Iterator {:type :sci/error, :line 34, :column 1, :file "charred/coerce.clj"}
So happy that it was only recently born 🧡👶

🎉 2
teodorlu16:03:26

While I'm at it -- should babashka.cli/auto-coerce support "nil" too? current behavior: (babashka.cli/auto-coerce "nil") => "nil" proposed behavior: (babashka.cli/auto-coerce "nil") => nil

borkdude16:03:55

I thought it already did

borkdude16:03:25

apparently not

borkdude16:03:33

it does false

borkdude16:03:53

yeah, I think so

👍 2
borkdude16:03:41

merged!

🎉 2
joakimen16:03:07

Any fzf-fans here who have been able to fully integrate fzf with babashka (e.g. via https://github.com/babashka/process)? By fully, I refer to the non-blocking nature of fzf, where it starts seemingly instantaneously, then searches and populates the selection on the fly, as opposed to everything upfront. I’ve used the example at https://github.com/babashka/babashka/blob/master/examples/fzf.clj , which covers most use-cases where the selection is small (e.g. listing git branches). However, if I want to, say, glob recursively in a huge project, it will block until the search is complete, before presenting the fzf-interface. This may just be a limitation in my understanding of https://github.com/babashka/process. I looked at the streaming options as well, but wasn’t sure where to go from here. Any pointers?

teodorlu16:03:38

FZF fan here -- but I haven't tried streaming FZF input yet! As far as I'm aware, process can take stdin as an eager string, or as a lazy stream. So I believe streaming from a process command to find or ls should work.

👀 2
teodorlu16:03:44

This is working for me:

(require '[babashka.process :refer [shell process]])

(defn demo-3 []
  (-> (process "ls")
      (select-keys [:out])
      (shell "fzf")))
Edit: it does something, but it doesn't look quite right.

joakimen16:03:16

Got this one working with your tip (fd is a find-replacement)

#!/usr/bin/env bb
(ns file.fzf
  (:require [babashka.process :as p]
            [clojure.string :as str]))

(let [find (:out (p/process "fd . '/'"))
      fzf @(p/process ["fzf" "-m"] {:in find
                                    :out :string
                                    :err :inherit})]
  (->> fzf :out str/split-lines (mapv println)))

💯 2
joakimen16:03:35

awesome, thanks!

🎉 2
joakimen16:03:16

Didn’t even cross my mind that you could pass a not-yet-deref’d process as the input of another process, then deref the second without deref’ing the first :melting_face:

❤️ 2
teodorlu16:03:49

All while having access to Clojure data structures 😁

🧠 2
teodorlu16:03:10

I think it should be possible to create a lazy sequence in clojure too and pass that to process or shell, but I haven't tried that either.

teodorlu16:03:56

You might also be interested in babashka.fs/glob if you're doing stuff like this:

user=> (prn (type (babashka.fs/glob "." "**/*.clj")))
clojure.lang.PersistentVector
nil
user=> (prn (babashka.fs/glob "." "**/*.clj"))
[#object[sun.nio.fs.UnixPath 0x1a7235f7 "src/teodorlu/shed/libclonecd.clj"] #object[sun.nio.fs.UnixPath 0xffd8e2c "src/teodorlu/shed/month.clj"] #object[sun.nio.fs.UnixPath 0x65282c8b "src/teodorlu/shed/update_repos.clj"] #object[sun.nio.fs.UnixPath 0x1d7cd503 "src/teodorlu/shed/path_lines.clj"] #object[sun.nio.fs.UnixPath 0x74f11d65 "src/teodorlu/shed/install.clj"] #object[sun.nio.fs.UnixPath 0x60e2d902 "src/teodorlu/shed/quick_clone.clj"] #object[sun.nio.fs.UnixPath 0x28e4e6cc "src/teodorlu/shed/libquickcd.clj"] #object[sun.nio.fs.UnixPath 0x651fbf8a "src/teodorlu/shed/fzf_stream_demo.clj"] #object[sun.nio.fs.UnixPath 0xf9394f5 "src/teodorlu/shed/explore.clj"] #object[sun.nio.fs.UnixPath 0x7429b4c9 "src/teodorlu/shed/browsetxt.clj"] #object[sun.nio.fs.UnixPath 0x1a6156d4 "src/teodorlu/shed/ukenummer.clj"]]
As far as I can tell, it's eager (persistent vector, not lazy seq).

joakimen17:03:55

Glob was my initial input ( files (->> (fs/glob "." "**") ) but it blocks, so wasn’t suited for this use-case (searching tons of files incrementally)

teodorlu17:03:04

Gotcha 👍

borkdude18:03:04

Perhaps fs/glob and fs/match could have a callback based API as well. e.g. fs/glob-async or so

borkdude18:03:14

if that would help anything

joakimen18:03:28

That would be cool, personally I’d love to shell out less

borkdude18:03:07

Feel free to post an issue about it and then I'll think about it a little

👍 2
borkdude18:03:17

in babashka/fs

2
joakimen09:05:14

Btw, made my first, small Clojure-library (babashka-compatible) for this 🙂 https://github.com/joakimen/fzf.clj Took lots of inspiration on project structure and setup from @rahul080327's https://github.com/lispyclouds/bblgum, since they're both wrapper-libraries around interactive terminal programs, so thanks for the good reference-project! All feedback and criticism is welcome of course

❤️ 6
💯 4
🎉 6
🆒 2
borkdude09:05:21

Congrats @U0422G22932! Feel free to also announce this in #C06MAR553 if you'd like

❤️ 2
☝️ 2
escherize21:05:44

I wrote a thing with fzf too: https://github.com/escherize/bask

🎉 6
Jakub Šťastný20:03:40

Super cool babashka's got babashka.proccess/exec! Loving it!

🎉 6
2
teodorlu21:03:25

is it possible to change the current directory with babashka? In Python:

>>> os.getcwd()
'/home/teodorlu'
>>> os.chdir("dev/teodorlu")
>>> os.getcwd()
'/home/teodorlu/dev/teodorlu'

borkdude21:03:22

The only option is to either source some generated bash stuff from bb or start a new shell in a different directory

👍 2
borkdude21:03:53

#C029PTWD3HR does support it (node.js)

👍 2
teodorlu21:03:13

Gotcha 👍 I'm trying to make an interactive babashka script that can change shell directories, which I asked about a bit earlier. I tried using a shell function that executes some of the babashka script's stdout, but ran into problems with my own user interface. Because I was messing with stdout, I got problems when I tried to shell out to less. So I've decided to try to (process/exec (System/getenv SHELL)) instead. For my case, I can probably just as well keep track of the current directory, and pass :dir *current-directory into the places where that's required.

👍 2
teodorlu13:03:04

I ended up doing something like this:

(babashka.process/exec "zsh" "-c" (str "cd " (fs/absolutize loc) "; exec zsh"))
(https://github.com/teodorlu/shed/tree/841aac4344ccbe41783538800be0929231494697/src/teodorlu/shed/libquickcd.clj) And it seems to work! 😁 Though there's plenty of pitfalls. Manually creating that cd command will break on any path with spaces in it, and I have to invoke the shell twice to set the path. But it doesn't look like https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/ProcessProperties.html#exec-java.nio.file.Path-java.lang.String...- supports passing in a directory. Now I'm going to see if this hacked-together Babashka cd replacement actually feels any good in practice.

Jakub Šťastný23:03:47

Is there a convenient way to distribute babashka projects? Clojure's uberjars are pretty nice, but I keep hitting walls with things that Clojure doesn't have that babashka does. Best would be "compile babashka AND my project using Graal", that sounds feasible, although I imagine it takes ages to build.

borkdude23:03:38

bbin is the recommended and easiest way for bb projects, but you can also do it using brew and other package managers, this just more work. Examples of projects that have been distributed like that are #C0400TZENE7 itself and #C03KCV7TM6F