PandocのwriteMarkdownでメタデータをYAML出力する
やりたいこと
Pandocに限らず、いくつかのMarkdownを扱えるプログラムでは、Markdownファイルの冒頭にYAML形式のメタデータを持つことが 出来ます(frontmatterというやつです)。
例えば、以下のような:
---
title: PandocのwriteMarkdownでメタデータをYAML出力する
author: cj.bc_sd
tags:
- haskell
- pandoc
---
# Header1
...
そこで、Pandocを用いて他のフォーマットから変換する際にこのYAML形式のメタデータを付与したいです。
Tl;Dr
WriterOptions.writerExtensions(src) にExt_yaml_metadata_block(解説)を追加するWriterOptions.writerTemplateに何かテンプレートを設定する(Nothingじゃなければ良い)デフォルトのものを
compileDefaultTemplate "markdown"(src)で取ってきても良いその場合、
PandocPureを使っている場合は適切にstFilesを弄る必要がある(後述)
解説
前提: Pandocをライブラリとして利用して変換する
Pandocをライブラリとして使う場合、以下のような流れで変換処理を行うことが出来ます:
Data.Text.IO.readFile等を用いてファイルの内容をTextとして読み込みます必要なら
Pandoc型に処理をしますWriterのうちの一つを用いて、Text型にできます
今回は、書き出しはMarkdown形式で行うので、以下のようになります。
Reader は元のフォーマットに対応したものであれば何でも良いのですが、この記事では
元のフォーマットとしてOrgを想定し、 readOrg を使用します。
又、 runPure は runIO にしても良いです。
runPure の場合は runIO よりも少し工程が増えますが、純粋性を持たせることは出来ます。
import Text.Pandoc
import qualified Data.Text.IO as TIO
main = do
rawContent <- TIO.readFile "/tmp/FILENAME.org"
txt <- handleError . runPure $ do
pandocDoc <- readOrg def rawContent
writeMarkdown def pandocDoc
TIO.writeFile "/tmp/dist.md" txt
以降は、これに変更を加えていく形で進めていこうと思います。
Extyamlmetadatablock 拡張を有効化する
まず、デフォルトの WriterOptions ではYAML形式のメタデータブロックは生成されません。
Ext_yaml_metadata_blockを有効化する必要があります。
尚、
import Text.Pandoc
import qualified Data.Text.IO as TIO
main = do
rawContent <- TIO.readFile "/tmp/FILENAME.org"
txt <- handleError . runPure $ do
let writerOpts = def { writerExtensions = extensionsFromList [Ext_yaml_metadata_block]}
pandocDoc <- readOrg def rawContent
writeMarkdown writerOpts pandocDoc
TIO.writeFile "/tmp/dist.md" txt
txt <- handleError . runPure $ do
+ let writerOpts = def { writerExtensions = extensionsFromList [Ext_yaml_metadata_block]}
pandocDoc <- readOrg def rawContent
- writeMarkdown def pandocDoc
+ writeMarkdown writerOpts pandocDoc
Writerのテンプレートを明示的に指定する
何故か、MarkdownのwriterではYAMLのfrontmatterを出力するためには明示的にテンプレートを指定する必要があります。
これは、テンプレートが明示的に指定されている時のみ Ext_yaml_metadata_block を確認するようになっているためです。
(...ドウシテ...??)
まぁ、これを行えばきちんとfrontmatterが出力されるはずです!
PandocIO モナド内で行う場合
PandocIO モナドを用いている場合は、 compileDefaultTemplate を使ってテンプレートを取り出し、それを素直に
writerTemplate に設定すれば良いです。尚、Org文書ではメタデータを埋め込めないため setMeta
を用いてコード内からメタデータを設定しています(setMeta (T.pack "author") "test" pandocDoc)
import Text.Pandoc
import Text.Pandoc.Builder (setMeta)
import qualified Data.Text.IO as TIO
import qualified Data.Text as T
main = do
rawContent <- TIO.readFile "/tmp/FILENAME.org"
result <- runIO $ do
tmpl <- compileDefaultTemplate (T.pack "markdown")
let writerOpts = def { writerExtensions = extensionsFromList [Ext_yaml_metadata_block]
, writerTemplate = Just tmpl
}
pandocDoc <- readOrg def rawContent
writeMarkdown writerOpts $ setMeta (T.pack "author") "test" pandocDoc
txt <- handleError result
TIO.writeFile "/tmp/dist.md" txt
PandocPure モナド内で行う場合
PandocPure モナド内で行う場合は、 PandocIO を用いたコードに加えた変更にプラスして少し手を加える必要があります。
PandocPure 内ではファイルシステムにアクセスすることが出来ず、デフォルトのテンプレートファイルが存在しないので、
そのまま compileDefaultTemplate してしまうと以下のような実行時エラーが吐かれます。
Could not find data file data/data/templates/default.markdown
そこで、 PandocPure 内にある仮想ファイルシステム的なものに該当のファイルを追加する必要があります。
そのためには、PureState の stFiles を modifyPureState で編集します。
import Text.Pandoc
import Text.Pandoc.Builder (setMeta)
import qualified Data.Text.IO as TIO
import qualified Data.Text as T
import Data.String (fromString)
-- | デフォルトのマークダウン用テンプレートファイル
--
-- ファイルの内容は @pandoc -D markdown@ コマンドの出力をそのまま使っています
defaultMarkdownTemplate :: FileInfo
defaultMarkdownTemplate = FileInfo (read "2023-06-03 0:00:00UTC") (fromString content)
where
content = unlines ["$if(titleblock)$"
, "$titleblock$"
, ""
, "$endif$"
, "$for(header-includes)$"
, "$header-includes$"
, ""
, "$endfor$"
, "$for(include-before)$"
, "$include-before$"
, ""
, "$endfor$"
, "$if(toc)$"
, "$table-of-contents$"
, ""
, "$endif$"
, "$body$"
, "$for(include-after)$"
, ""
, "$include-after$"
, "$endfor$"
]
main = do
rawContent <- TIO.readFile "/tmp/FILENAME.org"
txt <- handleError . runPure $ do
-- ファイルを追加します
files <- (getsPureState stFiles)
let dummyDataFiles = insertInFileTree "data/data/templates/default.markdown" defaultMarkdownTemplate files
modifyPureState (\st -> st {stFiles = dummyDataFiles })
-- ^
tmpl <- compileDefaultTemplate (T.pack "markdown")
let writerOpts = def { writerExtensions = extensionsFromList [Ext_yaml_metadata_block]
, writerTemplate = Just tmpl
}
pandocDoc <- readOrg def rawContent
writeMarkdown writerOpts $ setMeta (T.pack "author") "test" pandocDoc
TIO.writeFile "/tmp/dist.md" txt