{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NamedFieldPuns #-}
module Main where
import Text.Pandoc.JSON
import Text.Pandoc.Walk (walk)
import Text.Pandoc.Definition ()
import Data.String (IsString)
import Data.Maybe (fromMaybe)
import qualified Data.Map as Map
import Data.Text(Text)
import qualified Data.Text as T
import qualified Token.Haskell (tokenizer)
import qualified Token.Skylighting
import Filter (renderBlock, renderInline)
import FindColumns (findColumns)
import Alignment (Processed)
import Render.Latex (latexPackages)
import System.IO
import Debug.Trace(trace)
data Options = Options {
inlineSyntax :: Text
} deriving (Show)
main :: IO ()
main = toJSONFilter runner
-- https://www.logicmatters.net/latex-for-logicians/symbols/
-- Just (MetaList [MetaBlocks [RawBlock (Format "tex") "\\usepackage{scalerel}"]])
-- | Main body of Pandoc filter.
-- Reads format option, metadata,
-- and calls `walk` with `blockFormatter`.
runner :: Maybe Format -> Pandoc -> IO Pandoc
runner (fromMaybe (Format "text") -> format) input@(Pandoc (Meta meta) ast) = do
hPutStrLn stderr $ show opts
hPutStrLn stderr $ show $ Map.lookup "header-includes" meta
let top = if format `elem` [Format "latex", Format "tex"]
then Pandoc (Meta $ Map.alter modifyIncludes "header-includes" meta) ast
else input
return $ walk (blockFormatter opts format) top
where
-- | Check default syntax for inline code
opts = case Map.lookup "inline-code" meta of
Nothing -> Options "haskell" -- default
Just (MetaString s ) -> Options s -- never needed?
Just (MetaInlines [Str s]) -> Options s
Just otherValue -> error $ "inline-code: meta should be a string but is: " <> show otherValue
-- | Add package dependencies to the meta "header-includes".
modifyIncludes :: Maybe MetaValue -> Maybe MetaValue
modifyIncludes = Just
. addTeXPackages latexPackages
. fromMaybe (MetaList [])
-- Just (MetaBlocks [RawBlock (Format "tex") s])
where
addTeXPackages :: [Text] -> MetaValue -> MetaValue
addTeXPackages = addTeXInclude
. T.unlines
. fmap (\name -> "\\usepackage{" <> name <> "}")
addTeXInclude :: Text -> MetaValue -> MetaValue
addTeXInclude rawTeX (MetaList ls) = -- this variant is invalid for std templates
MetaList $ MetaBlocks [RawBlock (Format "tex") rawTeX]:ls
addTeXInclude rawTeX (MetaBlocks [RawBlock "tex" s]) = -- pandoc-types>=1.22
-- trace ("adding pandoc-types<1.20 (" <> T.unpack rawTeX <> ")") $
MetaBlocks [RawBlock "tex" $ mconcat [s, "\n", rawTeX]]
addTeXInclude rawTeX (MetaBlocks [RawBlock "latex" s]) = -- old pandoc-types
-- trace ("adding pandoc-types<1.20 (" <> T.unpack rawTeX <> ")") $
MetaBlocks [RawBlock "tex" $ mconcat [s, "\n", rawTeX]]
addTeXInclude rawTeX other = error $ "Add a new case for addTeXInclude " <> show other
-- | Select the desired format output then process it.
-- Run tokenizer, analysis, and formatter.
--
-- For non-CodeBlocks it runs `inlineFormatter`.
-- Fallback to original input, if failed to tokenize.
blockFormatter :: Options
-> Format -- ^ Output format, defaults to "plain" if not found
-> Block
-> Block
blockFormatter _opts format (CodeBlock attrs content) =
case fmap findColumns $ getTokenizer attrs content of
Just processed -> renderBlock format attrs processed
Nothing -> CodeBlock attrs content -- fallback
-- Do not touch other blocks than 'CodeBock'
blockFormatter opts format x = do
walk (inlineFormatter opts format) x
-- | Select the desired format output then process it.
-- Run tokenizer, and inline formatter.
-- Fallback to original input, if failed to tokenize.
inlineFormatter :: Options
-> Format -- ^ Output format, defaults to "plain" if not found
-> Inline
-> Inline
inlineFormatter Options {inlineSyntax} format (Code attrs txt) =
case getTokenizer (addClass attrs) txt of
Just processed -> renderInline format attrs
$ map discardLoc processed
Nothing -> Code attrs txt -- fallback
where
discardLoc (a, _, b) = (a, b)
addClass (a, classes, b) | inlineSyntax /= "" = (a, inlineSyntax:classes, b)
addClass o = o
inlineFormatter _opts _ x = x
-- | Pick tokenizer depending on options.
getTokenizer attrs | isHaskell attrs = Token.Haskell.tokenizer
getTokenizer (_,classes,_) = case Token.Skylighting.lookupTokenizer classes of
Just syntax -> Token.Skylighting.tokenizer syntax
Nothing -> \_ -> Nothing
-- | Check if the code block is tagged as Haskell.
isHaskell :: (Foldable t, Eq a1, IsString a1) =>
(a2, t a1, c) -> Bool
isHaskell (_, classes, _) = "haskell" `elem` classes