cabal-install-2.2.0.0: Distribution/Client/ParseUtils.hs
{-# LANGUAGE ExistentialQuantification, NamedFieldPuns #-}
-----------------------------------------------------------------------------
-- |
-- Module : Distribution.Client.ParseUtils
-- Maintainer : cabal-devel@haskell.org
-- Portability : portable
--
-- Parsing utilities.
-----------------------------------------------------------------------------
module Distribution.Client.ParseUtils (
-- * Fields and field utilities
FieldDescr(..),
liftField,
liftFields,
filterFields,
mapFieldNames,
commandOptionToField,
commandOptionsToFields,
-- * Sections and utilities
SectionDescr(..),
liftSection,
-- * Parsing and printing flat config
parseFields,
ppFields,
ppSection,
-- * Parsing and printing config with sections and subsections
parseFieldsAndSections,
ppFieldsAndSections,
-- ** Top level of config files
parseConfig,
showConfig,
)
where
import Distribution.ParseUtils
( FieldDescr(..), ParseResult(..), warning, LineNo, lineNo
, Field(..), liftField, readFieldsFlat )
import Distribution.Simple.Command
( OptionField, viewAsFieldDescr )
import Control.Monad ( foldM )
import Text.PrettyPrint ( (<+>), ($+$) )
import qualified Data.Map as Map
import qualified Text.PrettyPrint as Disp
( (<>), Doc, text, colon, vcat, empty, isEmpty, nest )
-------------------------
-- FieldDescr utilities
--
liftFields :: (b -> a)
-> (a -> b -> b)
-> [FieldDescr a]
-> [FieldDescr b]
liftFields get set = map (liftField get set)
-- | Given a collection of field descriptions, keep only a given list of them,
-- identified by name.
--
filterFields :: [String] -> [FieldDescr a] -> [FieldDescr a]
filterFields includeFields = filter ((`elem` includeFields) . fieldName)
-- | Apply a name mangling function to the field names of all the field
-- descriptions. The typical use case is to apply some prefix.
--
mapFieldNames :: (String -> String) -> [FieldDescr a] -> [FieldDescr a]
mapFieldNames mangleName =
map (\descr -> descr { fieldName = mangleName (fieldName descr) })
-- | Reuse a command line 'OptionField' as a config file 'FieldDescr'.
--
commandOptionToField :: OptionField a -> FieldDescr a
commandOptionToField = viewAsFieldDescr
-- | Reuse a bunch of command line 'OptionField's as config file 'FieldDescr's.
--
commandOptionsToFields :: [OptionField a] -> [FieldDescr a]
commandOptionsToFields = map viewAsFieldDescr
------------------------------------------
-- SectionDescr definition and utilities
--
-- | The description of a section in a config file. It can contain both
-- fields and optionally further subsections. See also 'FieldDescr'.
--
data SectionDescr a = forall b. SectionDescr {
sectionName :: String,
sectionFields :: [FieldDescr b],
sectionSubsections :: [SectionDescr b],
sectionGet :: a -> [(String, b)],
sectionSet :: LineNo -> String -> b -> a -> ParseResult a,
sectionEmpty :: b
}
-- | To help construction of config file descriptions in a modular way it is
-- useful to define fields and sections on local types and then hoist them
-- into the parent types when combining them in bigger descriptions.
--
-- This is essentially a lens operation for 'SectionDescr' to help embedding
-- one inside another.
--
liftSection :: (b -> a)
-> (a -> b -> b)
-> SectionDescr a
-> SectionDescr b
liftSection get' set' (SectionDescr name fields sections get set empty) =
let sectionGet' = get . get'
sectionSet' lineno param x y = do
x' <- set lineno param x (get' y)
return (set' x' y)
in SectionDescr name fields sections sectionGet' sectionSet' empty
-------------------------------------
-- Parsing and printing flat config
--
-- | Parse a bunch of semi-parsed 'Field's according to a set of field
-- descriptions. It accumulates the result on top of a given initial value.
--
-- This only covers the case of flat configuration without subsections. See
-- also 'parseFieldsAndSections'.
--
parseFields :: [FieldDescr a] -> a -> [Field] -> ParseResult a
parseFields fieldDescrs =
foldM setField
where
fieldMap = Map.fromList [ (fieldName f, f) | f <- fieldDescrs ]
setField accum (F line name value) =
case Map.lookup name fieldMap of
Just (FieldDescr _ _ set) -> set line value accum
Nothing -> do
warning $ "Unrecognized field " ++ name ++ " on line " ++ show line
return accum
setField accum f = do
warning $ "Unrecognized stanza on line " ++ show (lineNo f)
return accum
-- | This is a customised version of the functions from Distribution.ParseUtils
-- that also optionally print default values for empty fields as comments.
--
ppFields :: [FieldDescr a] -> (Maybe a) -> a -> Disp.Doc
ppFields fields def cur =
Disp.vcat [ ppField name (fmap getter def) (getter cur)
| FieldDescr name getter _ <- fields]
ppField :: String -> (Maybe Disp.Doc) -> Disp.Doc -> Disp.Doc
ppField name mdef cur
| Disp.isEmpty cur = maybe Disp.empty
(\def -> Disp.text "--" <+> Disp.text name
Disp.<> Disp.colon <+> def) mdef
| otherwise = Disp.text name Disp.<> Disp.colon <+> cur
-- | Pretty print a section.
--
-- Since 'ppFields' does not cover subsections you can use this to add them.
-- Or alternatively use a 'SectionDescr' and use 'ppFieldsAndSections'.
--
ppSection :: String -> String -> [FieldDescr a] -> (Maybe a) -> a -> Disp.Doc
ppSection name arg fields def cur
| Disp.isEmpty fieldsDoc = Disp.empty
| otherwise = Disp.text name <+> argDoc
$+$ (Disp.nest 2 fieldsDoc)
where
fieldsDoc = ppFields fields def cur
argDoc | arg == "" = Disp.empty
| otherwise = Disp.text arg
-----------------------------------------
-- Parsing and printing non-flat config
--
-- | Much like 'parseFields' but it also allows subsections. The permitted
-- subsections are given by a list of 'SectionDescr's.
--
parseFieldsAndSections :: [FieldDescr a] -> [SectionDescr a] -> a
-> [Field] -> ParseResult a
parseFieldsAndSections fieldDescrs sectionDescrs =
foldM setField
where
fieldMap = Map.fromList [ (fieldName f, f) | f <- fieldDescrs ]
sectionMap = Map.fromList [ (sectionName s, s) | s <- sectionDescrs ]
setField a (F line name value) =
case Map.lookup name fieldMap of
Just (FieldDescr _ _ set) -> set line value a
Nothing -> do
warning $ "Unrecognized field '" ++ name
++ "' on line " ++ show line
return a
setField a (Section line name param fields) =
case Map.lookup name sectionMap of
Just (SectionDescr _ fieldDescrs' sectionDescrs' _ set sectionEmpty) -> do
b <- parseFieldsAndSections fieldDescrs' sectionDescrs' sectionEmpty fields
set line param b a
Nothing -> do
warning $ "Unrecognized section '" ++ name
++ "' on line " ++ show line
return a
setField accum (block@IfBlock {}) = do
warning $ "Unrecognized stanza on line " ++ show (lineNo block)
return accum
-- | Much like 'ppFields' but also pretty prints any subsections. Subsection
-- are only shown if they are non-empty.
--
-- Note that unlike 'ppFields', at present it does not support printing
-- default values. If needed, adding such support would be quite reasonable.
--
ppFieldsAndSections :: [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc
ppFieldsAndSections fieldDescrs sectionDescrs val =
ppFields fieldDescrs Nothing val
$+$
Disp.vcat
[ Disp.text "" $+$ sectionDoc
| SectionDescr {
sectionName, sectionGet,
sectionFields, sectionSubsections
} <- sectionDescrs
, (param, x) <- sectionGet val
, let sectionDoc = ppSectionAndSubsections
sectionName param
sectionFields sectionSubsections x
, not (Disp.isEmpty sectionDoc)
]
-- | Unlike 'ppSection' which has to be called directly, this gets used via
-- 'ppFieldsAndSections' and so does not need to be exported.
--
ppSectionAndSubsections :: String -> String
-> [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc
ppSectionAndSubsections name arg fields sections cur
| Disp.isEmpty fieldsDoc = Disp.empty
| otherwise = Disp.text name <+> argDoc
$+$ (Disp.nest 2 fieldsDoc)
where
fieldsDoc = showConfig fields sections cur
argDoc | arg == "" = Disp.empty
| otherwise = Disp.text arg
-----------------------------------------------
-- Top level config file parsing and printing
--
-- | Parse a string in the config file syntax into a value, based on a
-- description of the configuration file in terms of its fields and sections.
--
-- It accumulates the result on top of a given initial (typically empty) value.
--
parseConfig :: [FieldDescr a] -> [SectionDescr a] -> a
-> String -> ParseResult a
parseConfig fieldDescrs sectionDescrs empty str =
parseFieldsAndSections fieldDescrs sectionDescrs empty
=<< readFieldsFlat str
-- | Render a value in the config file syntax, based on a description of the
-- configuration file in terms of its fields and sections.
--
showConfig :: [FieldDescr a] -> [SectionDescr a] -> a -> Disp.Doc
showConfig = ppFieldsAndSections