Posted on 2019-08-11 by Oleg Grenrus

As Cabal-3.0.0.0 is now released, I uploaded the cabal-fmt tool to Hackage. I have been using cabal-fmt for over a half year now for my own Haskell projects, and have been happy with this minimal, yet useful tool. cabal-fmt formats .cabal file preserving the field ordering and comments.

cabal-fmt is based on Distribution.Fields functionality. cabal-fmt is a thin addition on top. Same Distribution.Fields (and related Distribution.FieldsGrammar ) is also used in haskell-ci to parse and print .cabal -like files. I also use it in other tools to implement configuration files. In my opinion the lexical structure of .cabal files is more flexible and human-writing-friendly than YAML or JSON. YMMV. For example the header for this post is written as

title : "ANN: cabal-fmt" author : Oleg Grenrus

with quotes needed to disambiguate YAML. That's silly :) Cabal-like syntax would be

title : ANN : cabal-fmt author : Oleg Grenrus

However, enough bashing YAML.

cabal-fmt is opinionated tool, it does format few fields to my liking. Let us see how.

build-depends modules are formatted comma first (with cabal-version: 2.2 also with a leading comma), tabulated, sorted, and ^>= preferred when it can be used. For example:

build-depends: , base ^>=4.11.1.0 || ^>=4.12.0.0 , bytestring ^>=0.10.8.2 , Cabal ^>=3.0.0.0 , containers ^>=0.5.11.0 || ^>=0.6.0.1 , filepath ^>=1.4.2 , mtl ^>=2.2.2 , parsec ^>=3.1.13.0 , pretty ^>=1.1.3.6

or (for older cabal-version ):

build-depends: base >=4.3 && <4.14 , deepseq >=1.3.0.0 && <1.5 , time >=1.2.0.3 && <1.10

Single build-depends are formatted as a single line, like

build-depends: random >=1.0 && <1.2

#nub & sort

exposed-modules , other-modules , default-extensions and other-extensions are sorted and duplicates removed. For example.

exposed-modules: CabalFmt CabalFmt.Comments CabalFmt.Error CabalFmt.Fields CabalFmt.Fields.BuildDepends CabalFmt.Fields.Extensions CabalFmt.Fields.Modules CabalFmt.Fields.TestedWith CabalFmt.Monad CabalFmt.Options CabalFmt.Parser CabalFmt.Refactoring

Sometimes, you'll prefer some module to be the first, for cabal repl . In that case I would use two exposed-modules fields.

tested-with is one more field where I don't like the default formatting either. This field drives the job selection in haskell-ci . cabal-fmt combines version ranges for compilers, and prints GHC and GHCJS in upper case.

tested-with: GHC ==8.8.1 || ==8.6.5 || ==8.4.4 || ==8.2.2 || ==8.0.2 || ==7.10.3 GHCJS ==8.4

The line generated is long, especially for packages supporting a lot of GHC versions. Something I don't have a clear preference yet how to handle.

#Extra: expand exposed-modules and other-modules

The recent addition is an ability to (re)write field contents, while formatting. There's an old, ongoing discussion of allowing wildcard specification of exposed-modules in .cabal format. I'm against that change. Instead, rather cabal-fmt (or an imaginary IDE), would regenerate parts of .cabal file given some commands.

cabal-fmt: expand <directory> is a one (the only at the moment) such command.

cabal-fmt will look into directory for files, turn filenames into module names and append to the contents of exposed-modules . As the field is then nubbed and sorted, expanding is idempotent. For example cabal-fmt itself has:

-- cabal-fmt: expand src -- exposed-modules: CabalFmt ...

The functionality is simple. There is no removal of other-modules or main-is . I think that using different directory for these is good enough workaround, and may make things clearer: directory for public modules and a directory for private ones.

And that's all that cabal-fmt does. Formatting of other fields comes directly from Cabal . I have few ideas, what else can be done, e.g.

cabal-fmt: expand for extra-source-files

for formatting of reexported-modules

sorting of fields, e.g. putting type and default-language to the top of the component stanzas.

But these don't bother me enough yet, so they are not there.