module CabalParse where import IO import Char import System import Text.ParserCombinators.Poly -- | "cabalLookup" parses any particular request field from an already-parsed -- cabal file. -- The first arg is the contents of the .cabal file, the second is the -- field name to extract. The third arg manipulates the result string -- e.g. to translate module names from Haskell notation to filepaths. cabalLookup :: CabalFile -> String -> (String->String) -> Either String String cabalLookup cabal field munge = do case lookup field cabal of Just rhs -> case runParser (fieldtype field) rhs of (Left e, _) -> Left e (Right result, _) -> Right (munge result) Nothing -> Left ("field "++field++" not present") stop :: String -> IO a stop s = do hPutStrLn stderr ("cabal-parse:\n"++indent 2 s) exitFailure return undefined quiet :: String -> IO () quiet s = return () slash = map (\c -> if c=='.' then '/' else c) -- Mapping from field-name to type of rhs fieldtype :: String -> Parser Token String fieldtype f = case f of "exposed-modules" -> unLines (list string) "other-modules" -> unLines (list string) "c-sources" -> unLines (list string) "hs-source-dirs" -> unLines (list string) "hs-source-dir" -> string "build-depends" -> unLines (commalist string) "extensions" -> unLines (list string) "data-files" -> unLines (list string) "extra-source-files" -> unLines (list string) "extra-tmp-files" -> unLines (list string) "homepage" -> url "package-url" -> url _ -> freetext -- Simple Lexer: data Token = Word String | Colon | Comma | Newline | NewlineIndent deriving (Eq,Show) lexToken :: String -> [Token] lexToken [] = [] lexToken (':':ss) = Colon : lexToken ss lexToken ('\n':ss) = case ss of ('-':'-':_) -> NewlineIndent: lexToken ss ('\n':cs) -> Newline : lexToken cs (c:cs) | isSpace c -> NewlineIndent: lexToken cs _ -> Newline : lexToken ss lexToken ('-':'-':ss) = dropWhile (`notElem`[Newline,NewlineIndent]) (lexToken ss) lexToken (',':c:ss) | isSpace c = Comma : lexToken (c:ss) lexToken (c:ss) | isSpace c = lexToken ss lexToken (c:ss) = accumulate [c] ss accumulate :: String -> String -> [Token] accumulate acc [] = [] accumulate acc (c:cs) | isSpace c || c `elem` ":," = Word (reverse acc) : lexToken (c:cs) accumulate acc (c:cs) = accumulate (c:acc) cs -- Simple Parsers: type CabalFile = [(String,[Token])] -- parse a bunch of keyword/value bindings, without further -- interpretation of the rhs cabalFile :: Parser Token CabalFile cabalFile = many1 $ do (Word key) <- next Colon <- next `adjustErr` (("Missing colon after "++key++"\n")++) rhs <- manyFinally (satisfy (/=Newline)) (has Newline) return (map toLower key, rhs) -- parse a single definite token has :: Token -> Parser Token () has t = do satisfy (==t); return () -- parse something whilst ignoring non-significant newlines acrossLines p = do many (has NewlineIndent) x <- p many (has NewlineIndent) return x -- parse freeform text freetext :: Parser Token String freetext = free "" where free s = do n <- next case n of Word w -> free (reverse w++" "++s) Colon -> free (':':s) Comma -> free (',':s) NewlineIndent -> free ('\n':s) `onFail` return (reverse s) -- parse URL, like freeform text only all spaces squashed url :: Parser Token String url = freetext >>= return . filter (not.isSpace) -- parse a comma-separated list of items commalist :: Parser Token a -> Parser Token [a] commalist item = acrossLines (item `sepBy` acrossLines (has Comma)) -- parse a (possibly) comma-separated list of items list :: Parser Token a -> Parser Token [a] list item = acrossLines (item `sepBy` acrossLines (optional (has Comma))) -- parse a single module name, directory name, package name, etc string :: Parser Token String string = do (Word w) <- next; return w -- convert a list of strings into a whitespace-separated string unLines :: Parser Token [String] -> Parser Token String unLines p = p >>= return . init . unlines