module CabalFmt.Error (Error (..), renderError) where
import System.FilePath (normalise)
import Text.Parsec.Error (ParseError)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS8
import qualified Distribution.Parsec as C
import qualified Distribution.Simple.Utils as C (fromUTF8BS)
import qualified Distribution.Types.Version as C
data Error
= SomeError String
| CabalParseError FilePath BS.ByteString [C.PError] (Maybe C.Version) [C.PWarning]
| PanicCannotParseInput ParseError
deriving (Show)
renderError :: Error -> IO ()
renderError (SomeError err) = putStrLn $ "error: " ++ err
renderError (PanicCannotParseInput err) = putStrLn $ "panic! " ++ show err
renderError (CabalParseError filepath contents errors _ warnings) =
putStr $ renderParseError filepath contents errors warnings
renderParseError
:: FilePath
-> BS.ByteString
-> [C.PError]
-> [C.PWarning]
-> String
renderParseError filepath contents errors warnings = unlines $
[ "Errors encountered when parsing cabal file " <> filepath <> ":"
]
++ renderedErrors
++ renderedWarnings
where
filepath' = normalise filepath
rows :: [(String, Int, Bool)]
rows = zipWith f (BS8.lines contents) [1..] where
f bs i = let s = C.fromUTF8BS bs in (s, i, isEmptyOrComment s)
rowsZipper = listToZipper rows
isEmptyOrComment :: String -> Bool
isEmptyOrComment s = case dropWhile (== ' ') s of
"" -> True
('-':'-':_) -> True
_ -> False
renderedErrors = concatMap renderError' errors
renderedWarnings = concatMap renderWarning warnings
renderError' :: C.PError -> [String]
renderError' (C.PError pos@(C.Position row col) msg)
| pos == C.zeroPos = msgs
| otherwise = msgs ++ formatInput row col
where
msgs = [ "", filepath' ++ ":" ++ C.showPos pos ++ ": error:", trimLF msg, "" ]
renderWarning :: C.PWarning -> [String]
renderWarning (C.PWarning _ pos@(C.Position row col) msg)
| pos == C.zeroPos = msgs
| otherwise = msgs ++ formatInput row col
where
msgs = [ "", filepath' ++ ":" ++ C.showPos pos ++ ": warning:", trimLF msg, "" ]
trimLF :: String -> String
trimLF = dropWhile (== '\n') . reverse . dropWhile (== '\n') . reverse
formatInput :: Int -> Int -> [String]
formatInput row col = case advance (row - 1) rowsZipper of
Zipper xs ys -> before ++ after where
before = case span (\(_, _, b) -> b) xs of
(_, []) -> []
(zs, z : _) -> map formatInputLine $ z : reverse zs
after = case ys of
[] -> []
(z : _zs) ->
[ formatInputLine z
, " | " ++ replicate (col - 1) ' ' ++ "^"
]
formatInputLine :: (String, Int, Bool) -> String
formatInputLine (str, row, _) = leftPadShow row ++ " | " ++ str
leftPadShow :: Int -> String
leftPadShow n = let s = show n in replicate (5 - length s) ' ' ++ s
data Zipper a = Zipper [a] [a]
listToZipper :: [a] -> Zipper a
listToZipper = Zipper []
advance :: Int -> Zipper a -> Zipper a
advance n z@(Zipper xs ys)
| n <= 0 = z
| otherwise = case ys of
[] -> z
(y:ys') -> advance (n - 1) $ Zipper (y:xs) ys'