git.iko.soy/typeable/cabal2nix
on master help sign in
Cabal2Nix.hs: simplified the command line parser logic

I have examined two libraries that are supposed to manage command line flags, cmdlib and cmdargs, and both of them are unsatisfactory. The cmdargs library seems to work well, but it's wildly impure, i.e. the parser will function cannot be re-run in ghci! Also, it automatically adds flags to the command line (such as --verbose) and stores their state in some magic, global variable. It may work well in practice, but as a matter of principle I just don't want that kind of thing in my Haskell programs. The other library, cmdlib, seems to be better in that regard, but it's API is somewhat incomplete. It can configure wildly complex interfaces (such as the one from darcs), but *simple* interfaces don't seem to work well, i.e. there is no way to write a program that doesn't use sub-commands. Furthermore, I managed to crash the code three or four or times, which doesn't inspire trust either. So for the time being, we seem to be stuck with getopts.

commit18fb5f8510ea12e575c1202a81e4c4c569b513cd authorPeter Simons <simons@cryp.to> dateMon 22 Aug 2011, 21:28 (15 years ago) parent3b6f64f
1 file changed+60−46
src/Cabal2Nix.hs+60−46
src/Cabal2Nix.hs+60−46
@@ -7,63 +7,77 @@ import Distribution.NixOS.Derivation.Meta
77
88import Control.Exception ( bracket )
99import Control.Monad ( when )
10+import Data.List ( nub, sort )
1011import Distribution.PackageDescription ( package, packageDescription )
1112import Distribution.PackageDescription.Parse ( parsePackageDescription, ParseResult(..) )
1213import Distribution.Text ( disp )
1314import System.Console.GetOpt ( OptDescr(..), ArgDescr(..), ArgOrder(..), usageInfo, getOpt )
1415import System.Environment ( getArgs )
15−import System.Exit ( exitFailure )
16+import System.Exit ( exitFailure, exitSuccess )
1617import System.IO ( hPutStrLn, hFlush, stdout, stderr )
1718
18−data CliOption = PrintHelp | SHA256 String | Maintainer String | Platform String | NoHaddock
19− deriving (Eq)
19+data Configuration = Configuration
20+ { optPrintHelp :: Bool
21+ , optPrintVersion :: Bool
22+ , optSha256 :: String
23+ , optMaintainer :: [String]
24+ , optPlatform :: [String]
25+ , optHaddock :: Bool
26+ }
27+ deriving (Show)
2028
21−main :: IO ()
22−main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
23− let options :: [OptDescr CliOption]
24− options =
25− [ Option ['h'] ["help"] (NoArg PrintHelp) "show this help text"
26− , Option [] ["sha256"] (ReqArg SHA256 "HASH") "sha256 hash of source tarball"
27− , Option ['m'] ["maintainer"] (ReqArg Maintainer "MAINTAINER") "maintainer of this package (may be specified multiple times)"
28− , Option ['p'] ["platform"] (ReqArg Platform "PLATFORM") "supported build platforms (may be specified multiple times)"
29− , Option [] ["no-haddock"] (NoArg NoHaddock) "don't run Haddock when building this package"
30− ]
29+defaultConfiguration :: Configuration
30+defaultConfiguration = Configuration
31+ { optPrintHelp = False
32+ , optPrintVersion = False
33+ , optSha256 = ""
34+ , optMaintainer = []
35+ , optPlatform = []
36+ , optHaddock = True
37+ }
38+
39+options :: [OptDescr (Configuration -> Configuration)]
40+options =
41+ [ Option ['h'] ["help"] (NoArg (\o -> o { optPrintHelp = True })) "show this help text"
42+ , Option [] ["sha256"] (ReqArg (\x o -> o { optSha256 = x }) "HASH") "sha256 hash of source tarball"
43+ , Option ['m'] ["maintainer"] (ReqArg (\x o -> o { optMaintainer = x : optMaintainer o }) "MAINTAINER") "maintainer of this package (may be specified multiple times)"
44+ , Option ['p'] ["platform"] (ReqArg (\x o -> o { optPlatform = x : optPlatform o }) "PLATFORM") "supported build platforms (may be specified multiple times)"
45+ , Option [] ["no-haddock"] (NoArg (\o -> o { optHaddock = False })) "don't run Haddock when building this package"
46+ ]
3147
32− usage :: String
33− usage = usageInfo "Usage: cabal2nix [options] url-to-cabal-file" options ++ unlines
34− [ ""
35− , "Recognized URI schemes:"
36− , ""
37− , " cabal://pkgname-pkgversion download the specified package from Hackage"
38− , " http://host/path fetch the Cabel file via HTTP"
39− , " file:///local/path load the Cabal file from the local disk"
40− , " /local/path abbreviated version of file URI"
41− ]
48+usage :: String
49+usage = usageInfo "Usage: cabal2nix [options] url-to-cabal-file" options ++ unlines
50+ [ ""
51+ , "Recognized URI schemes:"
52+ , ""
53+ , " cabal://pkgname-pkgversion download the specified package from Hackage"
54+ , " http://host/path fetch the Cabel file via HTTP"
55+ , " file:///local/path load the Cabal file from the local disk"
56+ , " /local/path abbreviated version of file URI"
57+ ]
4258
43− cmdlineError :: String -> IO a
44− cmdlineError "" = hPutStrLn stderr usage >> exitFailure
45− cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
59+cmdlineError :: String -> IO a
60+cmdlineError "" = hPutStrLn stderr usage >> exitFailure
61+cmdlineError errMsg = hPutStrLn stderr errMsg >> cmdlineError ""
4662
63+main :: IO ()
64+main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
4765 args' <- getArgs
48− (opts,args) <- case getOpt Permute options args' of
49− (o,n,[] ) -> return (o,n)
50− (_,_,errs) -> cmdlineError (concatMap (\e -> '*':'*':'*':' ':e) errs)
51−
52− when (PrintHelp `elem` opts) (cmdlineError "")
66+ (cfg',args) <- case getOpt Permute options args' of
67+ (o,n,[] ) -> return (foldl (flip ($)) defaultConfiguration o,n)
68+ (_,_,errs) -> cmdlineError (concatMap ("*** "++) errs)
5369
54− let uri = args
55− hash = [ h | SHA256 h <- opts ]
56− noHaddock = NoHaddock `elem` opts
57− maints = [ if '.' `elem` m then m else "self.stdenv.lib.maintainers." ++ m | Maintainer m <- opts ]
58− plats' = [ if '.' `elem` p then p else "self.stdenv.lib.platforms." ++ p | Platform p <- opts ]
59− plats
60− | null plats' = if not (null maints) then ["self.ghc.meta.platforms"] else []
61− | otherwise = plats'
70+ when (optPrintHelp cfg') (putStr usage >> exitSuccess)
71+ when (length args /= 1) (cmdlineError "*** exactly one url-to-cabal-file must be specified\n")
6272
63− when (length uri /= 1) (cmdlineError "*** exactly one URI must be specified")
64− when (length hash > 1) (cmdlineError "*** the --sha256 option may be specified only once")
73+ let maints = [ if '.' `elem` m then m else "self.stdenv.lib.maintainers." ++ m | m <- optMaintainer cfg' ]
74+ plats = [ if '.' `elem` p then p else "self.stdenv.lib.platforms." ++ p | p <- optPlatform cfg' ]
75+ cfg = cfg'
76+ { optMaintainer = nub (sort maints)
77+ , optPlatform = nub (sort (if null plats then ["self.ghc.meta.platforms"] else plats))
78+ }
6579
66− cabal' <- fmap parsePackageDescription (readCabalFile (head uri))
80+ cabal' <- fmap parsePackageDescription (readCabalFile (head args))
6781 cabal <- case cabal' of
6882 ParseOk _ a -> return a
6983 ParseFailed err -> do
@@ -72,12 +86,12 @@ main = bracket (return ()) (\() -> hFlush stdout >> hFlush stderr) $ \() -> do
7286
7387 let packageId = package (packageDescription cabal)
7488
75− sha <- if null hash then hashPackage packageId else return (head hash)
89+ sha <- if null (optSha256 cfg) then hashPackage packageId else return (optSha256 cfg)
7690
77− let deriv = (cabal2nix cabal) { sha256 = sha, runHaddock = not noHaddock }
91+ let deriv = (cabal2nix cabal) { sha256 = sha, runHaddock = optHaddock cfg }
7892 deriv' = deriv { metaSection = (metaSection deriv)
79− { maintainers = maints
80− , platforms = plats
93+ { maintainers = optMaintainer cfg
94+ , platforms = optPlatform cfg
8195 }
8296 }
8397