Fork me on GitHub
#babashka
<
2023-03-28
>
benfleis08:03:54

I have been playing with babashka CLI, and found 2 bits worth mention: First Q: is there a pure flag (implicit boolean/true) option? I haven’t found it yet:

(def concat-files
  [{:cmds ["concat"]
    :coerce {:files []}
    :args->opts (repeat :files)
    :fn println}])

(cli/dispatch concat-files ["concat" "f1" "f2"])
(cli/dispatch concat-files ["concat" "--opt1" "arg1" "file1" "file2"])

; WANT: flag1 -> implicit boolean, eg --dry-run, but flag1 consumes file1,
; GET: :opts {:flag1 f1, :files [f2]}
(cli/dispatch concat-files ["concat" "--flag1" "file1" "file2"])
I tried reading the parse logic, and got lost as a non-native learning clj speaker 🙂 https://github.com/babashka/cli/blob/main/src/babashka/cli.cljc#L361 suggests that it might be possible, and I thought I’d ask here for a quick check. Second Observation: I haven’t found a way to do sth like -X foo1 -X foo2 file1 file2 . I tried coercing both X and args->opts with a coerce on files, but X consumes the whole array. Explicitly using -- in between works, but is not ideal.
(println
 (cli/parse-args
  ["-X" "foo1" "-X" "foo2" "file1" "file2"]
  {:coerce {:X [] :files []}
   :args->opts (repeat :files)}))
; {:opts {:X [foo1 foo2 file1 file2]}}

(println
 (cli/parse-args
  ["-X" "foo1" "-X" "foo2" "--" "file1" "file2"]
  {:coerce {:X [] :files []}
   :args->opts (repeat :files)}))
; {:args [file1 file2], :opts {:X [foo1 foo2]}}

borkdude08:03:31

@U3LHMD2E8 First: yes, use {:coerce {:opt :boolean}} for flags

borkdude08:03:56

Second: use {:coerce {:X [:string]}}

borkdude09:03:06

user=> (cli/parse-opts ["-X" "file1.clj" "-X" "file2.clj"] {:coerce {:X [:string]}})
{:X ["file1.clj" "file2.clj"]}

borkdude09:03:16

All together:

user=> (cli/parse-args ["-X" "file1.clj" "-X" "file2.clj" "--myflag" "arg1"] {:coerce {:myflag :boolean :X [:string]}})
{:args ["arg1"], :opts {:X ["file1.clj" "file2.clj"], :myflag true}}

benfleis09:03:53

Thanks!

👍 2
benfleis09:03:41

> {:coerce {:X [:string]}} The additional :string doesn’t seem to change behavior:

(def args ["-X" "foo1" "-X" "foo2" "file1" "file2"])
(cli/parse-args args ; original
                {:coerce {:X [] :files []}
                 :args->opts (repeat :files)})
(cli/parse-args args ; explicit :string
                {:coerce {:X [:string] :files []}
                 :args->opts (repeat :files)})
(cli/parse-args args ; explicit :string in both
                {:coerce {:X [:string] :files [:string]}
                 :args->opts (repeat :files)})

...
user=> {:opts {:X ["foo1" "foo2" "file1" "file2"]}}
user=> {:opts {:X ["foo1" "foo2" "file1" "file2"]}}
user=> {:opts {:X ["foo1" "foo2" "file1" "file2"]}}

borkdude09:03:33

babashka cli supports two ways of specifying multiple values: either by repeating the option name + value or by providing the option name followed by multiple values

borkdude09:03:44

to separate args from opts at the end you can use the -- separator

borkdude09:03:09

user=> (def args ["-X" "foo1" "-X" "foo2" "--" "file1" "file2"])
#'user/args
user=> (cli/parse-args args {:coerce {:X [] :files []} :args->opts (repeat :files)})
{:args ["file1" "file2"], :opts {:X ["foo1" "foo2"]}}

benfleis09:03:10

Ok, makes sense then: this particular form (with the args->opts coersion) will only with with -- in the presence of a trailing array-stuffing opt.

borkdude09:03:27

we could include an option to disable the --opt val1 val2 syntax

borkdude09:03:31

if that's what you want

benfleis09:03:28

For this it’s only a personal tool — I brought up these since they’re common CLI patterns, and in my experience a single --arg rarely takes >1 value

borkdude09:03:35

if it doesn't take > 1 value you should not specify it as something that can be multiple with :coerce []

borkdude09:03:55

user=> (cli/parse-args ["--foo" "1" "a" "b" "c"])
{:args ["a" "b" "c"], :opts {:foo 1}}

borkdude09:03:50

user=> (cli/parse-args ["--foo" "1" "a" "b" "c"] {:coerce {:foo :boolean}})
{:args ["1" "a" "b" "c"], :opts {:foo true}}

borkdude09:03:02

user=> (cli/parse-args ["--foo" "1" "a" "b" "c"] {:coerce {:foo []}})
{:opts {:foo [1 "a" "b" "c"]}}

borkdude09:03:35

but I can see that the last syntax might not be posix-y, I'm fine with optionally disabling that

benfleis09:03:00

I need multiple args for both — in my actual case, I have 0-n “pre-ambles”, and 1+ actual files. Thus the -X foo -X bar file1 file2 is the syntax I hoped to work, without requiring me to remember to add -- and force the parser to stop. I’d love to tackle this, but will have to look at that code again to see what I can figure out. [removed bits which were poorly written]

borkdude10:03:07

A meander-like matching library that loads fast in babashka:

$ bb -Sdeps '{:deps {sg.flybot/lasagna-pull {:mvn/version "0.4.150"}}}' -e "(require '[sg.flybot.pullable :as pull :refer [qfn]]) ((qfn '{:foo ?foo} [?foo]) {:foo :bar})"
[:bar]

Stephan Renatus11:03:30

soo…meander doesn’t? (honest question, I’m still new to this)

Stephan Renatus11:03:03

I had noticed the announcement of lasagna-pull, too, and wondered how it differed from meander… 💭

borkdude11:03:01

@U04GFG14FCP Meander also works:

$ time bb -Sdeps '{:deps {meander/epsilon {:mvn/version "0.0.650"}}}' -e "(require '[meander.epsilon :as m]) (m/match {:foo :bar} {:foo ?a} ?a)"
:bar
bb -Sdeps '{:deps {meander/epsilon {:mvn/version "0.0.650"}}}' -e    0.38s  user 0.02s system 98% cpu 0.404 total
but it starts in 400ms whereas lasagna starts in an insignificant amount of time

borkdude11:03:46

I'm sure there's more trade-offs, probably meander has better performance etc

borkdude11:03:54

but for bb scripts startup matters

🚀 4
renewdoit23:03:24

Thanks for discussing my library here. I did not know meander when I wrote lasagna-pull. From my initial observation, meander is much more complex than lasagna-pull; it is a real compiler from a logical programming background, whereas lasagna-pull is a pure practical effort to try to match Clojure’s map and sequences of maps, which might explain the start time difference @U04V15CAJ observed (or performance) between them.

👏 2
sb11:03:28

Is that possible get a data/string stream from the pod side at Babashka pods?

sb11:03:32

I send with “write” (as in the example) with current id back to the clj part, and after this closed the connection. Maybe I didn’t find the documentation.. I saw in the tests maybe.. I just want to ask is that possible or not

borkdude11:03:29

yes of course

borkdude11:03:02

what exactly do you mean, can you give an example?

sb11:03:04

I would like to send back the python stdout as stream.. with the same id

borkdude11:03:21

You can also use the socket transport and then your python program can just print whatever

👍 2
sb11:03:29

I would like to catch https://github.com/openai/whisper stdout with babashka. In -verbose mode. Whisper transcribe the audio.. in realtime mainly.. every 8 sec.. you get back the copy.. and I would like to drop to output. See this action

sb11:03:08

I try out the socket part in this case

borkdude11:03:37

I notice that socket transport isn't that greatly documented, but here is a pod which uses it: https://github.com/babashka/pod-babashka-lanterna

sb11:03:04

Thank you very much!!

sb11:03:24

I check it now.

borkdude11:03:23

(load-pod your-pod {:transport :socket}) on the caller side

👍 2
👀 2
borkdude11:03:09

or you can say in the manifest: https://github.com/babashka/pod-registry/blob/a474904c2b964c8212b7b62a773ab97a014a28c3/manifests/org.babashka/lanterna/0.0.1/manifest.edn#L5 and then it will work automatically (but for local development it's easier to use the manual option)

borkdude11:03:58

This is an example of the lanterna pod which uses the socket and all stdin/stdout is going normally:

bb -Sdeps '{:deps {io.github.borkdude/console-tetris {:git/sha "2d3bee34ea93c84608c7cc5994ae70480b2df54c"}}}' -m tetris.core

💪 2
sb11:03:28

Wow! Thank you 🙂

sb11:03:00

Ok, I need to go now. But if I will have question I drop to here. Thank you very much again your help!! 🍻

🍺 2