@@ -33,6 +33,7 @@ Pure implementations of the IO monads used in Pandoc's readers and writers.
33module Text.Pandoc.Free ( PandocActionF(..)
33module Text.Pandoc.Free ( PandocActionF(..)
34 , PandocAction
34 , PandocAction
38 , lookupEnv
39 , lookupEnv
@@ -41,7 +42,7 @@ module Text.Pandoc.Free ( PandocActionF(..)
41 , getDefaultReferenceDocx
42 , getDefaultReferenceDocx
42 , getDefaultReferenceODT
43 , getDefaultReferenceODT
43 , newStdGen
44 , newStdGen
44− , newUnique
45+ , newUniqueHash
46 , readFileStrict
47 , readFileStrict
47 , readFileLazy
48 , readFileLazy
@@ -51,18 +52,15 @@ module Text.Pandoc.Free ( PandocActionF(..)
51 , fetchItem'
52 , fetchItem'
60import Prelude hiding (readFile, fail)
58import Prelude hiding (readFile, fail)
61import qualified Control.Monad as M (fail)
59import qualified Control.Monad as M (fail)
62−import System.Random (StdGen)
60+import System.Random (StdGen, next)
63import qualified System.Random as IO (newStdGen)
61import qualified System.Random as IO (newStdGen)
64−import Codec.Archive.Zip (Archive)
62+import Codec.Archive.Zip (Archive, fromArchive)
65−import Data.Unique (Unique)
63+import Data.Unique (Unique, hashUnique, newUnique)
66import qualified Data.Unique as IO (newUnique)
64import qualified Data.Unique as IO (newUnique)
67import qualified Text.Pandoc.Shared as IO ( fetchItem
65import qualified Text.Pandoc.Shared as IO ( fetchItem
68 , fetchItem'
66 , fetchItem'
@@ -70,32 +68,35 @@ import qualified Text.Pandoc.Shared as IO ( fetchItem
70 , getDefaultReferenceODT
68 , getDefaultReferenceODT
72 , readDataFile)
70 , readDataFile)
73−import Text.Pandoc.MediaBag (MediaBag)
71+import Text.Pandoc.MediaBag (MediaBag, lookupMedia)
74−import Data.Time.Clock.POSIX (POSIXTime)
72+import Data.Time.Clock.POSIX (POSIXTime, utcTimeToPOSIXSeconds)
75import qualified Data.Time.Clock.POSIX as IO (getPOSIXTime)
73import qualified Data.Time.Clock.POSIX as IO (getPOSIXTime)
76import Text.Pandoc.Compat.Time (UTCTime)
74import Text.Pandoc.Compat.Time (UTCTime)
77import qualified Text.Pandoc.Compat.Time as IO (getCurrentTime)
75import qualified Text.Pandoc.Compat.Time as IO (getCurrentTime)
78−import Text.Pandoc.MIME (MimeType)
76+import Text.Pandoc.MIME (MimeType, getMimeType)
79import qualified Data.ByteString as B
77import qualified Data.ByteString as B
80import qualified Data.ByteString.Lazy as BL
78import qualified Data.ByteString.Lazy as BL
81import Control.Monad.Free
79import Control.Monad.Free
82import qualified Control.Exception as E
80import qualified Control.Exception as E
83import qualified System.Environment as IO (lookupEnv)
81import qualified System.Environment as IO (lookupEnv)
84−import Data.IORef (IORef)
85−import qualified Data.IORef as IO (newIORef, modifyIORef, readIORef)
86−import Text.Pandoc.UUID (UUID)
82+import Text.Pandoc.UUID
87import qualified Text.Pandoc.UUID as IO (getRandomUUID)
83import qualified Text.Pandoc.UUID as IO (getRandomUUID)
88−import qualified Text.Pandoc.UTF8 as UTF8 (readFile)
84+import qualified Text.Pandoc.UTF8 as UTF8 (readFile, toString)
85+import System.FilePath.Glob (match, compile)
86+import System.FilePath ((</>))
89−import qualified System.FilePath.Glob as IO (namesMatching)
87+import qualified System.FilePath.Glob as IO (glob)
90−
88+import Control.Monad.State hiding (fail)
89+import Control.Monad.Reader hiding (fail)
90+import Data.Word (Word8)
91−data PandocActionF ref nxt =
92+data PandocActionF nxt =
92 LookupEnv String (Maybe String -> nxt)
93 LookupEnv String (Maybe String -> nxt)
93 | GetCurrentTime (UTCTime -> nxt)
94 | GetCurrentTime (UTCTime -> nxt)
94 | GetPOSIXTime (POSIXTime -> nxt)
95 | GetPOSIXTime (POSIXTime -> nxt)
95 | GetDefaultReferenceDocx (Maybe FilePath) (Archive -> nxt)
96 | GetDefaultReferenceDocx (Maybe FilePath) (Archive -> nxt)
96 | GetDefaultReferenceODT (Maybe FilePath) (Archive -> nxt)
97 | GetDefaultReferenceODT (Maybe FilePath) (Archive -> nxt)
97 | NewStdGen (StdGen -> nxt)
98 | NewStdGen (StdGen -> nxt)
98− | NewUnique (Unique -> nxt)
99+ | NewUniqueHash (Int -> nxt)
99 | NewUUID (UUID -> nxt)
100 | NewUUID (UUID -> nxt)
100 | ReadFileStrict FilePath (B.ByteString -> nxt)
101 | ReadFileStrict FilePath (B.ByteString -> nxt)
101 | ReadFileLazy FilePath (BL.ByteString -> nxt)
102 | ReadFileLazy FilePath (BL.ByteString -> nxt)
@@ -105,83 +106,71 @@ data PandocActionF ref nxt =
105 (Either E.SomeException (B.ByteString, Maybe MimeType) -> nxt)
106 (Either E.SomeException (B.ByteString, Maybe MimeType) -> nxt)
106 | FetchItem' MediaBag (Maybe String) (String)
107 | FetchItem' MediaBag (Maybe String) (String)
107 (Either E.SomeException (B.ByteString, Maybe MimeType) -> nxt)
108 (Either E.SomeException (B.ByteString, Maybe MimeType) -> nxt)
108− | NewIORef ref (IORef ref -> nxt)
109− | ModifyIORef (IORef ref) (ref -> ref) nxt
110− | ReadIORef (IORef ref) (ref -> nxt)
111− | NamesMatching String ([FilePath] -> nxt)
109+ | Glob String ([FilePath] -> nxt)
112 | Warn String nxt
110 | Warn String nxt
113 | Fail String
111 | Fail String
114 deriving Functor
112 deriving Functor
116−type PandocAction a = Free (PandocActionF a)
114+type PandocAction = Free PandocActionF
118−lookupEnv :: String -> PandocAction a (Maybe String)
116+lookupEnv :: String -> PandocAction (Maybe String)
119lookupEnv s = liftF $ LookupEnv s id
117lookupEnv s = liftF $ LookupEnv s id
121−getCurrentTime :: PandocAction a UTCTime
119+getCurrentTime :: PandocAction UTCTime
122getCurrentTime = liftF $ GetCurrentTime id
120getCurrentTime = liftF $ GetCurrentTime id
124−getPOSIXTime :: PandocAction a POSIXTime
122+getPOSIXTime :: PandocAction POSIXTime
125getPOSIXTime = liftF $ GetPOSIXTime id
123getPOSIXTime = liftF $ GetPOSIXTime id
127−getDefaultReferenceDocx :: Maybe FilePath -> PandocAction a Archive
125+getDefaultReferenceDocx :: Maybe FilePath -> PandocAction Archive
128getDefaultReferenceDocx fp = liftF $ GetDefaultReferenceDocx fp id
126getDefaultReferenceDocx fp = liftF $ GetDefaultReferenceDocx fp id
130−getDefaultReferenceODT :: Maybe FilePath -> PandocAction a Archive
128+getDefaultReferenceODT :: Maybe FilePath -> PandocAction Archive
131getDefaultReferenceODT fp = liftF $ GetDefaultReferenceODT fp id
129getDefaultReferenceODT fp = liftF $ GetDefaultReferenceODT fp id
133−newStdGen :: PandocAction a StdGen
131+newStdGen :: PandocAction StdGen
134newStdGen = liftF $ NewStdGen id
132newStdGen = liftF $ NewStdGen id
136−newUnique :: PandocAction a Unique
134+newUniqueHash :: PandocAction Int
137−newUnique = liftF $ NewUnique id
135+newUniqueHash = liftF $ NewUniqueHash id
139−newUUID :: PandocAction a UUID
137+newUUID :: PandocAction UUID
140newUUID = liftF $ NewUUID id
138newUUID = liftF $ NewUUID id
142−readFileStrict :: FilePath -> PandocAction a B.ByteString
140+readFileStrict :: FilePath -> PandocAction B.ByteString
143readFileStrict fp = liftF $ ReadFileStrict fp id
141readFileStrict fp = liftF $ ReadFileStrict fp id
145−readFileLazy :: FilePath -> PandocAction a BL.ByteString
143+readFileLazy :: FilePath -> PandocAction BL.ByteString
146readFileLazy fp = liftF $ ReadFileLazy fp id
144readFileLazy fp = liftF $ ReadFileLazy fp id
148−readFileUTF8 :: FilePath -> PandocAction a String
146+readFileUTF8 :: FilePath -> PandocAction String
149readFileUTF8 fp = liftF $ ReadFileUTF8 fp id
147readFileUTF8 fp = liftF $ ReadFileUTF8 fp id
151−readDataFile :: Maybe FilePath -> FilePath -> PandocAction a B.ByteString
149+readDataFile :: Maybe FilePath -> FilePath -> PandocAction B.ByteString
152readDataFile mfp fp = liftF $ ReadDataFile mfp fp id
150readDataFile mfp fp = liftF $ ReadDataFile mfp fp id
154fetchItem :: Maybe String ->
152fetchItem :: Maybe String ->
155 String ->
153 String ->
156− PandocAction a (Either E.SomeException (B.ByteString, Maybe MimeType))
154+ PandocAction (Either E.SomeException (B.ByteString, Maybe MimeType))
157fetchItem ms s = liftF $ FetchItem ms s id
155fetchItem ms s = liftF $ FetchItem ms s id
160fetchItem' :: MediaBag ->
158fetchItem' :: MediaBag ->
161 Maybe String ->
159 Maybe String ->
162 String ->
160 String ->
163− PandocAction a (Either E.SomeException (B.ByteString, Maybe MimeType))
161+ PandocAction (Either E.SomeException (B.ByteString, Maybe MimeType))
164fetchItem' mb ms s = liftF $ FetchItem' mb ms s id
162fetchItem' mb ms s = liftF $ FetchItem' mb ms s id
166−warn :: String -> PandocAction a ()
164+warn :: String -> PandocAction ()
167warn s = liftF $ Warn s ()
165warn s = liftF $ Warn s ()
169−fail :: String -> PandocAction a b
167+fail :: String -> PandocAction b
170fail s = liftF $ Fail s
168fail s = liftF $ Fail s
172−newIORef :: a -> PandocAction a (IORef a)
173−newIORef v = liftF $ NewIORef v id
175−modifyIORef :: (IORef a) -> (a -> a) -> PandocAction a ()
176−modifyIORef ref f = liftF $ ModifyIORef ref f ()
178−readIORef :: (IORef a) -> PandocAction a a
179−readIORef ref = liftF $ ReadIORef ref id
181−namesMatching :: String -> PandocAction a [FilePath]
170+glob :: String -> PandocAction [FilePath]
182−namesMatching s = liftF $ NamesMatching s id
171+glob s = liftF $ Glob s id
184−runIO :: PandocAction ref nxt -> IO nxt
173+runIO :: PandocAction nxt -> IO nxt
185runIO (Free (LookupEnv s f)) = IO.lookupEnv s >>= runIO . f
174runIO (Free (LookupEnv s f)) = IO.lookupEnv s >>= runIO . f
186runIO (Free (GetCurrentTime f)) = IO.getCurrentTime >>= runIO . f
175runIO (Free (GetCurrentTime f)) = IO.getCurrentTime >>= runIO . f
187runIO (Free (GetPOSIXTime f)) = IO.getPOSIXTime >>= runIO . f
176runIO (Free (GetPOSIXTime f)) = IO.getPOSIXTime >>= runIO . f
@@ -190,7 +179,7 @@ runIO (Free (GetDefaultReferenceDocx mfp f)) =
190runIO (Free (GetDefaultReferenceODT mfp f)) =
179runIO (Free (GetDefaultReferenceODT mfp f)) =
191 IO.getDefaultReferenceODT mfp >>= runIO . f
180 IO.getDefaultReferenceODT mfp >>= runIO . f
192runIO (Free (NewStdGen f)) = IO.newStdGen >>= runIO . f
181runIO (Free (NewStdGen f)) = IO.newStdGen >>= runIO . f
193−runIO (Free (NewUnique f)) = IO.newUnique >>= runIO . f
182+runIO (Free (NewUniqueHash f)) = hashUnique <$> IO.newUnique >>= runIO . f
194runIO (Free (NewUUID f)) = IO.getRandomUUID >>= runIO . f
183runIO (Free (NewUUID f)) = IO.getRandomUUID >>= runIO . f
195runIO (Free (ReadFileStrict fp f)) = B.readFile fp >>= runIO . f
184runIO (Free (ReadFileStrict fp f)) = B.readFile fp >>= runIO . f
196runIO (Free (ReadFileLazy fp f)) = BL.readFile fp >>= runIO . f
185runIO (Free (ReadFileLazy fp f)) = BL.readFile fp >>= runIO . f
@@ -202,8 +191,120 @@ runIO (Free (FetchItem sourceUrl nm f)) =
202runIO (Free (FetchItem' media sourceUrl nm f)) =
191runIO (Free (FetchItem' media sourceUrl nm f)) =
203 IO.fetchItem' media sourceUrl nm >>= runIO . f
192 IO.fetchItem' media sourceUrl nm >>= runIO . f
204runIO (Free (Warn s nxt)) = IO.warn s >> runIO nxt
193runIO (Free (Warn s nxt)) = IO.warn s >> runIO nxt
205−runIO (Free (NewIORef v f)) = IO.newIORef v >>= runIO . f
194+runIO (Free (Glob s f)) = IO.glob s >>= runIO . f
206−runIO (Free (ModifyIORef ref f nxt)) = IO.modifyIORef ref f >> runIO nxt
207−runIO (Free (ReadIORef ref f)) = IO.readIORef ref >>= runIO . f
208−runIO (Free (NamesMatching s f)) = IO.namesMatching s >>= runIO . f
209runIO (Pure r) = return r
195runIO (Pure r) = return r
197+data TestState = TestState { stStdGen :: StdGen
198+ , stWord8Store :: [Word8] -- should be
201+ , stWarnings :: [String]
202+ , stUniqStore :: [Int] -- should be
209+data TestEnv = TestEnv { envEnv :: [(String, String)]
210+ , envTime :: UTCTime
211+ , envReferenceDocx :: Archive
212+ , envReferenceODT :: Archive
213+ , envFiles :: [(FilePath, B.ByteString)]
214+ , envUserDataDir :: [(FilePath, B.ByteString)]
215+ , envCabalDataDir :: [(FilePath, B.ByteString)]
216+ , envFontFiles :: [FilePath]
219+data TestException = TestException
222+instance E.Exception TestException
224+type Testing = ReaderT TestEnv (State TestState)
226+runTest :: PandocAction nxt -> Testing nxt
227+runTest (Free (LookupEnv s f)) = do
229+ return (lookup s env) >>= runTest . f
230+runTest (Free (GetCurrentTime f)) =
231+ asks envTime >>= runTest . f
232+runTest (Free (GetPOSIXTime f)) =
233+ (utcTimeToPOSIXSeconds <$> asks envTime) >>= runTest . f
234+runTest (Free (GetDefaultReferenceDocx _ f)) =
235+ asks envReferenceDocx >>= runTest . f
236+runTest (Free (GetDefaultReferenceODT _ f)) =
237+ asks envReferenceODT >>= runTest . f
238+runTest (Free (NewStdGen f)) = do
240+ let (_, nxtGen) = next g
241+ modify $ \st -> st { stStdGen = nxtGen }
242+ return g >>= runTest . f
243+runTest (Free (NewUniqueHash f)) = do
244+ uniqs <- gets stUniqStore
247+ modify $ \st -> st { stUniqStore = us }
248+ return u >>= runTest . f
249+ _ -> M.fail "uniq store ran out of elements"
250+runTest (Free (NewUUID f)) = do
251+ word8s <- gets stWord8Store
253+ -- note we use f' because f is a param of the function
254+ a:b:c:d:e:f':g:h:i:j:k:l:m:n:o:p:remaining -> do
255+ modify $ \st -> st { stWord8Store = remaining }
256+ return (UUID a b c d e f' g h i j k l m n o p) >>= runTest . f
257+ _ -> M.fail "word8 supply was not infinite"
258+runTest (Free (ReadFileStrict fp f)) = do
259+ fps <- asks envFiles
260+ case lookup fp fps of
261+ Just bs -> return bs >>= runTest . f
262+ Nothing -> error "openFile: does not exist"
263+runTest (Free (ReadFileLazy fp f)) = do
264+ fps <- asks envFiles
265+ case lookup fp fps of
266+ Just bs -> return (BL.fromStrict bs) >>= runTest . f
267+ Nothing -> error "openFile: does not exist"
268+runTest (Free (ReadFileUTF8 fp f)) = do
269+ fps <- asks envFiles
270+ case lookup fp fps of
271+ Just bs -> return (UTF8.toString bs) >>= runTest . f
272+ Nothing -> error "openFile: does not exist"
273+-- A few different cases of readDataFile to reimplement, for when
274+-- there is no filepath and it falls through to readDefaultDataFile
275+runTest (Free (ReadDataFile Nothing "reference.docx" f)) = do
276+ (B.concat . BL.toChunks . fromArchive) <$>
277+ (runTest $ getDefaultReferenceDocx Nothing) >>=
279+runTest (Free (ReadDataFile Nothing "reference.odt" f)) = do
280+ (B.concat . BL.toChunks . fromArchive) <$>
281+ (runTest $ getDefaultReferenceODT Nothing) >>=
283+runTest (Free (ReadDataFile Nothing fname f)) = do
284+ let fname' = if fname == "MANUAL.txt" then fname else "data" </> fname
285+ runTest (readFileStrict fname') >>= runTest . f
286+runTest (Free (ReadDataFile (Just userDir) fname f)) = do
287+ userDirFiles <- asks envUserDataDir
288+ case lookup (userDir </> fname) userDirFiles of
289+ Just bs -> return bs >>= runTest . f
290+ Nothing -> runTest (readDataFile Nothing fname) >>= runTest . f
291+runTest (Free (Fail s)) = M.fail s
292+runTest (Free (FetchItem _ fp f)) = do
293+ fps <- asks envFiles
294+ case lookup fp fps of
295+ Just bs -> return (Right (bs, getMimeType fp)) >>= runTest . f
296+ Nothing -> return (Left $ E.toException TestException) >>= runTest . f
297+runTest (Free (FetchItem' media sourceUrl nm f)) = do
298+ case lookupMedia nm media of
299+ Nothing -> runTest (fetchItem sourceUrl nm) >>= runTest . f
300+ Just (mime, bs) -> return (Right (B.concat $ BL.toChunks bs, Just mime)) >>= runTest . f
301+runTest (Free (Warn s nxt)) = do
302+ modify $ \st -> st { stWarnings = s : stWarnings st }
304+runTest (Free (Glob s f)) = do
305+ fontFiles <- asks envFontFiles
306+ return (filter (match (compile s)) fontFiles) >>= runTest . f
307+runTest (Pure r) = return r