1 {-# LANGUAGE ExistentialQuantification #-}
2 {-# LANGUAGE NamedFieldPuns #-}
3 {-# LANGUAGE RankNTypes #-}
5 -----------------------------------------------------------------------------
7 -----------------------------------------------------------------------------
10 -- Module : Distribution.Client.ParseUtils
11 -- Maintainer : cabal-devel@haskell.org
12 -- Portability : portable
15 module Distribution
.Client
.ParseUtils
16 ( -- * Fields and field utilities
24 , commandOptionToField
25 , commandOptionsToFields
27 -- * Sections and utilities
31 -- * FieldGrammar sections
34 -- * Parsing and printing flat config
39 -- * Parsing and printing config with sections and subsections
40 , parseFieldsAndSections
43 -- ** Top level of config files
49 import Distribution
.Client
.Compat
.Prelude
hiding (empty, get
)
52 import Distribution
.Deprecated
.ParseUtils
62 import Distribution
.Deprecated
.ViewAsFieldDescr
66 import Distribution
.Simple
.Command
70 import qualified Data
.ByteString
as BS
71 import qualified Data
.Map
as Map
72 import Text
.PrettyPrint
(($+$))
73 import qualified Text
.PrettyPrint
as Disp
84 -- For new parser stuff
85 import Distribution
.CabalSpecVersion
(cabalSpecLatest
)
86 import Distribution
.FieldGrammar
(parseFieldGrammar
, partitionFields
)
87 import qualified Distribution
.FieldGrammar
as FG
88 import qualified Distribution
.Fields
as F
89 import Distribution
.Fields
.ParseResult
(runParseResult
)
90 import Distribution
.Parsec
.Error
(showPError
)
91 import Distribution
.Parsec
.Position
(Position
(..))
92 import Distribution
.Parsec
.Warning
(showPWarning
)
93 import Distribution
.Simple
.Utils
(fromUTF8BS
, toUTF8BS
)
95 -------------------------
96 -- FieldDescr utilities
104 liftFields get set
= map (liftField get set
)
106 -- | Given a collection of field descriptions, keep only a given list of them,
107 -- identified by name.
109 -- TODO: This makes it easy to footgun by providing a non-existent field name.
110 filterFields
:: [String] -> [FieldDescr a
] -> [FieldDescr a
]
111 filterFields includeFields
= filter ((`
elem` includeFields
) . fieldName
)
113 -- | Given a collection of field descriptions, get a field with a given name.
114 getField
:: String -> [FieldDescr a
] -> Maybe (FieldDescr a
)
115 getField name
= find ((== name
) . fieldName
)
117 -- | Apply a name mangling function to the field names of all the field
118 -- descriptions. The typical use case is to apply some prefix.
119 mapFieldNames
:: (String -> String) -> [FieldDescr a
] -> [FieldDescr a
]
120 mapFieldNames mangleName
=
121 map (\descr
-> descr
{fieldName
= mangleName
(fieldName descr
)})
123 -- | Reuse a command line 'OptionField' as a config file 'FieldDescr'.
124 commandOptionToField
:: OptionField a
-> FieldDescr a
125 commandOptionToField
= viewAsFieldDescr
127 -- | Reuse a bunch of command line 'OptionField's as config file 'FieldDescr's.
128 commandOptionsToFields
:: [OptionField a
] -> [FieldDescr a
]
129 commandOptionsToFields
= map viewAsFieldDescr
131 -- | Add fields to a field list.
134 -> ([FieldDescr a
] -> [FieldDescr a
])
137 -- | Add a new field which is identical to an existing field but with a
141 -- ^ The existing field name.
143 -- ^ The new field name.
146 aliasField oldName newName fields
=
147 let fieldToRename
= getField oldName fields
148 in case fieldToRename
of
149 -- TODO: Should this throw?
151 Just fieldToRename
' ->
152 let newField
= fieldToRename
'{fieldName
= newName
}
155 ------------------------------------------
156 -- SectionDescr definition and utilities
159 -- | The description of a section in a config file. It can contain both
160 -- fields and optionally further subsections. See also 'FieldDescr'.
161 data SectionDescr a
= forall b
.
163 { sectionName
:: String
164 , sectionFields
:: [FieldDescr b
]
165 , sectionSubsections
:: [SectionDescr b
]
166 , sectionGet
:: a
-> [(String, b
)]
167 , sectionSet
:: LineNo
-> String -> b
-> a
-> ParseResult a
171 -- | 'FieldGrammar' section description
172 data FGSectionDescr g a
= forall s
.
174 { fgSectionName
:: String
175 , fgSectionGrammar
:: g s s
176 , -- todo: add subsections?
177 fgSectionGet
:: a
-> [(String, s
)]
178 , fgSectionSet
:: LineNo
-> String -> s
-> a
-> ParseResult a
181 -- | To help construction of config file descriptions in a modular way it is
182 -- useful to define fields and sections on local types and then hoist them
183 -- into the parent types when combining them in bigger descriptions.
185 -- This is essentially a lens operation for 'SectionDescr' to help embedding
186 -- one inside another.
192 liftSection get
' set
' (SectionDescr name fields sections get set
empty) =
193 let sectionGet
' = get
. get
'
194 sectionSet
' lineno param x y
= do
195 x
' <- set lineno param x
(get
' y
)
197 in SectionDescr name fields sections sectionGet
' sectionSet
' empty
199 -------------------------------------
200 -- Parsing and printing flat config
203 -- | Parse a bunch of semi-parsed 'Field's according to a set of field
204 -- descriptions. It accumulates the result on top of a given initial value.
206 -- This only covers the case of flat configuration without subsections. See
207 -- also 'parseFieldsAndSections'.
208 parseFields
:: [FieldDescr a
] -> a
-> [Field
] -> ParseResult a
209 parseFields fieldDescrs
=
212 fieldMap
= Map
.fromList
[(fieldName f
, f
) | f
<- fieldDescrs
]
214 setField
accum (F line name
value) =
215 case Map
.lookup name fieldMap
of
216 Just
(FieldDescr _ _ set
) -> set line
value accum
218 -- the 'world-file' field was removed in 3.8, however
219 -- it was automatically added to many config files
220 -- before that, so its warning is silently ignored
221 unless (name
== "world-file") $
223 "Unrecognized field " ++ name
++ " on line " ++ show line
225 setField
accum f
= do
226 warning
$ "Unrecognized stanza on line " ++ show (lineNo f
)
229 -- | This is a customised version of the functions from Distribution.Deprecated.ParseUtils
230 -- that also optionally print default values for empty fields as comments.
231 ppFields
:: [FieldDescr a
] -> (Maybe a
) -> a
-> Disp
.Doc
232 ppFields fields def cur
=
234 [ ppField name
(fmap getter def
) (getter cur
)
235 | FieldDescr name getter _
<- fields
238 ppField
:: String -> (Maybe Disp
.Doc
) -> Disp
.Doc
-> Disp
.Doc
239 ppField name mdef cur
250 |
otherwise = Disp
.text name Disp
.<> Disp
.colon
<+> cur
252 -- | Pretty print a section.
254 -- Since 'ppFields' does not cover subsections you can use this to add them.
255 -- Or alternatively use a 'SectionDescr' and use 'ppFieldsAndSections'.
256 ppSection
:: String -> String -> [FieldDescr a
] -> (Maybe a
) -> a
-> Disp
.Doc
257 ppSection name arg fields def cur
258 | Disp
.isEmpty fieldsDoc
= Disp
.empty
262 $+$ (Disp
.nest
2 fieldsDoc
)
264 fieldsDoc
= ppFields fields def cur
266 | arg
== "" = Disp
.empty
267 |
otherwise = Disp
.text arg
269 -----------------------------------------
270 -- Parsing and printing non-flat config
273 -- | Much like 'parseFields' but it also allows subsections. The permitted
274 -- subsections are given by a list of 'SectionDescr's.
275 parseFieldsAndSections
280 -> [FGSectionDescr FG
.ParsecFieldGrammar a
]
281 -- ^ FieldGrammar sections
285 parseFieldsAndSections fieldDescrs sectionDescrs fgSectionDescrs
=
288 fieldMap
= Map
.fromList
[(fieldName f
, f
) | f
<- fieldDescrs
]
289 sectionMap
= Map
.fromList
[(sectionName s
, s
) | s
<- sectionDescrs
]
290 fgSectionMap
= Map
.fromList
[(fgSectionName s
, s
) | s
<- fgSectionDescrs
]
292 setField a
(F line name
value) =
293 case Map
.lookup name fieldMap
of
294 Just
(FieldDescr _ _ set
) -> set line
value a
297 "Unrecognized field '"
302 setField a
(Section line name param fields
) =
303 case Left
<$> Map
.lookup name sectionMap
<|
> Right
<$> Map
.lookup name fgSectionMap
of
304 Just
(Left
(SectionDescr _ fieldDescrs
' sectionDescrs
' _ set sectionEmpty
)) -> do
305 b
<- parseFieldsAndSections fieldDescrs
' sectionDescrs
' [] sectionEmpty fields
307 Just
(Right
(FGSectionDescr _ grammar _getter setter
)) -> do
308 let fields1
= map convertField fields
309 (fields2
, sections
) = partitionFields fields1
310 -- TODO: recurse into sections
311 for_
(concat sections
) $ \(FG
.MkSection
(F
.Name
(Position line
' _
) name
') _ _
) ->
313 "Unrecognized section '"
317 case runParseResult
$ parseFieldGrammar cabalSpecLatest fields2 grammar
of
318 (warnings
, Right b
) -> do
319 for_ warnings
$ \w
-> warning
$ showPWarning
"???" w
320 setter line param b a
321 (warnings
, Left
(_
, errs
)) -> do
322 for_ warnings
$ \w
-> warning
$ showPWarning
"???" w
324 err
:| _errs
-> fail $ showPError
"???" err
327 "Unrecognized section '"
333 convertField
:: Field
-> F
.Field Position
334 convertField
(F line name str
) =
335 F
.Field
(F
.Name pos
(toUTF8BS name
)) [F
.FieldLine pos
$ toUTF8BS str
]
337 pos
= Position line
0
339 convertField
(Section line name _arg fields
) =
340 F
.Section
(F
.Name pos
(toUTF8BS name
)) [] (map convertField fields
)
342 pos
= Position line
0
344 -- | Much like 'ppFields' but also pretty prints any subsections. Subsection
345 -- are only shown if they are non-empty.
347 -- Note that unlike 'ppFields', at present it does not support printing
348 -- default values. If needed, adding such support would be quite reasonable.
349 ppFieldsAndSections
:: [FieldDescr a
] -> [SectionDescr a
] -> [FGSectionDescr FG
.PrettyFieldGrammar a
] -> a
-> Disp
.Doc
350 ppFieldsAndSections fieldDescrs sectionDescrs fgSectionDescrs val
=
351 ppFields fieldDescrs Nothing val
353 ( [ Disp
.text
"" $+$ sectionDoc
361 , (param
, x
) <- sectionGet val
363 ppSectionAndSubsections
370 , not (Disp
.isEmpty sectionDoc
)
372 ++ [ Disp
.text
"" $+$ sectionDoc
373 | FGSectionDescr
{fgSectionName
, fgSectionGrammar
, fgSectionGet
} <- fgSectionDescrs
374 , (param
, x
) <- fgSectionGet val
375 , let sectionDoc
= ppFgSection fgSectionName param fgSectionGrammar x
376 , not (Disp
.isEmpty sectionDoc
)
380 -- | Unlike 'ppSection' which has to be called directly, this gets used via
381 -- 'ppFieldsAndSections' and so does not need to be exported.
382 ppSectionAndSubsections
387 -> [FGSectionDescr FG
.PrettyFieldGrammar a
]
390 ppSectionAndSubsections name arg fields sections fgSections cur
391 | Disp
.isEmpty fieldsDoc
= Disp
.empty
395 $+$ (Disp
.nest
2 fieldsDoc
)
397 fieldsDoc
= showConfig fields sections fgSections cur
399 | arg
== "" = Disp
.empty
400 |
otherwise = Disp
.text arg
405 -- TODO: this should simply build 'PrettyField'
411 -> FG
.PrettyFieldGrammar a a
414 ppFgSection secName arg grammar x
415 |
null prettyFields
= Disp
.empty
419 $+$ (Disp
.nest
2 fieldsDoc
)
421 prettyFields
= FG
.prettyFieldGrammar cabalSpecLatest grammar x
424 | arg
== "" = Disp
.empty
425 |
otherwise = Disp
.text arg
429 [ Disp
.text fname
' <<>> Disp
.colon
<<>> doc
430 | F
.PrettyField _ fname doc
<- prettyFields
-- TODO: this skips sections
431 , let fname
' = fromUTF8BS fname
434 -----------------------------------------------
435 -- Top level config file parsing and printing
438 -- | Parse a string in the config file syntax into a value, based on a
439 -- description of the configuration file in terms of its fields and sections.
441 -- It accumulates the result on top of a given initial (typically empty) value.
445 -> [FGSectionDescr FG
.ParsecFieldGrammar a
]
449 parseConfig fieldDescrs sectionDescrs fgSectionDescrs
empty str
=
450 parseFieldsAndSections fieldDescrs sectionDescrs fgSectionDescrs
empty
453 -- | Render a value in the config file syntax, based on a description of the
454 -- configuration file in terms of its fields and sections.
455 showConfig
:: [FieldDescr a
] -> [SectionDescr a
] -> [FGSectionDescr FG
.PrettyFieldGrammar a
] -> a
-> Disp
.Doc
456 showConfig
= ppFieldsAndSections