I’m very pleased to announce the next major version of optparse-applicative. I was meaning to release this last month, but I had a baby last month instead, which has taken up quite a bit of my time. Still, it’s been a pleasure to put this release together, and I hope people enjoy the results. In this post, I’ll mention a few of the new features and fixes.
If you’re not yet using the completions scripts for optparse it’s well worth giving it a try now. I’ve fixed quite a lot of bugs, and added a few things which almost completely negate the need for writing custom shell scripts for completions for optparse programs at all.
The first few bugs related to how bash compgen actions were called, and these bugs got pretty ugly at times. For example, if one tried to complete a term which was partially quoted, such as
> example 'Documents/<tab>
then a particularly awful error message regarding syntax errors would be emitted to stderr by the called bash process as it tried to find the close to the quotion.
The solution was to dequote any quoted or partially quoted string, then hard quote it again before passing it to the
compgen builtin in bash. This also had the added benefit of fixing one of my personal annoyances for our completion system – premature expansion of
~ in a path completion: where
This is now fixed, and the
~ does not get rewritten on the command line by a bash completion.
Beyond bash though, we now support much richer completions for zsh and fish shells. What’s particularly nice is that these completions also now can include the help descriptions for the items being completed.
Here is an example screenshot when using fish shell using only the uncustomised optparse generated completion script
A similar level of detail is available with zsh completions as well.
One of my focuses for this release was to ensure that optparse gives a great error message for every type of failure we emit (this has actually been a focus of mine for some time); and there was one which was oddly handled by default.
option has had a builder,
noArgError which one can use to specify the error message shown to a user if they do not provide an argument for the respective option. We have used this for the provided
helper parser for instance such that it always shows the help text. The default for this was a conspicuously unhelpful though, being
ErrorMsg "". Now instead of not receiving an error message, one should see
The option `--bash-completion-script` expects an argument.
for example. This did require a change to the internal types to store the name of the option, but I managed to keep the builder api the same, so one can still use the old builder to change the error message if they desire.
Combined with other error message improvements mentioned in my previous post I don’t think one will ever receive an unhelpful or absent error message from an
optparse-applicative parser going forwards, which I think is a great improvement.
When an option, flag, or subcommand can’t be parsed, or no match for it could be found, we now can emit some suggestions of terms which the user may have meant. This is based off the edit distance between the entered argument and the possible items the parser knows about. Details regarding the implementation, are available in my last post
The end result looks a lot like git’s command completion, and is quite nice I think
Invalid argument `installl' Did you mean this? install
Invalid option `-x' Did you mean one of these? -v -h
Optparse has had a no intersperse option builder for a good while (since 0.7), but its description and behaviour didn’t actually match. When a parser was called with
noInterperse applied, it would behave the same as if the user had typed
-- at the start of the command: that is, no options would be parsed. In other parsing libraries with no intersperse options on (and old unix command line guidelines), one has to place all options before any arguments. Once a single positional argument is parsed, all remaining options are treated as positional as well. We now behave in the same way.
Fixing this issue inspired me to also alleviate a pain point which my colleagues and I had been having regarding mixing arguments and wrapping commands. At Ambiata, we created a build tool called mafia which acts as an opinionated wrapper around
cabal, providing a
nix like package cache, safe sandboxing, and submodule handling amongst other things.
When using it though, to pass an option to
cabal underneath, the user would need to first use
-- to tell optparse to treat all remaining options as arguments, and the argument list would be sent to
cabal. Now, with the
forwardOptions builder attached when creating the
parserInfo structures, any unrecognised options to
mafia can be passed through as is. I believe this will actually help in quite a few places where people are using mixed environments (such as a C command line parser).
Text and Bytestring are pervasive in the Haskell ecosystem, so it was annoying that
optparse-applicative didn’t support them trivially out of the box.
The builders we have provided for some time are
str :: ReadM String auto :: Read a => ReadM a
which I have found quite funny, in that
auto is good for simple types apart from string like ones (which require quoting when used with
str requires an
fmap pack to make it work with
Text. From 0.14 onwards these will be
str :: IsString a => ReadM a auto :: Read a => ReadM a
which means that the
str reader will be able to deal with
ByteString, as well as their lazy equivalents without requiring the user to jump through any hoops. Other builders and helpers such as
strArgument have also been updated to be polymorphic over
IsString in the same manner. This is a mildly breaking change, but I think it’s worthwhile.
If one has been using
str for the purposes of type inference (as we were in the
optparse-applicative test suite) then type signatures may now be required, or the use of
readerAsk, which is still fixed to a single type. As there’s not a lot of purpose in requesting a value and not using it, I don’t think many users will see any update issues from this change at all.
The final big thing I have done before release is to remove the module reëxports from the
Options.Applicative module and explicitly export a public API.
This should make things more robust going forwards, and let people know exactly what is a supported and useful part of the API, instead of having them have to infer if something is actually internal to the library.
In doing this I have actually removed a few function exports which were internal or deprecated, and added the
ParserHelp type (which is useful) to the main module exports.
Again, this is a minorly breaking change, it is however quite unlikely that it will cause issues for most users, and indeed, it should make it easier to maintain a rock solid API going forwards.
Thanks to Paolo Capriotti for trusting me to maintain his library, and to those who have submitted patches and bug reports and given advice. Herbert Valerio Riedel was especially helpful with keeping things compatible across past and (with any luck) future versions of GHC.