git.iko.soy/typeable/pandoc
on master help sign in
Introduce PandocMonad typeclass.
239880fJesse Rosenthal
pandoc.cabal+3−2
@@ -383,8 +383,9 @@ Library
383 Text.Pandoc.XML,
383 Text.Pandoc.XML,
384 Text.Pandoc.SelfContained,
384 Text.Pandoc.SelfContained,
385 Text.Pandoc.Process,
385 Text.Pandoc.Process,
386− Text.Pandoc.CSS
386+ Text.Pandoc.CSS,
387− Text.Pandoc.Free
387+ Text.Pandoc.Free,
388+ Text.Pandoc.Class
388 Other-Modules: Text.Pandoc.Readers.Docx.Lists,
389 Other-Modules: Text.Pandoc.Readers.Docx.Lists,
389 Text.Pandoc.Readers.Docx.Combine,
390 Text.Pandoc.Readers.Docx.Combine,
390 Text.Pandoc.Readers.Docx.Parse,
391 Text.Pandoc.Readers.Docx.Parse,
src/Text/Pandoc/Class.hsnew file+202
@@ -0,0 +1,202 @@
1+{-# LANGUAGE DeriveFunctor, DeriveDataTypeable, TypeSynonymInstances, FlexibleInstances #-}
2+
3+{-
4+Copyright (C) 2016 Jesse Rosenthal <jrosenthal@jhu.edu>
5+
6+This program is free software; you can redistribute it and/or modify
7+it under the terms of the GNU General Public License as published by
8+the Free Software Foundation; either version 2 of the License, or
9+(at your option) any later version.
10+
11+This program is distributed in the hope that it will be useful,
12+but WITHOUT ANY WARRANTY; without even the implied warranty of
13+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+GNU General Public License for more details.
15+
16+You should have received a copy of the GNU General Public License
17+along with this program; if not, write to the Free Software
18+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19+-}
20+
21+{- |
22+ Module : Text.Pandoc.Class
23+ Copyright : Copyright (C) 2016 Jesse Rosenthal
24+ License : GNU GPL, version 2 or above
25+
26+ Maintainer : Jesse Rosenthal <jrosenthal@jhu.edu>
27+ Stability : alpha
28+ Portability : portable
29+
30+Typeclass for pandoc readers and writers, allowing both IO and pure instances.
31+-}
32+
33+module Text.Pandoc.Class ( PandocMonad(..)
34+ , Testing
35+ , TestState(..)
36+ , TestEnv(..)
37+ , getPOSIXTime
38+ ) where
39+
40+import Prelude hiding (readFile, fail)
41+import qualified Control.Monad as M (fail)
42+import System.Random (StdGen, next)
43+import qualified System.Random as IO (newStdGen)
44+import Codec.Archive.Zip (Archive, fromArchive)
45+import Data.Unique (hashUnique)
46+import qualified Data.Unique as IO (newUnique)
47+import qualified Text.Pandoc.Shared as IO ( fetchItem
48+ , fetchItem'
49+ , getDefaultReferenceDocx
50+ , getDefaultReferenceODT
51+ , warn
52+ , readDataFile)
53+import Text.Pandoc.MediaBag (MediaBag, lookupMedia)
54+import Text.Pandoc.Compat.Time (UTCTime)
55+import qualified Text.Pandoc.Compat.Time as IO (getCurrentTime)
56+import Data.Time.Clock.POSIX ( utcTimeToPOSIXSeconds, POSIXTime )
57+import Text.Pandoc.MIME (MimeType, getMimeType)
58+import qualified Data.ByteString as B
59+import qualified Data.ByteString.Lazy as BL
60+import qualified Control.Exception as E
61+import qualified System.Environment as IO (lookupEnv)
62+import System.FilePath.Glob (match, compile)
63+import System.FilePath ((</>))
64+import qualified System.FilePath.Glob as IO (glob)
65+import Control.Monad.State hiding (fail)
66+import Control.Monad.Reader hiding (fail)
67+import Data.Word (Word8)
68+import Data.Typeable
69+
70+class Monad m => PandocMonad m where
71+ lookupEnv :: String -> m (Maybe String)
72+ getCurrentTime :: m UTCTime
73+ getDefaultReferenceDocx :: Maybe FilePath -> m Archive
74+ getDefaultReferenceODT :: Maybe FilePath -> m Archive
75+ newStdGen :: m StdGen
76+ newUniqueHash :: m Int
77+ readFileLazy :: FilePath -> m BL.ByteString
78+ readDataFile :: Maybe FilePath -> FilePath -> m B.ByteString
79+ fetchItem :: Maybe String ->
80+ String ->
81+ m (Either E.SomeException (B.ByteString, Maybe MimeType))
82+ fetchItem' :: MediaBag ->
83+ Maybe String ->
84+ String ->
85+ m (Either E.SomeException (B.ByteString, Maybe MimeType))
86+ warn :: String -> m ()
87+ fail :: String -> m b
88+ glob :: String -> m [FilePath]
89+
90+--Some functions derived from Primitives:
91+
92+getPOSIXTime :: (PandocMonad m) => m POSIXTime
93+getPOSIXTime = utcTimeToPOSIXSeconds <$> getCurrentTime
94+
95+instance PandocMonad IO where
96+ lookupEnv = IO.lookupEnv
97+ getCurrentTime = IO.getCurrentTime
98+ getDefaultReferenceDocx = IO.getDefaultReferenceDocx
99+ getDefaultReferenceODT = IO.getDefaultReferenceODT
100+ newStdGen = IO.newStdGen
101+ newUniqueHash = hashUnique <$> IO.newUnique
102+ readFileLazy = BL.readFile
103+ readDataFile = IO.readDataFile
104+ fail = M.fail
105+ fetchItem = IO.fetchItem
106+ fetchItem' = IO.fetchItem'
107+ warn = IO.warn
108+ glob = IO.glob
109+
110+
111+
112+data TestState = TestState { stStdGen :: StdGen
113+ , stWord8Store :: [Word8] -- should be
114+ -- inifinite,
115+ -- i.e. [1..]
116+ , stWarnings :: [String]
117+ , stUniqStore :: [Int] -- should be
118+ -- inifinite and
119+ -- contain every
120+ -- element at most
121+ -- once, e.g. [1..]
122+ }
123+
124+data TestEnv = TestEnv { envEnv :: [(String, String)]
125+ , envTime :: UTCTime
126+ , envReferenceDocx :: Archive
127+ , envReferenceODT :: Archive
128+ , envFiles :: [(FilePath, B.ByteString)]
129+ , envUserDataDir :: [(FilePath, B.ByteString)]
130+ , envCabalDataDir :: [(FilePath, B.ByteString)]
131+ , envFontFiles :: [FilePath]
132+ }
133+
134+data TestException = TestException
135+ deriving (Show, Typeable)
136+
137+instance E.Exception TestException
138+
139+type Testing = ReaderT TestEnv (State TestState)
140+
141+instance PandocMonad Testing where
142+ lookupEnv s = do
143+ env <- asks envEnv
144+ return (lookup s env)
145+
146+ getCurrentTime = asks envTime
147+
148+ getDefaultReferenceDocx _ = asks envReferenceDocx
149+
150+ getDefaultReferenceODT _ = asks envReferenceODT
151+
152+ newStdGen = do
153+ g <- gets stStdGen
154+ let (_, nxtGen) = next g
155+ modify $ \st -> st { stStdGen = nxtGen }
156+ return g
157+
158+ newUniqueHash = do
159+ uniqs <- gets stUniqStore
160+ case uniqs of
161+ u : us -> do
162+ modify $ \st -> st { stUniqStore = us }
163+ return u
164+ _ -> M.fail "uniq store ran out of elements"
165+
166+ readFileLazy fp = do
167+ fps <- asks envFiles
168+ case lookup fp fps of
169+ Just bs -> return (BL.fromStrict bs)
170+ Nothing -> error "openFile: does not exist"
171+
172+ readDataFile Nothing "reference.docx" = do
173+ (B.concat . BL.toChunks . fromArchive) <$> (getDefaultReferenceDocx Nothing)
174+ readDataFile Nothing "reference.odt" = do
175+ (B.concat . BL.toChunks . fromArchive) <$> (getDefaultReferenceODT Nothing)
176+ readDataFile Nothing fname = do
177+ let fname' = if fname == "MANUAL.txt" then fname else "data" </> fname
178+ BL.toStrict <$> (readFileLazy fname')
179+ readDataFile (Just userDir) fname = do
180+ userDirFiles <- asks envUserDataDir
181+ case lookup (userDir </> fname) userDirFiles of
182+ Just bs -> return bs
183+ Nothing -> readDataFile Nothing fname
184+
185+ fail = M.fail
186+
187+ fetchItem _ fp = do
188+ fps <- asks envFiles
189+ case lookup fp fps of
190+ Just bs -> return (Right (bs, getMimeType fp))
191+ Nothing -> return (Left $ E.toException TestException)
192+
193+ fetchItem' media sourceUrl nm = do
194+ case lookupMedia nm media of
195+ Nothing -> fetchItem sourceUrl nm
196+ Just (mime, bs) -> return (Right (B.concat $ BL.toChunks bs, Just mime))
197+
198+ warn s = modify $ \st -> st { stWarnings = s : stWarnings st }
199+
200+ glob s = do
201+ fontFiles <- asks envFontFiles
202+ return (filter (match (compile s)) fontFiles)