1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# 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.
-}
module Text.Pandoc.Filter
( Filter (..)
, Environment (..)
, applyFilters
, applyJSONFilter
) where
import System.CPUTime (getCPUTime)
import Data.Aeson
import Data.Maybe (fromMaybe)
import GHC.Generics (Generic)
import Text.Pandoc.Class (PandocMonad, findFileWithDataFallback, getVerbosity,
report)
import Text.Pandoc.Definition (Pandoc)
import Text.Pandoc.Filter.Environment (Environment (..))
import Text.Pandoc.Logging
import Text.Pandoc.Citeproc (processCitations)
import Text.Pandoc.Scripting (ScriptingEngine (engineApplyFilter))
import qualified Text.Pandoc.Filter.JSON as JSONFilter
import qualified Data.Text as T
import System.FilePath (takeExtension)
import Control.Applicative ((<|>))
import Control.Monad.Trans (MonadIO (liftIO))
import Control.Monad (foldM, when)
-- | Type of filter and path to filter file.
data Filter = LuaFilter FilePath
| JSONFilter FilePath
| CiteprocFilter -- built-in citeproc
deriving (Show, Generic, Eq)
instance FromJSON Filter where
parseJSON node =
(withObject "Filter" $ \m -> do
ty <- m .: "type"
fp <- m .:? "path"
let missingPath = fail $ "Expected 'path' for filter of type " ++ show ty
let filterWithPath constr = maybe missingPath (return . constr . T.unpack)
case ty of
"citeproc" -> return CiteprocFilter
"lua" -> filterWithPath LuaFilter fp
"json" -> filterWithPath JSONFilter fp
_ -> fail $ "Unknown filter type " ++ show (ty :: T.Text)) node
<|>
(withText "Filter" $ \t -> do
let fp = T.unpack t
if fp == "citeproc"
then return CiteprocFilter
else return $
case takeExtension fp of
".lua" -> LuaFilter fp
_ -> JSONFilter fp) node
instance ToJSON Filter where
toJSON CiteprocFilter = object [ "type" .= String "citeproc" ]
toJSON (LuaFilter fp) = object [ "type" .= String "lua",
"path" .= String (T.pack fp) ]
toJSON (JSONFilter fp) = object [ "type" .= String "json",
"path" .= String (T.pack fp) ]
-- | Modify the given document using a filter.
applyFilters :: (PandocMonad m, MonadIO m)
=> ScriptingEngine
-> Environment
-> [Filter]
-> [String]
-> Pandoc
-> m Pandoc
applyFilters scrngin fenv filters args d = do
expandedFilters <- mapM expandFilterPath filters
foldM applyFilter d expandedFilters
where
applyFilter doc (JSONFilter f) =
withMessages f $ JSONFilter.apply fenv args f doc
applyFilter doc (LuaFilter f) =
withMessages f $ engineApplyFilter scrngin fenv args f doc
applyFilter doc CiteprocFilter =
withMessages "citeproc" $ processCitations doc
withMessages f action = do
verbosity <- getVerbosity
when (verbosity == INFO) $ report $ RunningFilter f
starttime <- liftIO getCPUTime
res <- action
endtime <- liftIO getCPUTime
when (verbosity == INFO) $ report $ FilterCompleted f $ toMilliseconds $ endtime - starttime
return res
toMilliseconds picoseconds = picoseconds `div` 1000000000
-- | Expand paths of filters, searching the data directory.
expandFilterPath :: (PandocMonad m, MonadIO m) => Filter -> m Filter
expandFilterPath (LuaFilter fp) = LuaFilter <$> filterPath fp
expandFilterPath (JSONFilter fp) = JSONFilter <$> filterPath fp
expandFilterPath CiteprocFilter = return CiteprocFilter
filterPath :: PandocMonad m => FilePath -> m FilePath
filterPath fp = fromMaybe fp <$> findFileWithDataFallback "filters" fp
applyJSONFilter :: MonadIO m
=> Environment
-> [String]
-> FilePath
-> Pandoc
-> m Pandoc
applyJSONFilter = JSONFilter.apply
|