docster/src/Docster/Types.hs

109 lines
3.1 KiB
Haskell

{-# LANGUAGE OverloadedStrings #-}
-- | Core types and error definitions for Docster
module Docster.Types
( -- * Error Types
DocsterError(..)
-- * Output Format
, OutputFormat(..)
-- * Domain Types
, SourceDir(..)
, OutputDir(..)
, OutputPath(..)
, DiagramId(..)
, DiagramConfig(..)
-- * Traversal State
, TraversalState(..)
, initialTraversalState
, normalizeHeading
-- * Path Utilities
, computeOutputDir
, ensureOutputDir
) where
import Data.Text (Text)
import qualified Data.Text as T
import Data.Char (isAlphaNum, isSpace)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Control.Exception (Exception)
import System.FilePath (takeDirectory, takeBaseName, (</>))
import System.Directory (createDirectoryIfMissing)
-- | Custom error types for comprehensive error handling
data DocsterError
= InvalidUsage Text
| FileError Text
| PDFGenerationError Text
| ProcessError Text
deriving (Show)
instance Exception DocsterError
-- | Output format for document generation
data OutputFormat = PDF | HTML | DOCX
deriving (Show, Eq)
-- | Type-safe wrapper for source directory paths
newtype SourceDir = SourceDir FilePath
deriving (Show, Eq)
-- | Type-safe wrapper for output directory paths
newtype OutputDir = OutputDir FilePath
deriving (Show, Eq)
-- | Type-safe wrapper for output file paths
newtype OutputPath = OutputPath FilePath
deriving (Show, Eq)
-- | Type-safe wrapper for diagram identifiers
newtype DiagramId = DiagramId Text
deriving (Show, Eq)
-- | Configuration for diagram generation
data DiagramConfig = DiagramConfig
{ dcSourceDir :: SourceDir
, dcOutputDir :: OutputDir
, dcOutputFormat :: OutputFormat
} deriving (Show)
-- | Compute output directory from input file path
-- "docs/readme.md" -> "docs/output/readme"
computeOutputDir :: FilePath -> OutputDir
computeOutputDir inputPath =
let dir = takeDirectory inputPath
baseName = takeBaseName inputPath
in OutputDir $ if null dir || dir == "."
then "output" </> baseName
else dir </> "output" </> baseName
-- | Ensure output directory exists
ensureOutputDir :: OutputDir -> IO ()
ensureOutputDir (OutputDir dir) = createDirectoryIfMissing True dir
-- | State for heading-aware diagram naming during AST traversal
data TraversalState = TraversalState
{ tsCurrentHeading :: Maybe Text -- ^ Current heading text (normalized)
, tsHeadingCounters :: Map Text Int -- ^ Counter for diagrams per heading
, tsDocumentName :: Text -- ^ Fallback name when no heading
} deriving (Show, Eq)
-- | Create initial traversal state with document name as fallback
initialTraversalState :: Text -> TraversalState
initialTraversalState docName = TraversalState
{ tsCurrentHeading = Nothing
, tsHeadingCounters = Map.empty
, tsDocumentName = docName
}
-- | Normalize heading text for use as a filename
-- "File Flow Diagram!" -> "file_flow_diagram"
normalizeHeading :: Text -> Text
normalizeHeading = T.intercalate "_"
. T.words
. T.filter (\c -> isAlphaNum c || isSpace c)
. T.toLower