packages feed

pandoc-include-plus-0.1.1: src/Text/Pandoc/Filters/Include.hs

{-# LANGUAGE OverloadedStrings #-}
{-|
Module      : Columns
Description : When a code block has the "include" label, treat it as a list of files to include.
Copyright   : (c) Amy de Buitléir, 2023
License     : BSD--3
Maintainer  : amy@nualeargais.ie
Stability   : experimental
Portability : POSIX

See <https://github.com/mhwombat/pandoc-linear-table> for information
on how to use this filter.
-}
module Text.Pandoc.Filters.Include
  (
    transform
  ) where

import Data.Text        qualified as T
import Network.URI (isURI)
import System.FilePath  (combine, isAbsolute, isRelative, takeDirectory)
import Text.Pandoc      qualified as P
import Text.Pandoc.UTF8 qualified as U
import Text.Pandoc.Walk     (walk)

transform :: FilePath -> P.Block -> IO [P.Block]
transform d b@(P.CodeBlock (_, classes, kvs) s)
  | "include" `elem` classes = doIncludes d kvs s
  | otherwise         = return [b]
transform _ b = return [b]

doIncludes :: FilePath -> [(T.Text, T.Text)] -> T.Text -> IO [P.Block]
doIncludes d kvs s = concat <$> mapM (doInclude d levelAdjustment) fs
  where fs = lines $ T.unpack s
        levelAdjustment = case lookup "level" kvs of
                            Just v  -> read $ T.unpack v
                            Nothing -> 0

-- Note: This will terminate automatically when there are no further transformations.
doInclude :: FilePath -> Int -> FilePath -> IO [P.Block]
doInclude dir levelAdj f = do
  let path = if isRelative f then combine dir f else f
  let includeDir = takeDirectory path
  p <- readMarkdownFromFile path
  let (P.Pandoc _ bs) = walk (adjustImagePaths includeDir) p
  bs' <- concat <$> mapM (transform $ takeDirectory path) bs
  return $ map (adjustHeaderLevel levelAdj) bs'

readMarkdownFromFile :: FilePath -> IO P.Pandoc
readMarkdownFromFile f = U.readFile f >>= parseMarkdown

parseMarkdown :: T.Text -> IO P.Pandoc
parseMarkdown s = P.runIO (P.readMarkdown markdownReaderOptions s) >>= P.handleError

markdownReaderOptions :: P.ReaderOptions
markdownReaderOptions = P.def { P.readerStandalone = True,
                                P.readerExtensions = P.pandocExtensions }

adjustHeaderLevel :: Int -> P.Block -> P.Block
adjustHeaderLevel n (P.Header m attr xs)
  | m' <= 0   = P.Para xs
  | otherwise = P.Header m' attr xs
  where m' = n + m
adjustHeaderLevel _ b = b

adjustImagePaths :: FilePath -> P.Inline -> P.Inline
adjustImagePaths dir (P.Image attr xs (url, title)) = P.Image attr xs (adjustFilePath dir url, title)
adjustImagePaths _ x = x

adjustFilePath :: FilePath -> T.Text -> T.Text
adjustFilePath dir url
  | isURI s      = url
  | isAbsolute s = url
  | otherwise    = T.pack $ combine dir s
  where s = T.unpack url

{-
Useful for debugging in GHCi

:l Text.Pandoc.Filters.Include
(P.Pandoc _ bs) <- readMarkdownFromFile "test-files/test.md"
concat <$> mapM (transform "test-files") bs
-}