l3nz / cli-matic Goto Github PK
View Code? Open in Web Editor NEWCompact, hands-free [sub]command line parsing library for Clojure.
License: Eclipse Public License 2.0
Compact, hands-free [sub]command line parsing library for Clojure.
License: Eclipse Public License 2.0
See https://github.com/l3nz/cli-matic/blob/master/src/cli_matic/platform.cljs
Slurping
Environment
Return values
See https://github.com/bhb/expound
Maybe we should do detection and then use it only if available.
I like the idea, though I don't know how it should work:
Exception in thread "main" clojure.lang.ExceptionInfo: Call to #'cli-matic.help-gen/generate-help-possible-mistypes did not conform to spec:
core.cljc:364
-- Spec failed --------------------
Return value
[... ... ... [" vendite"]]
^^^^^^^^^^^^
should satisfy
string?
But it's actually a vector of strings OR vectors of strings. So it works but spec is wrong.
We should be able to validate all options to a command using spec, with a better error message than the default one.
Have no idea :-)
Thanks for this library @l3nz, it's really quite fantastic how easy and concise it makes all this stuff, I added an improved CLI interface to cljdoc using it: cljdoc/cljdoc@1ce56b8
For some subcommands I'd like to specify longer descriptions, spanning a couple of lines. I was wondering if there's any tooling or plans around this?
I could construct a :description
string with linebreaks myself but I think cli-matic
could help with the following aspects:
NAME:
FWIW there could also be a new key :usage
for longer text, reserving :description
for a one-line overview.
Unrelated feedback: it took me a while to spot the :default :present
variant in the README when trying to figure out how to make options mandatory. Maybe introducing another key :required?
or so would make this easier.
I see a few boolean options:
--flag
(true) or --no-flag
(false). The first one could be shortened to -f
--flag=1
. Would expect to recognise "Y", "Yes", "On", "T", "True" and "1" as truthy. Falsey would be "N", "No", "Off", "F", "False" and "0". Other values would raise an error.Why don't we use the same mechanism we use for options to pull positional argument?
Like - instead of:
{:option "pa" :short "a" :as "Parameter A" :type :int :default 0}
we could have
{:option "pa" :short 0 :as "Parameter A" :type :int :default 0}
This would use the first free parameter, store it in "pa" and check it's an int.
Pulled positional arguments remain anyway available in the arguments vector.
Positional arguments are only valid on sub-commands and must appear on the help line.
I think we should have an example/tutorial of how to make a simple Cli app that runs within GraalVM. I am sure it would have a decent success and in the end it's what everybody would love - simple CLI commands that are small, portable and start up in no time.
It would be worthwhile too add some tips section for better startup times, e.g.
time clj -J-Dclojure.spec.skip-macros=true -m recap sv
real 0m2.587s - user 0m6.997 - sys 0m0.332s
versus default:
time clj -J-Dclojure.spec.skip-macros=false -m recap sv
real 0m3.141s -user 0m8.707s -sys 0m0.391s
Writing it here so I don't forget ๐ธ
Can just specify it as empty, but it can take a few minutes to figure out what the problem is, especially for those less familiar with spec.
See https://github.com/EricGebhart/clj-cli-ext - not a bad idea, actually.
We could have an optional multi-line description added to the utility and to each subcommand, to be displayed in the help screens.
See https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46 part 6
Should we use them?
Should we offer an ANSI "spinner"?
We could have multiple aliased for each subcommand, like e.g.
COMMANDS:
add, a adds
sub, s subtracts
So writing
mycmd add --a 1 --b 2
and
mycmd a --a 1 --b 2
are the very same thing.
Caused by: java.lang.IllegalStateException: Can't change/establish root binding of: explain-out with set
at clojure.lang.Var.set(Var.java:223)
at cli_matic.optionals$orchestra_instrument.invokeStatic(optionals.clj:70)
at cli_matic.optionals$orchestra_instrument.invoke(optionals.clj:56)
at cli_matic.presets__init.load(Unknown Source)
at cli_matic.presets__init.(Unknown Source)
cli-matic.core
calls orchestra.spec.test/instrument
when being loaded. This instruments all spec'ed functions and not just cli-matic's own API. In my case this brought up surprising issues where I didn't expect them.
Would you consider a PR where only cli-matic
's own functions are instrumented?
Sometimes need float values
At the moment, they are not printed out correctly.
These should not be there:
WARNING: Use of undeclared Var cli-matic.core/Throwable at line 74 cli_matic/core.cljc
WARNING: Use of undeclared Var cli-matic.core/Throwable at line 233 cli_matic/core.cljc
WARNING: Use of undeclared Var cli-matic.core/IllegalAccessException at line 422 cli_matic/core.cljc
WARNING: Use of undeclared Var cli-matic.core/IllegalAccessException at line 422 cli_matic/core.cljc
WARNING: Use of undeclared Var cli-matic.core/IllegalAccessException at line 454 cli_matic/core.cljc
WARNING: Use of undeclared Var cli-matic.core/IllegalAccessException at line 454 cli_matic/core.cljc
WARNING: Use of undeclared Var cli-matic.core/Throwable at line 524 cli_matic/core.cljc
We want to have shorter and optional names for both options and subcommands.
The names should appear in the online help and of course be used at all.
Libs we depend on are supported.... what is to be done?
https://sunng.info/blog/writing-library-for-both-clojure-and-clojurescript.html
Not sure whether there are any changes that affect us, but it's better to use the current version in any case.
It would make sense to have separate namespaces for:
This way we could have a smaller core
.
It is just refactoring and tests.
I'd like to have them out. Cheshire is easy, but Orchestra? how do I use it in dev only?
It seems like we've got most of the data. It would be nice to generate (and install?) a man or um page.
Wouldn't be optional, as it's included with Clojure. I'm working on it.
I hacked something together that looks like
(let
[bf (java.io.BufferedReader. *in*)]
(if
(.ready bf)
(let
[conf (slurp *in*) vargs (into [] args)]
(run-cmd (conj vargs "--stdin" conf) STDIN_INPUT))
(run-cmd args FILE_INPUT))))
where STDIN_INPUT
(def
STDIN_INPUT
{:app
{:command "nparser",
:description "A command-line configuration generator",
:version "0.1.2"},
:global-opts [],
:commands
[{:command "to-json",
:description "Generate JSON from a config",
:opts
[{:option "stdin",
:as "Config input file",
:type :string,
:default :present}
{:option "grammar",
:as "Grammar file",
:type :string,
:default :present}],
:runs gen-json}
{:command "to-config",
:description "Generate config from an input file",
:opts
[{:option "stdin",
:as "JSON input file",
:type :string,
:default :present}],
:runs gen-config}]})
not very idiomatic, but would be great to incorporate this into cli-matic..
The primary use-case is now that we have GRAALVM we can native-compile CLJ code into binary artifacts to build command-line apps..
Why not having
--data '{"a": "b"}'
that returns {:data {"a" "b"}}
as a parameter, as long as you have Cheshire on the classpath?
And why you are at it, why not having
--data @somedata.json
That returns the contents of somedata.json
as a parsed structure?
"SUB: globals" [{:option "base", :as "The number base for output", :type :int, :default 10}]
"SUB: def" [{:option "a1", :short "a", :env "AA", :as "First addendum", :type :int, :default 0} {:option "a2", :short "b", :as "Second addendum", :type :int, :default 0}]
"Spec for subcmd " #object[clojure.core$constantly$fn__5394 0x79c3f01f "clojure.core$constantly$fn__5394@79c3f01f"]
"Validating Specs" [{:option "base", :as "The number base for output", :type :int, :default 10}] {:base 16, :a1 1, :a2 254, :_arguments []}
"Validating Specs" [{:option "a1", :short "a", :env "AA", :as "First addendum", :type :int, :default 0} {:option "a2", :short "b", :as "Second addendum", :type :int, :default 0}] {:base 16, :a1 1, :a2 254, :_arguments []}
"Failing global" nil
"Failing local" nil
"Failing total" nil
We don't want that.
If the command run is long-running, it would be nice to add a shutdown hook by calling :on-shutdown
:
(.addShutdownHook
(Runtime/getRuntime)
(Thread.
(fn []
(log "Bye!")
)))
We should be able to validate any option using spec, outputting a better error message.
If an option is tagged as ':env ABCD'; it will
This is because we call rewrite-opts
with three parameters in get-options-summary
.
Fix is easy, but how can we know it will not happen again?
I do a lot of work with Kubernetes stuff, and YAML is the preferred format.
I'm working on this in my fork; I'll open a pull request when it's ready.
It would be nice to detect mis-spellings and suggest alternatives.
Something similar happens in clj-sub-command
.
See https://crossclj.info/fun/clj-sub-command.core/candidates.html
For example what git does is:
$ git dd amm/
git: 'dd' is not a git command. See 'git --help'.
The most similar command is
add
Sometimes you have a given set of string values that are the only available options.
We want to:
OPTIONS:
-k, --kind S* Kind of mailing: (:Q :Q-REN :Q-UPG)
If we return anything from our main method that is not a number, it crashes!
JVM Exception: #error {
:cause clojure.lang.PersistentArrayMap cannot be cast to java.lang.Number
:via
[{:type java.lang.ClassCastException
:message clojure.lang.PersistentArrayMap cannot be cast to java.lang.Number
:at [clojure.lang.Numbers isZero Numbers.java 92]}]
:trace
[[clojure.lang.Numbers isZero Numbers.java 92]
[cli_matic.core$invoke_subcmd invokeStatic core.clj 412]
[cli_matic.core$invoke_subcmd invoke core.clj 405]
[cli_matic.core$run_cmd_STAR_ invokeStatic core.clj 442]
[cli_matic.core$run_cmd_STAR_ invoke core.clj 428]
[cli_matic.core$run_cmd invokeStatic core.clj 447]
[cli_matic.core$run_cmd invoke core.clj 444]
We should just ignore them and return zero.
I guess we should:
Or maybe use a script in lein to do the cleaning :-)
We have three cases:
:default
:default 3
:default :present
In the third case, we will abort with an error message if item is not present in after-parse option map.
Missing global options do not cause errors if you call a subcommand only for getting its help page.
If we have:
OPTIONS:
-a, --a1 N 0 First addendum [$AA]
-b, --a2 N 0 Second addendum
-?, --help
Why shouldn't we have:
$ toycalc add --interactive
First addendum (a1, def 0): 10
Second addendum (a2, def 0): 20
You are about to add 10 and 20 together. Are you sure you want to continue? Y
30
We could have --option-a
and --opt-a
as the same option.
If an option is tagged as :multiple true
, then it is actually a vector of all parameter invocations.
cmd sub --a 1 --a 2 --a 3
where :a is tagged as :multiple, should get you
{:a [1 2 3]}
In the order they are passed.
We should remove:
WARNING: Use of undeclared Var cli-matic.presets/slurp at line 24 cli_matic/presets.cljc
We should have an option to slurp a whole file at once given a filename, and return it as big string, vector of strings or decoded-json (with optional deps on cheshire).
This feature request adds optional functions that override the way cli-matic prints help, affording complete byte-by-byte control over the help text.
Rationale: Our internal CLI tooling needs to adhere to certain policies regarding help and usability for developers. Certain info such as Git build, version string, and grouping of commands needs to be printed in the prescribed format. This feature request would realize the ability to completely control the presentation of generated help text to satisfy those policies.
An example of generating global help:
(def banner
(str "Welcome to CLI-ABC123, Git "
build-info/git-hash))
(defn commands-segment
[cfg]
(str "Commands:\n"
(clojure.string/join
"\n"
(map
#(format " %-10s %s"
(get % :command)
(get % :description))
(cfg :commands)))))
(defn my-global-help
[cfg]
(clojure.string/join "\n" [banner (commands-segment cfg) ...]))
and using it as the global help text generator:
(run-cmd args {:app {:global-help my-global-help
...}})
We should get a better message out.
Exception in thread "main" java.lang.AssertionError: Assert failed: (distinct?* (remove nil? (map :short-opt %)))
at clojure.tools.cli$compile_option_specs.invokeStatic(cli.clj:265)
at clojure.tools.cli$compile_option_specs.invoke(cli.clj:265)
at clojure.tools.cli$parse_opts.invokeStatic(cli.clj:593)
at clojure.tools.cli$parse_opts.doInvoke(cli.clj:471)
at clojure.lang.RestFn.invoke(RestFn.java:425)
at cli_matic.core$parse_cmds.invokeStatic(core.clj:417)
at cli_matic.core$parse_cmds.invoke(core.clj:372)
at clojure.lang.AFn.applyToHelper(AFn.java:156)
at clojure.lang.AFn.applyTo(AFn.java:144)
at orchestra.spec.test$spec_checking_fn$fn__727.doInvoke(test.clj:123)
at clojure.lang.RestFn.invoke(RestFn.java:421)
at cli_matic.core$run_cmd_STAR_.invokeStatic(core.clj:521)
at cli_matic.core$run_cmd_STAR_.invoke(core.clj:519)
at cli_matic.core$run_cmd.invokeStatic(core.clj:543)
at cli_matic.core$run_cmd.invoke(core.clj:535)
I would like the option of executing a sub-command that does not take any options. the use-case might be just to query information.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.