Compare commits

...
12 Commits
Author SHA1 Message Date
mostalive 54c7502f0f fix test warnings: redundant Data.Text import, explicit Int types in Map ops 2026-04-30 21:45:42 +01:00
mostalive eb374d20f5 update synopsis and module docs to mention DOCX output 2026-04-30 21:42:39 +01:00
mostalive 57f4f9f165 add -docx command example to README 2026-04-30 21:42:11 +01:00
mostalive f016950ac7 add DOCX support to Mermaid diagram rendering
- Transform.hs: DOCX case returns blocks unchanged (no unicode substitution needed)
- Mermaid.hs: DOCX uses PNG images at normal scale (like HTML, not 3x like PDF)
2026-04-30 18:25:27 +01:00
mostalive f4dab3e354 update pandoc bounds to <3.8 (matches 3.7.0.2 used by lts-24.34) 2026-04-30 18:24:07 +01:00
mostalive 6b49db5801 refactor(Compiler): eliminate Maybe Text indirection in CompilationStrategy
- csWriter now writes files directly (WriterOptions -> Pandoc -> FilePath -> IO (Either DocsterError ()))
- csPostProcess no longer takes text content (String -> IO (Either DocsterError ()))
- Each strategy owns its complete output logic (PDF/HTML/DOCX)
- Remove generateOutputFile (eliminated unused CompilationStrategy parameter)
- Pipeline: generateOutputM >>= processOutput => writeAndProcessOutput (1 step)
- Pandoc 3.7 compatibility: writeDocx returns ByteString instead of ()

.gitignore: exclude stack/cabal config and generated files
2026-04-30 18:13:00 +01:00
mostalive b0457388dc remove unused parameter 2026-04-30 17:36:24 +01:00
mostalive 8abe1d1bc2 resolve compiler warnings 2026-04-30 17:33:27 +01:00
mostalive 9dd9313829 Add docx export 2026-04-30 17:27:59 +01:00
mostalive fa850d5017 write agents.md in qwen's own words 2026-04-30 17:27:37 +01:00
Your NameandClaude Opus 4.6 dda2fc15b2 Update docs and install script for Stack/GHC 9.10.3
Switch README and CLAUDE.md from cabal to stack commands, update GHC
version references, add pkg-config to install-deps.sh, and add Stack
as the recommended build method.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:27:36 +00:00
Your NameandClaude Opus 4.6 705d53b958 Update Stack resolver from lts-22.39 to lts-24.34 (GHC 9.10.3)
Fixes happy-1.20.1.1 build failure and updates to latest LTS snapshot.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:19:08 +00:00
16 changed files with 235 additions and 205 deletions
+10
View File
@@ -1,6 +1,8 @@
dist-newstyle
dist-newstyle
/.stack-work/
/.stack-root/
/.cabal-config/
*.mmd
*.png
*.svg
@@ -10,3 +12,11 @@ dist-newstyle
dist-newstyle
output/
*.log
cabal.project
lts-24-34.yaml
stack-setup-2.yaml
analytics-charts.md
architecture-deep-dive.md
devcontainer.org
.devcontainer/
root.json
+14 -15
View File
@@ -10,31 +10,36 @@ Docster is a Haskell CLI tool that converts Markdown files with embedded Mermaid
### Build
```bash
cabal build
stack build
```
### Run
```bash
# Convert to PDF
cabal run docster -- -pdf path/to/file.md
stack exec docster -- -pdf path/to/file.md
# Convert to HTML
cabal run docster -- -html path/to/file.md
stack exec docster -- -html path/to/file.md
```
### Test
```bash
stack test
```
### Test a single file
```bash
cabal run docster -- -pdf mermaid-to-svg/sample.md
stack exec docster -- -pdf mermaid-to-svg/sample.md
```
### Clean build artifacts
```bash
cabal clean
stack clean
```
### Interactive development
```bash
cabal repl
stack repl
```
## Architecture
@@ -57,17 +62,11 @@ The tool uses Pandoc's AST transformation capabilities to:
## Dependencies
External requirements:
- GHC 9.12.2 and Cabal 3.16 (install via ghcup)
- Pandoc library
- Stack (install via ghcup) — manages GHC 9.10.3 automatically via lts-24.34
- Pandoc library (Haskell dependency, pulled by Stack)
- TeX Live (for PDF generation via XeLaTeX)
- Mermaid CLI (`npm install -g @mermaid-js/mermaid-cli`)
To install GHC and Cabal:
```bash
source ~/.ghcup/env && ghcup install ghc 9.12.2
source ~/.ghcup/env && ghcup install cabal 3.16.0.0
source ~/.ghcup/env && ghcup set ghc 9.12.2
```
- pkg-config, libgmp-dev, libffi-dev, zlib1g-dev (see `install-deps.sh`)
## Common Issues
+33 -11
View File
@@ -6,19 +6,22 @@ A self-contained CLI tool that converts Markdown with Mermaid diagrams into PDF
docster -pdf path/to/file.md
docster -html path/to/file.md
docster -docx path/to/file.md
Mermaid code blocks (```mermaid) will be rendered to SVG and embedded.
Mermaid code blocks (```mermaid) will be rendered to SVG (HTML) or PNG (PDF/DOCX) and embedded.
## Installation
### Prerequisites
First install the required system dependencies:
Install the required system dependencies (Ubuntu/Debian):
```bash
./install-deps.sh
```
This installs build-essential, libgmp-dev, libffi-dev, zlib1g-dev, pkg-config, and TeX Live packages for PDF generation.
### Install Haskell toolchain
Install ghcup (Haskell toolchain installer):
@@ -28,13 +31,26 @@ curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
source ~/.ghcup/env
```
Install specific GHC and Cabal versions:
#### Option A: Build with Stack (recommended)
Stack manages its own GHC installation automatically.
```bash
ghcup install ghc 9.12.2
ghcup install stack
stack build
stack install
```
This uses the resolver defined in `stack.yaml` (currently lts-24.34 / GHC 9.10.3).
#### Option B: Build with Cabal
```bash
ghcup install ghc 9.10.3
ghcup install cabal 3.16.0.0
ghcup set ghc 9.12.2
ghcup set ghc 9.10.3
ghcup set cabal 3.16.0.0
cabal install --installdir=$HOME/.local/bin
```
### Install Mermaid CLI
@@ -43,18 +59,24 @@ ghcup set cabal 3.16.0.0
npm install -g @mermaid-js/mermaid-cli
```
### Build and install docster
### PATH setup
Make sure the install location is in your PATH. Add to your shell config if needed:
```bash
cabal install --installdir=$HOME/.local/bin
```
# For Stack
export PATH="$HOME/.local/bin:$PATH"
Make sure `~/.local/bin` is in your PATH. Add to your shell config if needed:
```bash
# For Cabal
export PATH="$HOME/.local/bin:$PATH"
```
## Development
```bash
stack build # compile
stack test # run tests
stack repl # interactive REPL
```
See [agents.md](agents.md) for information about the Claude Code agents used for Haskell refactoring in this project.
+36 -103
View File
@@ -1,115 +1,48 @@
# Claude Code Agents
# Docster — Project Guide
This project uses specialized Claude Code agents for different types of Haskell refactoring. Each agent has focused expertise to provide targeted improvements.
Docster is a Haskell CLI tool: Markdown + embedded Mermaid diagrams → PDF, HTML, or DOCX.
## Available Agents
## Quick Commands
### haskell-refactoring-expert
**Purpose**: Basic code quality and structural improvements
**Expertise**:
- Type consistency (String vs Text vs ByteString)
- Module organization and file splitting (>150 lines)
- Naming conventions and clarity
- Dependency management
- Basic code structure improvements
**When to use**:
- Inconsistent type usage across the codebase
- Large files that need module organization
- Poor naming or unclear function responsibilities
- Mixed concerns in single modules
**Example**: Converting a 300-line Main.hs into proper module hierarchy
### haskell-higher-order
**Purpose**: Advanced functional programming patterns and architectural refactoring
**Expertise**:
- Monad transformer patterns (ExceptT, ReaderT, StateT)
- Pipeline composition with monadic operators
- Higher-order abstractions and strategy patterns
- Effect management and pure/IO separation
- Functional design patterns
**When to use**:
- Nested case statements handling Either values in IO
- Duplicated functions that differ only in specific steps
- Manual threading of configuration or state
- Imperative-style code that could be more functional
- Complex error handling that needs cleanup
**Example**: Converting nested Either/IO handling to ExceptT pipelines
## Agent Boundaries and Trade-offs
### Complementary Design
These agents are designed to work **sequentially**:
1. **First pass**: `haskell-refactoring-expert` for structural cleanup
2. **Second pass**: `haskell-higher-order` for functional patterns
### Why Separate Agents?
**Benefits**:
- **Focused expertise**: Each agent has deep knowledge in its domain
- **Clear boundaries**: Easy to know which agent to use
- **Manageable complexity**: Avoids instruction bloat in single agent
- **Progressive enhancement**: Apply increasingly sophisticated refactoring
- **Composability**: Can run both agents or just one as needed
**Trade-offs**:
- **Coordination overhead**: Need to run multiple agents
- **Context switching**: Each agent analyzes code independently
- **Potential overlap**: Some patterns might fit both agents
### Decision Framework
**Use haskell-refactoring-expert when you have**:
- ❌ Mixed String/Text types
- ❌ Large monolithic files (>150 lines)
- ❌ Unclear naming or responsibilities
- ❌ Basic structural issues
**Use haskell-higher-order when you have**:
- ❌ Nested error handling (Either in IO)
- ❌ Duplicated function structures
- ❌ Manual state/config threading
- ❌ Imperative-style patterns
**Use both agents when**:
- ❌ You want comprehensive refactoring
- ❌ Code has both structural and architectural issues
- ❌ You're doing major codebase improvements
## Usage Patterns
### Sequential Refactoring
```bash
# Run basic refactoring first
/agent haskell-refactoring-expert "Please refactor the Main.hs file"
# Then apply advanced patterns
/agent haskell-higher-order "Please improve the error handling patterns"
stack build # build
stack test # run tests
stack exec docster -- -pdf file.md # convert to PDF
stack exec docster -- -html file.md # convert to HTML
stack exec docster -- -docx file.md # convert to DOCX
stack exec docster -- -pdf sample.md # test with a single file
stack clean # clean build artifacts
```
### Targeted Improvements
```bash
# Just structural cleanup
/agent haskell-refactoring-expert "Split this large module"
## Structure
# Just functional patterns
/agent haskell-higher-order "Convert these nested cases to monadic style"
```
docster.cabal # package definition
stack.yaml # GHC 9.12.2, lts-24.34
app/Main.hs # everything — entry point + all logic (~70 lines)
test/ # HSpec tests (TransformSpec.hs)
```
## Evolution Strategy
## How It Works
These agents can evolve independently:
- **haskell-refactoring-expert**: Add more structural patterns, linting rules
- **haskell-higher-order**: Add more advanced patterns (free monads, effect systems)
1. Parse Markdown via Pandoc AST
2. Walk the AST, find Mermaid code blocks
3. Run `mmdc` (mermaid-cli) to render each block → SVG (for HTML) or high-res PNG (for PDF)
4. Replace code blocks with image references in the AST
5. Compile final output via Pandoc (LaTeX/XeLaTeX for PDF, native for HTML/DOCX)
New specialized agents could be added:
- **haskell-performance**: Optimization-focused refactoring
- **haskell-testing**: Test-driven refactoring and property-based testing
- **haskell-domain**: Domain modeling and type design
Key functions in `Main.hs`:
- `transformDoc` — AST walker
- `processMermaidBlock` — calls `mmdc`, returns image reference
- `compileToPDF` / `compileToHTML` / `compileToDOCX` — final Pandoc compilation
The key is maintaining clear boundaries and complementary functionality.
## Dependencies
**System**: TeX Live (for PDF), `npm install -g @mermaid-js/mermaid-cli`
**Haskell**: Pandoc library, Stack manages GHC automatically
## Common Gotchas
- **Text vs String**: Codebase mixes `Data.Text` and `String`. Use `T.pack`/`T.unpack` for conversions.
- **PDF needs LaTeX**: BasicTeX/TinyTeX + `tlmgr` for missing packages.
- **mmdc in PATH**: `mermaid-cli` must be globally installed and on PATH.
+3 -2
View File
@@ -4,7 +4,7 @@
module Main (main) where
import Docster.Types (DocsterError(..))
import Docster.Compiler (compileToPDF, compileToHTML)
import Docster.Compiler (compileToPDF, compileToHTML, compileToDOCX)
import System.Environment (getArgs)
import Control.Exception (throwIO)
@@ -12,7 +12,8 @@ import Control.Exception (throwIO)
parseArgs :: [String] -> Either DocsterError (IO ())
parseArgs ["-pdf", path] = Right (compileToPDF path)
parseArgs ["-html", path] = Right (compileToHTML path)
parseArgs _ = Left $ InvalidUsage "Usage: docster -pdf|-html <file.md>"
parseArgs ["-docx", path] = Right (compileToDOCX path)
parseArgs _ = Left $ InvalidUsage "Usage: docster -pdf|-html|-docx <file.md>"
-- | Main entry point - parse arguments and execute appropriate action
main :: IO ()
+4 -4
View File
@@ -1,9 +1,9 @@
cabal-version: 3.0
name: docster
version: 0.1.0.0
synopsis: A self-contained CLI tool that converts Markdown with Mermaid diagrams to PDF/HTML
description: Docster converts Markdown documents containing Mermaid diagrams into PDF or HTML files
using Pandoc and Mermaid CLI. It automatically renders Mermaid code blocks to SVG
synopsis: A self-contained CLI tool that converts Markdown with Mermaid diagrams to PDF, HTML, or DOCX
description: Docster converts Markdown documents containing Mermaid diagrams into PDF, HTML, or DOCX files
using Pandoc and Mermaid CLI. It automatically renders Mermaid code blocks to SVG (HTML) or PNG (PDF/DOCX)
and embeds them in the output.
homepage: https://github.com/yourusername/docster
license: BSD-3-Clause
@@ -41,7 +41,7 @@ library
process >=1.6 && <1.7,
hashable >=1.4 && <1.6,
containers >=0.6 && <0.8,
pandoc >=3.0 && <3.2,
pandoc >=3.0 && <3.8,
pandoc-types >=1.23 && <1.25,
bytestring >=0.11 && <0.13,
temporary >=1.3 && <1.4,
+1 -1
View File
@@ -1,2 +1,2 @@
sudo apt update
sudo apt install -y build-essential libgmp-dev libffi-dev zlib1g-dev texlive-latex-base texlive-fonts-recommended texlive-latex-extra texlive-xetex
sudo apt install -y build-essential pkg-config libgmp-dev libffi-dev zlib1g-dev texlive-latex-base texlive-fonts-recommended texlive-latex-extra texlive-xetex
+79 -46
View File
@@ -1,17 +1,20 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
-- | Document compilation functionality for PDF and HTML output
-- | Document compilation functionality for PDF, HTML, and DOCX output
module Docster.Compiler
( -- * Compilation Functions
compileToPDF
, compileToHTML
, compileToDOCX
) where
import Docster.Types
( DocsterError(..), OutputFormat(..), SourceDir(..), OutputDir(..), OutputPath(..)
, DiagramConfig(..), computeOutputDir, ensureOutputDir
)
import Text.Pandoc.Writers ()
import qualified Data.ByteString.Lazy as BSL
import Docster.Transform (transformDocument)
import Docster.LaTeX (latexTemplate)
import Text.Pandoc
@@ -30,6 +33,7 @@ import Control.Monad.Trans.Reader (ReaderT, runReaderT, asks)
import Control.Monad.Trans.Class (lift)
import Control.Monad.IO.Class (liftIO)
import Data.Maybe (mapMaybe)
import Data.Char (ord)
-- | Success indicator for user feedback
successEmoji :: Text
@@ -38,8 +42,6 @@ successEmoji = "✅"
-- | Compilation context for pipeline operations
data CompilationContext = CompilationContext
{ ccStrategy :: CompilationStrategy
, ccSourceDir :: SourceDir
, ccOutputDir :: OutputDir
, ccInputPath :: FilePath
, ccOutputPath :: FilePath
, ccDocName :: Text
@@ -54,10 +56,10 @@ type CompilationM = ReaderT CompilationContext (ExceptT DocsterError IO)
data CompilationStrategy = CompilationStrategy
{ -- | Format for diagram configuration
csOutputFormat :: OutputFormat
-- | Pandoc writer function
, csWriter :: WriterOptions -> Pandoc -> PandocIO Text
-- | Post-processing function for the generated content
, csProcessOutput :: String -> Text -> IO (Either DocsterError ())
-- | Pandoc writer: writes output directly to the given file path
, csWriter :: WriterOptions -> Pandoc -> FilePath -> IO (Either DocsterError ())
-- | Post-processing after write (PDF→xelatex, HTML→open browser, DOCX→noop)
, csPostProcess :: String -> IO (Either DocsterError ())
-- | Success message formatter
, csSuccessMessage :: String -> Text
}
@@ -66,8 +68,14 @@ data CompilationStrategy = CompilationStrategy
pdfStrategy :: CompilationStrategy
pdfStrategy = CompilationStrategy
{ csOutputFormat = PDF
, csWriter = writeLaTeX
, csProcessOutput = processPDFOutput
, csWriter = \opts doc path -> do
result <- runIO (writeLaTeX opts doc)
case result of
Left err -> return $ Left $ FileError $ "LaTeX write failed: " <> T.pack (show err)
Right latex -> do
TIO.writeFile path (latexTemplate latex)
return $ Right ()
, csPostProcess = processPDFOutput
, csSuccessMessage = \path -> successEmoji <> " PDF written to " <> T.pack path
}
@@ -75,11 +83,32 @@ pdfStrategy = CompilationStrategy
htmlStrategy :: CompilationStrategy
htmlStrategy = CompilationStrategy
{ csOutputFormat = HTML
, csWriter = writeHtml5String
, csProcessOutput = processHTMLOutput
, csWriter = \opts doc path -> do
result <- runIO (writeHtml5String opts doc)
case result of
Left err -> return $ Left $ FileError $ "HTML write failed: " <> T.pack (show err)
Right html -> do
TIO.writeFile path html
return $ Right ()
, csPostProcess = processHTMLOutput
, csSuccessMessage = \path -> successEmoji <> " HTML written to " <> T.pack path
}
-- | DOCX compilation strategy (Pandoc writes file directly)
docxStrategy :: CompilationStrategy
docxStrategy = CompilationStrategy
{ csOutputFormat = DOCX
, csWriter = \opts doc path -> do
result <- runIO (writeDocx opts doc)
case result of
Left err -> return $ Left $ FileError $ "DOCX generation failed: " <> T.pack (show err)
Right docxBS -> do
BSL.writeFile path docxBS
return $ Right ()
, csPostProcess = \_ -> return $ Right () -- no post-processing needed
, csSuccessMessage = \path -> successEmoji <> " DOCX written to " <> T.pack path
}
-- | Parse LaTeX log content to extract meaningful error messages
parseLatexErrors :: Text -> Text
parseLatexErrors logContent =
@@ -143,11 +172,10 @@ extractFatalErrors = mapMaybe extractFatal
| "! " `T.isPrefixOf` line && not ("Missing character:" `T.isInfixOf` line) = Just $ T.drop 2 line
| otherwise = Nothing
-- | Process PDF output: LaTeX template application and direct XeLaTeX compilation
processPDFOutput :: String -> Text -> IO (Either DocsterError ())
processPDFOutput outputPath latexOutput = do
let completeLatex = latexTemplate latexOutput
logOutputPath = replaceExtension outputPath "log"
-- | Process PDF output: direct XeLaTeX compilation (LaTeX already written by csWriter)
processPDFOutput :: String -> IO (Either DocsterError ())
processPDFOutput outputPath = do
let logOutputPath = replaceExtension outputPath "log"
-- Use temporary directory for LaTeX compilation
withSystemTempDirectory "docster-latex" $ \tempDir -> do
@@ -155,9 +183,6 @@ processPDFOutput outputPath latexOutput = do
pdfFile = tempDir </> "document.pdf"
logFile = tempDir </> "document.log"
-- Write LaTeX content to temporary file
TIO.writeFile texFile completeLatex
-- Run XeLaTeX compilation
(exitCode, _stdout, stderr) <- readProcessWithExitCode "xelatex"
[ "-output-directory=" <> tempDir
@@ -193,11 +218,9 @@ processPDFOutput outputPath latexOutput = do
errorSummary <> "\n\n" <>
"Full LaTeX log written to: " <> T.pack logOutputPath
-- | Process HTML output: file writing and browser opening
processHTMLOutput :: String -> Text -> IO (Either DocsterError ())
processHTMLOutput outputPath html = do
TIO.writeFile outputPath html
-- | Process HTML output: open browser (HTML already written by csWriter)
processHTMLOutput :: String -> IO (Either DocsterError ())
processHTMLOutput outputPath = do
-- Open the generated HTML file in browser for verification
putStrLn $ "🌐 Opening " <> outputPath <> " in browser for error checking..."
void $ callProcess "open" [outputPath]
@@ -212,11 +235,30 @@ liftEitherM action = do
Left err -> lift $ throwE err
Right value -> return value
-- | Strip ANSI escape sequences (CSI codes like color/style) from text.
-- These appear in copy-pasted terminal output and break LaTeX compilation.
stripAnsiCodes :: Text -> Text
stripAnsiCodes input = case T.break (== '\x1b') input of
(before, rest)
| T.null rest -> before
| otherwise -> before <> stripAnsiCodes (skipEscape (T.tail rest))
where
-- Skip an ESC sequence: ESC [ <params> <final byte>
skipEscape t
| T.null t = t
| T.head t == '[' = skipCSIParams (T.tail t)
| otherwise = T.tail t -- non-CSI escape: skip one char after ESC
-- Skip CSI parameter/intermediate bytes until final byte (0x40-0x7E)
skipCSIParams t
| T.null t = t
| let c = ord (T.head t), c >= 0x40 && c <= 0x7E = T.tail t -- final byte, consume it
| otherwise = skipCSIParams (T.tail t)
-- | Pipeline step: Read content from input file
readContent :: CompilationM Text
readContent = do
inputPath <- asks ccInputPath
liftIO $ TIO.readFile inputPath
liftIO $ stripAnsiCodes <$> TIO.readFile inputPath
-- | Pipeline step: Parse markdown content into Pandoc AST
parseDocument :: Text -> CompilationM Pandoc
@@ -231,18 +273,13 @@ transformDocumentM pandoc = do
docName <- asks ccDocName
liftEitherM $ transformDocument config docName pandoc
-- | Pipeline step: Generate output using format-specific writer
generateOutputM :: Pandoc -> CompilationM Text
generateOutputM pandoc = do
strategy <- asks ccStrategy
liftEitherM $ generateOutput strategy pandoc
-- | Pipeline step: Process output and write to file
processOutput :: Text -> CompilationM ()
processOutput output = do
-- | Pipeline step: Write output and post-process (format-specific)
writeAndProcessOutput :: Pandoc -> CompilationM ()
writeAndProcessOutput pandoc = do
strategy <- asks ccStrategy
outputPath <- asks ccOutputPath
liftEitherM $ csProcessOutput strategy outputPath output
liftEitherM $ csWriter strategy def pandoc outputPath
liftEitherM $ (csPostProcess strategy) outputPath
-- | Pipeline step: Print success message
printSuccess :: CompilationM ()
@@ -256,8 +293,8 @@ compileWithStrategy :: CompilationStrategy -> SourceDir -> OutputDir -> Text ->
compileWithStrategy strategy sourceDir outputDir docName (OutputPath inputPath) (OutputPath outputPath) = do
let readerOptions = def { readerExtensions = getDefaultExtensions "markdown" }
config = DiagramConfig sourceDir outputDir (csOutputFormat strategy)
context = CompilationContext strategy sourceDir outputDir inputPath outputPath docName readerOptions config
pipeline = readContent >>= parseDocument >>= transformDocumentM >>= generateOutputM >>= processOutput >> printSuccess
context = CompilationContext strategy inputPath outputPath docName readerOptions config
pipeline = readContent >>= parseDocument >>= transformDocumentM >>= writeAndProcessOutput >> printSuccess
runExceptT $ runReaderT pipeline context
@@ -269,15 +306,7 @@ parseMarkdown readerOptions content = do
Left err -> Left $ FileError $ "Failed to parse markdown: " <> T.pack (show err)
Right pandoc -> Right pandoc
-- | Generate output using the strategy's writer with error handling
generateOutput :: CompilationStrategy -> Pandoc -> IO (Either DocsterError Text)
generateOutput strategy transformed = do
result <- runIO $ csWriter strategy def transformed
return $ case result of
Left err -> Left $ case csOutputFormat strategy of
PDF -> PDFGenerationError $ "LaTeX generation failed: " <> T.pack (show err)
HTML -> FileError $ "HTML generation failed: " <> T.pack (show err)
Right output -> Right output
-- | Compile markdown to PDF using XeLaTeX
compileToPDF :: FilePath -> IO ()
@@ -287,6 +316,10 @@ compileToPDF = compileWithFormat pdfStrategy "pdf"
compileToHTML :: FilePath -> IO ()
compileToHTML = compileWithFormat htmlStrategy "html"
-- | Compile markdown to DOCX
compileToDOCX :: FilePath -> IO ()
compileToDOCX = compileWithFormat docxStrategy "docx"
-- | Higher-order function to compile with any format strategy
compileWithFormat :: CompilationStrategy -> String -> FilePath -> IO ()
compileWithFormat strategy extension path = do
+37 -9
View File
@@ -9,15 +9,12 @@ module Docster.LaTeX
import Data.Text (Text)
import qualified Data.Text as T
-- | LaTeX template with comprehensive package support for PDF generation
-- | LaTeX template with modern corporate styling for PDF generation
latexTemplate :: Text -> Text
latexTemplate bodyContent = T.unlines
[ "\\documentclass{article}"
, "\\usepackage[utf8]{inputenc}"
-- Packages
, "\\usepackage{fontspec}"
, "\\setmainfont{DejaVu Serif}[Scale=1.0]"
, "\\setsansfont{DejaVu Sans}[Scale=1.0]"
, "\\setmonofont{DejaVu Sans Mono}[Scale=0.85]"
, "\\usepackage{graphicx}"
, "\\usepackage{adjustbox}"
, "\\usepackage{geometry}"
@@ -25,16 +22,46 @@ latexTemplate bodyContent = T.unlines
, "\\usepackage{booktabs}"
, "\\usepackage{array}"
, "\\usepackage{calc}"
, "\\geometry{margin=1in}"
, "\\usepackage{hyperref}"
, "\\usepackage{enumitem}"
, "\\usepackage{amsmath}"
, "\\usepackage{amssymb}"
, "\\usepackage{fancyvrb}"
, "\\usepackage{color}"
, "\\usepackage[dvipsnames,svgnames,x11names]{xcolor}"
, "\\usepackage{titlesec}"
, "\\usepackage{fancyhdr}"
, "\\usepackage{framed}"
-- Typography: Helvetica Neue + Menlo, sans-serif default
, "\\setmainfont{Helvetica Neue}"
, "\\setsansfont{Helvetica Neue}"
, "\\setmonofont{Menlo}[Scale=0.85]"
, "\\renewcommand{\\familydefault}{\\sfdefault}"
-- Layout: wider margins, block paragraphs
, "\\geometry{left=0.9in,right=0.9in,top=1in,bottom=1in}"
, "\\setlength{\\parindent}{0pt}"
, "\\setlength{\\parskip}{0.5em}"
-- Color scheme
, "\\definecolor{accent}{HTML}{1A365D}"
, "\\definecolor{codebg}{HTML}{F5F5F5}"
-- Hyperlinks: accent-colored, no boxes
, "\\usepackage[colorlinks=true,linkcolor=accent,urlcolor=accent,citecolor=accent]{hyperref}"
-- Heading styles
, "\\titleformat{\\section}{\\Large\\bfseries\\color{accent}}{\\thesection}{1em}{}[\\vspace{2pt}\\titlerule]"
, "\\titleformat{\\subsection}{\\large\\bfseries\\color{accent}}{\\thesubsection}{1em}{}"
, "\\titleformat{\\subsubsection}{\\normalsize\\bfseries\\color{accent}}{\\thesubsubsection}{1em}{}"
, "\\titlespacing*{\\section}{0pt}{1.5em}{0.8em}"
, "\\titlespacing*{\\subsection}{0pt}{1.2em}{0.5em}"
, "\\titlespacing*{\\subsubsection}{0pt}{1em}{0.4em}"
-- Page header/footer: minimal centered page number
, "\\pagestyle{fancy}"
, "\\fancyhf{}"
, "\\renewcommand{\\headrulewidth}{0pt}"
, "\\fancyfoot[C]{\\small\\thepage}"
-- Code blocks: light gray background
, "\\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\}}"
, "\\newenvironment{Shaded}{}{}"
, "\\newenvironment{Shaded}{\\begin{snugshade}}{\\end{snugshade}}"
, "\\definecolor{shadecolor}{HTML}{F5F5F5}"
, syntaxHighlightingCommands
-- Pandoc helpers
, "\\providecommand{\\tightlist}{%"
, " \\setlength{\\itemsep}{0pt}\\setlength{\\parskip}{0pt}}"
, "\\newcommand{\\real}[1]{#1}"
@@ -47,6 +74,7 @@ latexTemplate bodyContent = T.unlines
, "\\def\\maxheight{\\ifdim\\Gin@nat@height>\\textheight\\textheight\\else\\Gin@nat@height\\fi}"
, "\\makeatother"
, "\\setkeys{Gin}{width=\\maxwidth,height=\\maxheight,keepaspectratio}"
, "\\providecommand{\\pandocbounded}[1]{#1}"
, "\\begin{document}"
, bodyContent
, "\\end{document}"
+3
View File
@@ -79,6 +79,8 @@ generateDiagramPaths (DiagramConfig _ (OutputDir outDir) format) (DiagramId diag
in (svgFile, T.pack $ takeFileName svgFile)
PDF -> let pngFile = outDir </> diagIdStr <> ".png"
in (pngFile, T.pack pngFile)
DOCX -> let pngFile = outDir </> diagIdStr <> ".png"
in (pngFile, T.pack pngFile)
-- | Puppeteer configuration content for disabling sandbox
puppeteerConfigContent :: Text
@@ -90,6 +92,7 @@ callMermaidProcess format mmdFile outputFile = do
let baseArgs = case format of
HTML -> ["-i", mmdFile, "-o", outputFile]
PDF -> ["-i", mmdFile, "-o", outputFile, "--scale", "3"]
DOCX -> ["-i", mmdFile, "-o", outputFile]
-- Create temporary puppeteer config file
result <- bracket
+2 -1
View File
@@ -36,8 +36,9 @@ transformDocument config docName (Pandoc meta blocks) = do
Left err -> return $ Left err
Right (newBlocks, _finalState) ->
case dcOutputFormat config of
PDF -> return $ Right $ substituteUnicodeSymbols (Pandoc meta newBlocks)
PDF -> return $ Right $ substituteUnicodeSymbols (Pandoc meta newBlocks)
HTML -> return $ Right $ Pandoc meta newBlocks
DOCX -> return $ Right $ Pandoc meta newBlocks
-- | Process a single block with heading tracking state
processBlockStateful :: DiagramConfig -> Block -> TransformM Block
Binary file not shown.
+1 -1
View File
@@ -45,7 +45,7 @@ data DocsterError
instance Exception DocsterError
-- | Output format for document generation
data OutputFormat = PDF | HTML
data OutputFormat = PDF | HTML | DOCX
deriving (Show, Eq)
-- | Type-safe wrapper for source directory paths
+1 -1
View File
@@ -1,4 +1,4 @@
resolver: lts-22.39 # GHC 9.12.2 compatible
resolver: lts-24.34 # GHC 9.10.3
packages:
- .
+4 -4
View File
@@ -6,7 +6,7 @@
packages: []
snapshots:
- completed:
sha256: 6c5aeace2ca5ecde793a9e0acfaa730ec8f384aa2f6183a2a252f5f9ec55d623
size: 720039
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/22/39.yaml
original: lts-22.39
sha256: 45b164eaf5c16bd220d2c5d7ab9a66ca0cfbcde7753703a5cb3549172adde813
size: 728959
url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/24/34.yaml
original: lts-24.34
+6 -6
View File
@@ -4,7 +4,7 @@ module Docster.TransformSpec (spec) where
import Test.Hspec
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import Data.Text()
import qualified Data.Text as T
import Text.Pandoc.Definition (Inline(..))
@@ -64,21 +64,21 @@ spec = do
describe "diagram naming logic" $ do
it "first diagram under heading has no suffix" $
let baseName = "file_flow"
counter = Map.findWithDefault 0 baseName Map.empty
counter = Map.findWithDefault (0 :: Int) baseName Map.empty
diagName = if counter == 0 then baseName else baseName <> "_" <> T.pack (show counter)
in diagName `shouldBe` "file_flow"
it "second diagram gets _1 suffix" $
let baseName = "file_flow"
counters = Map.singleton "file_flow" 1
counter = Map.findWithDefault 0 baseName counters
counters = Map.singleton "file_flow" (1 :: Int)
counter = Map.findWithDefault (0 :: Int) baseName counters
diagName = if counter == 0 then baseName else baseName <> "_" <> T.pack (show counter)
in diagName `shouldBe` "file_flow_1"
it "third diagram gets _2 suffix" $
let baseName = "file_flow"
counters = Map.singleton "file_flow" 2
counter = Map.findWithDefault 0 baseName counters
counters = Map.singleton "file_flow" (2 :: Int)
counter = Map.findWithDefault (0 :: Int) baseName counters
diagName = if counter == 0 then baseName else baseName <> "_" <> T.pack (show counter)
in diagName `shouldBe` "file_flow_2"