prune-juice-0.7: src/Data/Prune/Section/Parser.hs
-- |Description: Parser for the "Data.Prune.ApplyStrategy.Smart" strategy.
module Data.Prune.Section.Parser where
import Prelude
import Control.Applicative ((<|>))
import Control.Arrow (left)
import Control.Monad (void)
import Data.Text (pack, unpack)
import Data.Void (Void)
import Text.Megaparsec (Parsec, many, noneOf, parse, some, try)
import Text.Megaparsec.Char (alphaNumChar, char, eol, hspace, hspace1, string)
import qualified Data.Prune.Section.Types as T
import qualified Data.Prune.Types as T
type Parser = Parsec Void String
targetName :: Parser T.CompilableName
targetName = T.CompilableName . pack <$> some (alphaNumChar <|> char '-')
restOfLine :: Parser String
restOfLine = many (noneOf ("\r\n" :: String)) <* eol
emptyLine :: Parser String
emptyLine = "" <$ eol
-- |Parse an indented line with @indentedLine numSpaces@, failing if the line isn't indented to @numSpaces@.
indentedLine :: Int -> Parser String
indentedLine numSpaces = do
spaces <- many (char ' ')
let n = length spaces
case n <= numSpaces of
True -> fail $ "indentation: " <> show n <> " (expected " <> show numSpaces <> ")"
False -> (spaces <>) <$> restOfLine
-- |Parse many indented lines with @indentedLines numSpaces@, traversing empty lines until the line isn't indented to @numSpaces@.
indentedLines :: Int -> Parser [String]
indentedLines numSpaces = (:) <$> restOfLine <*> many (try (indentedLine numSpaces <|> emptyLine))
nestedSection :: Parser T.NestedSection
nestedSection = do
numSpaces <- length <$> some (char ' ')
let buildDepends = do
void $ string "build-depends:"
T.BuildDependsNestedSection numSpaces <$> indentedLines numSpaces
import_ = do
void $ string "import:"
T.ImportNestedSection numSpaces <$> indentedLines numSpaces
other = T.OtherNestedSection numSpaces <$> indentedLines numSpaces
buildDepends <|> import_ <|> other
nestedSections :: Parser [T.NestedSection]
nestedSections = some nestedSection
section :: Parser T.Section
section =
let lib = do
void $ string "library"
hspace
void eol
T.TargetSection T.CompilableTypeLibrary Nothing <$> nestedSections
target typ typName = do
void $ string typName
hspace1
name <- targetName
hspace
void eol
T.TargetSection typ (Just name) <$> nestedSections
common = do
void $ string "common"
hspace1
name <- T.CommonName . pack <$> restOfLine
T.CommonSection name <$> nestedSections
sublib = target T.CompilableTypeLibrary "library"
exe = target T.CompilableTypeExecutable "executable"
test = target T.CompilableTypeTest "test-suite"
bench = target T.CompilableTypeBenchmark "benchmark"
other = T.OtherSection <$> indentedLines 0
in lib <|> sublib <|> exe <|> test <|> bench <|> common <|> other
sections :: Parser [T.Section]
sections = some section
-- |Parse using 'sections'.
parseCabalSections :: String -> Either String [T.Section]
parseCabalSections = left show . parse sections ""
-- |Render sections. @parseCabalSections . renderCabalSections@ should be equivalent to @Right@.
renderCabalSections :: [T.Section] -> String
renderCabalSections = foldr go mempty
where
go2 next accum = case next of
T.BuildDependsNestedSection numSpaces dependencies -> replicate numSpaces ' ' <> "build-depends:" <> unlines dependencies <> accum
T.ImportNestedSection numSpaces imports -> replicate numSpaces ' ' <> "import:" <> unlines imports <> accum
T.OtherNestedSection numSpaces rest -> replicate numSpaces ' ' <> unlines rest <> accum
go next accum =
let str = case next of
T.TargetSection compilableType compilableNameMay nested ->
let sectionType = case compilableType of
T.CompilableTypeLibrary -> "library"
T.CompilableTypeExecutable -> "executable"
T.CompilableTypeTest -> "test-suite"
T.CompilableTypeBenchmark -> "benchmark"
sectionName = case compilableNameMay of
Nothing -> ""
Just (T.CompilableName name) -> " " <> unpack name
in sectionType <> sectionName <> "\n" <> foldr go2 mempty nested
T.CommonSection (T.CommonName name) nested ->
"common " <> unpack name <> "\n" <> foldr go2 mempty nested
T.OtherSection xs -> unlines xs
in str <> accum
-- |Read sections from a file using 'parseCabalSections'.
readCabalSections :: FilePath -> IO (Either String [T.Section])
readCabalSections cabalFile = parseCabalSections <$> readFile cabalFile
-- |Write sections to a file using 'renderCabalSections'.
writeCabalSections :: FilePath -> [T.Section] -> IO ()
writeCabalSections cabalFile = writeFile cabalFile . renderCabalSections