1 {-# LANGUAGE ExistentialQuantification, NamedFieldPuns, RankNTypes #-}
3 -----------------------------------------------------------------------------
5 -- Module : Distribution.Client.ParseUtils
6 -- Maintainer : cabal-devel@haskell.org
7 -- Portability : portable
10 -----------------------------------------------------------------------------
12 module Distribution
.Client
.ParseUtils
(
14 -- * Fields and field utilities
21 commandOptionsToFields
,
23 -- * Sections and utilities
27 -- * FieldGrammar sections
30 -- * Parsing and printing flat config
35 -- * Parsing and printing config with sections and subsections
36 parseFieldsAndSections
,
39 -- ** Top level of config files
45 import Distribution
.Client
.Compat
.Prelude
hiding (empty, get
)
48 import Distribution
.Deprecated
.ParseUtils
49 ( FieldDescr
(..), ParseResult
(..), warning
, LineNo
, lineNo
50 , Field
(..), liftField
, readFields
)
51 import Distribution
.Deprecated
.ViewAsFieldDescr
54 import Distribution
.Simple
.Command
57 import Text
.PrettyPrint
( ($+$) )
58 import qualified Data
.ByteString
as BS
59 import qualified Data
.Map
as Map
60 import qualified Text
.PrettyPrint
as Disp
61 ( (<>), Doc
, text
, colon
, vcat
, empty, isEmpty
, nest
)
63 -- For new parser stuff
64 import Distribution
.CabalSpecVersion
(cabalSpecLatest
)
65 import Distribution
.FieldGrammar
(partitionFields
, parseFieldGrammar
)
66 import Distribution
.Fields
.ParseResult
(runParseResult
)
67 import Distribution
.Parsec
.Error
(showPError
)
68 import Distribution
.Parsec
.Position
(Position
(..))
69 import Distribution
.Parsec
.Warning
(showPWarning
)
70 import Distribution
.Simple
.Utils
(fromUTF8BS
, toUTF8BS
)
71 import qualified Distribution
.Fields
as F
72 import qualified Distribution
.FieldGrammar
as FG
75 -------------------------
76 -- FieldDescr utilities
79 liftFields
:: (b
-> a
)
83 liftFields get set
= map (liftField get set
)
86 -- | Given a collection of field descriptions, keep only a given list of them,
87 -- identified by name.
89 filterFields
:: [String] -> [FieldDescr a
] -> [FieldDescr a
]
90 filterFields includeFields
= filter ((`
elem` includeFields
) . fieldName
)
92 -- | Apply a name mangling function to the field names of all the field
93 -- descriptions. The typical use case is to apply some prefix.
95 mapFieldNames
:: (String -> String) -> [FieldDescr a
] -> [FieldDescr a
]
96 mapFieldNames mangleName
=
97 map (\descr
-> descr
{ fieldName
= mangleName
(fieldName descr
) })
100 -- | Reuse a command line 'OptionField' as a config file 'FieldDescr'.
102 commandOptionToField
:: OptionField a
-> FieldDescr a
103 commandOptionToField
= viewAsFieldDescr
105 -- | Reuse a bunch of command line 'OptionField's as config file 'FieldDescr's.
107 commandOptionsToFields
:: [OptionField a
] -> [FieldDescr a
]
108 commandOptionsToFields
= map viewAsFieldDescr
111 ------------------------------------------
112 -- SectionDescr definition and utilities
115 -- | The description of a section in a config file. It can contain both
116 -- fields and optionally further subsections. See also 'FieldDescr'.
118 data SectionDescr a
= forall b
. SectionDescr
{
119 sectionName
:: String,
120 sectionFields
:: [FieldDescr b
],
121 sectionSubsections
:: [SectionDescr b
],
122 sectionGet
:: a
-> [(String, b
)],
123 sectionSet
:: LineNo
-> String -> b
-> a
-> ParseResult a
,
127 -- | 'FieldGrammar' section description
128 data FGSectionDescr g a
= forall s
. FGSectionDescr
129 { fgSectionName
:: String
130 , fgSectionGrammar
:: g s s
131 -- todo: add subsections?
132 , fgSectionGet
:: a
-> [(String, s
)]
133 , fgSectionSet
:: LineNo
-> String -> s
-> a
-> ParseResult a
136 -- | To help construction of config file descriptions in a modular way it is
137 -- useful to define fields and sections on local types and then hoist them
138 -- into the parent types when combining them in bigger descriptions.
140 -- This is essentially a lens operation for 'SectionDescr' to help embedding
141 -- one inside another.
143 liftSection
:: (b
-> a
)
147 liftSection get
' set
' (SectionDescr name fields sections get set
empty) =
148 let sectionGet
' = get
. get
'
149 sectionSet
' lineno param x y
= do
150 x
' <- set lineno param x
(get
' y
)
152 in SectionDescr name fields sections sectionGet
' sectionSet
' empty
155 -------------------------------------
156 -- Parsing and printing flat config
159 -- | Parse a bunch of semi-parsed 'Field's according to a set of field
160 -- descriptions. It accumulates the result on top of a given initial value.
162 -- This only covers the case of flat configuration without subsections. See
163 -- also 'parseFieldsAndSections'.
165 parseFields
:: [FieldDescr a
] -> a
-> [Field
] -> ParseResult a
166 parseFields fieldDescrs
=
169 fieldMap
= Map
.fromList
[ (fieldName f
, f
) | f
<- fieldDescrs
]
171 setField
accum (F line name
value) =
172 case Map
.lookup name fieldMap
of
173 Just
(FieldDescr _ _ set
) -> set line
value accum
175 -- the 'world-file' field was removed in 3.8, however
176 -- it was automatically added to many config files
177 -- before that, so its warning is silently ignored
178 unless (name
== "world-file") $
179 warning
$ "Unrecognized field " ++ name
++ " on line " ++ show line
182 setField
accum f
= do
183 warning
$ "Unrecognized stanza on line " ++ show (lineNo f
)
186 -- | This is a customised version of the functions from Distribution.Deprecated.ParseUtils
187 -- that also optionally print default values for empty fields as comments.
189 ppFields
:: [FieldDescr a
] -> (Maybe a
) -> a
-> Disp
.Doc
190 ppFields fields def cur
=
191 Disp
.vcat
[ ppField name
(fmap getter def
) (getter cur
)
192 | FieldDescr name getter _
<- fields
]
194 ppField
:: String -> (Maybe Disp
.Doc
) -> Disp
.Doc
-> Disp
.Doc
195 ppField name mdef cur
196 | Disp
.isEmpty cur
= maybe Disp
.empty
197 (\def
-> Disp
.text
"--" <+> Disp
.text name
198 Disp
.<> Disp
.colon
<+> def
) mdef
199 |
otherwise = Disp
.text name Disp
.<> Disp
.colon
<+> cur
201 -- | Pretty print a section.
203 -- Since 'ppFields' does not cover subsections you can use this to add them.
204 -- Or alternatively use a 'SectionDescr' and use 'ppFieldsAndSections'.
206 ppSection
:: String -> String -> [FieldDescr a
] -> (Maybe a
) -> a
-> Disp
.Doc
207 ppSection name arg fields def cur
208 | Disp
.isEmpty fieldsDoc
= Disp
.empty
209 |
otherwise = Disp
.text name
<+> argDoc
210 $+$ (Disp
.nest
2 fieldsDoc
)
212 fieldsDoc
= ppFields fields def cur
213 argDoc | arg
== "" = Disp
.empty
214 |
otherwise = Disp
.text arg
217 -----------------------------------------
218 -- Parsing and printing non-flat config
221 -- | Much like 'parseFields' but it also allows subsections. The permitted
222 -- subsections are given by a list of 'SectionDescr's.
224 parseFieldsAndSections
225 :: [FieldDescr a
] -- ^ field
226 -> [SectionDescr a
] -- ^ legacy sections
227 -> [FGSectionDescr FG
.ParsecFieldGrammar a
] -- ^ FieldGrammar sections
229 -> [Field
] -> ParseResult a
230 parseFieldsAndSections fieldDescrs sectionDescrs fgSectionDescrs
=
233 fieldMap
= Map
.fromList
[ (fieldName f
, f
) | f
<- fieldDescrs
]
234 sectionMap
= Map
.fromList
[ (sectionName s
, s
) | s
<- sectionDescrs
]
235 fgSectionMap
= Map
.fromList
[ (fgSectionName s
, s
) | s
<- fgSectionDescrs
]
237 setField a
(F line name
value) =
238 case Map
.lookup name fieldMap
of
239 Just
(FieldDescr _ _ set
) -> set line
value a
241 warning
$ "Unrecognized field '" ++ name
242 ++ "' on line " ++ show line
245 setField a
(Section line name param fields
) =
246 case Left
<$> Map
.lookup name sectionMap
<|
> Right
<$> Map
.lookup name fgSectionMap
of
247 Just
(Left
(SectionDescr _ fieldDescrs
' sectionDescrs
' _ set sectionEmpty
)) -> do
248 b
<- parseFieldsAndSections fieldDescrs
' sectionDescrs
' [] sectionEmpty fields
250 Just
(Right
(FGSectionDescr _ grammar _getter setter
)) -> do
251 let fields1
= map convertField fields
252 (fields2
, sections
) = partitionFields fields1
253 -- TODO: recurse into sections
254 for_
(concat sections
) $ \(FG
.MkSection
(F
.Name
(Position line
' _
) name
') _ _
) ->
255 warning
$ "Unrecognized section '" ++ fromUTF8BS name
'
256 ++ "' on line " ++ show line
'
257 case runParseResult
$ parseFieldGrammar cabalSpecLatest fields2 grammar
of
258 (warnings
, Right b
) -> do
259 for_ warnings
$ \w
-> warning
$ showPWarning
"???" w
260 setter line param b a
261 (warnings
, Left
(_
, errs
)) -> do
262 for_ warnings
$ \w
-> warning
$ showPWarning
"???" w
264 err
:| _errs
-> fail $ showPError
"???" err
266 warning
$ "Unrecognized section '" ++ name
267 ++ "' on line " ++ show line
270 convertField
:: Field
-> F
.Field Position
271 convertField
(F line name str
) =
272 F
.Field
(F
.Name pos
(toUTF8BS name
)) [ F
.FieldLine pos
$ toUTF8BS str
]
274 pos
= Position line
0
276 convertField
(Section line name _arg fields
) =
277 F
.Section
(F
.Name pos
(toUTF8BS name
)) [] (map convertField fields
)
279 pos
= Position line
0
281 -- | Much like 'ppFields' but also pretty prints any subsections. Subsection
282 -- are only shown if they are non-empty.
284 -- Note that unlike 'ppFields', at present it does not support printing
285 -- default values. If needed, adding such support would be quite reasonable.
287 ppFieldsAndSections
:: [FieldDescr a
] -> [SectionDescr a
] -> [FGSectionDescr FG
.PrettyFieldGrammar a
] -> a
-> Disp
.Doc
288 ppFieldsAndSections fieldDescrs sectionDescrs fgSectionDescrs val
=
289 ppFields fieldDescrs Nothing val
292 [ Disp
.text
"" $+$ sectionDoc
294 sectionName
, sectionGet
,
295 sectionFields
, sectionSubsections
297 , (param
, x
) <- sectionGet val
298 , let sectionDoc
= ppSectionAndSubsections
300 sectionFields sectionSubsections
[] x
301 , not (Disp
.isEmpty sectionDoc
)
303 [ Disp
.text
"" $+$ sectionDoc
304 | FGSectionDescr
{ fgSectionName
, fgSectionGrammar
, fgSectionGet
} <- fgSectionDescrs
305 , (param
, x
) <- fgSectionGet val
306 , let sectionDoc
= ppFgSection fgSectionName param fgSectionGrammar x
307 , not (Disp
.isEmpty sectionDoc
)
310 -- | Unlike 'ppSection' which has to be called directly, this gets used via
311 -- 'ppFieldsAndSections' and so does not need to be exported.
313 ppSectionAndSubsections
:: String -> String
314 -> [FieldDescr a
] -> [SectionDescr a
] -> [FGSectionDescr FG
.PrettyFieldGrammar a
] -> a
-> Disp
.Doc
315 ppSectionAndSubsections name arg fields sections fgSections cur
316 | Disp
.isEmpty fieldsDoc
= Disp
.empty
317 |
otherwise = Disp
.text name
<+> argDoc
318 $+$ (Disp
.nest
2 fieldsDoc
)
320 fieldsDoc
= showConfig fields sections fgSections cur
321 argDoc | arg
== "" = Disp
.empty
322 |
otherwise = Disp
.text arg
327 -- TODO: this should simply build 'PrettyField'
329 :: String -- ^ section name
330 -> String -- ^ parameter
331 -> FG
.PrettyFieldGrammar a a
334 ppFgSection secName arg grammar x
335 |
null prettyFields
= Disp
.empty
337 Disp
.text secName
<+> argDoc
338 $+$ (Disp
.nest
2 fieldsDoc
)
340 prettyFields
= FG
.prettyFieldGrammar cabalSpecLatest grammar x
342 argDoc | arg
== "" = Disp
.empty
343 |
otherwise = Disp
.text arg
345 fieldsDoc
= Disp
.vcat
346 [ Disp
.text fname
' <<>> Disp
.colon
<<>> doc
347 | F
.PrettyField _ fname doc
<- prettyFields
-- TODO: this skips sections
348 , let fname
' = fromUTF8BS fname
352 -----------------------------------------------
353 -- Top level config file parsing and printing
356 -- | Parse a string in the config file syntax into a value, based on a
357 -- description of the configuration file in terms of its fields and sections.
359 -- It accumulates the result on top of a given initial (typically empty) value.
361 parseConfig
:: [FieldDescr a
] -> [SectionDescr a
] -> [FGSectionDescr FG
.ParsecFieldGrammar a
] -> a
362 -> BS
.ByteString
-> ParseResult a
363 parseConfig fieldDescrs sectionDescrs fgSectionDescrs
empty str
=
364 parseFieldsAndSections fieldDescrs sectionDescrs fgSectionDescrs
empty
367 -- | Render a value in the config file syntax, based on a description of the
368 -- configuration file in terms of its fields and sections.
370 showConfig
:: [FieldDescr a
] -> [SectionDescr a
] -> [FGSectionDescr FG
.PrettyFieldGrammar a
] -> a
-> Disp
.Doc
371 showConfig
= ppFieldsAndSections