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 Grenruswith quotes needed to disambiguate YAML. That's silly :) Cabal-like syntax would be
title: ANN: cabal-fmt
author: Oleg GrenrusHowever, 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.6or (for older cabal-version):
build-depends:
base >=4.3 && <4.14
, deepseq >=1.3.0.0 && <1.5
, time >=1.2.0.3 && <1.10Single build-depends are formatted as a single line, like
build-depends: random >=1.0 && <1.2exposed-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.RefactoringSometimes, 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.4The 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-filesreexported-modulestype 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.