Cabal pretty printer, Bug in Prelude, Cabal Test Suite Syntax

Jürgen Nicklisch-Franken jnf at
Mon Oct 4 18:16:34 EDT 2010

This patch adds a pretty printer for GenericPackageDescription. I added
a new module
Distribution.PackageDescription.PrettyPrint which exports
writeGenericPackageDescription and showGenericPackageDescription.
I use the field printers from Parse.hs, so that I had to add export
statements for them in Distribution.PackageDescription.Parse.
I decided to not use a class based approach because: 1. their is already
the Text class, which has a parse I didn't want to write, 
and 2. and more important, I didn't want to use -XFlexibleInstances
because I guess cabal should be portable.

I have not removed the old printer code, although this may be a good
idea (maybe mark it as deprecated first). Then
it would make sense as well to rename writeGenericPackageDescription to
writePackageDescription and 
showGenericPackageDescription to showPackageDescription, to be

I tested with the  code from Appendix 1 and the local cabal files of all
my installed packages. The test succeeds when writing and
reading a package description results in an equal description. This is
more rigorous then required, cause 
different GenericPackageDescr.. can have the same semantic, so for
example the order of exe definitions is not relevant.
But for our needs (GUI editor of cabal files for leksah), e.g.
preserving of order is important and it will  help to test the 
parser and printer to make this approach work.
Furthermore I want to print out the cabal file as short as possible,
e.g. not printing all empty fields like the old printer did.

Testing the code I found the following problems:

1. Found up an obvious "age old" bug:
hunk ./Distribution/PackageDescription/Parse.hs 359
-           ghcProfOptions        (\val binfo ->
+           ghcSharedOptions        (\val binfo ->

2. The order of elements are reversed by the parser. E.g executables: 
            (repos, flags, lib, exes, tests) <- getBody
            return (repos, flags, lib, exes ++ [(exename, flds)], tests)
since the rest is parsed in the line before, the element needs to be
prepended. so I changed to:
            (repos, flags, lib, exes, tests) <- getBody
            return (repos, flags, lib, (exename, flds): exes, tests)
The same applies to test fields.

The opposite order problem applies to storeXFields.., which is called in
the order of processing, 
so I changed it to 
storeXFieldsPD (f@('x':'-':_),val) pkg = Just pkg{ customFieldsPD = 
pkg) ++ [(f,val)]}

3. The next problem is that we print nothing for empty fields , which
works ok, except for 
compiler opts, where empty opts differ from not given opts, so I added
this line to optsField
in ParseUtils, which causes empty opts to be parsed, as if no opts are
        update f opts [] = [(f,opts)]

4. The next problem is that we loose final newlines with showFreeText.
Astonishing enough the culpid is lines 
from the List module , which evals lines "a\n" to ["a"].  By the way
even the claim that "'unlines' is an inverse 
operation to 'lines'" from the List module  is not true, as unlines
(lines "a") -> "a\n" (see Appendix 2). So I
use my own unlines' now!.
5. The last problem is connected to the CondTree structure. When I write
a Cabal file like:
    extensions:         ForeignFunctionInterface
    if flag(use_xft)
        build-depends: X11-xft >= 0.2, utf8-string
        extensions: ForeignFunctionInterface
the extension ForeignFunctionInterface in the conditional branch is
superflous, as it is specified in any case,
so the printer can simplify it to:      
    extensions:         ForeignFunctionInterface
    if flag(use_xft)
        build-depends: X11-xft >= 0.2, utf8-string
However the parse result for the two cabal files will differ. Now I gave
up here, and  added a variable 
simplifiedPrinting which disables the printer smartness for now.
However, I hope someone can make 
the parser smarter in this respect. 
Finally a question, is their a way to retreive the cabal files of all
packages on Haskell? This would
make my tests more extensive.


PS. I have added a remark about the new test suite syntax below. 

Appendix1: Test code

main = do
    allCabalFiles <- ...
    res <- mapM test allCabalFiles
    if and res
        then putStrLn "all test succeeded"
        else putStrLn $ "tests failed: " ++ show (length (filter (==
False) res)) ++
                        " of: " ++ show (length res)

test :: FilePath -> IO Bool
test cabalFilePath = catch (do
    gpd <- readPackageDescription silent cabalFilePath
    writeGenericPackageDescription tempPath  gpd
    gpd2 <- readPackageDescription silent tempPath
    if gpd == gpd2
        then do
            putStr "."
            return True
        else do
            putStrLn $ "failed for " ++ cabalFilePath
            return False)
            (\e -> do
                putStrLn $ "runtime error " ++ show e ++ " for " ++
                return False)

Appendix2: lines and unlines revised:

-- | 'unlines' is an inverse operation to 'lines'.
-- It joins lines, after appending a terminating newline to each.
unlines'             :: [String] -> String
unlines' []          = []
unlines' [x]         = x
unlines' (x:xs)      = x ++ ('\n' : unlines' xs)

-- | 'lines' breaks a string up into a list of strings at newline
-- characters.  The resulting strings do not contain newlines.
lines'                   :: String -> [String]
lines' []                =  [""]
lines' s                 =  let (l, s') = break (== '\n') s
                            in  l : case s' of
                                        []    -> []
                                        (_:s'') -> lines' s''

Appendix 3:
 I find the test suite syntax problematic, for an exe I have to remeber
the combination:
test-suite foo
    type: exitcode-stdio-1.0
    main-is: main.hs
, and for a module:
test-suite bar
    type: library-1.0
    test-module: Bar
So its the combination of exitcode-stdio- and main-is or library- and
test-module which does the trick.
Wouldn't it be easier to write something like:    
test-suite foo
    test-version: 1.0
    main-is: main.hs
, and for a module:
test-suite bar
    test-version: 1.0
    test-module: Bar

-------------- next part --------------
A non-text attachment was scrubbed...
Name: pp.patch
Type: text/x-patch
Size: 103958 bytes
Desc: not available
Url :

More information about the Libraries mailing list