I understand -X lets you call a specific function from some random namespace and pass it arguments. So you can configure an alias like:
:new {:extra-deps {seancorfield/clj-new {:mvn/version "RELEASE"}}
:exec-fn clj-new/create
:exec-args {:template lib}}
And then call clj -X:new
and clj will run the create
function from clj-new
namespace and pass it as argument {:template lib}
It seems -M
is just the new -A
where your alias had a :main-opts []
defined.
And I don't know what has become of -A
-A
will stop running :main-opts
at some point and will be used just for starting a REPL (with various aliases). At least, that's what I understand @alexmiller to have said about it.
(and you'll get deprecation warnings right now if you try to use -A
with any main opts -- either :main-opts
or via the command line)
@didibus ^ does that answer your "question"? ๐
(and, to be clear, -M
has expanded from respecting just :main-opts
to respecting "everything")
I see, so -A will be if you just want to like bring more or less deps in your repl sa you start it?
Or like on anything, since I assume you can combo like -M and -A
Oh, so -M will be smart? So like you can use -M for alias that define an exec-fn ? So I guess only when its ambiguous you need to explicitly use -X ?
What?
-X
invokes a function. -M
invokes a main.
So -X
looks for :exec-fn
or a function on the command-line. -M
runs :main-opts
(or main options on the command-line).
There's no overlap there.
Re: -A
, yes, my understanding, based on what Alex has said, is that -A
will at some point simply stop running :main-opts
and will instead always start a REPL (with all the dependencies and classpath stuff set up per whatever aliases you specify).
Once -A
stops running main options, there will be no overlap there either:
* -X
-- invoke a function with a single hash map argument
* -M
-- invoke clojure.main
with various options (`-m`, -e
presumably)
* -A
-- run a REPL
Ok, maybe I didn't get what you meant by: (and, to be clear,ย -Mย has expanded from respecting justย :main-optsย to respecting "everything")
What is everything?
(at the risk of sounding like a broken record, I still think expanding -R
for the REPL option and just plain deprecating -A
would have been better but...)
"everything" = dependencies and class paths and jvm options etc
-R
and -C
and -O
used to be individual options for resolve args, classpath args, and jvm options.
Oh I see, ok I just assumed that haha, I guess I missed the time when -M did not respect those
Didn't -A respect all those as well?
It used to be, prior to this big change, that -A
was "all things" and all the other options just dealt with a specific thing (and there was no -X
but there was a -M
).
-A
still does respect all of those.
At some point it will ignore main opts.
Ah ok I see. I feel like -A
should become: run any alias fully. If the alias has a :main-opts
run that, if it doesn't but has an exec
run that, if not just start a repl with the extra-deps and all
Well, no, that's not what it is and not what it's going to be. That ship has sailed too ๐
Its strange to provide in your README like: add this alias, run with -X, or add this alias but this one remember to run with -M
Like what's the point of configuring anything in the deps.edn file, if I still need to remember some of it at the command line ๐
I suspect, over time, most tools will change to -X
execution because it is more powerful/more convenient.
clj-new switched, depstar switched. I see deps-deploy switched. When tools.build
arrives, I bet it's going to be all -X
style stuff.
It depends though, passing args to -X is tricky, and not unix like at all, so if you make a CLI, like take clj-kondo for example, if I want to use it from clj -X then the argument I would pass it need to be formatted differently than when I'd call it with clj-kondo executable
-X
is "native" Clojure. -M
is artificial. The latter is all strings that need to be parsed and turned into Clojure data. The former already is Clojure data.
bleh on those words. -X is function execution, -M is clojure.main. They are both Clojure.
I just meant in terms of -X
calling a Clojure function directly -- and being idiomatic in terms of the arg being a single hash map with keys and values from the command-line -- whereas -M
is like a traditional JVM process, via -main
, and passing strings that Clojure needs to parse. Hence "native" in quotes ๐
With -main
(and -M
) everyone needs to write their own ad hoc argument parsing (even if they build on tools.cli
). There's no consistency there at all. With -X
, it's absolutely consistent and all tools will work the same way: they'll call a Clojure function with a hash map built as EDN.
I guess I see pros/cons for each. While -X is native Clojure, it is not native bash or powershell, so now that side of the interface needs to do the mapping.
You seem to presume there's a real standard in command-line arguments already... which there really isn't...
True, but I'm talking at least the formatting
How do you pass a vector of integers to a command-line program?
Dead easy with -X
. Not easy with -M
, even with tools.cli
(which, remember, I maintain these days!).
Right, but then, a very common use case is to pass a string, and with -X you need to do this shenanigan: '"hi there"'
I think that's actually less common that you might imagine. And for a lot of "string" arguments, it's actually fine to pass a symbol and have the function decide to handle symbols as strings if necessary.
For tooling, I think -X makes more sense, but for distribution I feel -M might make more sense. Like if you want to use tools.deps to distribute your command line tools to users.
Having used this new stuff for a while I don't agree. I think you will change your mind in due course.
Right now, it's just "different" and you're resisting that change.
I personally prefer -X a lot. I do feel now though I keep having to open my deps.edn to remember if that particular alias I have to call with -M or -X though ๐, that's my biggest problem. But I was wondering for -X what it mean for CLIs, it still seems to me if you're puporsely making a CLI tool, you wouldn't want it to take EDN as input, that would throw of most users. Like say I made a grep in Clojure. And then you'd probably use -M with it.
Having maintained tools.cli
for a while and having written and maintained a lot of command-line stuff over the decades, I hate having to deal with strings and ad hoc parsing and inventing semantics for all that stuff over and over and over again...
So you'd go as far as saying that if someone were to use Clojure for a CLI, it might be a good idea they just take EDN as args?
Yes, very much so.
It's consistent. It's provides access to all of Clojure's data types. You can provide structured data as arguments really easily.
How easy is that? Is it just:
(defn -main
[& {:keys [:option1 :option2]}]
...)
Would this mimic exactly clj -X ?Alex was saying that they're looking at a way to pass Vars directly, so you don't have to then resolve symbols, which will make this even more powerful.
-main
can't mimic -X
-- it must take a sequence of strings because that's what Java does.
public static void main( String[] );
(or whatever nasty syntax Java requires)
So its not possible to make a CLI with Clojure that takes the same argument format as when launched with -X ? I mean, its got to be no since clj does it ๐
of course it is - that's what -X calls
I don't understand your question.
clj
is a shell script.
Like say I'm writing a Command Line Application. Lets say an implementation of ls
but in Clojure. And I'm going to distribute this to people as say a Graal Native Binary. Or as some UbrJar that I wrap a bash script around to launch it.
Now say I would like it the arguments to this Clojure based ls
would be similar to when called with -X
. I want it to be EDN. So I want to do: ls :verbose true
for example
I guess I'm asking... Will tools.deps expose the parser it uses for -X
so I can use it in place of tools.cli
or something like that?
I can't answer that, I'm not part of the Clojure core team.
The parser is clojure.edn/read-string
It's more than that: there's a clojure.run.exec
ns (currently) that strings all of that together including the function invocation.
Like, (read-string (reduce str args))
?
Found it!
That's very much an implementation detail right now -- it's some pretty sketchy code ๐
Well, either them releasing it separately, or someone replicating it in a lib would be neat. Then you'd be able to make a CLI and the UX would be the same either used with -X
or from the command line directly.
Btw for some CLIs I support an โopts argument where you can pass all args as one EDN map. I find this more ergonomic in some cases than passing separate args with their own paths into the map which then also need individual quoting
Since -X
specifies which function to call (either directly or via :exec-fn
in aliases) it is pretty much integral to how these arguments are handled.
I mean, basically, -X
is tied to deps.edn
etc so the question you're asking about GraalVM binaries doesn't really make sense.
More like (apply hash-map (map edn/read-string args))
Every string arg at CLI is parsed to edn separately
Ya, I mean that's not a bad idea, even if not exactly what tools.deps does for -X (I don't know if it is or not), but for my next CLI such a simple parsing of args could make sense, I might try it out
If you "fix" the values of :exec-fn
, :exec-args
and do not accept a function to invoke, then yes you could have a standardized command-line based on keywords and EDN values (and I think there's code in t.d.a directly that handles that -- the clj-exec
thing is just a copy/variant specifically to support CLI invocation via -X
).
Yeah, if the target audience is not clojurists, it might be better to use something like tools.cli
Right. If you want to support the CLI for JVM invocation via deps.edn you can also add an -X one apart from the Unix style
Itโs not an either/or
That's what I mean. -X
is starting a new convention for Clojure CLI applications in how their command line args are specified. But I would find it weird if a command line app takes different style of arguments based on how it is invoked. So if I'm to make one and use -X
, it be nice if other ways to start it I can also just pass it -X
style arguments.
Like taking an -X that then takes an EDN string?
Oh you mean, in the CLI, have a -main and another function for -X ?
-X :keyword some-edn :another-keyword more-edn
-X is a bit more complicated since it has to deal with defaults and overrides, but I think this approach is very nice because you are able to have the same api both for -X and -M invocations
I'd be quite happy if all Clojure tooling worked like -X
๐
Yes, both
And please donโt deprecate one over the other wink wink, there is more than one build tool
Ya, you can do that, extra work though. It be nice if you could just only have the -X one, and use it even for -main somehow
(beer's empty, battery's nearly dead, and I have to get up and go have my brain swabbed in the morning to see whether or not I have COVID, so I'll bid y'all adieu)
Ya, it quite neat
At first I was thinking, ok but users might find :option ... weird, but Sean is right, CLIs are already pretty inconsistent here, some take -o --option --o and all that
Also I guess in theory, the EDX parsing can even return a map of symbol to value, so you can probably even use this to parse -o
which I think is a valid symbol
haha yeah
so is -h
, -?
and --help
๐
I'm off as well, nice day everyone
I experimented a bit with it some time before the -X got released: https://vlaaad.github.io/tools-cli-in-10-lines-of-code
Nice lol. So the only annoying bit is if you need to take a string as the value, it has to be: app -option '"the long string"'
yeah
Oh neat, I will definitely bookmark and read this blog, thanks
Off to bed now though, gn
clojure.run.exec should currently be considered a changing implementation detail
(and it has been actively and regularly changing)
@didibus @seancorfield I do think that ultimately most of the code there should be cleaned up and included in clojure (like clojure.main). right now, clj has to sneak that into your classpath and I'd prefer for it to rely on getting via your clojure dep. But the current "api" is pretty dirty and is doing half of clj's parsing job. until that's cleaner, not ready to do that yet.
I would consider a same kind of API for babashka scripts when this is considered done. I'm not 100% convinced of the way "nested" args are passed in, since that results in a lot of command line quotes for basic things like strings. Passing in an entire config map feels more natural to me, but maybe the design choice was: we don't want to decide how nested args should be merged, we only support assoc-in?
How would you for example add an element to a vector inside an arg map? {:a {:foos [1 2]}}
and now you want to add 3
?
not something we were trying to support
the idea is that you're mostly assembling maps, not updating them
{:some-tool {:scan-paths ["src"]}}
now add "test" to :scan-paths
, this is maybe a better example
I await your ask clojure request :)
first one: https://ask.clojure.org/index.php/10059/x-args-add-things-to-vector
second one: https://ask.clojure.org/index.php/10060/x-args-pass-in-entire-map-merge-strategy