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>
This commit is contained in:
2025-07-29 17:23:04 +02:00
co-authored by Claude
parent 68507e85d1
commit e248395557
6 changed files with 173 additions and 27 deletions
+52 -14
View File
@@ -6,33 +6,42 @@ 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.IO (writeFile)
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 (CodeBlock (id', classes, _) contents)
processMermaid block@(CodeBlock (id', classes, _) contents)
| "mermaid" `elem` classes = do
let baseName = if null id' then "diagram-" ++ take 6 (show (abs (hash contents))) else id'
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"
svgFile = baseName ++ ".svg"
pngFile = baseName ++ ".png"
writeFile mmdFile contents
exists <- doesFileExist svgFile
when (not exists) $ void $ callProcess "mmdc" ["-i", mmdFile, "-o", svgFile]
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 svgFile, "Mermaid diagram")]
processMermaid x = return x
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
-- Walk the Pandoc AST and process blocks using walkM
transformDoc :: Pandoc -> IO Pandoc
transformDoc = walkM processMermaid
@@ -44,22 +53,51 @@ main = do
["-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
pandoc <- runIOorExplode $ readMarkdown def content
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
pandoc <- runIOorExplode $ readMarkdown readerOptions content
transformed <- transformDoc pandoc
let outputPath = replaceExtension path "pdf"
result <- runIOorExplode $ makePDF "pdflatex" [] transformed
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 -> writeFile outputPath bs >> putStrLn ("✅ PDF written to " ++ outputPath)
Right bs -> BL.writeFile outputPath bs >> putStrLn ("✅ PDF written to " ++ outputPath)
compileToHTML :: FilePath -> IO ()
compileToHTML path = do
content <- TIO.readFile path
pandoc <- runIOorExplode $ readMarkdown def content
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"