orchid-0.0.6: src/Network/Orchid/Backend/DarcsBackend.hs
module Network.Orchid.Backend.DarcsBackend (darcsBackend) where
import Control.Applicative hiding (empty)
import Text.ParserCombinators.Parsec hiding (many, optional, (<|>), try)
import System.Directory (removeFile)
import System.Process (runInteractiveProcess, waitForProcess, runProcess)
import System.Exit
import qualified System.IO.UTF8 as U
#if (__GLASGOW_HASKELL__ < 609)
import Control.Exception hiding (IOException)
#else
import Control.Exception
#endif
import Misc.Commons (splitsWith, eitherToMaybe, trim)
import Network.Protocol.Uri ((/+), normalize)
import Network.Orchid.Core.Backend
#if (__GLASGOW_HASKELL__ < 609)
type IOException = Exception
#endif
type DarcsRepository = FilePath
{-
Create a Darcs wiki backend bound to the specified repository location. The
temp directory specfied will be used to store intermediate results from
patching old revisions. This directory can easily be shared with the cache
directory for the `CachingBackend' module.
-}
darcsBackend :: FilePath -> FilePath -> Backend
darcsBackend repo temp = Backend {
store = darcsStore repo
, delete = darcsDelete repo
, retrieve = darcsRetrieve repo temp
, history = darcsHistory repo
}
-------- store a documents in a new darcs patch -------------------------------
darcsStore :: DarcsRepository -> FilePath -> Revision -> String -> IO ()
darcsStore repo file cha@(Revision _ author name) doc = do
-- Write the new document to the source file.
U.writeFile (repo /+ file) doc
-- Add file to repository.
runProcess "darcs" ["add", "--case-ok", file]
(Just repo) Nothing Nothing Nothing Nothing
>>= waitForProcess
-- Record the changes for this file.
runProcess "darcs" [
"record", file
, "--all"
, "--patch-name=" ++ name
, "--author=" ++ author
] (Just repo) Nothing Nothing Nothing Nothing
>>= waitForProcess
return ()
-------- delete a document from the darcs repo --------------------------------
darcsDelete :: DarcsRepository -> FilePath -> Revision -> IO ()
darcsDelete repo file cha@(Revision _ author name) = do
-- Write the new document to the source file.
removeFile (repo /+ file)
-- Record the changes for this file.
runProcess "darcs" [
"record", file
, "--all"
, "--patch-name=" ++ name
, "--author=" ++ author
] (Just repo) Nothing Nothing Nothing Nothing
>>= waitForProcess
-- TODO: cache removal.
return ()
-------------------------------------------------------------------------------
darcsRetrieve :: DarcsRepository -> FilePath -> FilePath -> Revision -> IO (Maybe String)
darcsRetrieve repo temp file rev =
case name rev of
"" -> eitherToMaybe <$> (try (U.readFile (repo /+ file)) :: IO (Either IOException String))
n -> retrieveDiff file repo temp n
retrieveDiff :: FilePath -> FilePath -> FilePath -> String -> IO (Maybe String)
retrieveDiff file repo temp name = do
-- Load the diff from darcs.
(inp, out, err, pid) <-
runInteractiveProcess "darcs" [
"diff", file
, "--unified"
, "--store-in-memory"
, "--to-patch=^" ++ (escapePatch name) ++ "$"
]
(Just repo) Nothing
-- Pipe the diff from darcs straight to patch.
let prev = temp ++ file ++ "?" ++ name
pid' <- runProcess "patch" [
"--unified"
, "--output=" ++ prev
]
(Just repo) Nothing
(Just out) Nothing Nothing
-- Wait for both processes in pipe to terminate.
exit' <- waitForProcess pid'
exit <- waitForProcess pid
-- Return just the document or nothing on failure.
case exit of
ExitSuccess -> Just <$> U.readFile (repo /+ prev)
-- TODO: just to damn ugly.
ExitFailure _ -> removeFile (repo /+ prev) >> return Nothing
-- Escape special characters in text that will end up in an XML document.
escapePatch :: String -> String
escapePatch [] = []
escapePatch ('(':xs) = "\\(" ++ escapePatch xs
escapePatch (')':xs) = "\\)" ++ escapePatch xs
escapePatch ('+':xs) = "\\+" ++ escapePatch xs
escapePatch ('.':xs) = "\\." ++ escapePatch xs
escapePatch (x:xs) = x : escapePatch xs
-------- request and parse modification history -------------------------------
darcsHistory :: DarcsRepository -> FilePath -> IO (Maybe History)
darcsHistory repo file = do
(inp, out, err, pid) <-
runInteractiveProcess "darcs" ["changes", file] (Just repo) Nothing
s <- pHistory <$> U.hGetContents out
waitForProcess pid
return s
pHistory :: String -> Maybe History
pHistory h = splitsWith "\n\n" h >>= pRevisions . snd
pRevisions :: String -> Maybe History
pRevisions his = do
case splitsWith "\n\n" his of
Nothing -> fmap pure $ pRevision his
Just (c, cs) -> do
c' <- pRevision c
cs' <- pRevisions cs
return (c' : cs')
pRevision :: String -> Maybe Revision
pRevision revision = do
let tmplt = length "Sun Aug 10 19:20:52 CEST 2008"
(inf, body) <- splitsWith "\n" revision
let date = take tmplt inf
author = trim (drop tmplt inf)
name = dropWhile (flip elem " *") $ head $ lines body
return $ Revision date author name