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
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.
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
reexported-modules
type
and default-language
to the top of the component stanzas.But these don't bother me enough yet, so they are not there.
The implicit goal of a project is to iterate independently of cabal-install
, Find out what could be useful, and how it can be done, and later merge into cabal-install
's cabal format
functionality. Yet then providing enough configuration knobs to not be so opinionated.