1- {-# LANGUAGE CPP #-}
2- {-# LANGUAGE DeriveFoldable #-}
3- {-# LANGUAGE DeriveFunctor #-}
4- {-# LANGUAGE DeriveGeneric #-}
5- {-# LANGUAGE DeriveTraversable #-}
6- {-# LANGUAGE MultiWayIf #-}
7- {-# LANGUAGE OverloadedStrings #-}
1+ {-# LANGUAGE CPP #-}
2+ {-# LANGUAGE DeriveFoldable #-}
3+ {-# LANGUAGE DeriveFunctor #-}
4+ {-# LANGUAGE DeriveGeneric #-}
5+ {-# LANGUAGE DeriveTraversable #-}
6+ {-# LANGUAGE MultiWayIf #-}
7+ {-# LANGUAGE OverloadedStrings #-}
8+ {-# LANGUAGE ScopedTypeVariables #-}
89-- | License: GPL-3.0-or-later AND BSD-3-Clause
910--
1011module Cabal.Project (
@@ -15,6 +16,8 @@ module Cabal.Project (
1516 -- * Parse project
1617 readProject ,
1718 parseProject ,
19+ readProjectWithConditionals ,
20+ parseProjectWithConditionals ,
1821 -- * Resolve project
1922 resolveProject ,
2023 ResolveError (.. ),
@@ -25,6 +28,7 @@ module Cabal.Project (
2528
2629import Control.DeepSeq (NFData (.. ))
2730import Control.Exception (Exception (.. ), throwIO )
31+ import Control.Monad (unless )
2832import Control.Monad.IO.Class (liftIO )
2933import Control.Monad.Trans.Except (ExceptT , runExceptT , throwE )
3034import Data.Bifoldable (Bifoldable (.. ))
@@ -55,6 +59,7 @@ import qualified Data.Map.Strict as M
5559import qualified Distribution.CabalSpecVersion as C
5660import qualified Distribution.FieldGrammar as C
5761import qualified Distribution.Fields as C
62+ import qualified Distribution.Fields.ConfVar as C
5863import qualified Distribution.PackageDescription as C
5964import qualified Distribution.Parsec as C
6065
@@ -71,6 +76,12 @@ infixl 1 <&>
7176
7277-- $setup
7378-- >>> :set -XOverloadedStrings
79+ -- >>> import Data.String (fromString)
80+ -- >>> import qualified Distribution.PackageDescription as C
81+ -- >>> import Text.Show (showListWith)
82+ -- >>> import Data.Functor.Classes (liftShowsPrec)
83+ -- >>> let sB (C.CondBranch c t f) = showString "CondBranch _ " . showParen True (sT t) . showChar ' ' . liftShowsPrec (\_ -> sT) undefined 11 f; sT (C.CondNode x c xs) = showString "CondTree " . showsPrec 11 x . showString " _ " . showListWith sB xs
84+ -- >>> pp x = putStrLn (either show (flip sT "") x)
7485
7586-- | @cabal.project@ file
7687data Project uri opt pkg = Project
@@ -152,7 +163,7 @@ instance (NFData c, NFData b, NFData a) => NFData (Project c b a) where
152163 rnf x7 `seq` rnf x8 `seq` rnf x9 `seq`
153164 rnfList rnfPrettyField x10
154165 where
155- rnfList :: (a -> () ) -> [a ] -> ()
166+ rnfList :: (x -> () ) -> [x ] -> ()
156167 rnfList _ [] = ()
157168 rnfList f (x: xs) = f x `seq` rnfList f xs
158169
@@ -179,6 +190,13 @@ readProject fp = do
179190 prj1 <- resolveProject fp prj0 >>= either throwIO return
180191 readPackagesOfProject prj1 >>= either throwIO return
181192
193+ readProjectWithConditionals :: FilePath -> IO (C. CondTree C. ConfVar () (Project URI Void (FilePath , C. GenericPackageDescription )))
194+ readProjectWithConditionals fp = do
195+ contents <- BS. readFile fp
196+ prj0 <- either throwIO return (parseProjectWithConditionals fp contents)
197+ prj1 <- traverse (\ p -> resolveProject fp p >>= either throwIO return ) prj0
198+ traverse (\ p -> readPackagesOfProject p >>= either throwIO return ) prj1
199+
182200-- | Parse project file. Extracts only few fields.
183201--
184202-- >>> fmap prjPackages $ parseProject "cabal.project" "packages: foo bar/*.cabal"
@@ -207,6 +225,46 @@ parseProject = parseWith $ \fields0 -> do
207225
208226 parseSec _ = return id
209227
228+ -- | Parse project files with conditionals.
229+ --
230+ -- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" "packages: foo bar/*.cabal"
231+ -- CondTree ["foo","bar/*.cabal"] _ []
232+ --
233+ -- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu" ]
234+ -- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) Nothing]
235+ --
236+ -- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu", "if impl(ghc >=10)", " packages: zoo" ]
237+ -- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) Nothing,CondBranch _ (CondTree ["zoo"] _ []) Nothing]
238+ --
239+ -- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu", "else", " packages: zoo" ]
240+ -- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) (Just CondTree ["zoo"] _ [])]
241+ --
242+ -- >>> pp $ fmap (fmap prjPackages) $ parseProjectWithConditionals "cabal.project" $ fromString $ unlines [ "packages: foo bar/*.cabal", "if impl(ghc >=9)", " packages: quu", "elif impl(ghc >=10)", " packages: zoo", "else", " packages: yyz" ]
243+ -- CondTree ["foo","bar/*.cabal"] _ [CondBranch _ (CondTree ["quu"] _ []) (Just CondTree [] _ [CondBranch _ (CondTree ["zoo"] _ []) (Just CondTree ["yyz"] _ [])])]
244+ --
245+ parseProjectWithConditionals :: FilePath -> ByteString -> Either (ParseError NonEmpty ) (C. CondTree C. ConfVar () (Project Void String String ))
246+ parseProjectWithConditionals = parseWith $ \ fields0 -> flip parseCondTree fields0 $ \ fields1 sections -> do
247+ let fields2 = M. filterWithKey (\ k _ -> k `elem` knownFields) fields1
248+ parse fields0 fields2 sections
249+ where
250+ knownFields = C. fieldGrammarKnownFieldList $ grammar []
251+
252+ parse :: [C. Field a ] -> C. Fields C. Position -> [[C. Section C. Position ]] -> C. ParseResult (Project Void String String )
253+ parse otherFields fields sections = do
254+ let prettyOtherFields = map void $ C. fromParsecFields $ filter otherFieldName otherFields
255+ prj <- C. parseFieldGrammar C. cabalSpecLatest fields $ grammar prettyOtherFields
256+ foldl' (&) prj <$> traverse parseSec (concat sections)
257+
258+ -- Special case for source-repository-package. If you add another such
259+ -- special case, make sure to update otherFieldName appropriately.
260+ parseSec :: C. Section C. Position -> C. ParseResult (Project Void String String -> Project Void String String )
261+ parseSec (C. MkSection (C. Name _pos name) [] fields) | name == sourceRepoSectionName = do
262+ let fields' = fst $ C. partitionFields fields
263+ repos <- C. parseFieldGrammar C. cabalSpecLatest fields' sourceRepositoryPackageGrammar
264+ return $ over prjSourceReposL (++ toList (srpFanOut repos))
265+
266+ parseSec _ = return id
267+
210268-- | Returns 'True' if a field should be a part of 'prjOtherFields'. This
211269-- excludes any field that is a part of 'grammar' as well as
212270-- @source-repository-package@ (see 'parseProject', which has a special case
@@ -377,3 +435,61 @@ readPackagesOfProject :: Project uri opt FilePath -> IO (Either (ParseError NonE
377435readPackagesOfProject prj = runExceptT $ for prj $ \ fp -> do
378436 contents <- liftIO $ BS. readFile fp
379437 either throwE (\ gpd -> return (fp, gpd)) (parsePackage fp contents)
438+
439+ -------------------------------------------------------------------------------
440+ -- Read package files
441+ -------------------------------------------------------------------------------
442+
443+ parseCondTree
444+ :: forall a . (C. Fields C. Position -> [[C. Section C. Position ]] -> C. ParseResult a ) -- ^ parse
445+ -> [C. Field C. Position ]
446+ -> C. ParseResult (C. CondTree C. ConfVar () a )
447+ parseCondTree subparse = go
448+ where
449+ go fields = do
450+ let (fs, ss) = C. partitionFields fields
451+ (ss', branches) <- second concat . unzip <$> traverse (goIfs id id ) ss
452+ x <- subparse fs ss'
453+ return $ C. CondNode x () branches
454+
455+ goIfs
456+ :: ([C. Section C. Position ] -> [C. Section C. Position ])
457+ -> ([C. CondBranch C. ConfVar () a ] -> [C. CondBranch C. ConfVar () a ])
458+ -> [C. Section C. Position ]
459+ -> C. ParseResult ([C. Section C. Position ], [C. CondBranch C. ConfVar () a ])
460+ goIfs accS accB [] = do
461+ return (accS [] , accB [] )
462+ goIfs accS accB (C. MkSection (C. Name pos name) args fields : sections)
463+ | name == " if" = do
464+ test' <- C. parseConditionConfVar args
465+ fields' <- go fields
466+ goElse (C. CondBranch test' fields') accS accB sections
467+ | name == " else" = do
468+ C. parseFailure pos " standalone else"
469+ return ([] , [] )
470+ | name == " elif" = do
471+ C. parseFailure pos " standalone elif"
472+ goIfs accS accB sections
473+ goIfs accS accB (section : sections) = do
474+ goIfs (accS . (section : )) accB sections
475+
476+ goElse
477+ :: (Maybe (C. CondTree C. ConfVar () a ) -> C. CondBranch C. ConfVar () a )
478+ -> ([C. Section C. Position ] -> [C. Section C. Position ])
479+ -> ([C. CondBranch C. ConfVar () a ] -> [C. CondBranch C. ConfVar () a ])
480+ -> [C. Section C. Position ]
481+ -> C. ParseResult ([C. Section C. Position ], [C. CondBranch C. ConfVar () a ])
482+ goElse make accS accB (C. MkSection (C. Name pos name) args fields : sections)
483+ | name == " else" = do
484+ unless (null args) $ C. parseFailure pos " arguments passed to else"
485+ fields' <- go fields
486+ let condTree = make (Just fields')
487+ goIfs accS (accB . (condTree : )) sections
488+ | name == " elif" = do
489+ test' <- C. parseConditionConfVar args
490+ fields' <- go fields
491+ emptyA <- subparse mempty []
492+ goElse (make . Just . C. CondNode emptyA () . pure . C. CondBranch test' fields') accS accB sections
493+ goElse make accS accB sections = do
494+ let condTree = make Nothing
495+ goIfs accS (accB . (condTree : )) sections
0 commit comments