aboutsummaryrefslogtreecommitdiff
path: root/src/Text/Pandoc/App/Opt.hs
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2021-07-17 18:10:34 +0200
committerIgor Pashev <pashev.igor@gmail.com>2021-07-17 18:46:16 +0200
commit48459559a13a20083fc9b31eb523b8ea2bf0a63f (patch)
tree1c04e75709457403110a6f8c5c90099f22369de3 /src/Text/Pandoc/App/Opt.hs
parent0c39509d9b6a58958228cebf5d643598e5c98950 (diff)
parent46099e79defe662e541b12548200caf29063c1c6 (diff)
downloadpandoc-48459559a13a20083fc9b31eb523b8ea2bf0a63f.tar.gz
Merge branch 'master' of https://github.com/jgm/pandoc
Diffstat (limited to 'src/Text/Pandoc/App/Opt.hs')
-rw-r--r--src/Text/Pandoc/App/Opt.hs312
1 files changed, 261 insertions, 51 deletions
diff --git a/src/Text/Pandoc/App/Opt.hs b/src/Text/Pandoc/App/Opt.hs
index 00b4b5523..d54d932b7 100644
--- a/src/Text/Pandoc/App/Opt.hs
+++ b/src/Text/Pandoc/App/Opt.hs
@@ -7,7 +7,7 @@
{-# LANGUAGE FlexibleContexts #-}
{- |
Module : Text.Pandoc.App.Opt
- Copyright : Copyright (C) 2006-2020 John MacFarlane
+ Copyright : Copyright (C) 2006-2021 John MacFarlane
License : GNU GPL, version 2 or above
Maintainer : John MacFarlane <jgm@berkeley@edu>
@@ -20,21 +20,31 @@ module Text.Pandoc.App.Opt (
Opt(..)
, LineEnding (..)
, IpynbOutput (..)
+ , DefaultsState (..)
, defaultOpts
- , addMeta
+ , applyDefaults
+ , fullDefaultsPath
) where
+import Control.Monad.Except (MonadIO, liftIO, throwError, (>=>), foldM)
+import Control.Monad.State.Strict (StateT, modify, gets)
+import System.FilePath ( addExtension, (</>), takeExtension, takeDirectory )
+import System.Directory ( canonicalizePath )
import Data.Char (isLower, toLower)
+import Data.Maybe (fromMaybe)
import GHC.Generics hiding (Meta)
-import Text.Pandoc.Builder (setMeta)
import Text.Pandoc.Filter (Filter (..))
-import Text.Pandoc.Logging (Verbosity (WARNING))
+import Text.Pandoc.Logging (Verbosity (WARNING), LogMessage(..))
import Text.Pandoc.Options (TopLevelDivision (TopLevelDefault),
TrackChanges (AcceptChanges),
WrapOption (WrapAuto), HTMLMathMethod (PlainMath),
ReferenceLocation (EndOfDocument),
ObfuscationMethod (NoObfuscation),
CiteMethod (Citeproc))
-import Text.Pandoc.Shared (camelCaseStrToHyphenated)
+import Text.Pandoc.Class (readFileLazy, fileExists, setVerbosity, report,
+ PandocMonad(lookupEnv), getUserDataDir)
+import Text.Pandoc.Error (PandocError (PandocParseError, PandocSomeError))
+import Text.Pandoc.Shared (camelCaseStrToHyphenated, defaultUserDataDir,
+ findM, ordNub)
import qualified Text.Pandoc.Parsing as P
import Text.Pandoc.Readers.Metadata (yamlMap)
import Text.Pandoc.Class.PandocPure
@@ -43,7 +53,7 @@ import Data.Text (Text, unpack)
import Data.Default (def)
import qualified Data.Text as T
import qualified Data.Map as M
-import Text.Pandoc.Definition (Meta(..), MetaValue(..), lookupMeta)
+import Text.Pandoc.Definition (Meta(..), MetaValue(..))
import Data.Aeson (defaultOptions, Options(..))
import Data.Aeson.TH (deriveJSON)
import Control.Applicative ((<|>))
@@ -147,19 +157,194 @@ data Opt = Opt
, optNoCheckCertificate :: Bool -- ^ Disable certificate validation
, optEol :: LineEnding -- ^ Style of line-endings to use
, optStripComments :: Bool -- ^ Skip HTML comments
+ , optCSL :: Maybe FilePath -- ^ CSL stylesheet
+ , optBibliography :: [FilePath] -- ^ Bibliography files
+ , optCitationAbbreviations :: Maybe FilePath -- ^ Citation abbreviations
} deriving (Generic, Show)
instance FromYAML (Opt -> Opt) where
- parseYAML (Mapping _ _ m) =
- foldr (.) id <$> mapM doOpt (M.toList m)
+ parseYAML (Mapping _ _ m) = chain doOpt (M.toList m)
parseYAML n = failAtNode n "Expected a mapping"
+data DefaultsState = DefaultsState
+ {
+ curDefaults :: Maybe FilePath -- currently parsed file
+ , inheritanceGraph :: [[FilePath]] -- defaults file inheritance graph
+ } deriving (Show)
+
+instance (PandocMonad m, MonadIO m)
+ => FromYAML (Opt -> StateT DefaultsState m Opt) where
+ parseYAML (Mapping _ _ m) = do
+ let opts = M.mapKeys toText m
+ dataDir <- case M.lookup "data-dir" opts of
+ Nothing -> return Nothing
+ Just v -> Just . unpack <$> parseYAML v
+ f <- parseOptions (M.toList m)
+ case M.lookup "defaults" opts of
+ Just v -> do
+ g <- parseDefaults v dataDir
+ return $ g >=> f >=> resolveVarsInOpt
+ Nothing -> return $ f >=> resolveVarsInOpt
+ where
+ toText (Scalar _ (SStr s)) = s
+ toText _ = ""
+ parseYAML n = failAtNode n "Expected a mapping"
+
+resolveVarsInOpt :: forall m. (PandocMonad m, MonadIO m)
+ => Opt -> StateT DefaultsState m Opt
+resolveVarsInOpt
+ opt@Opt
+ { optTemplate = oTemplate
+ , optMetadataFiles = oMetadataFiles
+ , optOutputFile = oOutputFile
+ , optInputFiles = oInputFiles
+ , optSyntaxDefinitions = oSyntaxDefinitions
+ , optAbbreviations = oAbbreviations
+ , optReferenceDoc = oReferenceDoc
+ , optEpubMetadata = oEpubMetadata
+ , optEpubFonts = oEpubFonts
+ , optEpubCoverImage = oEpubCoverImage
+ , optLogFile = oLogFile
+ , optFilters = oFilters
+ , optDataDir = oDataDir
+ , optExtractMedia = oExtractMedia
+ , optCss = oCss
+ , optIncludeBeforeBody = oIncludeBeforeBody
+ , optIncludeAfterBody = oIncludeAfterBody
+ , optIncludeInHeader = oIncludeInHeader
+ , optResourcePath = oResourcePath
+ , optCSL = oCSL
+ , optBibliography = oBibliography
+ , optCitationAbbreviations = oCitationAbbreviations
+ }
+ = do
+ oTemplate' <- mapM resolveVars oTemplate
+ oMetadataFiles' <- mapM resolveVars oMetadataFiles
+ oOutputFile' <- mapM resolveVars oOutputFile
+ oInputFiles' <- mapM (mapM resolveVars) oInputFiles
+ oSyntaxDefinitions' <- mapM resolveVars oSyntaxDefinitions
+ oAbbreviations' <- mapM resolveVars oAbbreviations
+ oReferenceDoc' <- mapM resolveVars oReferenceDoc
+ oEpubMetadata' <- mapM resolveVars oEpubMetadata
+ oEpubFonts' <- mapM resolveVars oEpubFonts
+ oEpubCoverImage' <- mapM resolveVars oEpubCoverImage
+ oLogFile' <- mapM resolveVars oLogFile
+ oFilters' <- mapM resolveVarsInFilter oFilters
+ oDataDir' <- mapM resolveVars oDataDir
+ oExtractMedia' <- mapM resolveVars oExtractMedia
+ oCss' <- mapM resolveVars oCss
+ oIncludeBeforeBody' <- mapM resolveVars oIncludeBeforeBody
+ oIncludeAfterBody' <- mapM resolveVars oIncludeAfterBody
+ oIncludeInHeader' <- mapM resolveVars oIncludeInHeader
+ oResourcePath' <- mapM resolveVars oResourcePath
+ oCSL' <- mapM resolveVars oCSL
+ oBibliography' <- mapM resolveVars oBibliography
+ oCitationAbbreviations' <- mapM resolveVars oCitationAbbreviations
+ return opt{ optTemplate = oTemplate'
+ , optMetadataFiles = oMetadataFiles'
+ , optOutputFile = oOutputFile'
+ , optInputFiles = oInputFiles'
+ , optSyntaxDefinitions = oSyntaxDefinitions'
+ , optAbbreviations = oAbbreviations'
+ , optReferenceDoc = oReferenceDoc'
+ , optEpubMetadata = oEpubMetadata'
+ , optEpubFonts = oEpubFonts'
+ , optEpubCoverImage = oEpubCoverImage'
+ , optLogFile = oLogFile'
+ , optFilters = oFilters'
+ , optDataDir = oDataDir'
+ , optExtractMedia = oExtractMedia'
+ , optCss = oCss'
+ , optIncludeBeforeBody = oIncludeBeforeBody'
+ , optIncludeAfterBody = oIncludeAfterBody'
+ , optIncludeInHeader = oIncludeInHeader'
+ , optResourcePath = oResourcePath'
+ , optCSL = oCSL'
+ , optBibliography = oBibliography'
+ , optCitationAbbreviations = oCitationAbbreviations'
+ }
+
+ where
+ resolveVars :: FilePath -> StateT DefaultsState m FilePath
+ resolveVars [] = return []
+ resolveVars ('$':'{':xs) =
+ let (ys, zs) = break (=='}') xs
+ in if null zs
+ then return $ '$':'{':xs
+ else do
+ val <- lookupEnv' ys
+ (val ++) <$> resolveVars (drop 1 zs)
+ resolveVars (c:cs) = (c:) <$> resolveVars cs
+ lookupEnv' :: String -> StateT DefaultsState m String
+ lookupEnv' "." = do
+ mbCurDefaults <- gets curDefaults
+ maybe (return "")
+ (fmap takeDirectory . liftIO . canonicalizePath)
+ mbCurDefaults
+ lookupEnv' "USERDATA" = do
+ mbodatadir <- mapM resolveVars oDataDir
+ mbdatadir <- getUserDataDir
+ defdatadir <- liftIO defaultUserDataDir
+ return $ fromMaybe defdatadir (mbodatadir <|> mbdatadir)
+ lookupEnv' v = do
+ mbval <- fmap T.unpack <$> lookupEnv (T.pack v)
+ case mbval of
+ Nothing -> do
+ report $ EnvironmentVariableUndefined (T.pack v)
+ return mempty
+ Just x -> return x
+ resolveVarsInFilter (JSONFilter fp) =
+ JSONFilter <$> resolveVars fp
+ resolveVarsInFilter (LuaFilter fp) =
+ LuaFilter <$> resolveVars fp
+ resolveVarsInFilter CiteprocFilter = return CiteprocFilter
+
+
+
+parseDefaults :: (PandocMonad m, MonadIO m)
+ => Node Pos
+ -> Maybe FilePath
+ -> Parser (Opt -> StateT DefaultsState m Opt)
+parseDefaults n dataDir = parseDefsNames n >>= \ds -> return $ \o -> do
+ -- get parent defaults:
+ defsParent <- gets $ fromMaybe "" . curDefaults
+ -- get child defaults:
+ defsChildren <- mapM (fullDefaultsPath dataDir) ds
+ -- expand parent in defaults inheritance graph by children:
+ defsGraph <- gets inheritanceGraph
+ let defsGraphExp = expand defsGraph defsChildren defsParent
+ modify $ \defsState -> defsState{ inheritanceGraph = defsGraphExp }
+ -- check for cyclic inheritance:
+ if cyclic defsGraphExp
+ then throwError $
+ PandocSomeError $ T.pack $
+ "Error: Circular defaults file reference in " ++
+ "'" ++ defsParent ++ "'"
+ else foldM applyDefaults o defsChildren
+ where parseDefsNames x = (parseYAML x >>= \xs -> return $ map unpack xs)
+ <|> (parseYAML x >>= \x' -> return [unpack x'])
+
+parseOptions :: Monad m
+ => [(Node Pos, Node Pos)]
+ -> Parser (Opt -> StateT DefaultsState m Opt)
+parseOptions ns = do
+ f <- chain doOpt' ns
+ return $ return . f
+
+chain :: Monad m => (a -> m (b -> b)) -> [a] -> m (b -> b)
+chain f = foldM g id
+ where g o n = f n >>= \o' -> return $ o' . o
+
+doOpt' :: (Node Pos, Node Pos) -> Parser (Opt -> Opt)
+doOpt' (k',v) = do
+ k <- parseStringKey k'
+ case k of
+ "defaults" -> return id
+ _ -> doOpt (k',v)
+
doOpt :: (Node Pos, Node Pos) -> Parser (Opt -> Opt)
doOpt (k',v) = do
- k <- case k' of
- Scalar _ (SStr t) -> return t
- Scalar _ _ -> failAtNode k' "Non-string key"
- _ -> failAtNode k' "Non-scalar key"
+ k <- parseStringKey k'
case k of
"tab-stop" ->
parseYAML v >>= \x -> return (\o -> o{ optTabStop = x })
@@ -358,26 +543,18 @@ doOpt (k',v) = do
(parseYAML v >>= \x -> return (\o -> o{ optCss = optCss o <>
[unpack x] }))
"bibliography" ->
- do let addItem x o = o{ optMetadata =
- addMeta "bibliography" (T.unpack x)
- (optMetadata o) }
- (parseYAML v >>= \(xs :: [Text]) -> return $ \o ->
- foldr addItem o xs)
- <|>
- (parseYAML v >>= \(x :: Text) -> return $ \o -> addItem x o)
+ (parseYAML v >>= \x -> return (\o ->
+ o{ optBibliography = optBibliography o <>
+ map unpack x }))
+ <|>
+ (parseYAML v >>= \x -> return (\o ->
+ o{ optBibliography = optBibliography o <>
+ [unpack x] }))
"csl" ->
- do let addItem x o = o{ optMetadata =
- addMeta "csl" (T.unpack x)
- (optMetadata o) }
- (parseYAML v >>= \(xs :: [Text]) -> return $ \o ->
- foldr addItem o xs)
- <|>
- (parseYAML v >>= \(x :: Text) -> return $ \o -> addItem x o)
+ parseYAML v >>= \x -> return (\o -> o{ optCSL = unpack <$> x })
"citation-abbreviations" ->
- parseYAML v >>= \x ->
- return (\o -> o{ optMetadata =
- addMeta "citation-abbreviations" (T.unpack x)
- (optMetadata o) })
+ parseYAML v >>= \x -> return (\o -> o{ optCitationAbbreviations =
+ unpack <$> x })
"ipynb-output" ->
parseYAML v >>= \x -> return (\o -> o{ optIpynbOutput = x })
"include-before-body" ->
@@ -406,7 +583,8 @@ doOpt (k',v) = do
optIncludeInHeader o <> [unpack x] }))
"resource-path" ->
parseYAML v >>= \x ->
- return (\o -> o{ optResourcePath = map unpack x })
+ return (\o -> o{ optResourcePath = map unpack x <>
+ optResourcePath o })
"request-headers" ->
parseYAML v >>= \x ->
return (\o -> o{ optRequestHeaders = x })
@@ -492,38 +670,70 @@ defaultOpts = Opt
, optNoCheckCertificate = False
, optEol = Native
, optStripComments = False
+ , optCSL = Nothing
+ , optBibliography = []
+ , optCitationAbbreviations = Nothing
}
+parseStringKey :: Node Pos -> Parser Text
+parseStringKey k = case k of
+ Scalar _ (SStr t) -> return t
+ Scalar _ _ -> failAtNode k "Non-string key"
+ _ -> failAtNode k "Non-scalar key"
+
yamlToMeta :: Node Pos -> Parser Meta
yamlToMeta (Mapping _ _ m) =
either (fail . show) return $ runEverything (yamlMap pMetaString m)
where
pMetaString = pure . MetaString <$> P.manyChar P.anyChar
- runEverything p = runPure (P.readWithM p def "")
+ runEverything p =
+ runPure (P.readWithM p (def :: P.ParserState) ("" :: Text))
>>= fmap (Meta . flip P.runF def)
yamlToMeta _ = return mempty
-addMeta :: String -> String -> Meta -> Meta
-addMeta k v meta =
- case lookupMeta k' meta of
- Nothing -> setMeta k' v' meta
- Just (MetaList xs) ->
- setMeta k' (MetaList (xs ++ [v'])) meta
- Just x -> setMeta k' (MetaList [x, v']) meta
- where
- v' = readMetaValue v
- k' = T.pack k
+-- | Apply defaults from --defaults file.
+applyDefaults :: (PandocMonad m, MonadIO m)
+ => Opt
+ -> FilePath
+ -> StateT DefaultsState m Opt
+applyDefaults opt file = do
+ setVerbosity $ optVerbosity opt
+ modify $ \defsState -> defsState{ curDefaults = Just file }
+ inp <- readFileLazy file
+ case decode1 inp of
+ Right f -> f opt
+ Left (errpos, errmsg) -> throwError $
+ PandocParseError $ T.pack $
+ "Error parsing " ++ file ++ " line " ++
+ show (posLine errpos) ++ " column " ++
+ show (posColumn errpos) ++ ":\n" ++ errmsg
-readMetaValue :: String -> MetaValue
-readMetaValue s
- | s == "true" = MetaBool True
- | s == "True" = MetaBool True
- | s == "TRUE" = MetaBool True
- | s == "false" = MetaBool False
- | s == "False" = MetaBool False
- | s == "FALSE" = MetaBool False
- | otherwise = MetaString $ T.pack s
+fullDefaultsPath :: (PandocMonad m, MonadIO m)
+ => Maybe FilePath
+ -> FilePath
+ -> m FilePath
+fullDefaultsPath dataDir file = do
+ let fp = if null (takeExtension file)
+ then addExtension file "yaml"
+ else file
+ defaultDataDir <- liftIO defaultUserDataDir
+ let defaultFp = fromMaybe defaultDataDir dataDir </> "defaults" </> fp
+ fromMaybe fp <$> findM fileExists [fp, defaultFp]
+-- | In a list of lists, append another list in front of every list which
+-- starts with specific element.
+expand :: Ord a => [[a]] -> [a] -> a -> [[a]]
+expand [] ns n = fmap (\x -> x : [n]) ns
+expand ps ns n = concatMap (ext n ns) ps
+ where
+ ext x xs p = case p of
+ (l : _) | x == l -> fmap (: p) xs
+ _ -> [p]
+
+cyclic :: Ord a => [[a]] -> Bool
+cyclic = any hasDuplicate
+ where
+ hasDuplicate xs = length (ordNub xs) /= length xs
-- see https://github.com/jgm/pandoc/pull/4083
-- using generic deriving caused long compilation times