1
- {-# LANGUAGE LambdaCase #-}
2
1
{-# LANGUAGE OverloadedStrings #-}
2
+ {-# LANGUAGE RecordWildCards #-}
3
+
4
+ module Hie.Cabal.Parser
5
+ ( Package (.. )
6
+ , Component (.. )
7
+ , CompType (.. )
8
+ , Name
9
+ , extractPkgs
10
+ , parsePackage'
11
+ ) where
12
+
13
+ import Control.Applicative
14
+ import Control.Monad
15
+ import Data.Attoparsec.Text
16
+ import Data.Char
17
+ import Data.Foldable (asum )
18
+ import Data.Maybe (catMaybes ,
19
+ maybeToList )
20
+ import Data.Text (Text )
21
+ import qualified Data.Text as T
22
+ import Data.Text.Encoding (encodeUtf8 )
23
+ import Distribution.ModuleName (ModuleName ,
24
+ toFilePath )
25
+ import Distribution.Package (pkgName ,
26
+ unPackageName )
27
+ import Distribution.PackageDescription (Benchmark (benchmarkBuildInfo , benchmarkInterface , benchmarkName ),
28
+ BenchmarkInterface (BenchmarkExeV10 ),
29
+ Executable (buildInfo , exeName , modulePath ),
30
+ ForeignLib (foreignLibBuildInfo , foreignLibName ),
31
+ Library (libBuildInfo , libName ),
32
+ LibraryName (.. ),
33
+ TestSuiteInterface (TestSuiteExeV10 ),
34
+ benchmarkModules ,
35
+ exeModules ,
36
+ explicitLibModules ,
37
+ foreignLibModules )
38
+ import Distribution.PackageDescription.Configuration
39
+ import Distribution.PackageDescription.Parsec
40
+ import Distribution.Types.BuildInfo
41
+ import Distribution.Types.PackageDescription
42
+ import Distribution.Types.TestSuite
43
+ import Distribution.Types.UnqualComponentName
44
+ import Distribution.Utils.Path (getSymbolicPath )
45
+ import GHC.IO (unsafePerformIO )
46
+ import System.Directory (doesFileExist )
47
+ import System.FilePath ((<.>) , (</>) )
3
48
4
- module Hie.Cabal.Parser where
5
-
6
- import Control.Applicative
7
- import Control.Monad
8
- import Data.Attoparsec.Text
9
- import Data.Char
10
- import Data.Functor
11
- import Data.Maybe
12
- import Data.Text (Text )
13
- import qualified Data.Text as T
14
- import System.FilePath.Posix ((</>) )
15
49
16
50
type Name = Text
17
51
@@ -29,83 +63,6 @@ data Component
29
63
= Comp CompType Name Path
30
64
deriving (Show , Eq , Ord )
31
65
32
- parsePackage' :: Text -> Either String Package
33
- parsePackage' = parseOnly parsePackage
34
-
35
- -- Skip over entire fields that are known to be free-form. Ensures lines that
36
- -- look like the beginning of sections/stanzas are not inadvertently intepreted
37
- -- as such.
38
- -- List gathered by searching "free text field" in:
39
- -- https://cabal.readthedocs.io/en/3.4/buildinfo-fields-reference.html
40
- -- May be subject to change across Cabal versions.
41
- skipFreeformField :: Parser ()
42
- skipFreeformField =
43
- choice $
44
- flip (field 0 ) skipBlock
45
- <$> [ " author" ,
46
- " bug-reports" ,
47
- " category" ,
48
- " copyright" ,
49
- " description" ,
50
- " homepage" ,
51
- " maintainer" ,
52
- " package-url" ,
53
- " stability" ,
54
- " synopsis"
55
- ]
56
-
57
- parsePackage :: Parser Package
58
- parsePackage =
59
- ( do
60
- n <- field 0 " name" $ const parseString
61
- (Package _ t) <- parsePackage
62
- pure $ Package n t
63
- )
64
- <|> (skipFreeformField >> parsePackage)
65
- <|> ( do
66
- h <- parseComponent 0
67
- (Package n t) <- parsePackage
68
- pure $ Package n (h <> t)
69
- )
70
- <|> (skipToNextLine >> parsePackage)
71
- <|> pure (Package " " [] )
72
-
73
- componentHeader :: Indent -> Text -> Parser Name
74
- componentHeader i t = do
75
- _ <- indent i
76
- _ <- asciiCI t
77
- skipMany tabOrSpace
78
- n <- parseString <|> pure " "
79
- skipToNextLine
80
- pure n
81
-
82
- parseComponent :: Indent -> Parser [Component ]
83
- parseComponent i =
84
- parseExe i
85
- <|> parseLib i
86
- <|> parseBench i
87
- <|> parseTestSuite i
88
-
89
- parseLib :: Indent -> Parser [Component ]
90
- parseLib i =
91
- (parseSec i " library" $ Comp Lib )
92
- <|> (parseSec i " foreign-library" $ Comp Lib )
93
-
94
- parseTestSuite :: Indent -> Parser [Component ]
95
- parseTestSuite i = parseSec i " test-suite" $ Comp Test
96
-
97
- parseExe :: Indent -> Parser [Component ]
98
- parseExe = parseSecMain (Comp Exe ) " executable"
99
-
100
- parseBench :: Indent -> Parser [Component ]
101
- parseBench = parseSecMain (Comp Bench ) " benchmark"
102
-
103
- parseSecMain :: (Name -> Path -> Component ) -> Text -> Indent -> Parser [Component ]
104
- parseSecMain c s i = do
105
- n <- componentHeader i s
106
- p <- pathMain (i + 1 ) [" ./" ] " " [] []
107
- pure $ map (c n) p
108
-
109
66
parseQuoted :: Parser Text
110
67
parseQuoted = do
111
68
q <- char ' "' <|> char ' \' '
@@ -142,84 +99,88 @@ parseList i = many (nl <|> sl)
142
99
skipMany com
143
100
pure x
144
101
145
- pathMain :: Indent -> [Text ] -> Text -> [Text ] -> [Text ] -> Parser [Text ]
146
- pathMain i p m o a =
147
- (hsSourceDir i >>= (\ p' -> pathMain i p' m o a))
148
- <|> (field i " main-is" (const parseString) >>= (\ m' -> pathMain i p m' o a))
149
- <|> (field i " other-modules" parseList >>= flip (pathMain i p m) a)
150
- <|> (field i " autogen-modules" parseList >>= pathMain i p m o)
151
- <|> (skipBlockLine i >> pathMain i p m o a)
152
- <|> pure
153
- ( map (<//> m) p
154
- <> [ p' <//> (o'' <> " .hs" )
155
- | p' <- p,
156
- o' <- filter (`notElem` a) o,
157
- let o'' = T. replace " ." " /" o'
158
- ]
159
- )
160
-
161
- (<//>) :: Text -> Text -> Text
162
- a <//> b = T. pack (T. unpack a </> T. unpack b)
163
-
164
- infixr 5 <//>
165
-
166
- parseSec :: Indent -> Text -> (Name -> Path -> Component ) -> Parser [Component ]
167
- parseSec i compType compCon = do
168
- n <- componentHeader i compType
169
- p <- extractPath (i + 1 ) []
170
- let p' = if null p then [" ./" ] else p
171
- pure $ map (compCon n) p'
172
-
173
102
skipToNextLine :: Parser ()
174
103
skipToNextLine = skipWhile (not . isEndOfLine) >> endOfLine
175
104
176
- skipBlock :: Indent -> Parser ()
177
- skipBlock i = skipMany $ skipBlockLine i
178
-
179
105
comment :: Parser ()
180
106
comment = skipMany tabOrSpace >> " --" >> skipToNextLine
181
107
182
- skipBlockLine :: Indent -> Parser ()
183
- skipBlockLine i = (indent i >> skipToNextLine) <|> emptyOrComLine
184
-
185
108
emptyOrComLine :: Parser ()
186
109
emptyOrComLine = (skipMany tabOrSpace >> endOfLine) <|> comment
187
110
188
111
tabOrSpace :: Parser Char
189
112
tabOrSpace = char ' ' <|> char ' \t '
190
113
191
- hsSourceDir :: Indent -> Parser [Text ]
192
- hsSourceDir i = field i " hs-source-dirs" parseList
193
-
194
114
-- field :: Indent -> Text -> Parser Text
195
115
field ::
196
116
Indent ->
197
- Text ->
117
+ [ Text ] ->
198
118
(Indent -> Parser a ) ->
199
119
Parser a
200
120
field i f p =
201
121
do
202
122
i' <- indent i
203
- _ <- asciiCI f
123
+ _ <- asum $ map asciiCI f
204
124
skipMany tabOrSpace
205
125
_ <- char ' :'
206
126
skipMany tabOrSpace
207
127
p' <- p $ i' + 1
208
128
skipToNextLine
209
129
pure p'
210
130
211
- extractPath :: Indent -> [Path ] -> Parser [Path ]
212
- extractPath i ps =
213
- (field i " hs-source-dirs" parseList >>= (\ p -> extractPath i $ ps <> p))
214
- <|> (skipBlockLine i >> extractPath i ps)
215
- <|> (comment >> extractPath i ps)
216
- <|> pure ps
217
-
218
131
-- | Skip at least n spaces
219
132
indent :: Indent -> Parser Int
220
133
indent i = do
221
134
c <- length <$> many' tabOrSpace
222
135
if c >= i then pure c else fail " insufficient indent"
223
136
224
137
extractPkgs :: Parser [T. Text ]
225
- extractPkgs = join . catMaybes <$> many' (Just <$> field 0 " packages" parseList <|> (skipToNextLine >> pure Nothing ))
138
+ extractPkgs = join . catMaybes <$> many' (Just <$> field 0 [" packages" ] parseList <|> (skipToNextLine >> pure Nothing ))
139
+
140
+ parsePackage' :: T. Text -> Either String Package
141
+ parsePackage' t = do
142
+ let bytes = encodeUtf8 t
143
+ case runParseResult (parseGenericPackageDescription bytes) of
144
+ (_warnings, Left err) ->
145
+ error $ " Cannot parse Cabal file: " <> show err
146
+ (_warnings, Right res) -> do
147
+ let pkg = flattenPackageDescription res
148
+ Right $ extractPackage pkg
149
+
150
+ extractPackage :: PackageDescription -> Package
151
+ extractPackage PackageDescription {.. } = Package n cc where
152
+ n = T. pack . unPackageName $ pkgName package
153
+
154
+ cc = concat $
155
+ [mkComp Test (unqName $ testName t) (testBuildInfo t) (testExePath t) (testModules t) | t <- testSuites] ++
156
+ [mkComp Bench (unqName $ benchmarkName b) (benchmarkBuildInfo b) (benchmarkExePath b) (benchmarkModules b) | b <- benchmarks] ++
157
+ [mkComp Exe (unqName $ exeName e) (buildInfo e) [modulePath e] (exeModules e) | e <- executables] ++
158
+ [mkComp Lib (libName' l) (libBuildInfo l) [] (explicitLibModules l) | l <- maybeToList library ++ subLibraries ] ++
159
+ [mkComp Lib (unqName $ foreignLibName f) (foreignLibBuildInfo f) [] (foreignLibModules f) | f <- foreignLibs]
160
+
161
+ mkComp :: CompType -> T. Text -> BuildInfo -> [FilePath ] -> [ModuleName ] -> [Component ]
162
+ mkComp typ name bi fps mods =
163
+ [Comp typ name (T. pack fp)
164
+ | fp0 <- fps <> concatMap toFilePath' mods
165
+ , srcDir <- map getSymbolicPath $ hsSourceDirs bi
166
+ , let fp = srcDir </> fp0
167
+ , unsafePerformIO $ doesFileExist fp
168
+ ]
169
+
170
+ unqName = T. pack . unUnqualComponentName
171
+ libName' x = case libName x of
172
+ LMainLibName -> " "
173
+ LSubLibName u -> unqName u
174
+
175
+ benchmarkExePath :: Benchmark -> [FilePath ]
176
+ benchmarkExePath b = case benchmarkInterface b of
177
+ BenchmarkExeV10 _ f -> [f]
178
+ _ -> []
179
+
180
+ toFilePath' :: ModuleName -> [FilePath ]
181
+ toFilePath' mod = [ toFilePath mod <.> ext | ext <- [" hs" , " lhs" ]]
182
+
183
+ testExePath :: TestSuite -> [FilePath ]
184
+ testExePath t = case testInterface t of
185
+ TestSuiteExeV10 _ fp -> [fp]
186
+ _ -> []
0 commit comments