{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeFamilies #-} -- Copyright (C) 2010-2011 John Millikin -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . module Anansi.Types ( Block (..) , Content (..) , Position (..) , ParseError (..) , Document (..) , Loom , LoomM , LoomOptions (..) , parseLoomOptions , weave ) where import Prelude hiding (FilePath) import Control.Monad (liftM) import qualified Control.Monad.Reader as Reader import Control.Monad.Reader (ReaderT, EnvType, runReaderT) import qualified Control.Monad.Writer as Writer import Control.Monad.Writer (Writer, WriterType, execWriter) import Data.ByteString (ByteString) import qualified Data.Map import Data.Map (Map) import qualified Data.Text import Data.Text (Text) import Filesystem.Path.CurrentOS (FilePath) data Block = BlockText Text | BlockFile Text [Content] | BlockDefine Text [Content] deriving (Eq, Ord, Show) data Content = ContentText Position Text -- | A macro reference within a content block. The first 'Text' is -- any indentation found before the first @\'|\'@, and the second is -- the name of the macro. | ContentMacro Position Text Text deriving (Eq, Ord, Show) data Position = Position { positionFile :: FilePath , positionLine :: Integer } deriving (Eq, Ord, Show) data ParseError = ParseError { parseErrorPosition :: Position , parseErrorMessage :: Text } deriving (Eq, Show) data Document = Document { documentBlocks :: [Block] -- | A map of @:option@ commands found in the document. If -- the same option is specified multiple times, the most recent will -- be used. , documentOptions :: Map Text Text -- | The last @:loom@ command given, if any. A document does not -- require a loom name if it's just going to be tangled, or will be -- woven by the user calling 'weave'. Documents woven by -- 'defaultMain' do require a loom name. , documentLoomName :: Maybe Text } deriving (Eq, Show) -- | A loom contains all the logic required to convert a 'Document' into -- markup suitable for processing with an external documentation tool. -- -- Within a loom, use 'Reader.ask' to retrieve the 'LoomOptions', and -- 'Writer.tell' to append data to the output. type Loom = Document -> LoomM () newtype LoomM a = LoomM { unLoomM :: ReaderT LoomOptions (Writer ByteString) a } instance Functor LoomM where fmap = liftM instance Monad LoomM where return = LoomM . return (LoomM m) >>= f = LoomM $ do x <- m unLoomM (f x) instance Reader.MonadReader LoomM where type EnvType LoomM = LoomOptions ask = LoomM Reader.ask local f (LoomM m) = LoomM (Reader.local f m) instance Writer.MonadWriter LoomM where type WriterType LoomM = ByteString tell = LoomM . Writer.tell listen (LoomM m) = LoomM (Writer.listen m) pass m = LoomM (Writer.pass (unLoomM m)) -- | Write a document to some sort of document markup. This will typically be -- rendered into documentation by external tools, such as LaTeX or a web -- browser. -- -- This writes a 'ByteString' rather than 'Text' so that looms have full -- control over character encoding. weave :: Loom -> Document -> ByteString weave loom doc = execWriter (runReaderT (unLoomM (loom doc)) (parseLoomOptions (documentOptions doc))) -- | A set of processed @:option@ commands related to looms. Looms are always -- free to check options manually, but this simplifies common cases. data LoomOptions = LoomOptions { loomOptionTabSize :: Integer } deriving (Eq, Show) parseLoomOptions :: Map Text Text -> LoomOptions parseLoomOptions opts = LoomOptions { loomOptionTabSize = case Data.Map.lookup "anansi.tab-size" opts of Just x -> read (Data.Text.unpack x) Nothing -> case Data.Map.lookup "tab-size" opts of Just x -> read (Data.Text.unpack x) Nothing -> 8 }