Docopt
docopt.org
docopt.org
In Python, I was always skeptical of click which later took the attention though I think using StructOpt [0] in Rust has cured me of that to a degree though I much prefer field attributes over stacking function attributes per parameter (or Typer's form of parameter attributes). I'd probably still stick with argparse anyways because most of my Python scripts are built without dependencies (weird build systems or being one step above bash scripts).
"Docopt, and many tools like it, are cool in how they work, but very few of these tools deal with nesting of commands and composability in a way like Click. To the best of the developer’s knowledge, Click is the first Python library that aims to create a level of composability of applications that goes beyond what the system itself supports.
Docopt, for instance, acts by parsing your help pages and then parsing according to those rules. The side effect of this is that docopt is quite rigid in how it handles the command line interface. The upside of docopt is that it gives you strong control over your help page; the downside is that due to this it cannot rewrap your output for the current terminal width, and it makes translations hard. On top of that, docopt is restricted to basic parsing. It does not handle argument dispatching and callback invocation or types. This means there is a lot of code that needs to be written in addition to the basic help page to handle the parsing results.
Most of all, however, it makes composability hard. While docopt does support dispatching to subcommands, it, for instance, does not directly support any kind of automatic subcommand enumeration based on what’s available or it does not enforce subcommands to work in a consistent way."
https://click.palletsprojects.com/en/8.0.x/why/#why-not-doco...
I see this argument a lot as if it's self evident. Using X makes Y hard. Ok, what if you don't need Y?
Then knock yourself out using X. It's an argument about the purpose of the alternative and what it is intended to be useful for. Assuming that it is valid, it tells you both for what purposes you might want to use the alternative and for what purposes you might not want to bother.
Z improves on X for Y is fairly explicitly not an argument that Z is superior to X for all purposes.
My criteria are: well supported, composable, sub commands, reusable options, sane stack traces.
My current pattern: Cleo plus pydantic. I can create a module of commonly used flags and options that I can share among commands. Commands are first class objects. I can use them as entry points. I can readily parse the options into a static structure with defaults read from env or a file using pydantic. This is feed to runner/API functions, with minimal logic in the command class itself. The one thing that is lacking is I wish I could define pydantic models that double as commands. That is a pretty specific nit to pick.
Overall I love it. Poetry runs on it so I think it's got a long support life. It's beautiful. Easy to use. Just the right amount of opinionated for me. Supports auto complete. Built in commonly used flags like version, quiet, and verbose levels.
I find click and any of the decorator based systems super obtuse and difficult to compose. They add tons of noise to stack traces.
Docopt is way too string based. I like static types.
Argparse is... surprisingly not bad. The main drawback is reuse is not very easy and it's hard to write composable structures.
Docopt is the sort of thing that seems like it would work wonderfully but you'd be surprised how quickly you end up running into a situation that is ambiguous or just impossible to represent using docopt. On the other hand, I never really found that argparse-specified interfaces were significantly more verbose to set up.
it's a shame that the last i checked, there wasn't really a formal grammar for it and so doing a lot of the modern conveniences i like[0] is either hard or impossible, but the idea's still charming.
[0]: i want things like "ensure this is a path and it's a file and it exists and it's nonzero size and give it back to me as a `pathlib.Path` please" or "this argument must be an integer between 10 and 5002", etc.
This is theoretically even possible for C++ with some kind of compile-time reflection API, but the way the C preprocessor works probably makes it hard to preserve structured comments for automatic documentation extraction. It was quite easy to do in Nim.
The Rust Docopt implementation¹ was deprecated this year, which is probably good because clap v3² is so awesome. In a project of mine (tealdeer), I noticed that docopt.rs was responsible for the huge majority of CPU instructions when running the binary: https://github.com/dbrgn/tealdeer/issues/106#issuecomment-59... I then switched³ to clap and shaved off almost a megabyte from the release binary⁴. Performance improved as well, time required for rendering a tldr page went down from ~15.9 ms to ~12.4 ms⁵. With the migration, we also managed to reduce a lot of custom validation logic and move this logic into the derive macro attributes.
¹ https://github.com/docopt/docopt.rs
² https://github.com/clap-rs/clap
³ https://github.com/dbrgn/tealdeer/pull/108
⁴ https://github.com/dbrgn/tealdeer/pull/108#issuecomment-9448...
⁵ https://github.com/dbrgn/tealdeer/pull/108#issuecomment-9448...
Also, I don't necessarily blame clap for being heavyweight. Argument parsing is hard, and clap does it well - with nice help output!
I've also accepted the cost of proc macros (at least while syn is not in the standard library). For me, the derived style where I get populated structures out is well worth it.
Of course, any improvement is welcome. But clap works great for me, and is a dependency I happily pull in.
While the feature set justifies some of the cost, I don't want to be complacent about it and excuse any excess as just coming from the feature set. I think we have a lot of room for improvement.
The problem is, shoe-horning it into your project can be a huge burden. It's why I left it in my toolbox as more of a toy.