Blame src/Text/Pandoc/Readers/Org/Meta.hs

Packit Service d2f85f
{-# LANGUAGE FlexibleContexts #-}
Packit Service d2f85f
{-# LANGUAGE TupleSections    #-}
Packit Service d2f85f
{-
Packit Service d2f85f
Copyright (C) 2014-2017 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
Packit Service d2f85f
Packit Service d2f85f
This program is free software; you can redistribute it and/or modify
Packit Service d2f85f
it under the terms of the GNU General Public License as published by
Packit Service d2f85f
the Free Software Foundation; either version 2 of the License, or
Packit Service d2f85f
(at your option) any later version.
Packit Service d2f85f
Packit Service d2f85f
This program is distributed in the hope that it will be useful,
Packit Service d2f85f
but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service d2f85f
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service d2f85f
GNU General Public License for more details.
Packit Service d2f85f
Packit Service d2f85f
You should have received a copy of the GNU General Public License
Packit Service d2f85f
along with this program; if not, write to the Free Software
Packit Service d2f85f
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit Service d2f85f
-}
Packit Service d2f85f
Packit Service d2f85f
{- |
Packit Service d2f85f
   Module      : Text.Pandoc.Readers.Org.Meta
Packit Service d2f85f
   Copyright   : Copyright (C) 2014-2017 Albert Krewinkel
Packit Service d2f85f
   License     : GNU GPL, version 2 or above
Packit Service d2f85f
Packit Service d2f85f
   Maintainer  : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
Packit Service d2f85f
Packit Service d2f85f
Parsers for Org-mode meta declarations.
Packit Service d2f85f
-}
Packit Service d2f85f
module Text.Pandoc.Readers.Org.Meta
Packit Service d2f85f
  ( metaExport
Packit Service d2f85f
  , metaKey
Packit Service d2f85f
  , metaLine
Packit Service d2f85f
  ) where
Packit Service d2f85f
Packit Service d2f85f
import Text.Pandoc.Readers.Org.BlockStarts
Packit Service d2f85f
import Text.Pandoc.Readers.Org.ExportSettings (exportSettings)
Packit Service d2f85f
import Text.Pandoc.Readers.Org.Inlines
Packit Service d2f85f
import Text.Pandoc.Readers.Org.ParserState
Packit Service d2f85f
import Text.Pandoc.Readers.Org.Parsing
Packit Service d2f85f
Packit Service d2f85f
import Text.Pandoc.Builder (Blocks, Inlines)
Packit Service d2f85f
import qualified Text.Pandoc.Builder as B
Packit Service d2f85f
import Text.Pandoc.Class (PandocMonad)
Packit Service d2f85f
import Text.Pandoc.Definition
Packit Service d2f85f
Packit Service d2f85f
import Control.Monad (mzero, void, when)
Packit Service d2f85f
import Data.Char (toLower)
Packit Service d2f85f
import Data.List (intersperse)
Packit Service d2f85f
import qualified Data.Map as M
Packit Service d2f85f
import Network.HTTP (urlEncode)
Packit Service d2f85f
Packit Service d2f85f
-- | Returns the current meta, respecting export options.
Packit Service d2f85f
metaExport :: Monad m => OrgParser m (F Meta)
Packit Service d2f85f
metaExport = do
Packit Service d2f85f
  st <- getState
Packit Service d2f85f
  let settings = orgStateExportSettings st
Packit Service d2f85f
  return $ (if exportWithAuthor  settings then id else removeMeta "author")
Packit Service d2f85f
         . (if exportWithCreator settings then id else removeMeta "creator")
Packit Service d2f85f
         . (if exportWithEmail   settings then id else removeMeta "email")
Packit Service d2f85f
        <$> orgStateMeta st
Packit Service d2f85f
Packit Service d2f85f
removeMeta :: String -> Meta -> Meta
Packit Service d2f85f
removeMeta key meta' =
Packit Service d2f85f
  let metaMap = unMeta meta'
Packit Service d2f85f
  in Meta $ M.delete key metaMap
Packit Service d2f85f
Packit Service d2f85f
-- | Parse and handle a single line containing meta information
Packit Service d2f85f
-- The order, in which blocks are tried, makes sure that we're not looking at
Packit Service d2f85f
-- the beginning of a block, so we don't need to check for it
Packit Service d2f85f
metaLine :: PandocMonad m => OrgParser m Blocks
Packit Service d2f85f
metaLine = mempty <$ metaLineStart <* (optionLine <|> declarationLine)
Packit Service d2f85f
Packit Service d2f85f
declarationLine :: PandocMonad m => OrgParser m ()
Packit Service d2f85f
declarationLine = try $ do
Packit Service d2f85f
  key   <- map toLower <$> metaKey
Packit Service d2f85f
  (key', value) <- metaValue key
Packit Service d2f85f
  let addMetaValue st =
Packit Service d2f85f
        st { orgStateMeta = B.setMeta key' <$> value <*> orgStateMeta st }
Packit Service d2f85f
  when (key' /= "results") $ updateState addMetaValue
Packit Service d2f85f
Packit Service d2f85f
metaKey :: Monad m => OrgParser m String
Packit Service d2f85f
metaKey = map toLower <$> many1 (noneOf ": \n\r")
Packit Service d2f85f
                      <*  char ':'
Packit Service d2f85f
                      <*  skipSpaces
Packit Service d2f85f
Packit Service d2f85f
metaValue :: PandocMonad m => String -> OrgParser m (String, F MetaValue)
Packit Service d2f85f
metaValue key =
Packit Service d2f85f
  let inclKey = "header-includes"
Packit Service d2f85f
  in case key of
Packit Service d2f85f
    "author"          -> (key,) <$> metaInlinesCommaSeparated
Packit Service d2f85f
    "keywords"        -> (key,) <$> metaInlinesCommaSeparated
Packit Service d2f85f
    "title"           -> (key,) <$> metaInlines
Packit Service d2f85f
    "subtitle"        -> (key,) <$> metaInlines
Packit Service d2f85f
    "date"            -> (key,) <$> metaInlines
Packit Service d2f85f
    "nocite"          -> (key,) <$> accumulatingList key metaInlines
Packit Service d2f85f
    "header-includes" -> (key,) <$> accumulatingList key metaInlines
Packit Service d2f85f
    "latex_header"    -> (inclKey,) <$>
Packit Service d2f85f
                         accumulatingList inclKey (metaExportSnippet "latex")
Packit Service d2f85f
    "latex_class"     -> ("documentclass",) <$> metaString
Packit Service d2f85f
    -- Org-mode expects class options to contain the surrounding brackets,
Packit Service d2f85f
    -- pandoc does not.
Packit Service d2f85f
    "latex_class_options" -> ("classoption",) <$>
Packit Service d2f85f
                             metaModifiedString (filter (`notElem` "[]"))
Packit Service d2f85f
    "html_head"       -> (inclKey,) <$>
Packit Service d2f85f
                         accumulatingList inclKey (metaExportSnippet "html")
Packit Service d2f85f
    _                 -> (key,) <$> metaString
Packit Service d2f85f
Packit Service d2f85f
metaInlines :: PandocMonad m => OrgParser m (F MetaValue)
Packit Service d2f85f
metaInlines = fmap (MetaInlines . B.toList) <$> inlinesTillNewline
Packit Service d2f85f
Packit Service d2f85f
metaInlinesCommaSeparated :: PandocMonad m => OrgParser m (F MetaValue)
Packit Service d2f85f
metaInlinesCommaSeparated = do
Packit Service d2f85f
  itemStrs <- many1 (noneOf ",\n") `sepBy1` char ','
Packit Service d2f85f
  newline
Packit Service d2f85f
  items <- mapM (parseFromString inlinesTillNewline . (++ "\n")) itemStrs
Packit Service d2f85f
  let toMetaInlines = MetaInlines . B.toList
Packit Service d2f85f
  return $ MetaList . map toMetaInlines <$> sequence items
Packit Service d2f85f
Packit Service d2f85f
metaString :: Monad m => OrgParser m (F MetaValue)
Packit Service d2f85f
metaString = metaModifiedString id
Packit Service d2f85f
Packit Service d2f85f
metaModifiedString :: Monad m => (String -> String) -> OrgParser m (F MetaValue)
Packit Service d2f85f
metaModifiedString f = return . MetaString . f <$> anyLine
Packit Service d2f85f
Packit Service d2f85f
-- | Read an format specific meta definition
Packit Service d2f85f
metaExportSnippet :: Monad m => String -> OrgParser m (F MetaValue)
Packit Service d2f85f
metaExportSnippet format =
Packit Service d2f85f
  return . MetaInlines . B.toList . B.rawInline format <$> anyLine
Packit Service d2f85f
Packit Service d2f85f
-- | Accumulate the result of the @parser@ in a list under @key@.
Packit Service d2f85f
accumulatingList :: Monad m => String
Packit Service d2f85f
                 -> OrgParser m (F MetaValue)
Packit Service d2f85f
                 -> OrgParser m (F MetaValue)
Packit Service d2f85f
accumulatingList key p = do
Packit Service d2f85f
  value <- p
Packit Service d2f85f
  meta' <- orgStateMeta <$> getState
Packit Service d2f85f
  return $ (\m v -> MetaList (curList m ++ [v])) <$> meta' <*> value
Packit Service d2f85f
 where curList m = case lookupMeta key m of
Packit Service d2f85f
                     Just (MetaList ms) -> ms
Packit Service d2f85f
                     Just x             -> [x]
Packit Service d2f85f
                     _                  -> []
Packit Service d2f85f
Packit Service d2f85f
--
Packit Service d2f85f
-- export options
Packit Service d2f85f
--
Packit Service d2f85f
optionLine :: Monad m => OrgParser m ()
Packit Service d2f85f
optionLine = try $ do
Packit Service d2f85f
  key <- metaKey
Packit Service d2f85f
  case key of
Packit Service d2f85f
    "link"     -> parseLinkFormat >>= uncurry addLinkFormat
Packit Service d2f85f
    "options"  -> exportSettings
Packit Service d2f85f
    "todo"     -> todoSequence >>= updateState . registerTodoSequence
Packit Service d2f85f
    "seq_todo" -> todoSequence >>= updateState . registerTodoSequence
Packit Service d2f85f
    "typ_todo" -> todoSequence >>= updateState . registerTodoSequence
Packit Service d2f85f
    "macro"    -> macroDefinition >>= updateState . registerMacro
Packit Service d2f85f
    _          -> mzero
Packit Service d2f85f
Packit Service d2f85f
addLinkFormat :: Monad m => String
Packit Service d2f85f
              -> (String -> String)
Packit Service d2f85f
              -> OrgParser m ()
Packit Service d2f85f
addLinkFormat key formatter = updateState $ \s ->
Packit Service d2f85f
  let fs = orgStateLinkFormatters s
Packit Service d2f85f
  in s{ orgStateLinkFormatters = M.insert key formatter fs }
Packit Service d2f85f
Packit Service d2f85f
parseLinkFormat :: Monad m => OrgParser m (String, String -> String)
Packit Service d2f85f
parseLinkFormat = try $ do
Packit Service d2f85f
  linkType <- (:) <$> letter <*> many (alphaNum <|> oneOf "-_") <* skipSpaces
Packit Service d2f85f
  linkSubst <- parseFormat
Packit Service d2f85f
  return (linkType, linkSubst)
Packit Service d2f85f
Packit Service d2f85f
-- | An ad-hoc, single-argument-only implementation of a printf-style format
Packit Service d2f85f
-- parser.
Packit Service d2f85f
parseFormat :: Monad m => OrgParser m (String -> String)
Packit Service d2f85f
parseFormat = try $ replacePlain <|> replaceUrl <|> justAppend
Packit Service d2f85f
 where
Packit Service d2f85f
   -- inefficient, but who cares
Packit Service d2f85f
   replacePlain = try $ (\x -> concat . flip intersperse x)
Packit Service d2f85f
                     <$> sequence [tillSpecifier 's', rest]
Packit Service d2f85f
   replaceUrl   = try $ (\x -> concat . flip intersperse x . urlEncode)
Packit Service d2f85f
                     <$> sequence [tillSpecifier 'h', rest]
Packit Service d2f85f
   justAppend   = try $ (++) <$> rest
Packit Service d2f85f
Packit Service d2f85f
   rest            = manyTill anyChar         (eof <|> () <$ oneOf "\n\r")
Packit Service d2f85f
   tillSpecifier c = manyTill (noneOf "\n\r") (try $ string ('%':c:""))
Packit Service d2f85f
Packit Service d2f85f
inlinesTillNewline :: PandocMonad m => OrgParser m (F Inlines)
Packit Service d2f85f
inlinesTillNewline = do
Packit Service d2f85f
  updateLastPreCharPos
Packit Service d2f85f
  trimInlinesF . mconcat <$> manyTill inline newline
Packit Service d2f85f
Packit Service d2f85f
--
Packit Service d2f85f
-- ToDo Sequences and Keywords
Packit Service d2f85f
--
Packit Service d2f85f
todoSequence :: Monad m => OrgParser m TodoSequence
Packit Service d2f85f
todoSequence = try $ do
Packit Service d2f85f
  todoKws <- todoKeywords
Packit Service d2f85f
  doneKws <- optionMaybe $ todoDoneSep *> todoKeywords
Packit Service d2f85f
  newline
Packit Service d2f85f
  -- There must be at least one DONE keyword. The last TODO keyword is taken if
Packit Service d2f85f
  -- necessary.
Packit Service d2f85f
  case doneKws of
Packit Service d2f85f
    Just done  -> return $ keywordsToSequence todoKws done
Packit Service d2f85f
    Nothing    -> case reverse todoKws of
Packit Service d2f85f
                    []     -> mzero  -- no keywords present
Packit Service d2f85f
                    (x:xs) -> return $ keywordsToSequence (reverse xs) [x]
Packit Service d2f85f
Packit Service d2f85f
 where
Packit Service d2f85f
   todoKeywords :: Monad m => OrgParser m [String]
Packit Service d2f85f
   todoKeywords = try $
Packit Service d2f85f
     let keyword = many1 nonspaceChar <* skipSpaces
Packit Service d2f85f
         endOfKeywords = todoDoneSep <|> void newline
Packit Service d2f85f
     in manyTill keyword (lookAhead endOfKeywords)
Packit Service d2f85f
Packit Service d2f85f
   todoDoneSep :: Monad m => OrgParser m ()
Packit Service d2f85f
   todoDoneSep = void . try $ skipSpaces *> char '|' <* skipSpaces1
Packit Service d2f85f
Packit Service d2f85f
   keywordsToSequence :: [String] -> [String] -> TodoSequence
Packit Service d2f85f
   keywordsToSequence todo done =
Packit Service d2f85f
     let todoMarkers = map (TodoMarker Todo) todo
Packit Service d2f85f
         doneMarkers = map (TodoMarker Done) done
Packit Service d2f85f
     in todoMarkers ++ doneMarkers
Packit Service d2f85f
Packit Service d2f85f
macroDefinition :: Monad m => OrgParser m (String, [String] -> String)
Packit Service d2f85f
macroDefinition = try $ do
Packit Service d2f85f
  macroName <- many1 nonspaceChar <* skipSpaces
Packit Service d2f85f
  firstPart <- expansionPart
Packit Service d2f85f
  (elemOrder, parts) <- unzip <$> many ((,) <$> placeholder <*> expansionPart)
Packit Service d2f85f
  let expander = mconcat . alternate (firstPart:parts) . reorder elemOrder
Packit Service d2f85f
  return (macroName, expander)
Packit Service d2f85f
 where
Packit Service d2f85f
  placeholder :: Monad m => OrgParser m Int
Packit Service d2f85f
  placeholder = try . fmap read $ char '$' *> many1 digit
Packit Service d2f85f
Packit Service d2f85f
  expansionPart :: Monad m => OrgParser m String
Packit Service d2f85f
  expansionPart = try $ many (notFollowedBy placeholder *> noneOf "\n\r")
Packit Service d2f85f
Packit Service d2f85f
  alternate :: [a] -> [a] -> [a]
Packit Service d2f85f
  alternate []     ys     = ys
Packit Service d2f85f
  alternate xs     []     = xs
Packit Service d2f85f
  alternate (x:xs) (y:ys) = x : y : alternate xs ys
Packit Service d2f85f
Packit Service d2f85f
  reorder :: [Int] -> [String] -> [String]
Packit Service d2f85f
  reorder perm xs =
Packit Service d2f85f
    let element n = take 1 $ drop (n - 1) xs
Packit Service d2f85f
    in concatMap element perm