{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RankNTypes #-} ----------------------------------------------------------------------------- -- | -- Module : Distribution.Parsec.Types.FieldDescr -- License : BSD3 -- -- Maintainer : cabal-devel@haskell.org -- Portability : portable -- module Distribution.Parsec.Types.FieldDescr ( -- * Field name FieldName, -- * Field description FieldDescr (..), liftField, simpleField, boolField, optsField, listField, listFieldWithSep, commaListField, commaListFieldWithSep, spaceListField, -- ** Pretty printing ppFields, ppField, -- * Unknown fields UnknownFieldParser, warnUnrec, ignoreUnrec, ) where import Prelude () import Distribution.Compat.Prelude hiding (get) import qualified Data.ByteString as BS import Data.Ord (comparing) import qualified Distribution.Compat.Parsec as P import Distribution.Compiler (CompilerFlavor) import Distribution.Parsec.Class import Distribution.Parsec.Types.Common import Distribution.PrettyUtils import Text.PrettyPrint (Doc, colon, comma, fsep, hsep, isEmpty, nest, punctuate, text, vcat, ($+$), (<+>)) type FieldName = BS.ByteString ------------------------------------------------------------------------------- -- Unrecoginsed fields ------------------------------------------------------------------------------- -- | How to handle unknown fields. type UnknownFieldParser a = FieldName -> String -> a -> Maybe a -- | A default unrecognized field parser which simply returns Nothing, -- i.e. ignores all unrecognized fields, so warnings will be generated. warnUnrec :: UnknownFieldParser a warnUnrec _ _ _ = Nothing -- | A default unrecognized field parser which silently (i.e. no -- warnings will be generated) ignores unrecognized fields, by -- returning the structure being built unmodified. ignoreUnrec :: UnknownFieldParser a ignoreUnrec _ _ = Just ------------------------------------------------------------------------------- -- Field description ------------------------------------------------------------------------------- data FieldDescr a = FieldDescr { fieldName :: FieldName , fieldPretty :: a -> Doc , fieldParser :: a -> FieldParser a } liftField :: (b -> a) -> (a -> b -> b) -> FieldDescr a -> FieldDescr b liftField get set fd = FieldDescr (fieldName fd) (fieldPretty fd . get) (\b -> flip set b <$> fieldParser fd (get b)) simpleField :: FieldName -- ^ fieldname -> (a -> Doc) -- ^ show -> FieldParser a -- ^ parser -> (b -> a) -- ^ getter -> (a -> b -> b) -- ^ setter -> FieldDescr b simpleField name pretty parse get set = FieldDescr name (pretty . get) (\a -> flip set a <$> parse) boolField :: FieldName -> (b -> Bool) -> (Bool -> b -> b) -> FieldDescr b boolField name get set = liftField get set (FieldDescr name showF parseF) where showF = text . show parseF _ = P.munch1 isAlpha >>= postprocess postprocess str | str == "True" = pure True | str == "False" = pure False | lstr == "true" = parsecWarning PWTBoolCase caseWarning *> pure True | lstr == "false" = parsecWarning PWTBoolCase caseWarning *> pure False | otherwise = fail $ "Not a boolena: " ++ str where lstr = map toLower str caseWarning = "The " ++ show name ++ " field is case sensitive, use 'True' or 'False'." optsField :: FieldName -> CompilerFlavor -> (b -> [(CompilerFlavor,[String])]) -> ([(CompilerFlavor,[String])] -> b -> b) -> FieldDescr b optsField name flavor get set = liftField (fromMaybe [] . lookup flavor . get) (\opts b -> set (reorder (update flavor opts (get b))) b) $ field name showF (many $ parsecToken' <* P.munch isSpace) where update _ opts l | all null opts = l --empty opts as if no opts update f opts [] = [(f,opts)] update f opts ((f',opts'):rest) | f == f' = (f, opts' ++ opts) : rest | otherwise = (f',opts') : update f opts rest reorder = sortBy (comparing fst) showF = hsep . map text listField :: FieldName -> (a -> Doc) -> FieldParser a -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b listField = listFieldWithSep fsep listFieldWithSep :: Separator -> FieldName -> (a -> Doc) -> FieldParser a -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b listFieldWithSep separator name showF parseF get set = liftField get set' $ field name showF' (parsecOptCommaList parseF) where set' xs b = set (get b ++ xs) b showF' = separator . map showF commaListField :: FieldName -> (a -> Doc) -> FieldParser a -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b commaListField = commaListFieldWithSep fsep commaListFieldWithSep :: Separator -> FieldName -> (a -> Doc) -> FieldParser a -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b commaListFieldWithSep separator name showF parseF get set = liftField get set' $ field name showF' (parsecCommaList parseF) where set' xs b = set (get b ++ xs) b showF' = separator . punctuate comma . map showF spaceListField :: FieldName -> (a -> Doc) -> FieldParser a -> (b -> [a]) -> ([a] -> b -> b) -> FieldDescr b spaceListField name showF parseF get set = liftField get set' $ field name showF' (many $ parseF <* P.spaces) where set' xs b = set (get b ++ xs) b showF' = fsep . map showF -- Overriding field field :: FieldName -> (a -> Doc) -> FieldParser a -> FieldDescr a field name showF parseF = FieldDescr name showF (const parseF) ------------------------------------------------------------------------------- -- Pretty printing ------------------------------------------------------------------------------- ppFields :: [FieldDescr a] -> a -> Doc ppFields fields x = vcat [ ppField name (getter x) | FieldDescr name getter _ <- fields ] ppField :: FieldName -> Doc -> Doc ppField name fielddoc | isEmpty fielddoc = mempty | name `elem` nestedFields = text namestr <<>> colon $+$ nest indentWith fielddoc | otherwise = text namestr <<>> colon <+> fielddoc where namestr = show name -- TODO: do this nestedFields :: [BS.ByteString] nestedFields = [ "description" , "build-depends" , "data-files" , "extra-source-files" , "extra-tmp-files" , "exposed-modules" , "c-sources" , "js-sources" , "extra-libraries" , "includes" , "install-includes" , "other-modules" , "depends" ] -- | Handle deprecated fields -- -- *TODO:* use Parsec {- deprecField :: Field Position -> Parsec (Field Position) deprecField = undefined {- -} (Field (Name pos fld) val) = do fld' <- case lookup fld deprecatedFields of Nothing -> return fld Just newName -> do warning $ "The field " ++ show fld ++ " is deprecated, please use " ++ show newName return newName return (Field (Name pos fld') val) deprecField _ = cabalBug "'deprecField' called on a non-field" -}