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

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

Programmatically modifications of pandoc documents via JSON filters.
-}
module Text.Pandoc.Filter.JSON (apply) where

import Control.Monad (unless, when)
import Control.Monad.Trans (MonadIO (liftIO))
import Data.Aeson (eitherDecode', encode)
import Data.Char (toLower)
import Data.Maybe (isNothing)
import qualified Data.Text as T
import System.Directory (executable, doesFileExist, findExecutable,
                         getPermissions)
import System.Environment (getEnvironment)
import System.Exit (ExitCode (..))
import System.FilePath ((</>), takeExtension)
import Text.Pandoc.Definition (Pandoc)
import Text.Pandoc.Error (PandocError (PandocFilterError))
import Text.Pandoc.Filter.Environment (Environment (..))
import Text.Pandoc.Process (pipeProcess)
import Text.Pandoc.Version (pandocVersionText)
import Text.Pandoc.Shared (tshow)
import qualified Control.Exception as E
import qualified Text.Pandoc.UTF8 as UTF8

apply :: MonadIO m
      => Environment
      -> [String]
      -> FilePath
      -> Pandoc
      -> m Pandoc
apply ropts args f = liftIO . externalFilter ropts f args

externalFilter :: MonadIO m
               => Environment -> FilePath -> [String] -> Pandoc -> m Pandoc
externalFilter fenv f args' d = liftIO $ do
  exists <- doesFileExist f
  isExecutable <- if exists
                     then executable <$> getPermissions f
                     else return True
  let (f', args'') = if exists
                        then case map toLower (takeExtension f) of
                                  _      | isExecutable -> ("." </> f, args')
                                  ".py"  -> ("python", f:args')
                                  ".hs"  -> ("runhaskell", f:args')
                                  ".pl"  -> ("perl", f:args')
                                  ".rb"  -> ("ruby", f:args')
                                  ".php" -> ("php", f:args')
                                  ".js"  -> ("node", f:args')
                                  ".r"   -> ("Rscript", f:args')
                                  _      -> (f, args')
                        else (f, args')
  unless (exists && isExecutable) $ do
    mbExe <- findExecutable f'
    when (isNothing mbExe) $
      E.throwIO $ PandocFilterError fText (T.pack $ "Could not find executable " <> f')
  let ropts = envReaderOptions fenv
  env <- getEnvironment
  let env' = Just
           ( ("PANDOC_VERSION", T.unpack pandocVersionText)
           : ("PANDOC_READER_OPTIONS", UTF8.toStringLazy (encode ropts))
           : env )
  (exitcode, outbs) <- E.handle filterException $
                              pipeProcess env' f' args'' $ encode d
  case exitcode of
       ExitSuccess    -> either (E.throwIO . PandocFilterError fText . T.pack)
                                   return $ eitherDecode' outbs
       ExitFailure ec -> E.throwIO $ PandocFilterError fText
                           ("Filter returned error status " <> tshow ec)
 where fText = T.pack f

       filterException :: E.SomeException -> IO a
       filterException e = E.throwIO $ PandocFilterError fText $ tshow e
