{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module CabalFmt (cabalFmt) where
import Control.Monad (foldM, join)
import Control.Monad.Reader (asks, local)
import qualified Data.ByteString as BS
import qualified Distribution.CabalSpecVersion as C
import qualified Distribution.FieldGrammar.Parsec as C
import qualified Distribution.Fields as C
import qualified Distribution.Fields.ConfVar as C
import qualified Distribution.Fields.Pretty as C
import qualified Distribution.PackageDescription.FieldGrammar as C
import qualified Distribution.Parsec as C
import qualified Distribution.Pretty as C
import qualified Distribution.Simple.Utils as C
import qualified Distribution.Types.Condition as C
import qualified Distribution.Types.ConfVar as C
import qualified Distribution.Types.GenericPackageDescription as C
import qualified Distribution.Types.PackageDescription as C
import qualified Distribution.Types.Version as C
import qualified Text.PrettyPrint as PP
import CabalFmt.Comments
import CabalFmt.Fields
import CabalFmt.Fields.BuildDepends
import CabalFmt.Fields.Extensions
import CabalFmt.Fields.Modules
import CabalFmt.Fields.TestedWith
import CabalFmt.Monad
import CabalFmt.Options
import CabalFmt.Parser
import CabalFmt.Pragma
import CabalFmt.Prelude
import CabalFmt.Refactoring
cabalFmt :: MonadCabalFmt r m => FilePath -> BS.ByteString -> m String
cabalFmt filepath contents = do
inputFields' <- parseFields contents
let (inputFieldsC, endComments) = attachComments contents inputFields'
let parse c = case parsePragmas c of (ws, ps) -> traverse_ displayWarning ws *> return (c, ps)
inputFieldsP <- traverse (traverse parse) inputFieldsC
endCommentsPragmas <- case parsePragmas endComments of
(ws, ps) -> traverse_ displayWarning ws *> return ps
inputFieldsR <- foldM (&) inputFieldsP refactorings
let pragmas :: [Pragma]
pragmas = foldMap (foldMap snd) inputFieldsR <> endCommentsPragmas
optsEndo :: OptionsMorphism
optsEndo = foldMap pragmaToOM pragmas
cabalFile <- asks (optCabalFile . view options)
csv <- case cabalFile of
False -> return C.cabalSpecLatest
True -> do
gpd <- parseGpd filepath contents
return $ C.cabalSpecFromVersionDigits
$ C.versionNumbers
$ C.specVersion
$ C.packageDescription gpd
local (over options $ \o -> runOptionsMorphism optsEndo $ o { optSpecVersion = csv }) $ do
indentWith <- asks (optIndent . view options)
let inputFields = fmap (fmap fst) inputFieldsR
outputPrettyFields <- C.genericFromParsecFields
prettyFieldLines
prettySectionArgs
inputFields
return $ C.showFields' fromComments indentWith outputPrettyFields
& if nullComments endComments then id else
(++ unlines ("" : [ C.fromUTF8BS c | c <- unComments endComments ]))
fromComments :: Comments -> [String]
fromComments (Comments bss) = map C.fromUTF8BS bss
refactorings :: MonadCabalFmt r m => [Refactoring' r m]
refactorings =
[ refactoringFragments
, refactoringExpandExposedModules
]
prettyFieldLines :: MonadCabalFmt r m => C.FieldName -> [C.FieldLine ann] -> m PP.Doc
prettyFieldLines fn fls =
fromMaybe (C.prettyFieldLines fn fls) <$> knownField fn fls
knownField :: MonadCabalFmt r m => C.FieldName -> [C.FieldLine ann] -> m (Maybe PP.Doc)
knownField fn fls = do
opts <- asks (view options)
let v = optSpecVersion opts
return $ join $ fieldDescrLookup (fieldDescrs opts) fn $ \p pp ->
case C.runParsecParser' v p "<input>" (C.fieldLinesToStream fls) of
Right x -> Just (pp x)
Left _ -> Nothing
fieldDescrs :: Options -> FieldDescrs () ()
fieldDescrs opts
= buildDependsF opts
<> setupDependsF opts
<> defaultExtensionsF
<> otherExtensionsF
<> exposedModulesF
<> otherModulesF
<> testedWithF opts
<> coerceFieldDescrs C.packageDescriptionFieldGrammar
<> coerceFieldDescrs C.buildInfoFieldGrammar
prettySectionArgs :: MonadCabalFmt r m => C.FieldName -> [C.SectionArg ann] -> m [PP.Doc]
prettySectionArgs x args =
prettySectionArgs' x args `catchError` \_ ->
return (C.prettySectionArgs x args)
prettySectionArgs' :: MonadCabalFmt r m => a -> [C.SectionArg ann] -> m [PP.Doc]
prettySectionArgs' _ args = do
c <- runParseResult "<args>" "" $ C.parseConditionConfVar (map (C.zeroPos <$) args)
return [ppCondition c]
ppCondition :: C.Condition C.ConfVar -> PP.Doc
ppCondition (C.Var x) = ppConfVar x
ppCondition (C.Lit b) = PP.text (show b)
ppCondition (C.CNot c) = PP.char '!' PP.<> ppCondition c
ppCondition (C.COr c1 c2) = PP.parens (PP.hsep [ppCondition c1, PP.text "||", ppCondition c2])
ppCondition (C.CAnd c1 c2) = PP.parens (PP.hsep [ppCondition c1, PP.text "&&", ppCondition c2])
ppConfVar :: C.ConfVar -> PP.Doc
ppConfVar (C.OS os) = PP.text "os" PP.<> PP.parens (C.pretty os)
ppConfVar (C.Arch arch) = PP.text "arch" PP.<> PP.parens (C.pretty arch)
ppConfVar (C.Flag name) = PP.text "flag" PP.<> PP.parens (C.pretty name)
ppConfVar (C.Impl c v) = PP.text "impl" PP.<> PP.parens (C.pretty c PP.<+> C.pretty v)
pragmaToOM :: Pragma -> OptionsMorphism
pragmaToOM (PragmaOptIndent n) = mkOptionsMorphism $ \opts -> opts { optIndent = n }
pragmaToOM (PragmaOptTabular b) = mkOptionsMorphism $ \opts -> opts { optTabular = b }
pragmaToOM PragmaExpandModules {} = mempty
pragmaToOM PragmaFragment {} = mempty