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 #-}
8
9
-- | License: GPL-3.0-or-later AND BSD-3-Clause
9
10
--
10
11
module Cabal.Project (
@@ -15,6 +16,8 @@ module Cabal.Project (
15
16
-- * Parse project
16
17
readProject ,
17
18
parseProject ,
19
+ readProjectWithConditionals ,
20
+ parseProjectWithConditionals ,
18
21
-- * Resolve project
19
22
resolveProject ,
20
23
ResolveError (.. ),
@@ -25,6 +28,7 @@ module Cabal.Project (
25
28
26
29
import Control.DeepSeq (NFData (.. ))
27
30
import Control.Exception (Exception (.. ), throwIO )
31
+ import Control.Monad (unless )
28
32
import Control.Monad.IO.Class (liftIO )
29
33
import Control.Monad.Trans.Except (ExceptT , runExceptT , throwE )
30
34
import Data.Bifoldable (Bifoldable (.. ))
@@ -55,6 +59,7 @@ import qualified Data.Map.Strict as M
55
59
import qualified Distribution.CabalSpecVersion as C
56
60
import qualified Distribution.FieldGrammar as C
57
61
import qualified Distribution.Fields as C
62
+ import qualified Distribution.Fields.ConfVar as C
58
63
import qualified Distribution.PackageDescription as C
59
64
import qualified Distribution.Parsec as C
60
65
@@ -71,6 +76,12 @@ infixl 1 <&>
71
76
72
77
-- $setup
73
78
-- >>> :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)
74
85
75
86
-- | @cabal.project@ file
76
87
data Project uri opt pkg = Project
@@ -152,7 +163,7 @@ instance (NFData c, NFData b, NFData a) => NFData (Project c b a) where
152
163
rnf x7 `seq` rnf x8 `seq` rnf x9 `seq`
153
164
rnfList rnfPrettyField x10
154
165
where
155
- rnfList :: (a -> () ) -> [a ] -> ()
166
+ rnfList :: (x -> () ) -> [x ] -> ()
156
167
rnfList _ [] = ()
157
168
rnfList f (x: xs) = f x `seq` rnfList f xs
158
169
@@ -179,6 +190,13 @@ readProject fp = do
179
190
prj1 <- resolveProject fp prj0 >>= either throwIO return
180
191
readPackagesOfProject prj1 >>= either throwIO return
181
192
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
+
182
200
-- | Parse project file. Extracts only few fields.
183
201
--
184
202
-- >>> fmap prjPackages $ parseProject "cabal.project" "packages: foo bar/*.cabal"
@@ -207,6 +225,46 @@ parseProject = parseWith $ \fields0 -> do
207
225
208
226
parseSec _ = return id
209
227
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
+
210
268
-- | Returns 'True' if a field should be a part of 'prjOtherFields'. This
211
269
-- excludes any field that is a part of 'grammar' as well as
212
270
-- @source-repository-package@ (see 'parseProject', which has a special case
@@ -377,3 +435,61 @@ readPackagesOfProject :: Project uri opt FilePath -> IO (Either (ParseError NonE
377
435
readPackagesOfProject prj = runExceptT $ for prj $ \ fp -> do
378
436
contents <- liftIO $ BS. readFile fp
379
437
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