patch: pretty printing for Cabal
Jürgen Nicklisch-Franken
jnf at arcor.de
Mon Oct 11 18:30:23 EDT 2010
I did send this mail a week ago or so to the haskell-libraries mailing
list, but this was probably the wrong list.
I have in the meantime tested with all cabbal files on haddock, and have
fixed some issues.
This patch adds a pretty printer for GenericPackageDescription. It fixes
ticket http://hackage.haskell.org/trac/hackage/ticket/647.
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
symmetric.
I tested with the code from Appendix 1 and all the cabal files on
hackage. The test succeds 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 ->
binfo{ghcSharedOptions=val})
+ ghcSharedOptions (\val binfo ->
binfo{ghcSharedOptions=val})
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 =
(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
given:
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. This is connected to the CondTree structure. When I write a Cabl file
like:
...
library
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:
...
library
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.
6. If free text starts with a line with a dot, the dot must be in a new
line.
7. Version tags have to be printed, which was not the case before
(why?).
8. VersionRangeParens needs to be printed.
9. Compiler options have to be put in a canonical order after parsing,
which is easy as CompilerFlavors do derive Ord.
Kind regards
Jürgen Nicklisch-Franken
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 " ++
cabalFilePath
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: pp2.patch
Type: text/x-patch
Size: 109611 bytes
Desc: not available
Url : http://www.haskell.org/pipermail/cabal-devel/attachments/20101011/d6444a64/pp2-0001.bin
More information about the cabal-devel
mailing list