{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric      #-}
{-# LANGUAGE OverloadedStrings  #-}
{- |
   Module      : Text.Pandoc.Error
   Copyright   : Copyright (C) 2006-2022 John MacFarlane
   License     : GNU GPL, version 2 or above

   Maintainer  : John MacFarlane <jgm@berkeley.edu>
   Stability   : alpha
   Portability : portable

This module provides a standard way to deal with possible errors
encountered during parsing.

-}
module Text.Pandoc.Error (
  PandocError(..),
  renderError,
  handleError) where

import Control.Exception (Exception, displayException)
import Data.Typeable (Typeable)
import Data.Word (Word8)
import Data.Text (Text)
import Data.List (sortOn)
import qualified Data.Text as T
import Data.Ord (Down(..))
import GHC.Generics (Generic)
import Network.HTTP.Client (HttpException)
import System.Exit (ExitCode (..), exitWith)
import System.IO (stderr)
import qualified Text.Pandoc.UTF8 as UTF8
import Text.Pandoc.Sources (Sources(..))
import Text.Printf (printf)
import Text.Parsec.Error
import Text.Parsec.Pos hiding (Line)
import Text.Pandoc.Shared (tshow)
import Citeproc (CiteprocError, prettyCiteprocError)

data PandocError = PandocIOError Text IOError
                 | PandocHttpError Text HttpException
                 | PandocShouldNeverHappenError Text
                 | PandocSomeError Text
                 | PandocParseError Text
                 | PandocParsecError Sources ParseError
                 | PandocMakePDFError Text
                 | PandocOptionError Text
                 | PandocSyntaxMapError Text
                 | PandocFailOnWarningError
                 | PandocPDFProgramNotFoundError Text
                 | PandocPDFError Text
                 | PandocXMLError Text Text
                 | PandocFilterError Text Text
                 | PandocLuaError Text
                 | PandocCouldNotFindDataFileError Text
                 | PandocCouldNotFindMetadataFileError Text
                 | PandocResourceNotFound Text
                 | PandocTemplateError Text
                 | PandocAppError Text
                 | PandocEpubSubdirectoryError Text
                 | PandocMacroLoop Text
                 | PandocUTF8DecodingError Text Int Word8
                 | PandocIpynbDecodingError Text
                 | PandocUnsupportedCharsetError Text
                 | PandocUnknownReaderError Text
                 | PandocUnknownWriterError Text
                 | PandocUnsupportedExtensionError Text Text
                 | PandocCiteprocError CiteprocError
                 | PandocBibliographyError Text Text
                 deriving (Int -> PandocError -> ShowS
[PandocError] -> ShowS
PandocError -> String
(Int -> PandocError -> ShowS)
-> (PandocError -> String)
-> ([PandocError] -> ShowS)
-> Show PandocError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PandocError] -> ShowS
$cshowList :: [PandocError] -> ShowS
show :: PandocError -> String
$cshow :: PandocError -> String
showsPrec :: Int -> PandocError -> ShowS
$cshowsPrec :: Int -> PandocError -> ShowS
Show, Typeable, (forall x. PandocError -> Rep PandocError x)
-> (forall x. Rep PandocError x -> PandocError)
-> Generic PandocError
forall x. Rep PandocError x -> PandocError
forall x. PandocError -> Rep PandocError x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep PandocError x -> PandocError
$cfrom :: forall x. PandocError -> Rep PandocError x
Generic)

instance Exception PandocError

renderError :: PandocError -> Text
renderError :: PandocError -> Text
renderError e :: PandocError
e =
  case PandocError
e of
    PandocIOError _ err' :: IOError
err' -> String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ IOError -> String
forall e. Exception e => e -> String
displayException IOError
err'
    PandocHttpError u :: Text
u err' :: HttpException
err' ->
      "Could not fetch " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
u Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> HttpException -> Text
forall a. Show a => a -> Text
tshow HttpException
err'
    PandocShouldNeverHappenError s :: Text
s ->
      "Something we thought was impossible happened!\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
      "Please report this to pandoc's developers: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
s
    PandocSomeError s :: Text
s -> Text
s
    PandocParseError s :: Text
s -> Text
s
    PandocParsecError (Sources inputs :: [(SourcePos, Text)]
inputs) err' :: ParseError
err' ->
        let errPos :: SourcePos
errPos = ParseError -> SourcePos
errorPos ParseError
err'
            errLine :: Int
errLine = SourcePos -> Int
sourceLine SourcePos
errPos
            errColumn :: Int
errColumn = SourcePos -> Int
sourceColumn SourcePos
errPos
            errFile :: String
errFile = SourcePos -> String
sourceName SourcePos
errPos
            errorInFile :: Text
errorInFile =
              case ((SourcePos, Text) -> Down Int)
-> [(SourcePos, Text)] -> [(SourcePos, Text)]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn (Int -> Down Int
forall a. a -> Down a
Down (Int -> Down Int)
-> ((SourcePos, Text) -> Int) -> (SourcePos, Text) -> Down Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SourcePos -> Int
sourceLine (SourcePos -> Int)
-> ((SourcePos, Text) -> SourcePos) -> (SourcePos, Text) -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (SourcePos, Text) -> SourcePos
forall a b. (a, b) -> a
fst)
                      [ (SourcePos
pos,Text
t)
                        | (pos :: SourcePos
pos,t :: Text
t) <- [(SourcePos, Text)]
inputs
                        , SourcePos -> String
sourceName SourcePos
pos String -> String -> Bool
forall a. Eq a => a -> a -> Bool
== String
errFile
                        , SourcePos -> Int
sourceLine SourcePos
pos Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
errLine
                      ] of
                []  -> ""
                ((pos :: SourcePos
pos,txt :: Text
txt):_) ->
                  let ls :: [Text]
ls = Text -> [Text]
T.lines Text
txt [Text] -> [Text] -> [Text]
forall a. Semigroup a => a -> a -> a
<> [""]
                      ln :: Int
ln = (Int
errLine Int -> Int -> Int
forall a. Num a => a -> a -> a
- SourcePos -> Int
sourceLine SourcePos
pos) Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1
                   in if [Text] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
ls Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
ln Bool -> Bool -> Bool
&& Int
ln Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= 1
                         then [Text] -> Text
T.concat ["\n", [Text]
ls [Text] -> Int -> Text
forall a. [a] -> Int -> a
!! (Int
ln Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1)
                                       ,"\n", Int -> Text -> Text
T.replicate (Int
errColumn Int -> Int -> Int
forall a. Num a => a -> a -> a
- 1) " "
                                       ,"^"]
                         else ""
        in  "Error at " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ParseError -> Text
forall a. Show a => a -> Text
tshow  ParseError
err' Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
errorInFile
    PandocMakePDFError s :: Text
s -> Text
s
    PandocOptionError s :: Text
s -> Text
s
    PandocSyntaxMapError s :: Text
s -> Text
s
    PandocFailOnWarningError -> "Failing because there were warnings."
    PandocPDFProgramNotFoundError pdfprog :: Text
pdfprog ->
        Text
pdfprog Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " not found. Please select a different --pdf-engine or install " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
pdfprog Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " -- see also /usr/share/doc/pandoc/README.Debian"
    PandocPDFError logmsg :: Text
logmsg -> "Error producing PDF.\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
logmsg
    PandocXMLError fp :: Text
fp logmsg :: Text
logmsg -> "Invalid XML" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
        (if Text -> Bool
T.null Text
fp then "" else " in " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fp) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ":\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
logmsg
    PandocFilterError filtername :: Text
filtername msg :: Text
msg -> "Error running filter " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
        Text
filtername Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ":\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
msg
    PandocLuaError msg :: Text
msg -> "Error running Lua:\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
msg
    PandocCouldNotFindDataFileError fn :: Text
fn ->
        "Could not find data file " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fn
    PandocCouldNotFindMetadataFileError fn :: Text
fn ->
        "Could not find metadata file " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fn
    PandocResourceNotFound fn :: Text
fn ->
        "File " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fn Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " not found in resource path"
    PandocTemplateError s :: Text
s -> "Error compiling template " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
s
    PandocAppError s :: Text
s -> Text
s
    PandocEpubSubdirectoryError s :: Text
s ->
      "EPUB subdirectory name '" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
s Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "' contains illegal characters"
    PandocMacroLoop s :: Text
s ->
      "Loop encountered in expanding macro " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
s
    PandocUTF8DecodingError f :: Text
f offset :: Int
offset w :: Word8
w ->
      "UTF-8 decoding error in " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
f Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " at byte offset " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall a. Show a => a -> Text
tshow Int
offset Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
      " (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
T.pack (String -> Word8 -> String
forall r. PrintfType r => String -> r
printf "%2x" Word8
w) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ").\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
      "The input must be a UTF-8 encoded text."
    PandocIpynbDecodingError w :: Text
w ->
      "ipynb decoding error: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
w
    PandocUnsupportedCharsetError charset :: Text
charset ->
      "Unsupported charset " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
charset
    PandocUnknownReaderError r :: Text
r ->
      "Unknown input format " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
r Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
      case Text
r of
        "doc" -> "\nPandoc can convert from DOCX, but not from DOC." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                 "\nTry using Word to save your DOC file as DOCX," Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                 " and convert that with pandoc."
        "pdf" -> "\nPandoc can convert to PDF, but not from PDF."
        _     -> ""
    PandocUnknownWriterError w :: Text
w ->
       "Unknown output format " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
w Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
       case Text
w of
         "pdf" -> "To create a pdf using pandoc, use" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                  " -t latex|beamer|context|ms|html5" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                 "\nand specify an output file with " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                 ".pdf extension (-o filename.pdf)."
         "doc" -> "\nPandoc can convert to DOCX, but not to DOC."
         _     -> ""
    PandocUnsupportedExtensionError ext :: Text
ext f :: Text
f ->
      "The extension " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
ext Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " is not supported " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
      "for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
f
    PandocCiteprocError e' :: CiteprocError
e' ->
      CiteprocError -> Text
prettyCiteprocError CiteprocError
e'
    PandocBibliographyError fp :: Text
fp msg :: Text
msg ->
      "Error reading bibliography file " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
fp Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ":\n" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
msg


-- | Handle PandocError by exiting with an error message.
handleError :: Either PandocError a -> IO a
handleError :: Either PandocError a -> IO a
handleError (Right r :: a
r) = a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
r
handleError (Left e :: PandocError
e) =
  case PandocError
e of
    PandocIOError _ err' :: IOError
err' -> IOError -> IO a
forall a. IOError -> IO a
ioError IOError
err'
    _ -> Int -> Text -> IO a
forall a. Int -> Text -> IO a
err Int
exitCode (PandocError -> Text
renderError PandocError
e)
 where
  exitCode :: Int
exitCode =
    case PandocError
e of
      PandocIOError{} -> 1
      PandocFailOnWarningError{} -> 3
      PandocAppError{} -> 4
      PandocTemplateError{} -> 5
      PandocOptionError{} -> 6
      PandocUnknownReaderError{} -> 21
      PandocUnknownWriterError{} -> 22
      PandocUnsupportedExtensionError{} -> 23
      PandocCiteprocError{} -> 24
      PandocBibliographyError{} -> 25
      PandocEpubSubdirectoryError{} -> 31
      PandocPDFError{} -> 43
      PandocXMLError{} -> 44
      PandocPDFProgramNotFoundError{} -> 47
      PandocHttpError{} -> 61
      PandocShouldNeverHappenError{} -> 62
      PandocSomeError{} -> 63
      PandocParseError{} -> 64
      PandocParsecError{} -> 65
      PandocMakePDFError{} -> 66
      PandocSyntaxMapError{} -> 67
      PandocFilterError{} -> 83
      PandocLuaError{} -> 84
      PandocMacroLoop{} -> 91
      PandocUTF8DecodingError{} -> 92
      PandocIpynbDecodingError{} -> 93
      PandocUnsupportedCharsetError{} -> 94
      PandocCouldNotFindDataFileError{} -> 97
      PandocCouldNotFindMetadataFileError{} -> 98
      PandocResourceNotFound{} -> 99

err :: Int -> Text -> IO a
err :: Int -> Text -> IO a
err exitCode :: Int
exitCode msg :: Text
msg = do
  Handle -> Text -> IO ()
UTF8.hPutStrLn Handle
stderr Text
msg
  ExitCode -> IO Any
forall a. ExitCode -> IO a
exitWith (ExitCode -> IO Any) -> ExitCode -> IO Any
forall a b. (a -> b) -> a -> b
$ Int -> ExitCode
ExitFailure Int
exitCode
  a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return a
forall a. HasCallStack => a
undefined