docster/app/Main.hs
Willem van den Ende e248395557 Fix Haskell project setup and implement Mermaid processing
- Update docster.cabal to modern format with GHC 9.12.2 compatibility
- Fix Mermaid code block detection using getDefaultExtensions
- Switch from SVG to PNG output for PDF compatibility
- Add CLAUDE.md with development instructions
- Update tooling from mise to ghcup for Haskell management

Known issues:
- Generated diagram files are created in root directory instead of alongside source files
- PDF generation fails with LaTeX errors for complex documents (missing \tightlist support)
- HTML output lacks proper DOCTYPE (quirks mode)
- Debug output still present in code

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 17:23:04 +02:00

107 lines
3.9 KiB
Haskell

{-# LANGUAGE OverloadedStrings #-}
module Main where
import Text.Pandoc
import Text.Pandoc.Error
import Text.Pandoc.Class (runIOorExplode)
import Text.Pandoc.PDF (makePDF)
import Text.Pandoc.Walk (walkM)
import Text.Pandoc.Extensions (Extension(..), enableExtension, getDefaultExtensions)
import System.Environment (getArgs)
import System.FilePath (replaceExtension)
import System.Process (callProcess)
import System.Directory (doesFileExist)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as TIO
import Data.Hashable (hash)
import Control.Monad (when, void)
import qualified Data.ByteString.Lazy as BL
-- Transform Mermaid code blocks into image embeds
processMermaid :: Block -> IO Block
processMermaid block@(CodeBlock (id', classes, _) contents)
| "mermaid" `elem` classes = do
putStrLn $ "🎯 Found Mermaid block with classes: " ++ show classes
let baseName = if T.null id' then "diagram-" ++ take 6 (show (abs (hash (T.unpack contents)))) else T.unpack id'
mmdFile = baseName ++ ".mmd"
pngFile = baseName ++ ".png"
putStrLn $ "📝 Writing to " ++ mmdFile ++ " and generating " ++ pngFile
writeFile mmdFile (T.unpack contents)
void $ callProcess "mmdc" ["-i", mmdFile, "-o", pngFile]
putStrLn $ "✅ Generated " ++ pngFile
return $ Para [Image nullAttr [] (T.pack pngFile, "Mermaid diagram")]
processMermaid x = do
-- Debug: show what blocks we're processing
case x of
CodeBlock (_, classes, _) _ -> putStrLn $ "📄 Found code block with classes: " ++ show classes
_ -> return ()
return x
-- Walk the Pandoc AST and process blocks using walkM
transformDoc :: Pandoc -> IO Pandoc
transformDoc = walkM processMermaid
main :: IO ()
main = do
args <- getArgs
case args of
["-pdf", path] -> compileToPDF path
["-html", path] -> compileToHTML path
_ -> putStrLn "Usage: docster -pdf|-html <file.md>"
pdfTemplate :: T.Text
pdfTemplate = T.unlines [
"\\documentclass{article}",
"\\usepackage[utf8]{inputenc}",
"\\usepackage{graphicx}",
"\\usepackage{geometry}",
"\\geometry{margin=1in}",
"\\usepackage{hyperref}",
"\\usepackage{enumitem}",
"\\providecommand{\\tightlist}{%",
" \\setlength{\\itemsep}{0pt}\\setlength{\\parskip}{0pt}}",
"\\title{$title$}",
"\\author{$author$}",
"\\date{$date$}",
"\\begin{document}",
"$if(title)$\\maketitle$endif$",
"$body$",
"\\end{document}"
]
compileToPDF :: FilePath -> IO ()
compileToPDF path = do
content <- TIO.readFile path
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
pandoc <- runIOorExplode $ readMarkdown readerOptions content
transformed <- transformDoc pandoc
let outputPath = replaceExtension path "pdf"
writerOptions = def
-- First generate LaTeX with proper document structure
latexOutput <- runIOorExplode $ writeLaTeX writerOptions transformed
let latexWithHeader = "\\documentclass{article}\n\\usepackage[utf8]{inputenc}\n\\usepackage{graphicx}\n\\usepackage{geometry}\n\\geometry{margin=1in}\n\\begin{document}\n" <> latexOutput <> "\n\\end{document}"
result <- runIOorExplode $ makePDF "pdflatex" [] (\_ _ -> return latexWithHeader) def transformed
case result of
Left err -> error $ "PDF error: " ++ show err
Right bs -> BL.writeFile outputPath bs >> putStrLn ("✅ PDF written to " ++ outputPath)
compileToHTML :: FilePath -> IO ()
compileToHTML path = do
content <- TIO.readFile path
putStrLn $ "📖 Reading: " ++ path
putStrLn $ "📝 Content: " ++ T.unpack content
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
pandoc <- runIOorExplode $ readMarkdown readerOptions content
putStrLn $ "🔍 Parsed AST: " ++ show pandoc
transformed <- transformDoc pandoc
let outputPath = replaceExtension path "html"
html <- runIOorExplode $ writeHtml5String def transformed
TIO.writeFile outputPath html
putStrLn ("✅ HTML written to " ++ outputPath)