As discussed at Convenience in the Haskell ecosystem - #169 by Bodigrim, I’ve developed a small command-line tool to add build-depends. I tried a few examples and convinced myself that it’s robust enough, but would appreciate more user acceptance testing before I decide to clutter Hackage with one more package.
$ cabal-add --help
Usage: cabal-add (-f|--cabal-file FILE) [-c|--component ARG] DEP
Extend build-depends from the command line
Available options:
-f,--cabal-file FILE Cabal file to edit in place.
-c,--component ARG Package component to update (the main library, if
omitted). Wildcards such as 'exe', 'test' or 'bench'
are supported.
DEP Package(s) to add to build-depends section. Version
bounds can be provided as well, use quotes to escape
comparisons from your shell. E. g., 'foo < 0.2'.
As promised, I had a go at breaking this, since I got some time today. I managed to very nearly do it with a common stanza… but it appears that Cabal is fine with having two build-depends clauses in a target, so that doesn’t actually break anything. Very impressive work! (It even detects whether I’ve used leading or trailing commas, which is really nice.)
That being said, I dislike the restriction that it can only add packages at the beginning of the build-depends. Personally, I strongly prefer a style where the first line is reserved for base, and this tool messes it up. It would be nicer to, for instance, insert the new package in alphabetical order, or something similar.
I’m afraid this goes beyond the scope of cabal-add: it cannot reasonably guess all your stylistic preferences. For instance, “the first line is reserved for base” and “dependencies are sorted alphabetically” are conflicting requirements: does, say, aeson go above or below? Is sorting case-sensitive or not? One tool for one job; use cabal format, or cabal-fmt, or another, yet unwritten Cabal formatter.
It’s pretty much the same as with HLS: a code action for a missing module / function inserts an import somewhere, but one has to run a code formatter of their choice to bring everything up to their preferences.
Is it worth starting a discussion about upstreaming this into Cabal? That would let us start to do things like incorporating cabal add into error messages (as discussed in the Ergonomics of Cabal thread).
We’re not adding a new ad-hoc semi-parser into cabal proper – that’s the opposite direction of how that codebase needs to evolve. We need the exact-printer to add features to cabal directly. Instead, this should be a prime example of a tool that works with the new external commands system and can be promoted as a flagship example of such.
I think it does suggest one potential feature for external commands: The ability to hook into error messages so that cabal-add could suggest to use itself when the relevant error fires.
FWIW I personally do not intend to push on upstreaming into Cabal, but just to be clear: cabal-add builds atop Distribution.Fields and does not reimplement a parser.
Ideally one should go [Field Position] -> [Field ByteString] (see the snippet at Meta: Exact-printer Mega-issue · Issue #7544 · haskell/cabal · GitHub), then express adding a new dependency as [Field ByteString] -> [Field ByteString] transformation, then fold everything back. This is doable even in the current state of Cabal-syntax, but I took a shortcut sufficient for my purposes: instead of transforming [Field ByteString], I just figure out at which Position to insert build-depends and perform insertion on the original ByteString.
The most cumbersome part is to guess the desired style (newlines, indentation, leading / trailing comma). But this will not get any better with exact printer advances.
I published Haddocks for anyone interested to take a peek at the architecture: Distribution.Client.Add
Now this is very interesting: I hadn’t known that Cabal already provides Position information. This already gets you most of the way to an exact-printing parser — as you note, it’s not exactly the same thing, but it gives a great deal more information than I had thought was available.
@Bodigrim how hard do you think it’d be to make it a little more general than modifying exactly one type of field? It feels to me that the answer is: not hard. I’m pondering about developing it in a full-fledged exact parser/printer.
@artem adding stuff (dependencies, modules, etc.) is fairly easy: even if you fail to find an existing field and insert an item there, you can always just add a new field with a single item at the beginning of the section. Removing or modifying stuff is more complicated, especially because of conditions and common stanzas.