content: {-# LANGUAGE OverloadedStrings #-}
  {- |
     Module      : Text.Pandoc.Readers.Org.ExportSettings
     Copyright   : © 2016-2024 Albert Krewinkel
     License     : GNU GPL, version 2 or above
  
     Maintainer  : Albert Krewinkel <albert+pandoc@tarleb.com>
  
  Parsers for Org-mode export options.
  -}
  module Text.Pandoc.Readers.Org.ExportSettings
    ( exportSettings
    ) where
  
  import Text.Pandoc.Class.PandocMonad (PandocMonad, report)
  import Text.Pandoc.Logging (LogMessage (UnknownOrgExportOption))
  import Text.Pandoc.Readers.Org.ParserState
  import Text.Pandoc.Readers.Org.Parsing
  
  import Control.Monad (mzero, void)
  import Data.Char (toLower)
  import Data.Maybe (listToMaybe)
  import Data.Text (Text, unpack)
  
  -- | Read and handle space separated org-mode export settings.
  exportSettings :: PandocMonad m => OrgParser m ()
  exportSettings = void $ sepBy skipSpaces exportSetting
  
  -- | Setter function for export settings.
  type ExportSettingSetter a = a -> ExportSettings -> ExportSettings
  
  -- | Read and process a single org-mode export option.
  exportSetting :: PandocMonad m => OrgParser m ()
  exportSetting = choice
    [ booleanSetting "^" (\val es -> es { exportSubSuperscripts = val })
    , booleanSetting "'" (\val es -> es { exportSmartQuotes = val })
    , booleanSetting "*" (\val es -> es { exportEmphasizedText = val })
    , booleanSetting "-" (\val es -> es { exportSpecialStrings = val })
    , ignoredSetting ":"
    , ignoredSetting "<"
    , booleanSetting "\\n" (\val es -> es { exportPreserveBreaks = val })
    , archivedTreeSetting "arch" (\val es -> es { exportArchivedTrees = val })
    , booleanSetting "author" (\val es -> es { exportWithAuthor = val })
    , ignoredSetting "c"
    -- org-mode allows the special value `comment` for creator, which we'll
    -- interpret as true as it doesn't make sense in the context of Pandoc.
    , booleanSetting "creator" (\val es -> es { exportWithCreator = val })
    , complementableListSetting "d" (\val es -> es { exportDrawers = val })
    , ignoredSetting "date"
    , booleanSetting "e" (\val es -> es { exportWithEntities = val })
    , booleanSetting "email" (\val es -> es { exportWithEmail = val })
    , booleanSetting "f" (\val es -> es { exportWithFootnotes = val })
    , integerSetting "H" (\val es -> es { exportHeadlineLevels = val })
    , ignoredSetting "inline"
    , ignoredSetting "num"
    , booleanSetting "p" (\val es -> es { exportWithPlanning = val })
    , ignoredSetting "pri"
    , ignoredSetting "prop"
    , ignoredSetting "stat"
    , booleanSetting "tags" (\val es -> es { exportWithTags = val })
    , ignoredSetting "tasks"
    , texSetting     "tex" (\val es -> es { exportWithLatex = val })
    , ignoredSetting "timestamp"
    , ignoredSetting "title"
    , ignoredSetting "toc"
    , booleanSetting "todo" (\val es -> es { exportWithTodoKeywords = val })
    , booleanSetting "|" (\val es -> es { exportWithTables = val })
    , ignoreAndWarn
    ] <?> "export setting"
  
  -- | Generic handler for export settings. Takes a parser which converts
  -- the plain option text into a data structure.
  genericExportSetting :: Monad m
                       => OrgParser m a
                       -> Text
                       -> ExportSettingSetter a
                       -> OrgParser m ()
  genericExportSetting optionParser settingIdentifier setter = try $ do
    _     <- textStr settingIdentifier *> char ':'
    value <- optionParser
    updateState $ modifyExportSettings value
   where
     modifyExportSettings val st =
       st { orgStateExportSettings = setter val . orgStateExportSettings $ st }
  
  -- | A boolean option, either nil (False) or non-nil (True).
  booleanSetting :: Monad m => Text ->  ExportSettingSetter Bool -> OrgParser m ()
  booleanSetting = genericExportSetting elispBoolean
  
  -- | An integer-valued option.
  integerSetting :: Monad m => Text -> ExportSettingSetter Int -> OrgParser m ()
  integerSetting = genericExportSetting parseInt
   where
     parseInt = try $
       many1 digit >>= maybe mzero (return . fst) . listToMaybe . reads
  
  -- | Either the string "headline" or an elisp boolean and treated as an
  -- @ArchivedTreesOption@.
  archivedTreeSetting :: Monad m
                      => Text
                      -> ExportSettingSetter ArchivedTreesOption
                      -> OrgParser m ()
  archivedTreeSetting =
    genericExportSetting $ archivedTreesHeadlineSetting <|> archivedTreesBoolean
   where
     archivedTreesHeadlineSetting =
       ArchivedTreesHeadlineOnly <$ optionString "headline"
  
     archivedTreesBoolean = try $ do
       exportBool <- elispBoolean
       return $
         if exportBool
         then ArchivedTreesExport
         else ArchivedTreesNoExport
  
  -- | A list or a complement list (i.e. a list starting with `not`).
  complementableListSetting :: Monad m
                            => Text
                            -> ExportSettingSetter (Either [Text] [Text])
                            -> OrgParser m ()
  complementableListSetting = genericExportSetting $ choice
    [ Left  <$> complementTextList
    , Right <$> stringList
    , (\b -> if b then Left [] else Right []) <$> elispBoolean
    ]
   where
     -- Read a plain list of strings.
     stringList :: Monad m => OrgParser m [Text]
     stringList = try $
       char '('
         *> sepBy elispText spaces
         <* char ')'
  
     -- Read an emacs lisp list specifying a complement set.
     complementTextList :: Monad m => OrgParser m [Text]
     complementTextList = try $
       string "(not "
         *> sepBy elispText spaces
         <* char ')'
  
     elispText :: Monad m => OrgParser m Text
     elispText = try $
       char '"'
         *> manyTillChar alphaNum (char '"')
  
  -- | Parses either @t@, @nil@, or @verbatim@ into a 'TeXExport' value.
  texSetting :: Monad m
             => Text
             -> ExportSettingSetter TeXExport
             -> OrgParser m ()
  texSetting = genericExportSetting $ texVerbatim <|> texBoolean
   where
     texVerbatim = TeXVerbatim <$ optionString "verbatim"
  
     texBoolean = try $ do
       exportBool <- elispBoolean
       return $
         if exportBool
         then TeXExport
         else TeXIgnore
  
  -- | Read but ignore the export setting.
  ignoredSetting :: Monad m => Text -> OrgParser m ()
  ignoredSetting s = try (() <$ textStr s <* char ':' <* many1 nonspaceChar)
  
  -- | Read any setting string, but ignore it and emit a warning.
  ignoreAndWarn :: PandocMonad m => OrgParser m ()
  ignoreAndWarn = try $ do
    opt <- many1Char nonspaceChar
    report (UnknownOrgExportOption opt)
    return ()
  
  -- | Read an elisp boolean.  Only NIL is treated as false, non-NIL values are
  -- interpreted as true.
  elispBoolean :: Monad m => OrgParser m Bool
  elispBoolean = try $ do
    value <- many1 nonspaceChar
    return $ case map toLower value of
               "nil" -> False
               "{}"  -> False
               "()"  -> False
               _     -> True
  
  -- | Try to parse a literal string as the option value. Returns the
  -- string on success.
  optionString :: Monad m => Text -> OrgParser m Text
  optionString s = try $ do
    _ <- string (unpack s)
    lookAhead (newline <|> spaceChar)
    return s
  