sr-extra 1.46.3.2 → 1.64
raw patch · 39 files changed
+2941/−458 lines, 39 filesdep +Cabaldep +Diffdep +ListLikedep −old-localedep −old-timedep −regex-compatdep ~basedep ~mtldep ~network-uri
Dependencies added: Cabal, Diff, ListLike, cereal, exceptions, fgl, filemanip, generic-data, hslogger, lens, mmorph, process-extras, safecopy, show-combinators, show-please, syb, template-haskell, text, th-lift, th-lift-instances, th-orphans, userid, uuid, uuid-orphans, uuid-types
Dependencies removed: old-locale, old-time, regex-compat
Dependency ranges changed: base, mtl, network-uri
Files
- Extra/CIO.hs +0/−308
- Extra/Debug.hs +85/−0
- Extra/Debug2.hs +61/−0
- Extra/Either.hs +3/−12
- Extra/EnvPath.hs +44/−0
- Extra/Except.hs +208/−0
- Extra/Exit.hs +1/−1
- Extra/FP.hs +71/−0
- Extra/Files.hs +23/−23
- Extra/GPGSign.hs +2/−4
- Extra/Generics.hs +31/−0
- Extra/Generics/Show.hs +278/−0
- Extra/HughesPJ.hs +1/−1
- Extra/IO.hs +111/−18
- Extra/IOThread.hs +2/−2
- Extra/List.hs +6/−6
- Extra/Lock.hs +4/−3
- Extra/Log.hs +54/−0
- Extra/Misc.hs +32/−25
- Extra/Monad/Supply.hs +113/−0
- Extra/Net.hs +5/−3
- Extra/Orphans.hs +132/−0
- Extra/Orphans2.hs +107/−0
- Extra/Orphans3.hs +247/−0
- Extra/Pretty.hs +104/−0
- Extra/Process.hs +325/−0
- Extra/SSH.hs +8/−9
- Extra/SafeCopy.hs +104/−0
- Extra/SafeCopyDebug.hs +102/−0
- Extra/Serialize.hs +186/−0
- Extra/SerializeDebug.hs +109/−0
- Extra/TH.hs +93/−0
- Extra/Text.hs +75/−0
- Extra/Time.hs +29/−10
- Extra/URI.hs +7/−4
- Extra/Verbosity.hs +77/−0
- Test/QUnit.hs +4/−1
- Test/QuickCheck/Properties.hs +1/−1
- sr-extra.cabal +96/−27
− Extra/CIO.hs
@@ -1,308 +0,0 @@--- |CIO is a type class for the TIO monad, which tracks the cursor--- position of the console so that indentation and prefixes can be--- added to the output. TIO also has a style component which lets you--- control the output verbosity and the appearance of the prefix.--- There is an instance for the regular IO monad which doesn't use any--- of these features, to allow functions which do not use the TIO--- monad call functions in the Debian library.------ NOTE: a copy of this library is in the Extra library as--- well. Please update both locations.--- --- This code is provided for backwards compatibility, I don't--- endorse its use in new projects.-module Extra.CIO {-# DEPRECATED "Use System.Unix.QIO in Unixutils." #-}- ( -- * The CIO class- CIO(..)-- -- * Style constructors and transformers- , TStyle(..)- , defStyle- , withStyle-- , setVerbosity- , addVerbosity- , setPrefix- , addPrefix- , appPrefix- , setPrefixes- , addPrefixes- , appPrefixes- , hGetPrefix-- -- * Output functions- , putStr- , ePutStr- , vPutStr- , vEPutStr-- , hPutChar- , putChar- , ePutChar- , vHPutChar- , vPutChar- , vEPutChar-- , hPutStrBl- , putStrBl- , ePutStrBl- , vHPutStrBl- , vPutStrBl- , vEPutStrBl-- , hPutStrLn- , putStrLn- , ePutStrLn- , vHPutStrLn- , vPutStrLn- , vEPutStrLn-- , bol- , eBOL- , vHBOL- , vBOL- , vEBOL-- , hColor- , blue- , green- , red- , magenta- ) where--import Prelude hiding (putStr, putChar, putStrLn)-import qualified System.IO as IO-import Control.Monad.Trans-import Control.Exception---- |Class representing ways of doing console (terminal?) output.-class MonadIO m => CIO m where- -- |Write output to a handle.- hPutStr :: IO.Handle -> String -> m ()- -- |If we are not already at the beginning of a line, move the cursor- -- to the beginning of the next one.- hBOL :: IO.Handle -> m ()- -- |Return the \"effective verbosity\" for a task. If the argument- -- is 2 it means the caller is computing ev for a task that- -- normally does output when the verbosity level is 2 or higher.- -- If the verbosity of the current style is 1, then the ev or- -- effective verbosity is 2-1 = -1, so the output should be- -- quieter.- ev :: Int -> m Int- -- |Modify the current output style.- setStyle :: (TStyle -> TStyle) -> m a -> m a- -- |Implementation of try for this monad- tryCIO :: m a -> m (Either SomeException a)---- |A record used to hold the output style information for a task.--- This The prefixes that will appear at the beginning of each line,--- and the desired verbosity level. Suggested verbosity level policy:------ * -1: No output of any kind, as if you were directing all output to /dev/null--- --- * 0: Error output only, suitable for a run whose log you might examine later--- --- * 1: casual progress reporting - if you were running on a console but didn't--- expect anything to go wrong------ * 2: detailed progress reporting - show more progress, particularly things--- that might fail during the normal operation of the autobuilder: patches--- that fail to apply, dpkg-buildpackage runs that return errors, etc.------ * 3: Debugging output - use this level or higher if you suspect the--- autobuilder itself is failing, or you are doing development work on--- the autobuilder.-data TStyle- = TStyle { prefix :: String -- ^ Add this string at the beginning of each line- , verbosity :: Int -- ^ Ignore v functions whose argument is more than this- , hPrefix :: [(IO.Handle, String)] -- ^ Per-handle prefix- } deriving Show--defStyle :: TStyle-defStyle = TStyle { prefix = ": "- , hPrefix = []- , verbosity = 0- }---- |Use a new style for the TIO action-withStyle :: CIO m => TStyle -> m a -> m a-withStyle newStyle = setStyle (const newStyle)--setVerbosity :: Int -> TStyle -> TStyle-setVerbosity n style = style {verbosity = n}-addVerbosity :: Int -> TStyle -> TStyle-addVerbosity n style = setVerbosity (n + verbosity style) style---- |Set the output style for a handle to prefixed.-setPrefix :: String -> TStyle -> TStyle-setPrefix prefix style = style {prefix = prefix}---- | Prepend some text to the prefix.-addPrefix :: String -> TStyle -> TStyle-addPrefix newPrefix style = style {prefix = newPrefix ++ prefix style}---- | Append some text to the prefix.-appPrefix :: String -> TStyle -> TStyle-appPrefix newPrefix style = style {prefix = prefix style ++ newPrefix}--hSetPrefix :: IO.Handle -> String -> TStyle -> TStyle-hSetPrefix handle string style =- style {hPrefix = (handle, string) : filter ((/= handle) . fst) (hPrefix style)}--hAddPrefix :: IO.Handle -> String -> TStyle -> TStyle-hAddPrefix handle string style =- hSetPrefix handle (string ++ prefix) style- where prefix = maybe "" id (lookup handle (hPrefix style))--hAppPrefix :: IO.Handle -> String -> TStyle -> TStyle-hAppPrefix handle string style =- hSetPrefix handle (prefix ++ string) style- where prefix = maybe "" id (lookup handle (hPrefix style))---- |Get the current prefix for a particular handle-hGetPrefix :: IO.Handle -> TStyle -> String-hGetPrefix handle style = prefix style ++ maybe "" id (lookup handle (hPrefix style))---- |Set the output style for the stdout and stderr handle to prefixed,--- using whatever prefixes were most recently set (default is [1] and [2].)-setPrefixes :: String -> String -> TStyle -> TStyle-setPrefixes stdoutPrefix stderrPrefix style =- hSetPrefix IO.stdout stdoutPrefix . hSetPrefix IO.stderr stderrPrefix $ style---- |Switch to prefixed mode and modify both the stdout and stderr prefixes.-addPrefixes :: String -> String -> TStyle -> TStyle-addPrefixes oPrefix ePrefix style =- hAddPrefix IO.stdout oPrefix . hAddPrefix IO.stderr ePrefix $ style--appPrefixes :: String -> String -> TStyle -> TStyle-appPrefixes oPrefix ePrefix style =- hAppPrefix IO.stdout oPrefix . hAppPrefix IO.stderr ePrefix $ style---- |Perform an action if the effective verbosity level is >= 0,--- otherwise return the default value d.-vIO :: CIO m => Int -> a -> m a -> m a-vIO v d f =- do v' <- ev v- if v' >= 0 then f else return d---- |Write a string to stdout.-putStr :: CIO m => String -> m ()-putStr = hPutStr IO.stdout---- |Write a string to stderr.-ePutStr :: CIO m => String -> m ()-ePutStr = hPutStr IO.stderr---- |Verbosity controlled version of ePutStr-vEPutStr :: CIO m => Int -> String -> m ()-vEPutStr = vHPutStr IO.stderr---- |Write a string to stdout depending on the verbosity level.-vPutStr :: CIO m => Int -> String -> m ()-vPutStr = vHPutStr IO.stdout---- |Write a character.-hPutChar :: CIO m => IO.Handle -> Char -> m ()-hPutChar h c = hPutStr h [c]---- |Write a character to stdout.-putChar :: CIO m => Char -> m ()-putChar = hPutChar IO.stdout---- |Write a character to stderr.-ePutChar :: CIO m => Char -> m ()-ePutChar = hPutChar IO.stderr---- |Verbosity controlled version of hPutStr-vHPutStr :: CIO m => IO.Handle -> Int -> String -> m ()-vHPutStr h v s = vIO v () (hPutStr h s)---- |Verbosity controlled version of hPutChar.-vHPutChar :: CIO m => IO.Handle -> Int -> Char -> m ()-vHPutChar h v c = vHPutStr h v [c]---- |Verbosity controlled version of putChar-vPutChar :: CIO m => Int -> Char -> m ()-vPutChar = vHPutChar IO.stdout---- |Verbosity controlled version of ePutChar-vEPutChar :: CIO m => Int -> Char -> m ()-vEPutChar = vHPutChar IO.stderr---- |Move to beginning of next line (if necessary) and output a string.-hPutStrBl :: CIO m => IO.Handle -> String -> m ()-hPutStrBl h s = hBOL h >> hPutStr h s---- |hPutStrBl to stdout.-putStrBl :: CIO m => String -> m ()-putStrBl = hPutStrBl IO.stdout---- |hPutStrBl to stderr.-ePutStrBl :: CIO m => String -> m ()-ePutStrBl = hPutStrBl IO.stderr---- |Verbosity controlled version of hPutStrBl-vHPutStrBl :: CIO m => IO.Handle -> Int -> String -> m ()-vHPutStrBl h v s = vHBOL h v >> vHPutStr h v s---- |Verbosity controlled version of putStrBl-vPutStrBl :: CIO m => Int -> String -> m ()-vPutStrBl = vHPutStrBl IO.stdout ---- |Verbosity controlled version of ePutStrBl-vEPutStrBl :: CIO m => Int -> String -> m ()-vEPutStrBl = vHPutStrBl IO.stderr---- |Write a newline character and a string.-hPutStrLn :: CIO m => IO.Handle -> String -> m ()-hPutStrLn h s = hBOL h >> hPutStr h s---- |hPutStrLn to stdout.-putStrLn :: CIO m => String -> m ()-putStrLn s = hPutStrLn IO.stdout s---- |hPutStrLn to stderr.-ePutStrLn :: CIO m => String -> m ()-ePutStrLn = hPutStrLn IO.stderr---- |Verbosity controlled version of hPutStrLn.-vHPutStrLn :: CIO m => IO.Handle -> Int -> String -> m ()-vHPutStrLn h v s = vHBOL h v >> vHPutStr h v s---- |Verbosity controlled version of putStrLn-vPutStrLn :: CIO m => Int -> String -> m ()-vPutStrLn = vHPutStrLn IO.stdout ---- |Verbosity controlled version of ePutStrLn-vEPutStrLn :: CIO m => Int -> String -> m ()-vEPutStrLn = vHPutStrLn IO.stderr---- |hBOL to stdout.-bol :: CIO m => m ()-bol = hBOL IO.stdout---- |hBOL to stderr.-eBOL :: CIO m => m ()-eBOL = hBOL IO.stderr--vHBOL :: CIO m => IO.Handle -> Int -> m ()-vHBOL h v = vIO v () (hBOL h)---- |Verbosity controlled version of BOL-vBOL :: CIO m => Int -> m ()-vBOL = vHBOL IO.stdout---- |Verbosity controlled version of eBOL-vEBOL :: CIO m => Int -> m ()-vEBOL = vHBOL IO.stderr---- These only work in a terminal, not in an emacs shell.-hColor h s = case h of- _ | h == IO.stdout -> green s- _ | h == IO.stdout -> red s- _ -> magenta s--blue s = "\ESC[34m" ++ s ++ "\ESC[30m"-green s = "\ESC[32m" ++ s ++ "\ESC[30m"-red s = "\ESC[31m" ++ s ++ "\ESC[30m"-magenta s = "\ESC[35m" ++ s ++ "\ESC[30m"
+ Extra/Debug.hs view
@@ -0,0 +1,85 @@+-- | Declarations pulled out of th-unify++{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeSynonymInstances #-}++module Extra.Debug+ ( HasMessageInfo(..)+ , Verbosity(message)+ , quietly, noisily, indented+ , R(..))+ where++import Control.Lens (Lens', makeLenses, over, view)+import Control.Monad (when)+import Control.Monad.Reader (local, MonadReader, ReaderT)+import Control.Monad.RWS (RWST)+import Control.Monad.Trans (MonadIO(liftIO))+import Data.Char (isSpace)+import Data.List (dropWhileEnd, intercalate)+import Instances.TH.Lift ()+import Language.Haskell.TH.Instances ()+import System.IO (hPutStrLn, stderr)++class HasMessageInfo a where+ verbosity :: Lens' a Int+ prefix :: Lens' a String++-- | Class of monads with a verbosity level and a stored indentation string.+class (HasMessageInfo r, MonadReader r m) => Verbosity r m where+ message :: Int -> String -> m ()+ -- ^ If the monad's verbosity level exceeds the verbosity argument,+ -- prepend the current indentation string to the lines of a message+ -- and output it.++instance (MonadIO m, HasMessageInfo r, Monoid w) => Verbosity r (RWST r w s m) where+ message minv s = do+ v <- view verbosity+ p <- view prefix+ when (v >= minv) $ (liftIO . hPutStrLn stderr . indent p) s++instance (MonadIO m, HasMessageInfo r) => Verbosity r (ReaderT r m) where+ message minv s = do+ v <- view verbosity+ p <- view prefix+ liftIO $ putStrLn ("v=" ++ show v ++ " vmin=" ++ show minv)+ when (v >= minv) $ (liftIO . putStrLn . indent p) s++-- | Indent the lines of a message with a prefix+indent :: String -> String -> String+indent pre s = intercalate "\n" $ fmap (dropWhileEnd isSpace . (pre ++)) (lines s)++-- | If the current verbosity level is >= minv perform the action with+-- additional indentation.+indented :: (HasMessageInfo r, MonadReader r m) => Int -> m a -> m a+indented minv action = do+ (v :: Int) <- view verbosity+ if v >= minv then local (over prefix (" " ++)) action else action++-- | Perform the action with reduced verbosity+quietly :: (HasMessageInfo r, MonadReader r m) => Int -> m a -> m a+quietly n = local (over verbosity (\i -> i - n))++-- | Perform the action with increased verbosity+noisily :: (HasMessageInfo r, MonadReader r m) => Int -> m a -> m a+noisily n = local (over verbosity (+ n))++-- | A type with a HasMessageInfo instance to use in the Reader or RWS monad.+data R+ = R+ { _verbosityR :: Int+ , _prefixR :: String+ }++$(makeLenses ''R)++instance HasMessageInfo R where+ verbosity = verbosityR+ prefix = prefixR
+ Extra/Debug2.hs view
@@ -0,0 +1,61 @@+-- | Generate debug messages - verbosity controls, indentation.++{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS -Wall -Wredundant-constraints #-}++module Extra.Debug2+ ( HasTraceInfo(verbosityLens, prefixLens)+ , message+ , trace+ , indented+ , verbosity+ ) where++import Control.Lens ((%=), (.=), Lens', use)+import Control.Monad (when)+import Control.Monad.RWS+import Data.List (intercalate)+import qualified Debug.Trace+import Language.Haskell.TH.Instances ()+import System.Log.Logger (Priority(..))++-- Perform an action with modified state.+bracketState :: MonadState s m => Lens' s a -> (a -> a) -> m r -> m r+bracketState lns f action = do+ a0 <- use lns+ lns %= f+ r <- action+ lns .= a0+ return r++class HasTraceInfo a where+ verbosityLens :: Lens' a Priority+ prefixLens :: Lens' a String++-- | If the verbosity argument monad's verbosity level exceeds the verbosity argument,+-- prepend the current indentation string to the lines of a message+-- and output it.+message :: (HasTraceInfo s, MonadState s m) => (String -> m ()) -> Priority -> String -> m ()+message f p s = do+ v <- use verbosityLens+ when (p >= v) $ do+ i <- use prefixLens+ f (intercalate "\n" $ fmap (i ++) (lines s))++trace :: (HasTraceInfo s, MonadState s m) => String -> m ()+trace = message (\s -> Debug.Trace.trace s (return ())) DEBUG++-- | Perform an action with added indentation+indented :: (HasTraceInfo s, MonadState s m) => String -> m r -> m r+indented s action = bracketState prefixLens (<> s) action++-- | Perform an action with modified verbosity+verbosity :: (HasTraceInfo s, MonadState s m) => Priority -> m r -> m r+verbosity v action = bracketState verbosityLens (const v) action
Extra/Either.hs view
@@ -1,20 +1,11 @@ module Extra.Either where -import Data.Either (partitionEithers, rights)--isRight (Right _) = True-isRight (Left _) = False--isLeft = not . isRight+import Data.Either -- |Turn a list of eithers into an either of lists concatEithers :: [Either a b] -> Either [a] [b] concatEithers xs =- case partitionEithers xs of + case partitionEithers xs of ([], rs) -> Right rs (ls, _) -> Left ls--{-# DEPRECATED rightOnly "Use rights" #-}-rightOnly = rights-{-# DEPRECATED eitherFromList "Use concatEithers" #-}-eitherFromList = concatEithers+{-# DEPRECATED concatEithers "This is terrible. Delete your account." #-}
+ Extra/EnvPath.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE FlexibleInstances, StandaloneDeriving, TemplateHaskell, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}+module Extra.EnvPath+ ( EnvRoot(..), rootPath+ , EnvPath(..), envRoot, envPath+ , outsidePath+ , appendPath+ , rootEnvPath+ , HasEnvRoot(envRootLens)+ ) where++import Control.Lens (Lens', makeLenses, over)+import Extra.Pretty (PP(PP))+import Text.PrettyPrint.HughesPJClass (text)+import Distribution.Pretty (Pretty(pretty))++-- |The root directory of an OS image.+data EnvRoot = EnvRoot { _rootPath :: FilePath } deriving (Ord, Eq, Read, Show)++-- |A directory inside of an OS image.+data EnvPath = EnvPath { _envRoot :: EnvRoot+ , _envPath :: FilePath+ } deriving (Ord, Eq, Read, Show)++$(makeLenses ''EnvRoot)+$(makeLenses ''EnvPath)++outsidePath :: EnvPath -> FilePath+outsidePath path = _rootPath (_envRoot path) ++ _envPath path++appendPath :: FilePath -> EnvPath -> EnvPath+appendPath suff path = over envPath (++ suff) path++rootEnvPath :: FilePath -> EnvPath+rootEnvPath s = EnvPath { _envRoot = EnvRoot "", _envPath = s }++instance Pretty (PP EnvRoot) where+ pretty (PP x) = text (_rootPath x)++-- | Class used to access an EnvRoot in a value, typically in a reader+-- monad.+class HasEnvRoot r where envRootLens :: Lens' r EnvRoot+instance HasEnvRoot EnvRoot where envRootLens = id+
+ Extra/Except.hs view
@@ -0,0 +1,208 @@+{-# LANGUAGE CPP, DeriveAnyClass, FunctionalDependencies, OverloadedStrings, TemplateHaskell, UndecidableInstances #-}+{-# OPTIONS -Wall -Wredundant-constraints -Wno-orphans #-}++module Extra.Except+ ( -- * Control.Exception extensions+ withException+ , displaySomeExceptionType+ -- * Control.Monad.Except extensions+ , tryError+ , withError+ , mapError+ , handleError+ , HasIOException(fromIOException)+ , IOException'(..)+#if 1+ , Tried(unTried)+ , tryIO+ , logIOError+#else+ , LyftIO(lyftIO, IOMonad, ErrorType)+ , tryLiftIO+ , logLiftIO+ , lyftIO'+ , LyftIO'+ , LyftIO2+ , lyftIO2++ , MonadIOError+ , liftIOError+ , tryIOError+ , logIOError+#endif+ , module Control.Monad.Except+ ) where++import Control.Exception ({-evaluate,-} Exception, IOException, SomeException(..))+import Control.Monad.Catch+import Control.Monad.Except+--import Control.Monad.Reader (ReaderT)+--import Control.Monad.RWS (RWST)+--import Control.Monad.State (StateT)+--import Control.Monad.Writer (WriterT)+import Data.Typeable (typeOf)+import Extra.Log (logException, Priority(ERROR))++-- | Apply a function to whatever @Exception@ type is inside a+-- @SomeException@:+--+-- >>> catch (readFile "/tmp/nonexistant") (withException (return . show . typeOf))+-- "IOException"+withException :: forall r. (forall e. Exception e => e -> r) -> SomeException -> r+withException f (SomeException e) = f e++-- | Use 'withException' to obtain the exception's type name (similar+-- to 'Control.Exception.displayException')+--+-- >>> catch (readFile "/tmp/nonexistant") (return . displaySomeExceptionType)+-- "IOException"+displaySomeExceptionType :: SomeException -> String+displaySomeExceptionType = withException (show . typeOf)++-- | MonadError analog to the 'try' function.+tryError :: MonadError e m => m a -> m (Either e a)+tryError action = (Right <$> action) `catchError` (pure . Left)++-- | Modify the value (but not the type) of an error+withError :: MonadError e m => (e -> e) -> m a -> m a+withError f action = tryError action >>= either (throwError . f) return++handleError :: MonadError e m => (e -> m a) -> m a -> m a+handleError = flip catchError++-- | MonadError analogue of the 'mapExceptT' function.+mapError :: (MonadError e m, MonadError e' n) => (m (Either e a) -> n (Either e' b)) -> m a -> n b+mapError f action = f (tryError action) >>= liftEither++-- | In order to guarantee IOException is caught, do NOT create this+-- 'HasIOException' instance for IOException.+--+-- > instance HasIOException IOException where fromIOException = id+--+-- Because there is an @instance MonadError IOException IO@ in+-- @Control.Monad.Except@, "thrown" IOexceptions are not be caught by+-- 'runExceptT':+--+-- >>> runExceptT (liftIO (readFile "/etc/nonexistant") :: ExceptT IOException IO String)+-- *** Exception: /etc/nonexistant: openFile: does not exist (No such file or directory)+--+-- (*** means the exception reached the top level.) However, if we+-- use 'liftIOError' to implement a version of 'readFile' that has a+-- 'MonadIOError' constraint:+--+-- >>> let readFile' path = liftIOError (readFile path)+-- >>> :type readFile'+-- readFile' :: MonadIOError e m => FilePath -> m String+--+-- and then create a suitable error type+--+-- >>> newtype Error = Error IOException deriving Show+-- >>> instance MonadIOError Error where liftIOError io = liftIO (try io) >>= either (throwError . fromIOException) return+--+-- Now the thrown 'IOException' will always be caught and lifted into+-- the 'MonadError':+--+-- >>> runExceptT (readFile' "/etc/nonexistant" :: ExceptT Error IO String)+-- Left (Error /etc/nonexistant: openFile: does not exist (No such file or directory))+class HasIOException e where fromIOException :: IOException -> e++newtype IOException' = IOException' IOException+instance Show IOException' where show (IOException' e) = "(IOException' " <> show (show e) <> ")"+instance HasIOException IOException' where fromIOException = IOException'++#if 1++-- Newtype wrapper around values that are the result of an IO+-- operation invoked with try. The Tried constructor is private+-- so it can only appear as the result of the lyftIO operation.+newtype Tried a = Tried {unTried :: a} deriving Functor++tryIO :: (MonadIO m, MonadCatch m, Exception e, MonadError e m) => m a -> ExceptT e m (Tried a)+tryIO io = lift (try io >>= liftEither . fmap Tried)++logIOError :: (MonadIO m, MonadError e m) => m a -> m a+logIOError = handleError (\e -> liftIO ($logException ERROR (pure e)) >> throwError e)++#else++class (MonadIO (IOMonad m),+ -- The idea of LyftIO is to be a wrapper around a MonadIO+ -- instance that is not itself a MonadIO instance. that means+ -- you have to use lyftIO to run IO rather than liftIO, and+ -- lyftIO always catches exceptions.+ Exception (ErrorType m),+ MonadCatch m,+ MonadCatch (IOMonad m),+ MonadError (ErrorType m) m,+ MonadError (ErrorType m) (IOMonad m),+ -- IOMonad m must have the same error type as m, this is+ -- generally done through error constraints like+ -- HasIOException.+ HasIOException (ErrorType m)) => LyftIO m where+ type ErrorType m+ type IOMonad m :: * -> *+ lyftIO :: IOMonad m a -> m a++instance {-# Overlapping #-} (MonadIO m,+ e ~ ErrorType m,+ Exception e,+ MonadCatch m,+ MonadError e (IOMonad (ExceptT e m)),+ HasIOException e) => LyftIO (ExceptT e m) where+ type ErrorType (ExceptT e m) = e+ type IOMonad (ExceptT e m) = m+ lyftIO io = lift (try io >>= liftEither)++instance LyftIO m => LyftIO (ReaderT r m) where+ type ErrorType (ReaderT r m) = ErrorType m+ type IOMonad (ReaderT r m) = IOMonad m+ lyftIO = lift . lyftIO+instance LyftIO m => LyftIO (StateT s m) where+ type ErrorType (StateT s m) = ErrorType m+ type IOMonad (StateT s m) = IOMonad m+ lyftIO = lift . lyftIO+instance (Monoid w, LyftIO m) => LyftIO (WriterT w m) where+ type ErrorType (WriterT w m) = ErrorType m+ type IOMonad (WriterT w m) = IOMonad m+ lyftIO = lift . lyftIO+instance (Monoid w, LyftIO m) => LyftIO (RWST r w s m) where+ type ErrorType (RWST r w s m) = ErrorType m+ type IOMonad (RWST r w s m) = IOMonad m+ lyftIO = lift . lyftIO++-- | LyftIO analog to the 'try' function.+tryLiftIO :: LyftIO m => IOMonad m a -> m (Either (ErrorType m) a)+tryLiftIO = tryError . lyftIO++logLiftIO :: forall m a. LyftIO m => m a -> m a+logLiftIO = handleError (\e -> lyftIO ($logException ERROR (pure e) :: IOMonad m (ErrorType m)) >> throwError e)++type LyftIO' m = (LyftIO m, IOMonad m ~ ExceptT (ErrorType m) IO)++-- | Lift an IO action into any LyftIO instance. Well, almost any+-- LyftIO instance.+lyftIO' :: forall m a. (LyftIO' m) => IO a -> m a+lyftIO' io = lyftIO (withExceptT f (liftIO io))+ where f :: IOException' -> ErrorType m+ f = (fromIOException . (\(IOException' e) -> e))++type LyftIO2 e m = (LyftIO' m, e ~ ErrorType m)++-- | Lift an @IOMonad m a@ action into @m a@.+lyftIO2 :: LyftIO2 e m => IOMonad m a -> m a+lyftIO2 action = lyftIO action++-- Backwards compatibiity++type MonadIOError e m = (LyftIO' m, e ~ ErrorType m)++liftIOError :: MonadIOError e m => IO a -> m a+liftIOError = lyftIO'++tryIOError :: MonadIOError e m => IO a -> m (Either e a)+tryIOError = tryError . liftIOError++logIOError :: MonadIOError e m => m a -> m a+logIOError = handleError (\e -> liftIOError ($logException ERROR (pure e)) >> throwError e)++#endif
Extra/Exit.hs view
@@ -4,7 +4,7 @@ import System.Environment import System.Exit import System.IO-import Text.PrettyPrint.HughesPJ+import Text.PrettyPrint -- |exitFailure with nicely formatted help text on stderr exitWithHelp :: (String -> Doc) -- ^ generate help text, the argument is the result of getProgName
+ Extra/FP.hs view
@@ -0,0 +1,71 @@+-- | Copied from packman. Tried to use the library but got a compile error:+--+-- <no location info>: error:+-- <command line>: can't load .so/.DLL for: /usr/lib/haskell-packages/ghc/lib/x86_64-linux-ghc-8.4.3/libHSpackman-0.5.0-Fv7reuyLHo03M7x4FerNmn-ghc8.4.3.so (/usr/lib/haskell-packages/ghc/lib/x86_64-linux-ghc-8.4.3/libHSpackman-0.5.0-Fv7reuyLHo03M7x4FerNmn-ghc8.4.3.so: undefined symbol: mblock_address_space)++{-# LANGUAGE DeriveAnyClass, DeriveDataTypeable, DeriveGeneric, TemplateHaskell #-}++module Extra.FP+ ( FP(..)+ , matches+ , toFP+ , typeFP+ , typeRepFP+ ) where+++--import Data.Binary (Binary(..), Get)+import Data.Data (Data, Proxy, typeRep)+import Data.SafeCopy (base, deriveSafeCopy)+import Extra.Serialize (Serialize)+import Data.Typeable (Typeable, typeOf)+import Data.Typeable (typeRepFingerprint)+import Data.Word (Word64)+import qualified GHC.Fingerprint+import GHC.Generics (Generic)++------------------------------------------------------------------+-- $ComparingTypes+-----------------------------------------------+-- Helper functions to compare types at runtime:+-- We use type "fingerprints" defined in 'GHC.Fingerprint.Type'++-- This should ensure (as of GHC.7.8) that types with the same name+-- but different definition get different hashes. (however, we also+-- require the executable to be exactly the same, so this is not+-- strictly necessary anyway).++-- Typeable context for dynamic type checks. ++-- | The module uses a custom GHC fingerprint type with its two Word64+-- fields, to be able to /read/ fingerprints+data FP = FP Word64 Word64 deriving (Read, Show, Eq, Data, Typeable, Ord, Generic, Serialize)++-- | checks whether the type of the given expression matches the given Fingerprint+matches :: Typeable a => a -> FP -> Bool+matches x (FP c1 c2) = f1 == c1 && f2 == c2+ where (GHC.Fingerprint.Fingerprint f1 f2) = typeRepFingerprint (typeOf x)++-- | creates an 'FP' from a GHC 'Fingerprint'+toFP :: GHC.Fingerprint.Fingerprint -> FP+toFP (GHC.Fingerprint.Fingerprint f1 f2) = FP f1 f2++-- | returns the type fingerprint of an expression+typeFP :: Typeable a => a -> FP+typeFP = toFP . typeRepFingerprint . typeOf++typeRepFP :: Typeable a => Proxy a -> FP+typeRepFP p = toFP (typeRepFingerprint (typeRep p))++{-+-- | Binary instance for fingerprint data (encoding TypeRep and+-- executable in binary-encoded @Serialized a@)+instance Binary FP where+ put (FP f1 f2) = do put f1+ put f2+ get = do f1 <- get :: Get Word64+ f2 <- get :: Get Word64+ return (FP f1 f2)+-}++$(deriveSafeCopy 1 'base ''FP)
Extra/Files.hs view
@@ -5,7 +5,7 @@ -- An example of an inconsistant state would be if we got a failure -- when writing out a file, but were unable to restore the original -- file to its original position.-module Extra.Files +module Extra.Files ( getSubDirectories , renameAlways , renameMissing@@ -15,7 +15,7 @@ , writeAndZipFile , backupFile , writeFileIfMissing- , maybeWriteFile -- writeFileUnlessSame+ , maybeWriteFile -- writeFileUnlessSame , createSymbolicLinkIfMissing , prepareSymbolicLink , forceRemoveLink@@ -24,16 +24,16 @@ import qualified Codec.Compression.GZip as GZip import qualified Codec.Compression.BZip as BZip-import Control.Exception-import Control.Monad+import Control.Exception+import Control.Monad import qualified Data.ByteString.Lazy as B-import Data.List-import Data.Maybe-import Extra.Misc-import System.Unix.Directory-import System.Directory-import System.IO.Error hiding (try, catch)-import System.Posix.Files as SPF+import Data.List+import Data.Maybe+import Extra.Misc+import System.Unix.Directory+import System.Directory+import System.IO.Error+import System.Posix.Files as SPF -- | Return the list of subdirectories, omitting . and .. and ignoring -- symbolic links.@@ -52,7 +52,7 @@ installFiles pairs = do backedUp <- mapM (uncurry renameAlways) (zip originalFiles backupFiles) case lefts backedUp of- [] -> + [] -> do renamed <- mapM (uncurry renameAlways) (zip replacementFiles originalFiles) case lefts renamed of [] -> return $ Right ()@@ -63,9 +63,9 @@ -- files back into place. do restored <- mapM (uncurry renameAlways) (zip backupFiles originalFiles) case lefts restored of- -- We succeeded in failing.+ -- We succeeded in failing. [] -> return . Left . concat . lefts $ renamed- -- Restore failed. Throw an exception.+ -- Restore failed. Throw an exception. _ -> error ("installFiles: Couldn't restore original files after write failure:" ++ concat (map message (zip3 replacementFiles originalFiles renamed)) ++ concat (map message (zip3 originalFiles backupFiles restored)))@@ -75,9 +75,9 @@ -- Restore the backup for any missing original files. do restored <- mapM (uncurry renameMissing) (zip backupFiles originalFiles) case lefts restored of- -- We succeeded in failing.+ -- We succeeded in failing. [] -> return . Left . concat . lefts $ backedUp- -- Restore failed. Throw an exception.+ -- Restore failed. Throw an exception. _ -> error ("installFiles: Couldn't restore original files after write failure: " ++ concat (map message (zip3 originalFiles backupFiles backedUp)) ++ concat (map message (zip3 backupFiles originalFiles restored)))@@ -126,7 +126,7 @@ False -> return $ Right () True -> do status <- getSymbolicLinkStatus path- -- To do: should we remove the directory contents?+ -- To do: should we remove the directory contents? let rm = if isDirectory status then removeDirectory else removeLink try (rm path) >>= return . either (\ (e :: SomeException) -> Left ["Couldn't remove " ++ path ++ ": " ++ show e]) (const . Right $ ()) @@ -152,18 +152,18 @@ -- |like removeLink, but does not fail if link did not exist forceRemoveLink :: FilePath -> IO () forceRemoveLink fp = removeLink fp `Control.Exception.catch` (\e -> unless (isDoesNotExistError e) (ioError e))- + -- | Write out three versions of a file, regular, gzipped, and bzip2ed. writeAndZipFileWithBackup :: FilePath -> B.ByteString -> IO (Either [String] ()) writeAndZipFileWithBackup path text = backupFile path >>= either (\ e -> return (Left ["Failure renaming " ++ path ++ " -> " ++ path ++ "~: " ++ show e])) (\ _ -> try (B.writeFile path text) >>=- either (\ (e :: SomeException) ->+ either (\(e :: SomeException) -> restoreBackup path >>=- either (\ e -> error ("Failed to restore backup: " ++ path ++ "~ -> " ++ path ++ ": " ++ show e))+ either (\ex -> error ("Failed to restore backup: " ++ path ++ "~ -> " ++ path ++ ": " ++ show ex)) (\ _ -> return (Left ["Failure writing " ++ path ++ ": " ++ show e])))- (\ _ -> zipFile path))+ (\_ -> zipFile path)) -- | Write out three versions of a file, regular, gzipped, and bzip2ed. -- This new version assumes the files are written to temporary locations,@@ -207,7 +207,7 @@ maybeWrite (Left (e :: IOException)) | isDoesNotExistError e = writeFile path text maybeWrite (Left e) = error ("maybeWriteFile: " ++ show e) maybeWrite (Right old) | old == text = return ()- maybeWrite (Right _old) = + maybeWrite (Right _old) = --hPutStrLn stderr ("Old text: " ++ show old) >> --hPutStrLn stderr ("New text: " ++ show text) >> replaceFile path text@@ -238,7 +238,7 @@ -- isAlreadyBusyError exceptions before the writeFile succeeds. replaceFile :: FilePath -> String -> IO () replaceFile path text =- --tries 100 10 f -- There is now a fix for this problem, see ghc ticket 2122.+ --tries 100 10 f -- There is now a fix for this problem, see ghc ticket 2122. f where f :: IO ()
Extra/GPGSign.hs view
@@ -20,7 +20,7 @@ sign :: PGPKey'' -> FilePath -> IO FilePath sign keyname path =- do (_, _,err,pid) <- runInteractiveProcess cmd args workingDir env+ do (_, _,err,pid) <- runInteractiveProcess cmd args workingDir Nothing status <- waitForProcess pid case status of ExitSuccess -> return outputPath@@ -39,7 +39,6 @@ ] outputPath = path ++ ".gpg" workingDir = Nothing -- Just (dirName path)- env = Nothing data PGPKey = Key String | Default deriving Show @@ -48,7 +47,7 @@ pgpSignFile :: PGPKey -> FilePath -> IO Bool pgpSignFile keyname path =- do (_, _,err,pid) <- runInteractiveProcess cmd args workingDir env+ do (_, _,err,pid) <- runInteractiveProcess cmd args workingDir Nothing status <- waitForProcess pid case status of ExitSuccess -> return True@@ -69,4 +68,3 @@ defaultKey = case keyname of Key name -> ["--default-key", name]; Default -> [] outputPath = path ++ ".gpg" workingDir = Nothing -- Just (dirName path)- env = Nothing
+ Extra/Generics.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE ConstraintKinds, FlexibleContexts #-}++module Extra.Generics+ ( gFind+ , Extra.Generics.GShow+ , gshow+ , gshows+ , gshowsPrec+ ) where++import Control.Monad (MonadPlus, msum)+import Data.Generics (Data, listify, Proxy, Typeable)+import Generic.Data (gshowsPrec)+import Generic.Data.Internal.Show (GShow)+import GHC.Generics (Generic, Rep)++-- | @gFind a@ will extract any elements of type @b@ from+-- @a@'s structure in accordance with the MonadPlus+-- instance, e.g. Maybe Foo will return the first Foo+-- found while [Foo] will return the list of Foos found.+gFind :: (MonadPlus m, Data a, Typeable b) => a -> m b+gFind = msum . map return . listify (const True)++type GShow a = (Generic a, Generic.Data.Internal.Show.GShow Proxy (Rep a))++-- | Generic version of show based on generic-data's gshowsPrec function.+gshow :: (Generic a, Generic.Data.Internal.Show.GShow Proxy (Rep a)) => a -> String+gshow x = gshows x ""++gshows :: (Generic a, Generic.Data.Internal.Show.GShow Proxy (Rep a)) => a -> ShowS+gshows x = gshowsPrec 0 x
+ Extra/Generics/Show.hs view
@@ -0,0 +1,278 @@+-- | This is a both a working implementation of Generic Show and a+-- template for using GHC.Generics to implement similar functions+-- without requiring that every type it can operate on have a Show+-- instance. It does require an instance for each base type.++{-# LANGUAGE CPP #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE EmptyCase #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+-- {-# LANGUAGE KindSignatures #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+-- {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS -Wall -Wredundant-constraints #-}++module Extra.Generics.Show+ ( GShow0, gprecShows, gshowsPrec+ , GShow1, gLiftPrecShows, gLiftShowsPrec+ , GShow, gshow, gshows+ , myshow+ ) where++--import Debug.Trace+import Data.Foldable (foldl')+import Data.Functor.Classes (Show1(..))+import Data.Functor.Identity (Identity(Identity))+import Data.Proxy (Proxy(Proxy))+import Generic.Data (gconIndex)+import GHC.Generics as G+import GHC.TypeLits+import Text.Show.Combinators (PrecShowS, {-ShowFields,-} noFields, showField, showListWith, showInfix, showApp, showCon, showRecord)+import qualified Text.Show.Combinators as Show (appendFields)++-- For primitive instances+-- import Data.Typeable (Typeable, typeOf)++-- For tests+#if !__GHCJS__+import Test.HUnit+import Language.Haskell.TH (location)+import Language.Haskell.TH.Lift (lift)+import Language.Haskell.TH.Instances ()+#endif++-- for debug+-- import Debug.Trace+--import GHC.TypeLits++-- The K1 instance had constraint Show a, which means that every field+-- traversed must have a Show instance. I removed this constraint,+-- and then also had to remove it from doK1. Now I needed doK1 to Use+-- the Show instance for a set of base types and otherwise use the+-- Generic instance. This can be done by creating a new class DoK1+-- and using overlapping instances.++type TypeTuple = (String, String, String, Bool)+type ConstrTuple = (Int, String, Fixity)++-- Constraints for doing recusion into subtypes+type DoRep a = (Generic a, DoM1 Proxy (Rep a))+type DoRep1 f = (Generic1 f, DoM1 Identity (Rep1 f))++class DoS1 p f where doS1 :: forall a. p (Rd a) -> TypeTuple -> ConstrTuple -> f a -> S1Result+instance {-# OVERLAPPABLE #-} DoRep a => DoS1 p (K1 R a) where doS1 p ti ci (K1 a) = doRecursion p ti ci a+instance DoRep1 f => DoS1 Identity (Rec1 f) where doS1 (Identity st) ti ci (Rec1 r) = doRec1 st ti ci r+instance DoS1 Identity Par1 where doS1 (Identity st) ti ci (Par1 a) = doPar1 st ti ci a+instance (Show1 f, DoS1 p g) => DoS1 p (f :.: g) where doS1 p ti ci (Comp1 c) = doComp1 p ti ci c++class DoFields p f where doFields :: forall a. p (Rd a) -> TypeTuple -> ConstrTuple -> f a -> [S1Result]+instance (DoFields p f, DoFields p g) => DoFields p (f :*: g) where doFields p ti ci (x :*: y) = doFields p ti ci x <> doFields p ti ci y+instance (DoS1 p f, Selector s) => DoFields p (M1 S s f) where doFields p ti ci (M1 x) = [doS1 p ti ci x]+instance DoFields p U1 where doFields _ _ _ U1 = []++class DoNamed p f where doNamed :: forall a. p (Rd a) -> TypeTuple -> ConstrTuple -> f a -> [(String, S1Result)]+instance (DoNamed p f, DoNamed p g) => DoNamed p (f :*: g) where doNamed p ti ci (x :*: y) = doNamed p ti ci x <> doNamed p ti ci y+instance (Selector c, DoS1 p f) => DoNamed p (M1 S c f) where doNamed p ti ci x'@(M1 x) = [(G.selName x', doS1 p ti ci x)]+instance DoNamed p U1 where doNamed _ _ _ U1 = []++-- Its tempting to add the Constructor constraint here but it leads to+-- a missing constraint on an unexported GHC.Generics class.+class DoConstructor p c f where doConstructor :: forall a. p (Rd a) -> TypeTuple -> ConstrTuple -> M1 C c f a -> C1Result+instance (DoFields p f, KnownSymbol s) => DoConstructor p ('MetaCons s y 'False) f+ where doConstructor p ti ci (M1 x) = doNormal ti ci (zip [0..] (doFields p ti ci x))+instance DoNamed p f => DoConstructor p ('MetaCons s y 'True) f where doConstructor p ti ci (M1 x) = doRecord ti ci (zip [0..] (doNamed p ti ci x))++class DoDatatype p d f where doDatatype :: forall a. p (Rd a) -> TypeTuple -> M1 D d f a -> D1Result+instance (DoD1 p f, Datatype d) => DoDatatype p d f where doDatatype p ti (M1 x) = doD1 p ti x++class DoD1 p f where doD1 :: forall a. p (Rd a) -> TypeTuple -> f a -> D1Result+instance (DoDatatype p d f, Datatype d) => DoD1 p (M1 D d f) where doD1 p ti x@(M1 _) = doDatatype p ti x+instance (DoD1 p f, DoD1 p g) => DoD1 p (f :+: g) where doD1 p ti (L1 x) = doD1 p ti x+ doD1 p ti (R1 y) = doD1 p ti y+instance (Constructor c, DoConstructor p c f) => DoD1 p (M1 C c f) where doD1 p ti x = doConstructor p ti (gconIndex x, G.conName x, G.conFixity x) x+instance DoD1 p V1 where doD1 _ _ v = case v of {}++class DoM1 p f where doM1 :: forall a. p (Rd a) -> f a -> D1Result+instance (DoDatatype p d f, Datatype d) => DoM1 p (M1 D d f) where doM1 p x@(M1 _) = let ti = (G.datatypeName x, G.moduleName x, G.packageName x, G.isNewtype x) in doD1 p ti x++-- customization for generic Show --++-- Instances for primitive types+instance DoS1 p (K1 R Int) where doS1 p ti ci (K1 a) = doLeaf p ti ci a+instance DoS1 p (K1 R Char) where doS1 p ti ci (K1 a) = doLeaf p ti ci a+instance {-# OVERLAPPING #-} DoS1 p (K1 R String) where doS1 p ti ci (K1 a) = doLeaf p ti ci a -- overlaps [a]+instance DoS1 Proxy (K1 R a) =>+ DoS1 p (K1 R [a]) where doS1 p ti ci (K1 xs) = doList p ti ci (fmap myshows xs)+instance DoS1 p (K1 R ()) where doS1 p ti ci (K1 ()) = doTuple p ti ci []+instance (DoS1 Proxy (K1 R a),+ DoS1 Proxy (K1 R b)) => DoS1 p (K1 R (a, b))+ where doS1 p ti ci (K1 (a, b)) = doTuple p ti ci [myshows a, myshows b]+instance (DoS1 Proxy (K1 R a),+ DoS1 Proxy (K1 R b),+ DoS1 Proxy (K1 R c)) => DoS1 p (K1 R (a, b, c))+ where doS1 p ti ci (K1 (a, b, c)) = doTuple p ti ci [myshows a, myshows b, myshows c]+instance (DoS1 Proxy (K1 R a),+ DoS1 Proxy (K1 R b),+ DoS1 Proxy (K1 R c),+ DoS1 Proxy (K1 R d)) => DoS1 p (K1 R (a, b, c, d))+ where doS1 p ti ci (K1 (a, b, c, d)) = doTuple p ti ci [myshows a, myshows b, myshows c, myshows d]+instance (DoS1 Proxy (K1 R a),+ DoS1 Proxy (K1 R b),+ DoS1 Proxy (K1 R c),+ DoS1 Proxy (K1 R d),+ DoS1 Proxy (K1 R e)) => DoS1 p (K1 R (a, b, c, d, e))+ where doS1 p ti ci (K1 (a, b, c, d, e)) = doTuple p ti ci [myshows a, myshows b, myshows c, myshows d, myshows e]++type Rd a = (Int -> a -> ShowS, [a] -> ShowS) -- Like a Reader monad+type S1Result = PrecShowS -- The result of a single field of a constructor+type C1Result = PrecShowS -- Result of processing one constructors+type D1Result = PrecShowS -- Result of processing a type value++doLeaf :: Show a => p -> TypeTuple -> ConstrTuple -> a -> S1Result+doLeaf _p _ti _ci a _prec = shows a++doList :: p -> TypeTuple -> ConstrTuple -> [ShowS] -> S1Result+doList _ _ _ [] = \_ -> showChar '[' . showChar ']'+doList _ _ _ xs = \_ -> showChar '[' . foldl1 (\a b -> a . showString "," . b) xs . showChar ']'++doTuple :: p -> TypeTuple -> ConstrTuple -> [ShowS] -> S1Result+doTuple _ _ _ [] = \_ -> showString "()"+doTuple _ _ _ ks = \_ -> showChar '(' . foldl1 (\a b -> a . showString "," . b) ks . showChar ')'++doUnboxed :: Show a => p -> TypeTuple -> ConstrTuple -> a -> S1Result+doUnboxed _p _ti _ci a _prec s = show a <> s++doRecursion :: (Generic a, DoM1 Proxy (Rep a)) => p -> TypeTuple -> ConstrTuple -> a -> S1Result+doRecursion _p _ti _ci a = flip gshowsPrec a++-- Rec1 marks a field (S1) which is itself a record+doRec1 :: (Generic1 f, DoM1 Identity (Rep1 f)) => forall a. Rd a -> TypeTuple -> ConstrTuple -> f a -> S1Result+doRec1 sp _ti _ci r = flip (uncurry gLiftShowsPrec sp) r++-- Par1 marks a field (S1) which is just a type parameter+doPar1 :: Rd a -> TypeTuple -> ConstrTuple -> a -> S1Result+doPar1 (op1', _) _ti _ci a = flip op1' a++-- Comp1 is the marks a field (S1) of type (:.:), composition+doComp1 :: (Show1 f, DoS1 p g) => p (Rd a) -> TypeTuple -> ConstrTuple -> f (g a) -> S1Result+doComp1 p ti ci c = flip (liftShowsPrec (flip (doS1 p ti ci)) (showListWith (flip (doS1 p ti ci) 0))) c++-- Handle the unnamed fields of a constructor (C1)+doNormal :: TypeTuple -> ConstrTuple -> [(Int, S1Result)] -> C1Result+doNormal _ (_, name, Infix _ fy) (k1 : k2 : ks) = foldl' showApp (showInfix name fy (snd k1) (snd k2)) (fmap snd ks)+doNormal _ (_, name, Infix _ _) ks = foldl' showApp (showCon ("(" ++ name ++ ")")) (fmap snd ks)+doNormal _ (_, name, Prefix) ks = foldl' showApp (showCon name ) (fmap snd ks)++-- Handle the named fields of a constructor (C1)+doRecord :: TypeTuple -> ConstrTuple -> [(Int, (String, S1Result))] -> C1Result+doRecord _ (_, cname, _) [] = showRecord cname noFields+-- Do not render the Top type, it is private to this module and is+-- only used to get the recursion to work.+doRecord ("Top", "Extra.Generics.Show", _, _) _ [(_, (_, r))] = r+doRecord _ (_, cname, Prefix) ks = showRecord cname (foldl1 Show.appendFields (fmap (uncurry showField) (fmap snd ks)))+doRecord _ (_, cname, Infix _ _) ks = showRecord ("(" ++ cname ++ ")") (foldl1 Show.appendFields (fmap (uncurry showField) (fmap snd ks)))++---------------------------------------------------++-- | Generic representation of 'Show' types.+type GShow0 = DoM1 Proxy++newtype Top a = Top {unTop :: a} deriving Generic+infixr 0 `Top`++-- | Generic 'showsPrec'.+--+-- @+-- instance 'Show' MyType where+-- 'showsPrec' = 'gshowsPrec'+-- @+gprecShows :: forall a. GShow a => a -> PrecShowS+gprecShows = doM1 (Proxy :: Proxy (Rd a)) . from++gshowsPrec :: GShow a => Int -> a -> ShowS+gshowsPrec = flip gprecShows++-- | Generic representation of 'Data.Functor.Classes.Show1' types.+-- class Show1 (f :: * -> *) where+-- liftShowsPrec :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> Int -> f a -> ShowS+-- liftShowsList :: (Int -> a -> ShowS) -> ([a] -> ShowS) -> [f a] -> ShowS+type GShow1 = DoM1 Identity++gLiftPrecShows ::+ (GShow1 f)+ => (Int -> a -> ShowS)+ -> ([a] -> ShowS)+ -> f a -> PrecShowS+gLiftPrecShows = curry (doM1 . Identity)++-- | Generic 'liftShowsPrec'.+gLiftShowsPrec ::+ (Generic1 f, GShow1 (Rep1 f))+ => (Int -> a -> ShowS)+ -> ([a] -> ShowS)+ -> Int+ -> f a -> ShowS+gLiftShowsPrec op1' op2' =+ flip (gLiftPrecShows op1' op2' . from1)++type GShow a = (Generic a, GShow0 (Rep a))++gshow :: GShow a => a -> String+gshow x = gshows x ""+gshows :: GShow a => a -> ShowS+gshows = gshowsPrec 0++#if 0+class MyShow a where+ myshows :: a -> ShowS+ default myshows :: (Generic a, GShow (Rep a)) => a -> ShowS+ myshows a = gshows (Top a)+ myshow :: a -> String+ default myshow :: (Generic a, GShow (Rep a)) => a -> String+ myshow a = gshow+#else+myshow :: (DoS1 Proxy (K1 R a)) => a -> String+myshow a = gshow (Top a)++myshows :: (DoS1 Proxy (K1 R a)) => a -> ShowS+myshows a = gshows (Top a)+#endif++#if !__GHCJS__+data Foo = Foo {n :: Int, ch :: Char} deriving (Generic, Show)+data Bar = Bar [Foo] String deriving (Generic, Show)+data Rose a = Fork a [Rose a] deriving (Generic, Show)+data Tree a = Leaf a | Node (Tree a) (Tree a) deriving (Generic, Show)+data WithInt a = WithInt Int a deriving (Generic, Show)++_tests :: IO ()+_tests = do+ r@Counts{..} <-+ runTestTT+ (TestList+ [ TestCase (assertEqual "T1" "Foo {n = 1, ch = 'x'}" (myshow (Foo 1 'x')))+ , TestCase (assertEqual "T2" "Bar [Foo {n = 1, ch = 'x'}] \"hello\"" (myshow (Bar [Foo 1 'x'] "hello")))+ , TestCase (assertEqual "T3" "Fork 'a' [Fork 'b' []]" (myshow (Fork 'a' [Fork 'b' []])))+ , TestCase (assertEqual "T4" "Node (Leaf 'a') (Node (Leaf 'b') (Leaf 'c'))" (myshow (Node (Leaf 'a') (Node (Leaf 'b') (Leaf 'c')))))+ , TestCase (assertEqual "T5" "WithInt 5 (WithInt 2 'a')" (myshow (WithInt 5 (WithInt 2 'a'))))+ , let (expected, loc) = $(lift =<< (\x -> (show x, x)) <$> location) in+ TestCase (assertEqual "T6" expected (myshow loc))+ , TestCase (assertEqual "T7" "[1,2,3]" (myshow ([1,2,3] :: [Int])))+ , TestCase (assertEqual "T8" "[1]" (myshow ([1] :: [Int])))+ , TestCase (assertEqual "T9" "1" (myshow (1 :: Int)))+ , TestCase (assertEqual "T10" "('x',2,\"abc\")" (myshow ('x',(2 :: Int),("abc" :: String))))+ ])+ case (errors, failures) of+ (0, 0) -> return ()+ _ -> error (show r)+#endif
Extra/HughesPJ.hs view
@@ -2,7 +2,7 @@ import Data.Maybe import Extra.Terminal-import Text.PrettyPrint.HughesPJ+import Text.PrettyPrint -- |render a Doc using the current terminal width renderWidth :: Doc -> IO String
Extra/IO.hs view
@@ -1,20 +1,113 @@-module Extra.IO {-# DEPRECATED "CIO is deprecated" #-} where+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS -Wall #-} -import Extra.CIO-import qualified System.IO as IO-import Control.Exception (try)+module Extra.IO+ ( -- * IO functions to write files and notice when they change.+ testAndWriteDotNew+ , testAndWriteBackup+ , testAndWriteFile+ , testAndWrite+ , writeFileWithBackup+ , findHaskellFiles+ , timeComputation+ ) where --- |Use this module to call functions in the CIO module from the--- regular IO monad. This instance ignores all style and state--- information. The verbosity controlled output functions will ignore--- any calls when v is greater than zero. This allows you to call the--- functions in the haskell-debian package from the regular IO monad.--- --- This is in a separate module from CIO so you don't accidentally do--- a liftIO of some other CIO operation and get this instance.-instance CIO IO where- hPutStr h s = IO.hPutStr h s- hBOL h = IO.hPutStr h "\n"- ev v = return (- v)- setStyle _ f = f- tryCIO = try+import Control.Exception as E (IOException, throw, try)+import Control.Monad (when)+import Control.Monad.Trans (liftIO, MonadIO)+import Data.Monoid ((<>))+import Data.Text as Text (length, take, Text)+import Data.Text.IO as Text (readFile, writeFile)+import Data.Time (getCurrentTime, diffUTCTime, getCurrentTime, NominalDiffTime)+import Extra.Log (alog)+import Extra.Text (diffText)+import System.Directory (getCurrentDirectory, removeFile, renameFile)+import System.FilePath.Find as Find+ ((==?), (&&?), always, extension, fileType, FileType(RegularFile), find)+import System.IO.Error (isDoesNotExistError)+import System.Log.Logger ({-logM,-} Priority(DEBUG, ERROR))++testAndWriteDotNew :: FilePath -> Text -> IO ()+testAndWriteDotNew dest new = testAndWrite writeDotNew dest new++testAndWriteBackup :: FilePath -> Text -> IO ()+testAndWriteBackup dest new = testAndWrite (\dest' _ new' -> writeFileWithBackup dest' new') dest new++testAndWriteFile :: FilePath -> Text -> IO ()+testAndWriteFile = testAndWriteDotNew+{-# DEPRECATED testAndWriteFile "Use testAndWriteDotNew" #-}++-- | See if the new Paths code matches the old, if not write it to a+-- file with the suffix ".new" and throw an error so the new code can+-- be inspected and checked in. If the new file does match, the+-- existing .new file is removed.+testAndWrite :: (FilePath -> Text -> Text -> IO ()) -> FilePath -> Text -> IO ()+testAndWrite changeAction dest new = do+ here <- getCurrentDirectory+ alog "Extra.IO" DEBUG ("testAndWriteFile " <> show dest <> " " <> show (shorten 50 new) <> " (cwd=" <> show here <> ")")+ removeFileMaybe (dest <> ".new")+ try (Text.readFile dest >>= \old ->+ when (old /= new) (changeAction dest old new)) >>=+ either (\(e :: IOException) ->+ case isDoesNotExistError e of+ True -> do+ alog "Extra.IO" DEBUG "testAndWriteFile - no existing version"+ Text.writeFile dest new+ False -> do+ alog "Extra.IO" ERROR ("testAndWriteFile " <> show dest <> " - IOException " ++ show e)+ throw e)+ return++-- | Shorten a string to a maximum length by replacing its suffix with "..."+shorten :: Int -> Text -> Text+shorten n t | n <= 3 = Text.take n t -- no room for an ellipsis+shorten n t | Text.length t > n - 3 = Text.take (n - 3) t <> "..."+shorten _ t = t++-- | If the new file does not match the old, write it to file.new and error.+writeDotNew :: FilePath -> Text -> Text -> IO ()+writeDotNew dest old new = do+ alog "Extra.IO" DEBUG ("testAndWriteFile - mismatch, writing " <> show (dest <> ".new"))+ Text.writeFile (dest <> ".new") new+ error ("Generated " <> dest <> ".new does not match existing " <> dest <> ":\n" <>+ diffText (dest, old) (dest <> ".new", new) <>+ "\nIf these changes look reasonable move " <> dest <> ".new to " <> dest <> " and retry.")+++-- | Rename existing file with suffix "~" and write a new file+writeFileWithBackup :: FilePath -> Text -> IO ()+writeFileWithBackup dest text = do+ removeFileMaybe (dest <> "~")+ renameFileMaybe dest (dest <> "~")+ removeFileMaybe dest+ Text.writeFile dest text++-- | Remove a file if it exists+removeFileMaybe :: FilePath -> IO ()+removeFileMaybe p =+ try (removeFile p) >>=+ either (\(e :: IOException) -> case isDoesNotExistError e of+ True -> pure ()+ False -> throw e) pure++-- | Rename a file if it exists+renameFileMaybe :: FilePath -> FilePath -> IO ()+renameFileMaybe oldpath newpath =+ try (renameFile oldpath newpath) >>=+ either (\(e :: IOException) -> case isDoesNotExistError e of+ True -> pure ()+ False -> throw e) pure++-- | Find all regular files with extension .hs+findHaskellFiles :: FilePath -> IO [FilePath]+findHaskellFiles dir = find always (Find.extension ==? ".hs" &&? fileType ==? RegularFile) dir++-- | Perform an IO operation and return the elapsed time along with the result.+timeComputation :: MonadIO m => m r -> m (r, NominalDiffTime)+timeComputation a = do+ !start <- liftIO getCurrentTime+ !r <- a+ !end <- liftIO getCurrentTime+ return (r, diffUTCTime end start)
Extra/IOThread.hs view
@@ -21,9 +21,9 @@ tid <- forkIO $ ioThread f c return (tid, IOThread c) where- ioThread f c =+ ioThread f' c = forever $ do (a, mvar) <- readChan c- b <- try $ f a+ b <- try $ f' a putMVar mvar b -- |issue a request to the IO thread and get back the result
Extra/List.hs view
@@ -43,13 +43,13 @@ cartesianProduct [xs] = map (: []) xs cartesianProduct (xs : yss) = distribute xs (cartesianProduct yss)- where distribute xs yss = concat (map (\ x -> map (x :) yss) xs)+ where distribute xs' yss' = concat (map (\ x -> map (x :) yss') xs') -- |FIXME: implement for a string wordsBy :: Eq a => (a -> Bool) -> [a] -> [[a]] wordsBy p s = case (break p s) of- (s, []) -> [s]+ (s', []) -> [s'] (h, t) -> h : wordsBy p (drop 1 t) -- |Like maybe, but with empty vs. non-empty list@@ -61,18 +61,18 @@ -- over f. This is like "sortBy (\ a b -> compare (f a) (f b))" -- except that f is applied O(n) times instead of O(n log n) sortByMapped :: (a -> b) -> (b -> b -> Ordering) -> [a] -> [a]-sortByMapped f compare list =+sortByMapped f cmp list = map fst sorted where- sorted = sortBy (\ (_, x) (_, y) -> compare x y) pairs+ sorted = sortBy (\ (_, x) (_, y) -> cmp x y) pairs pairs = zip list (map f list) -- |Monadic version of sortByMapped sortByMappedM :: (a -> IO b) -> (b -> b -> Ordering) -> [a] -> IO [a]-sortByMappedM f compare list =+sortByMappedM f cmp list = do pairs <- mapM f list >>= return . (zip list)- let sorted = sortBy (\ (_, x) (_, y) -> compare x y) pairs+ let sorted = sortBy (\ (_, x) (_, y) -> cmp x y) pairs return (map fst sorted) partitionM :: (Monad m) => (a -> m Bool) -> [a] -> m ([a], [a])
Extra/Lock.hs view
@@ -6,10 +6,9 @@ import Control.Exception import Control.Monad.RWS-import Prelude hiding (catch) import System.Directory import System.IO-import System.IO.Error hiding (try, catch)+import System.IO.Error import System.Posix.Files import System.Posix.IO import System.Posix.Unistd@@ -48,9 +47,10 @@ -- |Like withLock, but instead of giving up immediately, try n times -- with a wait between each. --awaitLock :: (MonadIO m) => Int -> Int -> FilePath -> m a -> m (Either Exception a)+awaitLock :: (Ord a, Num a) => a -> Int -> [Char] -> IO a -> IO a awaitLock tries usecs path task = attempt 0- where + where attempt n | n >= tries = error "awaitLock: too many failures" attempt n = withLock path task `catch` checkLockError where@@ -60,4 +60,5 @@ processID :: IO String processID = readSymbolicLink "/proc/self" +lockedBy :: String -> FilePath -> IOError lockedBy pid path = mkIOError alreadyInUseErrorType ("Locked by " ++ pid) Nothing (Just path)
+ Extra/Log.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS -Wall #-}++module Extra.Log+ ( alog+ , Priority(..)+ , logException+ ) where++import Control.Monad.Except (MonadError(catchError, throwError))+import Control.Monad.Trans (liftIO, MonadIO)+import Data.Time (getCurrentTime)+import Data.Time.Format (FormatTime(..), formatTime, defaultTimeLocale)+import Language.Haskell.TH (ExpQ, Exp, Loc(..), location, pprint, Q)+import Language.Haskell.TH.Instances ()+import qualified Language.Haskell.TH.Lift as TH (Lift(lift))+import System.Log.Logger (Priority(..), logM)++alog :: MonadIO m => String -> Priority -> String -> m ()+alog modul priority msg = liftIO $ do+ time <- getCurrentTime+ logM modul priority $ unwords [formatTimeCombined time, msg]++-- | Format the time as describe in the Apache combined log format.+-- http://httpd.apache.org/docs/2.2/logs.html#combined+--+-- The format is:+-- [day/month/year:hour:minute:second zone]+-- day = 2*digit+-- month = 3*letter+-- year = 4*digit+-- hour = 2*digit+-- minute = 2*digit+-- second = 2*digit+-- zone = (`+' | `-') 4*digit+--+-- (Copied from happstack-server)+formatTimeCombined :: FormatTime t => t -> String+formatTimeCombined = formatTime defaultTimeLocale "%d/%b/%Y:%H:%M:%S %z"++-- | Create an expression of type (MonadIO m => Priority -> m a -> m+-- a) that we can apply to an expression so that it catches, logs, and+-- rethrows any exception.+logException :: ExpQ+logException =+ [| \priority action ->+ action `catchError` (\e -> do+ liftIO (logM (loc_module $__LOC__)+ priority+ ("Logging exception: " <> (pprint $__LOC__) <> " -> " ++ show e))+ throwError e) |]++__LOC__ :: Q Exp+__LOC__ = TH.lift =<< location
Extra/Misc.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE CPP #-}+ module Extra.Misc ( -- * String functions@@ -7,40 +9,38 @@ , mapSnd -- * FilePath functions , parentPath- , canon+ -- , canon -- * Map and Set functions , listMap , listDiff -- * System.Posix- , checkSuperUser , md5sum- , sameInode , sameMd5sum- , tarDir- -- * Processes- -- , splitOutput- -- * ByteString- , cd- -- * Debugging , read'+#if !__GHCJS__+ , sameInode+ , cd+ , checkSuperUser+ , tarDir+#endif ) where -import Control.Exception import qualified Data.ByteString.Lazy.Char8 as B import qualified Data.Digest.Pure.MD5-import Data.List+import Data.List import qualified Data.Map as Map-import Data.Maybe+import Data.Maybe import qualified Data.Set as Set-import Extra.List+import System.FilePath+#if !__GHCJS__+import Control.Exception+import Extra.List (wordsBy)+import System.Directory import System.Exit-import System.FilePath-import System.Directory-import System.Posix.Files-import System.Posix.User (getEffectiveUserID)+import System.Posix.Files+import System.Posix.User (getEffectiveUserID) import System.Process (readProcessWithExitCode)--- import System.Process.Progress (keepStdout, keepStderr, keepResult)-import Text.Regex+#endif mapSnd :: (b -> c) -> (a, b) -> (a, c) mapSnd f (a, b) = (a, f b)@@ -62,10 +62,10 @@ justify s n = foldr doWord [[]] (words s) where doWord w [] = [[w]]- doWord w (ws : etc) = - if length (concat (intersperse " " (w:ws))) <= n then- (w : ws) : etc else- [w] : ws : etc+ doWord w (ws : etc) =+ if length (concat (intersperse " " (w:ws))) <= n then+ (w : ws) : etc else+ [w] : ws : etc -- |dirname parentPath :: FilePath -> FilePath@@ -82,6 +82,7 @@ listDiff :: Ord a => [a] -> [a] -> [a] listDiff a b = Set.toList (Set.difference (Set.fromList a) (Set.fromList b)) +#if 0 -- | Weak attempt at canonicalizing a file path. canon :: FilePath -> FilePath canon path =@@ -95,12 +96,14 @@ merge (x : "." : xs) = (merge (x : xs)) merge (x : xs) = x : merge xs merge [] = []+#endif {-# DEPRECATED md5sum "Use Data.ByteString.Lazy.Char8.readFile path >>= return . show . Data.Digest.Pure.MD5.md5" #-} -- | Run md5sum on a file and return the resulting checksum as text. md5sum :: FilePath -> IO String md5sum path = B.readFile path >>= return . show . Data.Digest.Pure.MD5.md5 +#if !__GHCJS__ -- | Predicate to decide if two files have the same inode. sameInode :: FilePath -> FilePath -> IO Bool sameInode a b =@@ -108,6 +111,7 @@ aStatus <- getFileStatus a bStatus <- getFileStatus b return (deviceID aStatus == deviceID bStatus && fileID aStatus == fileID bStatus)+#endif -- | Predicate to decide if two files have the same md5 checksum. sameMd5sum :: FilePath -> FilePath -> IO Bool@@ -123,11 +127,13 @@ -} -- |A version of read with a more helpful error message.+read' :: Read p => String -> p read' s = case reads s of [] -> error $ "read - no parse: " ++ show s ((x, _s) : _) -> x +#if !__GHCJS__ checkSuperUser :: IO Bool checkSuperUser = getEffectiveUserID >>= return . (== 0) @@ -146,10 +152,11 @@ (s : _) -> Just s cd :: FilePath -> IO a -> IO a-cd name m = - bracket +cd name m =+ bracket (do cwd <- getCurrentDirectory setCurrentDirectory name return cwd) (\oldwd -> do setCurrentDirectory oldwd) (const m)+#endif
+ Extra/Monad/Supply.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}++-- | Support for computations which consume values from a (possibly infinite)+-- supply. See <http://www.haskell.org/haskellwiki/New_monads/MonadSupply> for+-- details.+module Extra.Monad.Supply+( MonadSupply (..)+, SupplyT+, Supply+, evalSupplyT+, evalSupply+, runSupplyT+, runSupply+, supplies+) where++import Control.Monad.Fix+import Control.Monad.Identity+import Control.Monad.RWS+import Control.Monad.State+import Control.Monad.Except+import Control.Monad.Reader+import Control.Monad.Writer+import Control.Monad.Morph (MFunctor(..))+import qualified Data.Semigroup as Sem++class Monad m => MonadSupply s m | m -> s where+ supply :: m s+ peek :: m s+ exhausted :: m Bool++-- | Supply monad transformer.+newtype SupplyT s m a = SupplyT (StateT [s] m a)+ deriving (Functor, Applicative, Monad, MonadTrans, MonadIO, MonadFix)++instance MonadError e m => MonadError e (SupplyT s m) where+ throwError = SupplyT . lift . throwError+ catchError (SupplyT action) handler = SupplyT (action `catchError` (\e -> (\(SupplyT m) -> m) $ handler e))++-- | Supply monad.+newtype Supply s a = Supply (SupplyT s Identity a)+ deriving (Functor, Applicative, Monad, MonadSupply s)+++instance Monad m => MonadSupply s (SupplyT s m) where+ supply = SupplyT $ do ~(x:xs) <- get+ put xs+ return x+ peek = SupplyT $ gets head+ exhausted = SupplyT $ gets null++-- Monad transformer instances+instance (MonadSupply s m) => MonadSupply s (ExceptT e m) where+ supply = lift supply+ peek = lift peek+ exhausted = lift exhausted++instance MonadSupply s m => MonadSupply s (StateT st m) where+ supply = lift supply+ peek = lift peek+ exhausted = lift exhausted++instance MonadSupply s m => MonadSupply s (ReaderT r m) where+ supply = lift supply+ peek = lift peek+ exhausted = lift exhausted++instance (Monoid w, MonadSupply s m) => MonadSupply s (WriterT w m) where+ supply = lift supply+ peek = lift peek+ exhausted = lift exhausted++instance (MonadSupply s m, Monoid w) => MonadSupply s (RWST r w st m) where+ supply = lift supply+ peek = lift peek+ exhausted = lift exhausted++instance MFunctor (SupplyT a) where+ hoist f (SupplyT s) = SupplyT (hoist f s)++-- | Monoid instance for the supply monad. Actually any monad/monoid pair+-- gives rise to this monoid instance, but we can't write its type like that+-- because it would conflict with existing instances provided by Data.Monoid.+--instance (Monoid a, Monad m) => Monoid (m a) where+instance Sem.Semigroup a => Sem.Semigroup (Supply s a) where+ m1 <> m2 = do+ x1 <- m1+ x2 <- m2+ return (x1 Sem.<> x2)++instance (Monoid a) => Monoid (Supply s a) where+ mempty = return mempty+ mappend = (<>)++-- | Get n supplies.+supplies :: MonadSupply s m => Int -> m [s]+supplies n = replicateM n supply++evalSupplyT :: Monad m => SupplyT s m a -> [s] -> m a+evalSupplyT (SupplyT s) = evalStateT s++evalSupply :: Supply s a -> [s] -> a+evalSupply (Supply s) = runIdentity . evalSupplyT s++runSupplyT :: SupplyT s m a -> [s] -> m (a,[s])+runSupplyT (SupplyT s) = runStateT s++runSupply :: Supply s a -> [s] -> (a,[s])+runSupply (Supply s) = runIdentity . runSupplyT s
Extra/Net.hs view
@@ -1,7 +1,8 @@-module Extra.Net- ( webServerDirectoryContents- ) where+{-# LANGUAGE CPP #-} +module Extra.Net where++#if 0 import qualified Data.ByteString.Lazy.Char8 as L import Data.Maybe import Text.Regex@@ -18,3 +19,4 @@ re = mkRegex "( <A HREF|<a href)=\"([^/][^\"]*)/\"" second (Just [_, b]) = Just b second _ = Nothing+#endif
+ Extra/Orphans.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS -Wno-orphans #-}++module Extra.Orphans where++import Data.Graph.Inductive as G+import Data.List (intercalate)+import Data.Proxy (Proxy(Proxy))+import Data.SafeCopy (base, contain,+ SafeCopy(errorTypeName, getCopy, kind, putCopy, version))+import Data.Serialize.Get (label)+import Data.Text as T hiding (concat, intercalate)+import Data.Text.Lazy as LT hiding (concat, intercalate)+import Data.Time (UTCTime(..), Day(ModifiedJulianDay), TimeOfDay(..), timeOfDayToTime, DiffTime)+import Data.UserId (UserId(..))+import Data.UUID.Orphans ()+import Data.UUID (UUID)+import Data.UUID.V4 as UUID (nextRandom)+import Data.UUID.Orphans ({-instance SafeCopy UUID-})+import Extra.Orphans2 ()+import GHC.Generics (Generic)+import Instances.TH.Lift ()+import Language.Haskell.TH (Ppr(ppr))+import Language.Haskell.TH.Lift -- (deriveLift, deriveLiftMany)+import Language.Haskell.TH.PprLib (ptext)+import Network.URI (URI(..), URIAuth(..), uriToString)+import System.IO.Unsafe (unsafePerformIO)+#if !__GHCJS__+import Test.QuickCheck (Arbitrary(arbitrary), choose, elements, Gen, listOf, listOf1, resize)+#endif++instance SafeCopy (Proxy t) where+ putCopy Proxy = contain (do { return () })+ getCopy = contain (label "Data.Proxy.Proxy:" (pure Proxy))+ version = 0+ kind = base+ errorTypeName _ = "Data.Proxy.Proxy"++#if 1+deriving instance Generic Day+deriving instance Generic UTCTime+#else+#endif++deriving instance Generic URIAuth++-- $(deriveLift ''UserId)+instance Lift UserId where lift (UserId x0) = [|UserId $(lift x0)|]++$(deriveLift ''G.Gr)+$(deriveLift ''G.NodeMap)++instance Ppr UserId where ppr = ptext . show++#if !__GHCJS__+instance Arbitrary T.Text where+ arbitrary = T.pack <$> arbitrary++instance Arbitrary LT.Text where+ arbitrary = LT.pack <$> arbitrary++instance Arbitrary UUID where+ arbitrary = pure (unsafePerformIO UUID.nextRandom)++instance Arbitrary UserId where+ arbitrary = UserId <$> choose (0, 20)++instance Arbitrary UTCTime where arbitrary = UTCTime <$> arbitrary <*> arbitrary+instance Arbitrary Day where arbitrary = ModifiedJulianDay <$> arbitrary+instance Arbitrary DiffTime where arbitrary = timeOfDayToTime <$> arbitrary+instance Arbitrary TimeOfDay where arbitrary = TimeOfDay <$> choose (0,23) <*> choose (0,59) <*> (fromInteger <$> choose (0,60999999999999))++-- from https://gist.github.com/roman++newtype URIPair+ = URIPair { fromPair :: (String, String) }+ deriving (Show)++genWord :: Gen String+genWord = listOf1 (choose ('a', 'z'))++genCanonicalURI :: Gen URI+genCanonicalURI =+ URI <$> elements ["http:", "https:"]+ <*> (Just <$> genURIAuthority)+ <*> (('/':) <$> genPaths)+ <*> pure ""+ <*> pure ""+ where+ genURIAuthority =+ URIAuth <$> pure ""+ <*> genRegName+ <*> pure ""+ genRegName = do+ domainName <- elements ["noomii", "google", "yahoo"]+ return $ mconcat ["www.", domainName, ".com"]+ genPaths = resize 10 (intercalate "/" <$> listOf genWord)++genNormalURI :: URI -> Gen URI+genNormalURI uri = do+ qs <- genQueryString+ fragment <- genFragment+ return $ uri { uriQuery = qs, uriFragment = fragment }+ where+ genParam = do+ name <- genWord+ value <- genWord+ return $ name ++ "=" ++ value+ genQueryString = resize 10 $+ ('?':) <$> (intercalate "&" <$> listOf genParam)+ genFragment = ('#':) <$> genWord++instance Arbitrary URIPair where+ arbitrary = do+ canonical <- genCanonicalURI+ normal <- genNormalURI canonical+ return (URIPair (uriToString id canonical "", uriToString id normal ""))++instance Arbitrary URI where+ arbitrary = genCanonicalURI >>= genNormalURI+#endif++instance SafeCopy URI where version = 0+instance SafeCopy URIAuth where version = 0++$(concat <$>+ sequence+ [ deriveLiftMany [''URI, ''URIAuth] ])
+ Extra/Orphans2.hs view
@@ -0,0 +1,107 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS -Wall -fno-warn-orphans #-}++module Extra.Orphans2 where++import Data.Generics (TyCon, TypeRep, tyConFingerprint, tyConModule, tyConName, tyConPackage, splitTyConApp)+import Data.Int (Int32)+import Data.List (intercalate)+import Data.ListLike as LL+import Data.Map as Map (Map, toList)+import Data.SafeCopy (SafeCopy(..))+import Data.Set as Set (Set, toList)+#if !__GHCJS__+import Debug.Show (V(V))+#endif+import Language.Haskell.TH+import Language.Haskell.TH.PprLib (Doc, hcat, ptext, vcat)+import Language.Haskell.TH.Syntax (ModName, NameFlavour, OccName, PkgName)+import Prelude hiding (concat, foldl1)++instance Ppr (Type, Int32) where+ ppr (t, n) = pprPair (t, n)++instance Ppr Int32 where ppr = ptext . show++instance Ppr (Name, [Type]) where+ ppr (name, params) = ppr (foldl1 AppT (ConT name : params))++pprPair :: (Ppr a, Ppr b) => (a, b) -> Doc+pprPair (a, b) = hcat [ptext "(", ppr a, ptext ",", ppr b, ptext ")"]++pprList :: [Doc] -> Doc+pprList xs = hcat [ptext "[", hcat (intersperse (ptext ",") xs), ptext "]"]++-- deriving instance Data CmdSpec++#if 0+instance Arbitrary ReportImageID where arbitrary = ReportImageID <$> arbitrary+instance Arbitrary ReportElemID where arbitrary = ReportElemID <$> arbitrary+instance (Arbitrary v, Enum k, Ord k) => Arbitrary (Order k v) where+ arbitrary = sized $ \n -> do+ vs <- vectorOf n (arbitrary :: Gen v)+ ks <- shuffle (take n [toEnum 0..] :: [k])+ fromPairs <$> shuffle (zip ks vs)+#endif++instance Ppr Char where ppr = ptext . show+instance Ppr Float where ppr = ptext . show+-- instance Ppr ReportElemID where ppr = ptext . show+-- instance Ppr ReportImageID where ppr = ptext . show+instance (Ppr k, Ppr v) => Ppr (Map k v) where ppr = pprList . fmap pprPair . Map.toList+#if 0+instance (Ppr k, Ppr v) => Ppr (Order k v) where ppr = pprList . fmap pprPair . LL.toList . toPairs+#endif+instance Ppr (Int, Char) where ppr = ptext . show++instance Ppr Bool where+ ppr True = ptext "True"+ ppr False = ptext "False"++#if !__GHCJS__+instance Show (V TyCon) where+ show (V c) =+ "TyCon {" +++ "module=" ++ show (tyConModule c) +++ " name=" ++ show (tyConName c) +++ " package=" ++ show (tyConPackage c) +++ " fingerprint=" ++ show (tyConFingerprint c) ++ "}"++instance Show (V TypeRep) where+ show (V r) =+ case splitTyConApp r of+ (c, rs) -> "[" ++ intercalate "," (show (V c) : fmap (show . V) rs) ++ "]"+#endif++instance Ppr TypeRep where+ ppr = ptext . show++instance Ppr () where+ ppr () = ptext "()"++-- | 'Int' is the 'Data.Path.Index.ContainerKey' type for all lists, so+-- we need to make sure all the required instances exist.+instance Ppr Int where+ ppr = ptext . show++instance Ppr (Set Type, Set Type) where+ ppr (extra, missing) = vcat [ptext "extra:", ppr extra, ptext "missing:", ppr missing]++instance Ppr (Set Type) where+ ppr s = hcat [ptext "Set.fromList [", ppr (Set.toList s), ptext "]"]++instance SafeCopy OccName where version = 0+instance SafeCopy NameSpace where version = 0+instance SafeCopy PkgName where version = 0+instance SafeCopy ModName where version = 0+instance SafeCopy NameFlavour where version = 0+instance SafeCopy Name where version = 0+instance SafeCopy Loc where version = 1
+ Extra/Orphans3.hs view
@@ -0,0 +1,247 @@+-- | Serialize and SafeCopy instances for the template haskell Exp type.+-- Some Arbitrary, Lift, and Ppr instances.++{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE PackageImports #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS -Wall -Wredundant-constraints -Wno-orphans #-}++module Extra.Orphans3 where++import Data.Data (Data, dataTypeOf, gunfold, mkNoRepType, toConstr, TypeRep)+import Data.Foldable+--import Data.Function.Memoize (deriveMemoizable)+import Data.Generics.Instances ()+import Data.ListLike as LL hiding (concat, sequence, toList)+-- import Data.Order (Order, toPairs)+import Data.Proxy (Proxy(Proxy))+import Data.SafeCopy (SafeCopy(..))+import Extra.Serialize (Serialize)+import Extra.Orphans ()+import Language.Haskell.TH+import Language.Haskell.TH.Instances ()+import Language.Haskell.TH.PprLib (Doc, hcat, ptext)+import Language.Haskell.TH.Syntax+import Prelude hiding (concat, foldl1)+#if !__GHCJS__+import Network.URI+import Test.QuickCheck (Arbitrary(arbitrary), elements, Gen, oneof)+#endif++pprPair :: (Ppr a, Ppr b) => (a, b) -> Doc+pprPair (a, b) = hcat [ptext "(", ppr a, ptext ",", ppr b, ptext ")"]++pprList :: [Doc] -> Doc+pprList xs = hcat [ptext "[", hcat (intersperse (ptext ",") xs), ptext "]"]++#if 0+instance (Ppr k, Ord k, Enum k, Show k, Ppr v) => Ppr (Order k v) where+ ppr = pprList . toList . fmap pprPair . toPairs+#endif++#if __GLASGOW_HASKELL__ >= 802+instance Data TypeRep where+ toConstr _ = error "toConstr"+ gunfold _ _ = error "gunfold"+ dataTypeOf _ = mkNoRepType "Data.Typeable.TypeRep"+#endif++deriving instance Serialize (Proxy a)++#if !__GHCJS__+instance Lift (Proxy a) where lift Proxy = [|Proxy|]++instance Arbitrary NameSpace where+ arbitrary = elements [VarName, DataName, TcClsName]++instance Arbitrary Type where+ arbitrary = oneof [ ForallT <$> arbitrary <*> arbitrary <*> arbitrary+ , AppT <$> arbitrary <*> arbitrary+ , SigT <$> arbitrary <*> arbitrary+ , VarT <$> arbitraryTypeVariableName+ , ConT <$> arbitrary+ , PromotedT <$> arbitrary+ , TupleT <$> arbitrary+ , UnboxedTupleT <$> arbitrary+ , pure ArrowT+ , pure EqualityT+ , pure ListT+ , PromotedTupleT <$> arbitrary+ , pure PromotedNilT+ , pure PromotedConsT+ , pure StarT+ , pure ConstraintT+ , LitT <$> arbitrary ]++instance Arbitrary (Proxy a) where arbitrary = elements [Proxy]+instance Arbitrary Name where arbitrary = pure (mkName "aName")+instance Arbitrary TyLit where arbitrary = oneof [NumTyLit <$> arbitrary, StrTyLit <$> arbitraryConstructorName]+instance Arbitrary TyVarBndr where arbitrary = oneof [PlainTV <$> arbitraryTypeVariableName, KindedTV <$> arbitraryTypeVariableName <*> arbitraryKind]++instance Arbitrary URIAuth where+ arbitrary =+ URIAuth <$> pure ""+ <*> genRegName+ <*> pure ""+ where+ genRegName = do+ domainName <- elements ["noomii", "google", "yahoo"]+ return $ concat ["www.", domainName, ".com"]++arbitraryKind :: Gen Kind+arbitraryKind = oneof [pure StarT {-, finish me -}]++arbitraryConstructorName :: Gen String+arbitraryConstructorName = pure "AConstructor"++arbitraryTypeVariableName :: Gen Name+arbitraryTypeVariableName = pure (mkName "aTyVarName")++-- s = $(location >>= \Loc{loc_module=m, loc_start=(sl,sc), loc_end=(el,ec)} -> lift (m <> ":" <> show sl))+#endif++deriving instance Serialize AnnTarget+deriving instance Serialize Bang+deriving instance Serialize Body+deriving instance Serialize Callconv+deriving instance Serialize Clause+deriving instance Serialize Con+deriving instance Serialize Dec+deriving instance Serialize DerivClause+deriving instance Serialize DerivStrategy+deriving instance Serialize Exp+deriving instance Serialize FamilyResultSig+deriving instance Serialize Fixity+deriving instance Serialize FixityDirection+deriving instance Serialize Foreign+deriving instance Serialize FunDep+deriving instance Serialize Guard+deriving instance Serialize InjectivityAnn+deriving instance Serialize Inline+deriving instance Serialize Lit+deriving instance Serialize Match+deriving instance Serialize ModName+deriving instance Serialize Name+deriving instance Serialize NameFlavour+deriving instance Serialize NameSpace+deriving instance Serialize OccName+deriving instance Serialize Overlap+deriving instance Serialize Pat+deriving instance Serialize PatSynArgs+deriving instance Serialize PatSynDir+deriving instance Serialize Phases+deriving instance Serialize PkgName+deriving instance Serialize Pragma+deriving instance Serialize Range+deriving instance Serialize Role+deriving instance Serialize RuleBndr+deriving instance Serialize RuleMatch+deriving instance Serialize Safety+deriving instance Serialize SourceStrictness+deriving instance Serialize SourceUnpackedness+deriving instance Serialize Stmt+deriving instance Serialize TyLit+deriving instance Serialize Type+deriving instance Serialize TypeFamilyHead+deriving instance Serialize TySynEqn+deriving instance Serialize TyVarBndr++#if 0+deriving instance NFData AnnTarget+deriving instance NFData Bang+deriving instance NFData Body+deriving instance NFData Callconv+deriving instance NFData Clause+deriving instance NFData Con+deriving instance NFData Dec+deriving instance NFData DerivClause+deriving instance NFData DerivStrategy+deriving instance NFData Exp+deriving instance NFData FamilyResultSig+deriving instance NFData Fixity+deriving instance NFData FixityDirection+deriving instance NFData Foreign+deriving instance NFData FunDep+deriving instance NFData Guard+deriving instance NFData InjectivityAnn+deriving instance NFData Inline+deriving instance NFData Lit+deriving instance NFData Match+deriving instance NFData ModName+deriving instance NFData Name+deriving instance NFData NameFlavour+deriving instance NFData NameSpace+deriving instance NFData OccName+deriving instance NFData Overlap+deriving instance NFData Pat+deriving instance NFData PatSynArgs+deriving instance NFData PatSynDir+deriving instance NFData Phases+deriving instance NFData PkgName+deriving instance NFData Pragma+deriving instance NFData Range+deriving instance NFData Role+deriving instance NFData RuleBndr+deriving instance NFData RuleMatch+deriving instance NFData Safety+deriving instance NFData TH.SourceStrictness+deriving instance NFData TH.SourceUnpackedness+deriving instance NFData Stmt+deriving instance NFData TyLit+deriving instance NFData Type+deriving instance NFData TypeFamilyHead+deriving instance NFData TySynEqn+deriving instance NFData TyVarBndr+#endif++instance SafeCopy AnnTarget where version = 1+instance SafeCopy Bang where version = 1+instance SafeCopy Body where version = 1+instance SafeCopy Callconv where version = 1+instance SafeCopy Clause where version = 1+instance SafeCopy Con where version = 1+instance SafeCopy Dec where version = 1+instance SafeCopy DerivClause where version = 1+instance SafeCopy DerivStrategy where version = 1+instance SafeCopy Exp where version = 1+instance SafeCopy FamilyResultSig where version = 1+instance SafeCopy Fixity where version = 1+instance SafeCopy FixityDirection where version = 1+instance SafeCopy Foreign where version = 1+instance SafeCopy FunDep where version = 1+instance SafeCopy Guard where version = 1+instance SafeCopy InjectivityAnn where version = 1+instance SafeCopy Inline where version = 1+instance SafeCopy Lit where version = 1+instance SafeCopy Match where version = 1+-- instance SafeCopy ModName where version = 1+-- instance SafeCopy Name where version = 1+-- instance SafeCopy NameFlavour where version = 1+-- instance SafeCopy NameSpace where version = 1+-- instance SafeCopy OccName where version = 1+instance SafeCopy Overlap where version = 1+instance SafeCopy Pat where version = 1+instance SafeCopy PatSynArgs where version = 1+instance SafeCopy PatSynDir where version = 1+instance SafeCopy Phases where version = 1+-- instance SafeCopy PkgName where version = 1+instance SafeCopy Pragma where version = 1+instance SafeCopy Range where version = 1+instance SafeCopy Role where version = 1+instance SafeCopy RuleBndr where version = 1+instance SafeCopy RuleMatch where version = 1+instance SafeCopy Safety where version = 1+instance SafeCopy SourceStrictness where version = 1+instance SafeCopy SourceUnpackedness where version = 1+instance SafeCopy Stmt where version = 1+instance SafeCopy TyLit where version = 1+instance SafeCopy Type where version = 1+instance SafeCopy TypeFamilyHead where version = 1+instance SafeCopy TySynEqn where version = 1+instance SafeCopy TyVarBndr where version = 1
+ Extra/Pretty.hs view
@@ -0,0 +1,104 @@+-- | A constructor we can wrap around values to avoid any built in+-- Pretty instance - for example, instance Pretty [a].+--+-- * display is now prettyShow+-- * display' is now prettyText+-- * ppDisplay is now ppShow+-- * ppDisplay' is now ppText++{-# LANGUAGE DeriveFunctor, FlexibleContexts, FlexibleInstances, OverloadedStrings, TypeSynonymInstances #-}++module Extra.Pretty+ ( PP(PP, unPP)+ , prettyText+ , ppPrint+ , pprPair+ , pprSet+ , ppShow+ , ppText+ , pprint1, pprint1', render1+ , pprintW+ , friendlyNames+ -- * Re-export+ , prettyShow+ , renderW+ ) where++import Data.Data (Data)+import Data.Generics (everywhere, mkT)+import Data.Set as Set (Set, toList)+import Data.Text (Text, unpack, pack)+import Distribution.Pretty (Pretty(pretty), prettyShow)+import Language.Haskell.TH+import Language.Haskell.TH.PprLib as TH (Doc, hcat, hsep, ptext, to_HPJ_Doc)+import Language.Haskell.TH.Syntax+import qualified Text.PrettyPrint as HPJ+import Text.PrettyPrint.HughesPJClass as HPJ (Doc, text, empty)++-- | This type is wrapped around values before we pretty print them so+-- we can write our own Pretty instances for common types without+-- polluting the name space of clients of this package with instances+-- they don't want.+newtype PP a = PP {unPP :: a} deriving (Functor)++instance Pretty (PP Text) where+ pretty = text . unpack . unPP++instance Pretty (PP String) where+ pretty = text . unPP++instance Pretty (PP a) => Pretty (PP (Maybe a)) where+ pretty = maybe empty ppPrint . unPP++prettyText :: Pretty a => a -> Text+prettyText = pack . prettyShow++ppPrint :: Pretty (PP a) => a -> HPJ.Doc+ppPrint = pretty . PP++ppShow :: Pretty (PP a) => a -> String+ppShow = prettyShow . PP++ppText :: Pretty (PP a) => a -> Text+ppText = pack . prettyShow . PP++-- | Make a template haskell value more human reader friendly. The+-- result may or may not be compilable. That's ok, though, because+-- the input is usually uncompilable - it imports hidden modules, uses+-- infix operators in invalid positions, puts module qualifiers in+-- places where they are not allowed, and maybe other things.+friendlyNames :: Data a => a -> a+friendlyNames =+ everywhere (mkT friendlyName)+ where+ friendlyName (Name x _) = Name x NameS -- Remove all module qualifiers++-- | Render a 'Doc' on a single line.+render1 :: TH.Doc -> String+render1 = HPJ.renderStyle (HPJ.style {HPJ.mode = HPJ.OneLineMode}) . to_HPJ_Doc++-- | Render a 'Doc' with the given width.+renderW :: Int -> TH.Doc -> String+renderW w = HPJ.renderStyle (HPJ.style {HPJ.lineLength = w}) . to_HPJ_Doc++-- | Pretty print the result of 'render1'.+pprint1' :: Ppr a => a -> [Char]+pprint1' = render1 . ppr++-- | 'pprint1'' with friendlyNames.+pprint1 :: (Ppr a, Data a) => a -> [Char]+pprint1 = pprint1' . friendlyNames++-- | Pretty print the result of 'renderW'.+pprintW' :: Ppr a => Int -> a -> [Char]+pprintW' w = renderW w . ppr++-- | 'pprintW'' with friendly names.+pprintW :: (Ppr a, Data a) => Int -> a -> [Char]+pprintW w = pprintW' w . friendlyNames++pprPair :: (Ppr a, Ppr b) => (a, b) -> TH.Doc+pprPair (a, b) = hcat [ptext "(", ppr a, ptext ", ", ppr b, ptext ")"]++pprSet :: Ppr a => Set a -> TH.Doc+pprSet s = hcat [ptext "[", hsep (fmap ppr (Set.toList s)), ptext "]"]
+ Extra/Process.hs view
@@ -0,0 +1,325 @@+-- | Various redundant versions of shell command running code+-- collected here from several different places.++{-# LANGUAGE CPP, FlexibleContexts, RankNTypes, ScopedTypeVariables, TemplateHaskell, TypeFamilies #-}+{-# OPTIONS_GHC -Wall -Wredundant-constraints #-}+module Extra.Process+ ( CreateProcess+ , timeTask+ , RunOptions(..)+ -- * builders for RunOptions+ , showCommand+ , showCommandAndResult+ , putIndented+ -- * Simple process runners+ , run+ , runV, runVE+ , runQ, runQE+ , runIO+ , testExit+ , processException+ , insertProcessEnv+ , modifyProcessEnv+ , HasLoc(withLoc)+ -- * Refugees+ , runV2, runVE2, runQ2, runQE2+ , run2+ , runV3, runQE3+ ) where++import Control.Arrow (second)+import Control.Exception (evaluate, Exception, IOException, throw)+import Control.Lens (view)+import Control.Monad.Catch (catch, MonadCatch, try)+import Control.Monad.Except (throwError)+import Control.Monad.Reader (MonadReader)+import Control.Monad.State (evalState, StateT, get, put)+import Control.Monad.Trans (liftIO, MonadIO)+import qualified Data.ByteString.Lazy.Char8 as L+import Data.ListLike (break, head, hPutStr, null, singleton, tail)+import Data.Semigroup (Semigroup((<>)))+import Data.String (IsString(fromString))+import Data.Text (unpack)+import Data.Text.Encoding (decodeUtf8)+import Data.Time (diffUTCTime, getCurrentTime, NominalDiffTime)+import Data.Typeable (typeOf)+import Extra.Except -- (LyftIO(lyftIO, ErrorType), LyftIO', lyftIO', withError, withException)+import Extra.EnvPath (HasEnvRoot(envRootLens), rootPath)+import Extra.Verbosity (ePutStrLn)+import Extra.TH (here, prettyLocs)+import GHC.IO.Exception (IOErrorType(OtherError))+import Language.Haskell.TH.Syntax (Loc)+import Prelude hiding (break, head, null, tail)+import System.Environment (getEnvironment)+import System.Exit (ExitCode(..))+import System.IO (hPutStrLn, stdout, stderr)+import System.IO.Error (mkIOError)+import System.Process (CreateProcess(cwd, env))+import System.Process.ListLike (Chunk(..), collectOutput, ListLikeProcessIO, readCreateProcessLazy, readCreateProcessWithExitCode, showCreateProcessForUser)++-- | Run a task and return the elapsed time along with its result.+timeTask :: MonadIO m => m a -> m (a, NominalDiffTime)+timeTask x =+ do start <- liftIO $ getCurrentTime+ result <- x >>= liftIO . evaluate+ finish <- liftIO $ getCurrentTime+ return (result, diffUTCTime finish start)++data RunOptions a m+ = StartMessage (String -> CreateProcess -> m ())+ | OverOutput ([Chunk a] -> m [Chunk a]) -- e.g. putIndented+ | FinishMessage (String -> CreateProcess -> (ExitCode, a, a) -> m ())+ -- | Verbosity Int+ | RunOptions [RunOptions a m]++instance Semigroup (RunOptions a m) where+ RunOptions a <> RunOptions b = RunOptions (a <> b)+ RunOptions a <> b = RunOptions (a <> [b])+ a <> RunOptions b = RunOptions ([a] <> b)+ a <> b = RunOptions [a, b]++showCommand :: MonadIO m => String -> CreateProcess -> m ()+showCommand prefix p = liftIO $ ePutStrLn (prefix ++ showCreateProcessForUser p)++showCommandAndResult :: MonadIO m => [Char] -> CreateProcess -> (ExitCode, a, a) -> m ()+showCommandAndResult prefix p (code, _, _) =+ liftIO $ ePutStrLn (prefix ++ showCreateProcessForUser p ++ " -> " ++ show code)++putIndented :: forall a c m. (Eq c, ListLikeProcessIO a c, IsString a, MonadIO m) => [Chunk a] -> m [Chunk a]+putIndented chunks =+ liftIO $ mapM_ echo (indentChunks " 1> " " 2> " chunks) >> return chunks+ where+ echo :: Chunk a -> IO (Chunk a)+ echo c@(Stdout x) = hPutStr stdout x >> return c+ echo c@(Stderr x) = hPutStr stderr x >> return c+ echo c = return c++run ::+ forall a c e m. (HasLoc e, MonadIO m, MonadError e m, ListLikeProcessIO a c)+ => RunOptions a m+ -> CreateProcess+ -> a+ -> m (ExitCode, a, a)+run opts p input = do+ start " -> " p+ (result :: (ExitCode, a, a)) <- withError (withLoc $here) $ liftIO (readCreateProcessLazy p input) >>= overOutput >>= return . collectOutput+ finish " <- " p result+ return result+ where+ -- We need the options as a Foldable type+ opts' :: [RunOptions a m]+ opts' = case opts of RunOptions xs -> xs; x -> [x]+ start :: String -> CreateProcess -> m ()+ start = foldr (\o f -> case o of (StartMessage f') -> f'; _ -> f) (\_ _ -> pure ()) opts'+ finish :: String -> CreateProcess -> (ExitCode, a, a) -> m ()+ finish = foldr (\o f -> case o of (FinishMessage f') -> f'; _ -> f) (\_ _ _ -> pure ()) opts'+ overOutput :: [Chunk a] -> m [Chunk a]+ overOutput = foldr (\o f -> case o of (OverOutput f') -> f'; _ -> f) return opts'++--runVE :: (Eq c, IsString a, ListLikeProcessIO a c, MonadIO m, MonadCatch m) => CreateProcess -> a -> m (Either SomeException (ExitCode, a, a))+--runVE p input = try $ runV p input++runV ::+ (Eq c, IsString a, ListLikeProcessIO a c, HasLoc e, MonadIO m, MonadError e m)+ => CreateProcess -> a -> m (ExitCode, a, a)+runV p input = run (StartMessage showCommand <> OverOutput putIndented <> FinishMessage showCommandAndResult) p input++runVE ::+ (Eq c, IsString a, ListLikeProcessIO a c, MonadCatch m, HasLoc e, Exception e, MonadError e m, MonadIO m)+ => CreateProcess -> a -> m (Either e (ExitCode, a, a))+runVE p i = try $ runV p i++--runQE :: (ListLikeProcessIO a c, MonadIO m, MonadCatch m) => CreateProcess -> a -> m (Either SomeException (ExitCode, a, a))+--runQE p input = try $ runQ p input++runQ ::+ (ListLikeProcessIO a c, HasLoc e, MonadIO m, MonadError e m)+ => CreateProcess -> a -> m (ExitCode, a, a)+runQ p input = run (StartMessage showCommand <> FinishMessage showCommandAndResult) p input++runQE ::+ (ListLikeProcessIO a c, MonadCatch m, HasLoc e, MonadIO m, MonadError e m, Exception e)+ => CreateProcess -> a -> m (Either e (ExitCode, a, a))+runQE p i = try $ runQ p i++runIO :: CreateProcess -> IO L.ByteString+runIO cp = do+ (code, out, err) <- readCreateProcessWithExitCode cp L.empty+ case code of+ ExitSuccess -> return out+ ExitFailure _ -> throwError $ userError $ unlines $+ [ show code+ , " command: " ++ showCreateProcessForUser cp+ , " stderr: " ++ unpack (decodeUtf8 (L.toStrict err))+ , " stdout: " ++ unpack (decodeUtf8 (L.toStrict out)) ]++-- | Pure function to indent the text of a chunk list.+indentChunks :: forall a c. (ListLikeProcessIO a c, Eq c, IsString a) => String -> String -> [Chunk a] -> [Chunk a]+indentChunks outp errp chunks =+ evalState (Prelude.concat <$> mapM (indentChunk nl (fromString outp) (fromString errp)) chunks) BOL+ where+ nl :: c+ nl = Data.ListLike.head (fromString "\n" :: a)++-- | The monad state, are we at the beginning of a line or the middle?+data BOL = BOL | MOL deriving (Eq)++-- | Indent the text of a chunk with the prefixes given for stdout and+-- stderr. The state monad keeps track of whether we are at the+-- beginning of a line - when we are and more text comes we insert one+-- of the prefixes.+indentChunk :: forall a c m. (Monad m, ListLikeProcessIO a c, Eq c) => c -> a -> a -> Chunk a -> StateT BOL m [Chunk a]+indentChunk nl outp errp chunk =+ case chunk of+ Stdout x -> doText Stdout outp x+ Stderr x -> doText Stderr errp x+ _ -> return [chunk]+ where+ doText :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]+ doText con pre x = do+ let (hd, tl) = break (== nl) x+ (<>) <$> doHead con pre hd <*> doTail con pre tl+ doHead :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]+ doHead _ _ x | null x = return []+ doHead con pre x = do+ bol <- get+ case bol of+ BOL -> put MOL >> return [con (pre <> x)]+ MOL -> return [con x]+ doTail :: (a -> Chunk a) -> a -> a -> StateT BOL m [Chunk a]+ doTail _ _ x | null x = return []+ doTail con pre x = do+ bol <- get+ put BOL+ tl <- doText con pre (tail x)+ return $ (if bol == BOL then [con pre] else []) <> [con (singleton nl)] <> tl++-- | Turn process exit codes into exceptions.+{-+throwProcessResult' :: (ExitCode -> Maybe IOError) -> CreateProcess -> [Chunk a] -> IO [Chunk a]+throwProcessResult' f p chunks = mapResultM (\ code -> maybe (return $ Result code) (throw $ processException p code) (f code)) chunks++-- | Turn process exit codes into exceptions with access to the+-- original CreateProcess record.+throwProcessResult'' :: Exception e => (CreateProcess -> ExitCode -> Maybe e) -> CreateProcess -> [Chunk a] -> IO [Chunk a]+throwProcessResult'' f p chunks = mapResultM (\ code -> maybe (return $ Result code) throw (f p code)) chunks++throwProcessFailure :: CreateProcess -> [Chunk a] -> IO [Chunk a]+throwProcessFailure p = throwProcessResult' (testExit Nothing (Just . processException p . ExitFailure)) p++mapResultM :: Monad m => (ExitCode -> m (Chunk a)) -> [Chunk a] -> m [Chunk a]+mapResultM f chunks = mapM (\ x -> case x of Result code -> f code; _ -> return x) chunks+-}++testExit :: a -> (Int -> a) -> ExitCode -> a+testExit s _ ExitSuccess = s+testExit _ f (ExitFailure n) = f n++-- | Copied from "System.Process", the exception thrown when the+-- process started by 'System.Process.readProcess' gets an+-- 'ExitFailure'.+processException :: CreateProcess -> ExitCode -> IOError+processException p code =+ mkIOError OtherError (showCreateProcessForUser p ++ maybe "" (\ d -> "(in " ++ show d ++ ")") (cwd p) ++ " -> " ++ show code) Nothing Nothing++-- | Set an environment variable in the CreateProcess, initializing it+-- with what is in the current environment.+insertProcessEnv :: [(String, String)] -> CreateProcess -> IO CreateProcess+insertProcessEnv pairs = modifyProcessEnv (map (second Just) pairs)++modEnv1 :: [(String, String)] -> (String, Maybe String) -> [(String, String)]+modEnv1 env0 (name, mvalue) = maybe [] (\ v -> [(name, v)]) mvalue ++ filter ((/= name) . fst) env0++modifyProcessEnv :: MonadIO m => [(String, Maybe String)] -> CreateProcess -> m CreateProcess+modifyProcessEnv pairs p = do+ env0 <- liftIO $ maybe getEnvironment return (env p)+ let env' = foldl modEnv1 env0 pairs+ return $ p {env = Just env'}++runV2 ::+ (MonadIO m, MonadCatch m, Eq c, IsString a, ListLikeProcessIO a c)+ => [Loc] -> CreateProcess -> a -> m (ExitCode, a, a)+runV2 locs p input =+ run2 locs (StartMessage (showCommand' locs) <> OverOutput putIndented <> FinishMessage showCommandAndResult) p input++runVE2 ::+ forall a c e m. (Eq c, IsString a, ListLikeProcessIO a c, MonadIO m, MonadCatch m, Exception e)+ => [Loc] -> CreateProcess -> a -> m (Either e (ExitCode, a, a))+runVE2 locs p input = do+ try (runV2 locs p input)++runQ2 ::+ (MonadIO m, MonadCatch m, ListLikeProcessIO a c)+ => [Loc] -> CreateProcess -> a -> m (ExitCode, a, a)+runQ2 locs p input =+ run2 locs (StartMessage (showCommand' locs) <> FinishMessage showCommandAndResult) p input++runQE2 ::+ (ListLikeProcessIO a c, MonadIO m, MonadCatch m, Exception e)+ => [Loc] -> CreateProcess -> a -> m (Either e (ExitCode, a, a))+runQE2 locs p input =+ try (runQ2 locs p input)++showCommand' :: MonadIO m => [Loc] -> String -> CreateProcess -> m ()+showCommand' locs prefix p = do+ -- key <- view osKey+ liftIO $ ePutStrLn (prefix ++ showCreateProcessForUser p ++ " (" ++ {-"in " ++ show key ++ ", " ++-} "called from " ++ show (prettyLocs ($here : locs)) ++ ")")++run2 ::+ forall a c m. (MonadIO m, ListLikeProcessIO a c, MonadCatch m)+ => [Loc]+ -> RunOptions a m+ -> CreateProcess+ -> a+ -> m (ExitCode, a, a)+run2 locs opts p input = do+ start " -> " p+ (result :: (ExitCode, a, a)) <- catch (liftIO (readCreateProcessLazy p input) >>= overOutput >>= return . collectOutput)+ (\se -> withException (\e -> liftIO (hPutStrLn stderr ("(at " ++ show (prettyLocs ($here : locs)) ++ ": " ++ show e ++ ") :: " ++ show (typeOf e)))) se >> throw se)+ finish " <- " p result+ liftIO $ evaluate result+ where+ -- We need the options as a Foldable type+ opts' :: [RunOptions a m]+ opts' = case opts of RunOptions xs -> xs; x -> [x]+ start :: String -> CreateProcess -> m ()+ start = foldr (\o f -> case o of (StartMessage f') -> f'; _ -> f) (\_ _ -> pure ()) opts'+ finish :: String -> CreateProcess -> (ExitCode, a, a) -> m ()+ finish = foldr (\o f -> case o of (FinishMessage f') -> f'; _ -> f) (\_ _ _ -> pure ()) opts'+ overOutput :: [Chunk a] -> m [Chunk a]+ overOutput = foldr (\o f -> case o of (OverOutput f') -> f'; _ -> f) return opts'++-- stray copies of the stuff above that I moved here, with MonadApt constraints++runV3 ::+ (Eq c, IsString a, ListLikeProcessIO a c, HasEnvRoot r, MonadReader r m, MonadIO m, MonadCatch m)+ => [Loc] -> CreateProcess -> a -> m (ExitCode, a, a)+runV3 locs p input =+ run2 locs (StartMessage showCommand3' <> OverOutput putIndented <> FinishMessage showCommandAndResult) p input++{-+runVE3 ::+ (Eq c, IsString a, ListLikeProcessIO a c, MonadApt r m, MonadError e m, MonadIO m, MonadCatch m)+ => [Loc] -> CreateProcess -> a -> m (Either IOException (ExitCode, a, a))+runVE3 locs p input = try $ runV3 locs p input+-}++runQ3 ::+ (ListLikeProcessIO a c, HasEnvRoot r, MonadReader r m, MonadIO m, MonadCatch m)+ => [Loc] -> CreateProcess -> a -> m (ExitCode, a, a)+runQ3 locs p input =+ run2 locs (StartMessage showCommand3' <> FinishMessage showCommandAndResult) p input++runQE3 ::+ (ListLikeProcessIO a c, HasEnvRoot r, MonadReader r m, MonadIO m, MonadCatch m)+ => [Loc] -> CreateProcess -> a -> m (Either IOException (ExitCode, a, a))+runQE3 locs p input = try $ runQ3 locs p input++showCommand3' :: (HasEnvRoot r, MonadReader r m, MonadIO m) => String -> CreateProcess -> m ()+showCommand3' prefix p = do+ key <- view (envRootLens . rootPath)+ liftIO $ ePutStrLn (prefix ++ showCreateProcessForUser p ++ " (in " ++ show key ++ ")")++-- | Modify a value (typically an exception) to include a source code+-- location: e.g. @withError (withLoc $here) $ tryError action@.+class HasLoc e where withLoc :: Loc -> e -> e
Extra/SSH.hs view
@@ -4,13 +4,12 @@ , sshCopy ) where -import System.Cmd import System.Directory import System.Posix.User import System.Environment import System.Exit import System.IO-import System.Process (readProcessWithExitCode, showCommandForUser)+import System.Process (readProcessWithExitCode, showCommandForUser, system) -- |Set up access to destination (user\@host). sshExportDeprecated :: String -> Maybe Int -> IO (Either String ()) sshExportDeprecated dest port =@@ -42,12 +41,12 @@ -- |See if we already have access to the destination (user\@host). sshVerify :: String -> Maybe Int -> IO Bool sshVerify dest port =- do result <- system (sshTestCmd dest port)+ do result <- system sshTestCmd return $ case result of- ExitSuccess -> True -- We do- ExitFailure _ -> False -- We do not+ ExitSuccess -> True -- We do+ ExitFailure _ -> False -- We do not where- sshTestCmd dest port =+ sshTestCmd = ("ssh -o 'PreferredAuthentications hostbased,publickey' " ++ (maybe "" (("-p " ++) . show) port) ++ " " ++ show dest ++ " pwd > /dev/null && exit 0") @@ -68,15 +67,15 @@ (code, out, err) <- readFile keypath >>= readProcessWithExitCode "ssh" args case code of ExitFailure n -> return . Left $ "Failure: " ++ showCommandForUser "ssh" args ++ " -> " ++ show n ++- "\n\nstdout: " ++ out ++ "\n\nstderr: " ++ err+ "\n\nstdout: " ++ out ++ "\n\nstderr: " ++ err _ -> return . Right $ () where sshOpenRemoteCmd =- ("chmod g-w . && " ++ -- Ssh will not work if the permissions aren't just so+ ("chmod g-w . && " ++ -- Ssh will not work if the permissions aren't just so "chmod o-rwx . && " ++ "mkdir -p .ssh && " ++ "chmod 700 .ssh && " ++- "cat >> .ssh/authorized_keys2 && " ++ -- Add the key to the authorized key list+ "cat >> .ssh/authorized_keys2 && " ++ -- Add the key to the authorized key list "chmod 600 .ssh/authorized_keys2") -- This used to be main.
+ Extra/SafeCopy.hs view
@@ -0,0 +1,104 @@+-- | This module exports a template haskell function to create+-- Serialize instances based on the SafeCopy instance, and an+-- alternative decode function that puts the decode type in the error+-- message. It also re-exports all other Data.Serialize symbols++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++module Extra.SafeCopy+ ( module Data.SafeCopy+ , DecodeError(..)+ , HasDecodeError(fromDecodeError)+ , decode+ , encode+ , decode'+ , decodeM+ , decodeM'+ ) where++import Control.Exception (ErrorCall(..), evaluate, )+import Control.Lens (Getter, _Left, over, Prism', prism, re)+import Control.Monad.Catch (catch, MonadCatch)+import Control.Monad.Except (MonadError, throwError)+import Data.ByteString as B (ByteString, null)+#ifndef OMIT_DATA_INSTANCES+import Data.Data (Data)+#endif+import Data.Data (Proxy(Proxy), Typeable, typeRep)+import Data.SafeCopy (base, SafeCopy, safeGet, safePut)+import Data.Serialize hiding (decode, encode)+import qualified Data.Serialize as Serialize (decode, encode)+import Data.Text as T hiding (concat, intercalate)+import Data.Text.Lazy as LT hiding (concat, intercalate)+import Data.Text.Encoding as TE+import Data.Text.Lazy.Encoding as TLE+import Data.Time (UTCTime(..), Day(ModifiedJulianDay), toModifiedJulianDay, DiffTime)+import Data.UUID.Orphans ()+import Data.UUID (UUID)+import Data.UUID.Orphans ()+import Extra.Orphans ()+import Extra.Serialize (DecodeError(..), HasDecodeError(..))+import Extra.Time (Zulu(..))+import Language.Haskell.TH (Dec, Loc, TypeQ, Q)+import Network.URI (URI(..), URIAuth(..))+import System.IO.Unsafe (unsafePerformIO)++encode :: SafeCopy a => a -> ByteString+encode = runPut . safePut++decode :: forall a. (SafeCopy a) => ByteString -> Either String a+decode b = case runGetState safeGet b 0 of+ Left s -> Left s+ Right (a, remaining) | B.null remaining -> Right a+ Right (a, remaining) -> Left ("decode " <> show b <> " failed to consume " <> show remaining)++-- | Monadic version of decode.+decodeM ::+ forall a e m. (SafeCopy a, HasDecodeError e, MonadError e m)+ => ByteString+ -> m a+decodeM bs =+ case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a++-- | Like 'decodeM', but also catches any ErrorCall thrown and lifts+-- it into the MonadError instance. I'm not sure whether this can+-- actually happen. What I'm seeing is probably an error call from+-- outside the serialize package, in which case this (and decode') are+-- pointless.+decodeM' ::+ forall e m a. (SafeCopy a, HasDecodeError e, MonadError e m, MonadCatch m)+ => ByteString+ -> m a+decodeM' bs = go `catch` handle+ where+ go = case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a+ handle :: ErrorCall -> m a+ handle (ErrorCall s) = throwError $ fromDecodeError $ DecodeError bs s++-- | Version of decode that catches any thrown ErrorCall and modifies+-- its message.+decode' :: forall a. (SafeCopy a) => ByteString -> Either String a+decode' b =+ unsafePerformIO (evaluate (decode b :: Either String a) `catch` handle)+ where+ handle :: ErrorCall -> IO (Either String a)+ handle e = return $ Left (show e)++-- | Serialize/deserialize prism.+deserializePrism :: forall a. (SafeCopy a) => Prism' ByteString a+deserializePrism = prism encode (\s -> either (\_ -> Left s) Right (decode s :: Either String a))++-- | Inverting a prism turns it into a getter.+serializeGetter :: forall a. (SafeCopy a) => Getter a ByteString+serializeGetter = re deserializePrism
+ Extra/SafeCopyDebug.hs view
@@ -0,0 +1,102 @@+-- | This module exports a template haskell function to create+-- Serialize instances based on the SafeCopy instance, and an+-- alternative decode function that puts the decode type in the error+-- message. It also re-exports all other Data.Serialize symbols++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++module Extra.SafeCopyDebug+ ( module Extra.SafeCopy+ , Debug+ , DecodeError(..)+ , HasDecodeError(fromDecodeError)+ , decode+ , decode'+ , decodeM+ , decodeM'+ ) where++import Control.Exception (ErrorCall(..), evaluate, )+import Control.Lens (Getter, _Left, over, Prism', prism, re)+import Control.Monad.Catch (catch, MonadCatch)+import Control.Monad.Except (MonadError, throwError)+import Data.ByteString as B (ByteString, null)+#ifndef OMIT_DATA_INSTANCES+import Data.Data (Data)+#endif+import Data.Data (Proxy(Proxy), Typeable, typeRep)+import Data.SafeCopy (base, SafeCopy, safeGet, safePut)+import Data.Serialize hiding (decode, encode)+import qualified Data.Serialize as Serialize (decode, encode)+import Data.Text as T hiding (concat, intercalate)+import Data.Text.Lazy as LT hiding (concat, intercalate)+import Data.Text.Encoding as TE+import Data.Text.Lazy.Encoding as TLE+import Data.Time (UTCTime(..), Day(ModifiedJulianDay), toModifiedJulianDay, DiffTime)+import Data.UUID.Orphans ()+import Data.UUID (UUID)+import Data.UUID.Orphans ()+import Extra.Orphans ()+import Extra.SafeCopy hiding (decode, decode', decodeM, decodeM')+import Extra.SerializeDebug (Debug, DecodeError(..), HasDecodeError(..))+import Extra.Time (Zulu(..))+import Language.Haskell.TH (Dec, Loc, TypeQ, Q)+import Network.URI (URI(..), URIAuth(..))+import System.IO.Unsafe (unsafePerformIO)++decode :: forall a. (SafeCopy a, Debug a) => ByteString -> Either String a+decode b = case runGetState safeGet b 0 of+ Left s -> Left s+ Right (a, remaining) | B.null remaining -> Right a+ Right (a, remaining) -> Left ("decode " <> show b <> " :: " <> show (typeRep (Proxy :: Proxy a)) <> " failed to consume " <> show remaining)++-- | Monadic version of decode.+decodeM ::+ forall a e m. (SafeCopy a, Debug a, HasDecodeError e, MonadError e m)+ => ByteString+ -> m a+decodeM bs =+ case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a++-- | Like 'decodeM', but also catches any ErrorCall thrown and lifts+-- it into the MonadError instance. I'm not sure whether this can+-- actually happen. What I'm seeing is probably an error call from+-- outside the serialize package, in which case this (and decode') are+-- pointless.+decodeM' ::+ forall e m a. (SafeCopy a, Debug a, HasDecodeError e, MonadError e m, MonadCatch m)+ => ByteString+ -> m a+decodeM' bs = go `catch` handle+ where+ go = case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a+ handle :: ErrorCall -> m a+ handle (ErrorCall s) = throwError $ fromDecodeError $ DecodeError bs s++-- | Version of decode that catches any thrown ErrorCall and modifies+-- its message.+decode' :: forall a. (SafeCopy a, Debug a) => ByteString -> Either String a+decode' b =+ unsafePerformIO (evaluate (decode b :: Either String a) `catch` handle)+ where+ handle :: ErrorCall -> IO (Either String a)+ handle e = return $ Left (show e)++-- | Serialize/deserialize prism.+deserializePrism :: forall a. (SafeCopy a, Debug a) => Prism' ByteString a+deserializePrism = prism encode (\s -> either (\_ -> Left s) Right (decode s :: Either String a))++-- | Inverting a prism turns it into a getter.+serializeGetter :: forall a. (SafeCopy a, Debug a) => Getter a ByteString+serializeGetter = re deserializePrism
+ Extra/Serialize.hs view
@@ -0,0 +1,186 @@+-- | This module exports a template haskell function to create+-- Serialize instances based on the SafeCopy instance, and an+-- alternative decode function that puts the decode type in the error+-- message. It also re-exports all other Data.Serialize symbols++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++module Extra.Serialize+ ( DecodeError(..)+ , HasDecodeError(fromDecodeError)+ , module Data.Serialize+ , decodePrism, deserializePrism+ , encodeGetter, serializeGetter+ , deriveSerializeViaSafeCopy+ , decode+ , Serialize.encode+ , decode'+ , decodeM+ , decodeM'+ ) where++import Control.Exception (ErrorCall(..), evaluate, )+import Control.Lens (Getter, _Left, over, Prism', prism, re)+import Control.Monad.Catch (catch, MonadCatch)+import Control.Monad.Except (MonadError, throwError)+import Data.ByteString as B (ByteString, null)+#ifndef OMIT_DATA_INSTANCES+import Data.Data (Data)+#endif+import Data.Data (Proxy(Proxy))+import Data.SafeCopy (base, SafeCopy(..), safeGet, safePut)+import Data.Serialize hiding (decode, encode)+import qualified Data.Serialize as Serialize (decode, encode)+import Data.Text as T hiding (concat, intercalate)+import Data.Text.Lazy as LT hiding (concat, intercalate)+import Data.Text.Encoding as TE+import Data.Text.Lazy.Encoding as TLE+import Data.Time (UTCTime(..), Day(ModifiedJulianDay), toModifiedJulianDay, DiffTime)+import Data.UUID.Orphans ()+import Data.UUID (UUID)+import Data.UUID.Orphans ()+import Extra.Orphans ()+import Extra.Time (Zulu(..))+import GHC.Generics (Generic)+import Language.Haskell.TH (Dec, Loc, TypeQ, Q)+import Network.URI (URI(..), URIAuth(..))+import System.IO.Unsafe (unsafePerformIO)++data DecodeError = DecodeError ByteString String deriving (Generic, Eq, Ord)++class HasDecodeError e where fromDecodeError :: DecodeError -> e+instance HasDecodeError DecodeError where fromDecodeError = id+instance Serialize DecodeError where get = safeGet; put = safePut++encode :: Serialize a => a -> ByteString+encode = Serialize.encode++-- | Decode a value from a strict ByteString, reconstructing the original+-- structure. Unlike Data.Serialize.decode, this function only succeeds+-- if all the input is consumed. Not sure if this is in use anywhere.+--+-- > Extra.Serialize.decode (encode 'x' <> encode 'y') :: Either String Char+-- Left "decode \"xy\" failed to consume \"y\""+-- > Data.Serialize.decode (encode 'x' <> encode 'y') :: Either String Char+-- Right 'x'+decode :: forall a. Serialize a => ByteString -> Either String a+decode b =+ case runGetState get b 0 of+ Left s -> Left s+ Right (a, remaining) | B.null remaining -> Right a+ Right (a, remaining) -> Left ("decode " <> show b <> " failed to consume " <> show remaining)++-- | A Serialize instance based on safecopy. This means that+-- migrations will be performed upon deserialization, which is handy+-- if the value is stored in the browser's local storage. Thus, zero+-- downtime upgrades!+deriveSerializeViaSafeCopy :: TypeQ -> Q [Dec]+deriveSerializeViaSafeCopy typ =+ [d|instance {-SafeCopy $typ =>-} Serialize $typ where+ get = safeGet+ put = safePut|]++instance Serialize T.Text where+ put = put . TE.encodeUtf8+ get = TE.decodeUtf8 <$> get++instance Serialize LT.Text where+ put = put . TLE.encodeUtf8+ get = TLE.decodeUtf8 <$> get++-- | This is private, we can't create a Generic instance for it.+instance Serialize DiffTime where+ get = fromRational <$> get+ put = put . toRational++instance Serialize UTCTime where+ get = uncurry UTCTime <$> get+ put (UTCTime day time) = put (day, time)++instance Serialize Day where+ get = ModifiedJulianDay <$> get+ put = put . toModifiedJulianDay++-- deriving instance Generic UUID deriving instance Serialize UUID Use+-- the SafeCopy methods to implement Serialize. This is a pretty neat+-- trick, it automatically does SafeCopy migration on any deserialize+-- of a type with this implementation.+instance Serialize UUID where+ get = safeGet+ put = safePut++deriving instance Serialize Loc+deriving instance Serialize URI+deriving instance Serialize URIAuth+deriving instance Serialize Zulu++-- | Monadic version of decode.+decodeM ::+ forall a e m. (Serialize a, HasDecodeError e, MonadError e m)+ => ByteString+ -> m a+decodeM bs =+ case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a++-- | Like 'decodeM', but also catches any ErrorCall thrown and lifts+-- it into the MonadError instance. I'm not sure whether this can+-- actually happen. What I'm seeing is probably an error call from+-- outside the serialize package, in which case this (and decode') are+-- pointless.+decodeM' ::+ forall e m a. (Serialize a, HasDecodeError e, MonadError e m, MonadCatch m)+ => ByteString+ -> m a+decodeM' bs = go `catch` handle+ where+ go = case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a+ handle :: ErrorCall -> m a+ handle (ErrorCall s) = throwError $ fromDecodeError $ DecodeError bs s++-- | Version of decode that catches any thrown ErrorCall and modifies+-- its message.+decode' :: forall a. (Serialize a) => ByteString -> Either String a+decode' b =+ unsafePerformIO (evaluate (decode b :: Either String a) `catch` handle)+ where+ handle :: ErrorCall -> IO (Either String a)+ handle e = return $ Left (show e)++-- | Serialize/deserialize prism.+deserializePrism :: forall a. (Serialize a) => Prism' ByteString a+deserializePrism = decodePrism+{-# DEPRECATED deserializePrism "dumb name - use decodePrism" #-}++-- | Serialize/deserialize prism.+decodePrism :: forall a. (Serialize a) => Prism' ByteString a+decodePrism = prism encode (\s -> either (\_ -> Left s) Right (decode s :: Either String a))++-- | Inverting a prism turns it into a getter.+serializeGetter :: forall a. (Serialize a) => Getter a ByteString+serializeGetter = re deserializePrism+{-# DEPRECATED serializeGetter "dumb name - use encodeGetter" #-}++encodeGetter :: forall a. (Serialize a) => Getter a ByteString+encodeGetter = re deserializePrism++instance SafeCopy DecodeError where version = 1++#ifndef OMIT_DATA_INSTANCES+deriving instance Data DecodeError+#endif++#ifndef OMIT_SHOW_INSTANCES+deriving instance Show DecodeError+#endif
+ Extra/SerializeDebug.hs view
@@ -0,0 +1,109 @@+-- | This module exports a template haskell function to create+-- Serialize instances based on the SafeCopy instance, and an+-- alternative decode function that puts the decode type in the error+-- message. It also re-exports all other Data.Serialize symbols++{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++module Extra.SerializeDebug+ ( module Extra.Serialize+ , Debug+ , DecodeError(..)+ , HasDecodeError(fromDecodeError)+ , deserializePrism+ , serializeGetter+ , decode+ , decode'+ , decodeM+ , decodeM'+ ) where++import Control.Exception (ErrorCall(..), evaluate, )+import Control.Lens (Getter, _Left, over, Prism', prism, re)+import Control.Monad.Catch (catch, MonadCatch)+import Control.Monad.Except (MonadError, throwError)+import Data.ByteString as B (ByteString, null)+#ifndef OMIT_DATA_INSTANCES+import Data.Data (Data)+#endif+import Data.Data (Proxy(Proxy), Typeable, typeRep)+import Data.SafeCopy (base, SafeCopy, safeGet, safePut)+import Data.Serialize hiding (decode)+import qualified Data.Serialize as Serialize (decode, encode)+import Data.Text as T hiding (concat, intercalate)+import Data.Text.Lazy as LT hiding (concat, intercalate)+import Data.Text.Encoding as TE+import Data.Text.Lazy.Encoding as TLE+import Data.Time (UTCTime(..), Day(ModifiedJulianDay), toModifiedJulianDay, DiffTime)+import Data.UUID.Orphans ()+import Data.UUID (UUID)+import Data.UUID.Orphans ()+import Extra.Orphans ()+import Extra.Serialize hiding (decode, decode', decodeM, decodeM', deserializePrism, serializeGetter)+import Extra.Time (Zulu(..))+import Language.Haskell.TH (Dec, Loc, TypeQ, Q)+import Network.URI (URI(..), URIAuth(..))+import System.IO.Unsafe (unsafePerformIO)++type Debug a = (Typeable a, Show a)++-- | Decode a value from a strict ByteString, reconstructing the original+-- structure. Unlike Data.Serialize.decode, this function only succeeds+-- if all the input is consumed.+decode :: forall a. (Serialize a, Debug a) => ByteString -> Either String a+decode b =+ case runGetState get b 0 of+ Left s -> Left s+ Right (a, remaining) | B.null remaining -> Right a+ Right (a, remaining) -> Left ("decode " <> show b <> " :: " <> show (typeRep (Proxy :: Proxy a)) <> " failed to consume " <> show remaining)++-- | Monadic version of decode.+decodeM ::+ forall a e m. (Serialize a, Debug a, HasDecodeError e, MonadError e m)+ => ByteString+ -> m a+decodeM bs =+ case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a++-- | Like 'decodeM', but also catches any ErrorCall thrown and lifts+-- it into the MonadError instance. I'm not sure whether this can+-- actually happen. What I'm seeing is probably an error call from+-- outside the serialize package, in which case this (and decode') are+-- pointless.+decodeM' ::+ forall e m a. (Serialize a, Debug a, HasDecodeError e, MonadError e m, MonadCatch m)+ => ByteString+ -> m a+decodeM' bs = go `catch` handle+ where+ go = case decode bs of+ Left s -> throwError (fromDecodeError (DecodeError bs s))+ Right a -> return a+ handle :: ErrorCall -> m a+ handle (ErrorCall s) = throwError $ fromDecodeError $ DecodeError bs s++-- | Version of decode that catches any thrown ErrorCall and modifies+-- its message.+decode' :: forall a. (Serialize a, Debug a) => ByteString -> Either String a+decode' b =+ unsafePerformIO (evaluate (decode b :: Either String a) `catch` handle)+ where+ handle :: ErrorCall -> IO (Either String a)+ handle e = return $ Left (show e)++-- | Serialize/deserialize prism.+deserializePrism :: forall a. (Serialize a, Debug a) => Prism' ByteString a+deserializePrism = prism encode (\s -> either (\_ -> Left s) Right (decode s :: Either String a))++-- | Inverting a prism turns it into a getter.+serializeGetter :: forall a. (Serialize a, Debug a) => Getter a ByteString+serializeGetter = re deserializePrism
+ Extra/TH.hs view
@@ -0,0 +1,93 @@+-- | Pretty print Loc info for stack traces++{-# LANGUAGE CPP, FlexibleInstances, ScopedTypeVariables, TemplateHaskell #-}+{-# OPTIONS -Wall -Wno-orphans #-}++module Extra.TH+ ( here+ , Loc+ , noLoc+ , prettyLocs+ , addConstraints+ , addConstraint+ , removeConstraints+ ) where++import Control.Lens (_1, over)+import Data.Generics (everywhere, mkT)+import Data.List (intersperse, nub)+import Data.Map as Map (findWithDefault, fromList, fromListWith, lookup, Map, toList)+import Data.Maybe (mapMaybe)+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import Data.Tuple (swap)+import Extra.Generics (gFind)+import Language.Haskell.TH (appT, conT, Dec(InstanceD), ExpQ, Loc(..), location, Name, nameBase, newName, Q, Type(..), TypeQ, varT)+import Language.Haskell.TH.Instances ()+import Language.Haskell.TH.Lift (lift)+import Text.PrettyPrint.HughesPJClass (Doc, hcat, Pretty(..), text)++here :: ExpQ+here = lift =<< location++instance Pretty Loc where+ pPrint = prettyLoc++noLoc :: Loc+noLoc = Loc "" "" "" (0, 0) (0, 0)++prettyLoc :: Loc -> Doc+prettyLoc (Loc _filename _package modul (line, col) _) = text (modul <> ":" ++ show line ++ ":" ++ show col)++prettyLocs :: [Loc] -> Doc+prettyLocs locs = text "[" <> hcat (intersperse (text " ← ") (fmap prettyLoc locs)) <> text "]"++-- | Add some simple constraints to a 'Dec'. The Dec is typically one+-- emitted by a function such as makeAcidic or deriveSafeCopy.+addConstraints :: [(Name, String)] -> Dec -> Q Dec+addConstraints cs (InstanceD mo cxt typ decs) = do+ let mp :: Map String [Name]+ mp = Map.fromListWith (<>) (fmap (swap . over _1 (: [])) cs)+ (extra :: [Type]) <- concat <$> mapM (uncurry constrain) (Map.toList mp)+ return $ InstanceD mo (cxt ++ extra) typ decs+ where+ -- Apply some type constraints to a type variable+ constrain vstring tnames = do+ vname <- newName vstring+ mapM (\tname -> appT (conT tname) (varT vname)) tnames+addConstraints _ d = return d++-- | Unify the type variables in the existing constraints with those+-- in the new constraint based on nameBase. If the type variable does+-- not appear in the declaration it is an error.+addConstraint :: Name -> TypeQ -> Dec -> Q Dec+addConstraint tname typeq (InstanceD mo cxt inst decs) = do+ let vnames = nub $ mapMaybe isVar (gFind (inst : cxt) :: [Type])+ mp :: Map String Name+ mp = Map.fromList (fmap (\n -> (nameBase n, n)) vnames)+ (extra :: Type) <- everywhere (mkT (fixTypeVar mp)) <$> appT (conT tname) typeq+ return $ InstanceD mo (cxt ++ [extra]) inst decs+ where+ isVar :: Type -> Maybe Name+ isVar (VarT name) = Just name+ isVar _ = Nothing+ fixTypeVar :: Map String Name -> Type -> Type+ fixTypeVar mp (VarT n) = VarT (maybe n id (Map.lookup (nameBase n) mp))+ fixTypeVar _mp t = t+addConstraint _ _ d = return d++-- | Remove simple constraints from a 'Dec' based on type class name+-- and type variable name.+removeConstraints :: [(Name, String)] -> Dec -> Q Dec+removeConstraints cs (InstanceD mo cxt typ decs) = do+ let mp :: Map String [Name]+ mp = Map.fromListWith (<>) (fmap (swap . over _1 (: [])) cs)+ return $ InstanceD mo (filter (testPred mp) cxt) typ decs+ where+ testPred :: Map String [Name] -> Type -> Bool+ -- Discard any predicate that matches a map element+ testPred mp (AppT (ConT tname) (VarT vname)) =+ not (elem tname (Map.findWithDefault [] (nameBase vname) mp))+ testPred _ _ = True -- keep anything else+removeConstraints _ d = return d
+ Extra/Text.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE CPP, RankNTypes #-}++module Extra.Text+ ( diffText+ , camelWords+ , capitalize+ , Describe(describe')+ , describe+ , textshow+ , trunc+#if !__GHCJS__+ , tests+#endif+ ) where++import Data.Algorithm.DiffContext (getContextDiff, prettyContextDiff)+import Data.Char (isUpper, toUpper)+import Data.ListLike (groupBy)+import Data.Text (split, Text, pack, unpack)+#if !__GHCJS__+import Test.HUnit (assertEqual, Test(TestCase, TestList))+#endif+import qualified Text.PrettyPrint as HPJ++-- | Output the difference between two string in the style of diff(1). This+-- can be used with Test.HUnit.assertString: assertString (diffText ("a", "1\n2\n3\n"), ("b", "1\n3\n"))+diffText :: (String, Text) -> (String, Text) -> String+diffText (nameA, textA) (nameB, textB) =+ show (prettyContextDiff+ (HPJ.text nameA)+ (HPJ.text nameB)+ (HPJ.text . unpack)+ (getContextDiff 2 (split (== '\n') textA) (split (== '\n') textB)))++-- | Convert a camel case string (no whitespace) into a natural+-- language looking phrase:+-- camelWords "aCamelCaseFOObar123" -> "A Camel Case FOObar123"+camelWords :: String -> String+camelWords s =+ case groupBy (\ a b -> isUpper a == isUpper b) (dropWhile (== '_') s) of -- "aCamelCaseFOObar123"+ (x : xs) -> concat $ capitalize x : map (\ (c : cs) -> if isUpper c then ' ' : c : cs else c : cs) xs+ [] -> ""++#if !__GHCJS__+-- Most of these fail.+tests :: Test+tests =+ TestList+ [ TestCase (assertEqual "camel words 0" "A Camel Case FOO Bar 123" (camelWords "aCamelCaseFOOBar123"))+ , TestCase (assertEqual "camel words 1" "My Generator" (camelWords "myGenerator"))+ , TestCase (assertEqual "camel words 2" "PDF Generator" (camelWords "pDFGenerator"))+ , TestCase (assertEqual "camel words 3" "PDF Generator" (camelWords "PDFGenerator"))+ , TestCase (assertEqual "camel words 4" "Report PDF Generator" (camelWords "reportPDFGenerator")) ]+#endif++capitalize :: String -> String+capitalize [] = []+capitalize (c:cs) = (toUpper c) : cs++-- | Override the default description associated with the type of @a@.+-- The first argument indicates the field of the parent record that+-- contains the @a@ value, if any.+class Describe a where+ describe' :: Maybe String -> a -> Maybe String++describe :: Describe a => a -> Maybe String+describe = describe' Nothing++-- | Truncate a string to avoid writing monster lines into the log.+trunc :: String -> String+trunc s = if length s > 1000 then take 1000 s ++ "..." else s++-- | The ever needed, never available show that returns a Text.+textshow :: Show a => a -> Text+textshow = pack . show
Extra/Time.hs view
@@ -1,20 +1,20 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP, DeriveAnyClass, DeriveDataTypeable, DeriveGeneric, TemplateHaskell #-} module Extra.Time ( formatDebianDate- , myTimeDiffToString+ -- , myTimeDiffToString+ , Zulu(..), utcTime ) where import Control.Exception-import Data.List+import Control.Lens (makeLenses)+import Data.Data (Data)+import Data.SafeCopy (base, SafeCopy(..)) import Data.Time-#if MIN_VERSION_time(1,5,0)-import qualified System.Locale as Old (defaultTimeLocale)-import qualified System.Time as Old (formatTimeDiff, tdPicosec)-#else-import System.Locale as Old-import System.Time as Old+import Extra.Orphans ()+import GHC.Generics+#if !__GHCJS__+import Test.QuickCheck #endif-import Text.Printf {- This function is so complicated because there seems to be no way to get the Data.Time to format seconds without the fractional part,@@ -51,6 +51,8 @@ testsecond = 15.29 teststring = "Tue, 19 Dec 2006 17:19:15 UTC" +#if 0+-- | Retired due to use of old-time. myTimeDiffToString diff = do case () of@@ -63,3 +65,20 @@ ms = ps2ms ps ps2ms ps = quot (ps + 500000000) 1000000000 ps = Old.tdPicosec diff+#endif++-- | A version of UTCTime with a Show instance that returns a Haskell+-- expression.+newtype Zulu = Zulu {_utcTime :: UTCTime} deriving (Eq, Ord, Data, Generic)++$(makeLenses ''Zulu)+instance SafeCopy Zulu where version = 1; kind = base++#if !__GHCJS__+instance Arbitrary Zulu where arbitrary = Zulu <$> arbitrary+#endif+-- instance ParseTime Zulu+-- instance FormatTime Zulu++instance Show Zulu where+ showsPrec d (Zulu t) = showParen (d > 10) $ showString ("Zulu (read " ++ show (show t) ++ ")")
Extra/URI.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE CPP #-}+ -- |Make URI an instance of Read and Ord, and add functions to -- manipulate the uriQuery. module Extra.URI@@ -12,15 +14,14 @@ ) where import Network.URI -- (URIAuth(..), URI(..), parseURI, uriToString, escapeURIString, isUnreserved, unEscapeString)-import Data.List(intersperse, groupBy, inits)-import Data.Maybe(isJust, isNothing, catMaybes)+import Data.List(intersperse, groupBy) import Control.Arrow(second) -- |Create a relative URI with the given query. relURI :: FilePath -> [(String, String)] -> URI-relURI path pairs = URI {uriScheme = "",+relURI upath pairs = URI {uriScheme = "", uriAuthority = Nothing,- uriPath = path,+ uriPath = upath, uriQuery = formatURIQuery pairs, uriFragment = ""} @@ -70,6 +71,7 @@ -- Everything else gets escaped. escapeURIForQueryValue = escapeURIString isUnreserved +#if 0 -- Make URI an instance of Read. This will throw an error if no -- prefix up to ten characters long of the argument string looks like -- a URI. If such a prefix is found, it will continue trying longer@@ -89,3 +91,4 @@ (a : _) -> a goodURIs = takeWhile isJust moreURIs (badURIs, moreURIs) = span isNothing allURIs+#endif
+ Extra/Verbosity.hs view
@@ -0,0 +1,77 @@+{-# LANGUAGE CPP, RankNTypes #-}+{-# OPTIONS_GHC -Wall #-}+module Extra.Verbosity+ ( modifyEnv+ , qPutStr+ , qPutStrLn+ , ePutStr+ , ePutStrLn+ , eBracket+ , quieter+ , noisier+ , withVerbosity+ , withModifiedVerbosity+ , verbosity+ ) where++import Control.Monad (when)+import Control.Monad.Catch (bracket_, MonadMask)+#ifndef FIXED_VERBOSITY_LEVEL+import Control.Monad.Catch (bracket)+#endif+import Control.Monad.Trans (liftIO, MonadIO)+import System.IO (hPutStr, hPutStrLn, stderr)+import System.Posix.Env (getEnv, setEnv, unsetEnv)++-- | Generalization of Posix setEnv/unSetEnv.+modifyEnv :: String -> (Maybe String -> Maybe String) -> IO ()+modifyEnv name f =+ getEnv name >>= maybe (unsetEnv name) (\ x -> setEnv name x True) . f++ePutStr :: MonadIO m => String -> m ()+ePutStr = liftIO . hPutStr stderr+ePutStrLn :: MonadIO m => String -> m ()+ePutStrLn = liftIO . hPutStrLn stderr++qPutStr :: MonadIO m => String -> m ()+qPutStr s = do+ v <- verbosity+ when (v > 0) (ePutStr s)++qPutStrLn :: MonadIO m => String -> m ()+qPutStrLn s = do+ v <- verbosity+ when (v > 0) (ePutStrLn s)++quieter :: (MonadIO m, MonadMask m) => Int -> m a -> m a+quieter n action = withModifiedVerbosity (\ v -> v - n) action++noisier :: (MonadIO m, MonadMask m) => Int -> m a -> m a+noisier n action = withModifiedVerbosity (\ v -> v + n) action++withVerbosity :: MonadIO m => Int -> m a -> m a+withVerbosity v action = liftIO (setEnv "VERBOSITY" (show v) True) >> action++withModifiedVerbosity :: (MonadIO m, MonadMask m) => (Int -> Int) -> m a -> m a+#ifdef FIXED_VERBOSITY_LEVEL+withModifiedVerbosity _f action =+ action+#else+withModifiedVerbosity f action =+ bracket verbosity -- acquire+ (\ v0 -> liftIO (modifyEnv "VERBOSITY" (const (Just (show v0))))) -- release+ (\ v0 -> liftIO (modifyEnv "VERBOSITY" (const (Just (show (f v0))))) >> action)+#endif++verbosity :: MonadIO m => m Int+#ifdef NO_VERBOSITY_CONTROL+verbosity = return 1000+#else+verbosity = liftIO $ getEnv "VERBOSITY" >>= return . maybe 1 read+#endif++eBracket :: (MonadIO m, MonadMask m) => String -> m a -> m a+eBracket message action =+ bracket_ (ePutStrLn (" -> " ++ message))+ (ePutStrLn (" <- " ++ message))+ action
Test/QUnit.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}+{-# LANGUAGE CPP, FlexibleInstances, TypeSynonymInstances #-} ----------------------------------------------------------------------------- -- | -- Module : Test.QUnit@@ -64,3 +64,6 @@ QC.GaveUp{} -> let ntest = QC.numTests result in HU.assertFailure $ "Arguments exhausted after" ++ show ntest ++ (if ntest == 1 then " test." else " tests.") QC.Failure{} -> let reason = QC.reason result in HU.assertFailure reason QC.NoExpectedFailure{} -> HU.assertFailure $ "No Expected Failure"+#if !MIN_VERSION_QuickCheck(2,11,0)+ QC.InsufficientCoverage{} -> HU.assertFailure $ "Insufficient Coverage"+#endif
Test/QuickCheck/Properties.hs view
@@ -2,7 +2,7 @@ import Test.QuickCheck -isIdempotentBy :: (Arbitrary a, Eq a, Show a) => (a -> a) -> Gen a -> Property+isIdempotentBy :: ({-Arbitrary a,-} Eq a, Show a) => (a -> a) -> Gen a -> Property isIdempotentBy f src = forAll src $ \a -> f a == f (f a) isIdempotent :: (Arbitrary a, Eq a, Show a) => (a -> a) -> Property
sr-extra.cabal view
@@ -1,67 +1,136 @@ Name: sr-extra-Version: 1.46.3.2+Version: 1.64 License: BSD3-License-File: COPYING+License-File: COPYING Author: David Fox Category: Unclassified-Description: A hodge-podge of functions and modules that do not have a better home+Synopsis: Module limbo+Description:+ A hodge-podge of functions, modules, and instances. These+ generally end up here because+ 1. they are needed in two unrelated packages,+ 2. they belong in some upstream module,+ 3. they can't be moved to an upstream module because+ they would add dependencies, or+ 4. they are deprecated but still in use Maintainer: David Fox <dsf@seereason.com> Homepage: https://github.com/seereason/sr-extra-Synopsis: A grab bag of modules. Build-Type: Simple-Cabal-Version: >= 1.2+Cabal-Version: >= 1.4+ flag network-uri- Description: Get Network.URI from the network-uri package- Default: True+ Description: Get Network.URI from the network-uri package rather than the+ full network package.+ Default: True +flag omit-serialize+ Description: Omit all the Serialize instances, on the assumption+ that we will use SafeCopy instances instead.+ Default: False+ Library+ GHC-Options: -Wall -Wredundant-constraints Build-Depends: base < 5, bytestring, bzlib,+ Cabal, containers,+ Diff, directory,+ exceptions,+ fgl, filepath,- HUnit,+ generic-data,+ hslogger,+ lens,+ ListLike,+ mmorph, mtl,- old-locale,- old-time, pretty,- process, pureMD5,- QuickCheck >= 2 && < 3, random,- regex-compat,+ safecopy >= 0.9.5,+ show-combinators,+ syb,+ template-haskell,+ text,+ th-lift,+ th-lift-instances,+ th-orphans, time >= 1.1, unix, Unixutils >= 1.51,+ userid,+ uuid,+ uuid-orphans,+ uuid-types, zlib- if flag(network-uri)- Build-Depends: network-uri >= 2.6- else- Build-Depends: network >= 2.4- ghc-options: -O2 -W- C-Sources: cbits/gwinsz.c+ C-Sources: cbits/gwinsz.c Include-Dirs: cbits Install-Includes: gwinsz.h+ Extensions: ConstraintKinds, CPP, DataKinds, DeriveDataTypeable, DeriveFunctor, DeriveGeneric+ Extensions: FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, RankNTypes+ Extensions: ScopedTypeVariables, StandaloneDeriving, TypeApplications, TypeFamilies Exposed-modules: Extra.Bool,- Extra.CIO,+ Extra.Debug,+ Extra.Debug2, Extra.Either,+ Extra.EnvPath,+ Extra.Except, Extra.Exit, Extra.Files,- Extra.GPGSign,+ Extra.FP,+ Extra.Generics,+ Extra.Generics.Show,+ Extra.IOThread Extra.List, Extra.HughesPJ,- Extra.Lock,+ Extra.Log, Extra.Misc,+ Extra.Monad.Supply, Extra.Net,- Extra.SSH,+ Extra.Orphans,+ Extra.Orphans2,+ Extra.Orphans3,+ Extra.Pretty,+ Extra.SafeCopy,+ Extra.SafeCopyDebug,+ Extra.Serialize,+ Extra.SerializeDebug,+ Extra.Text,+ Extra.TH, Extra.Time, Extra.Terminal,- Extra.IO, Extra.URI, Extra.URIQuery,- Test.QUnit,- Test.QuickCheck.Properties,- Extra.IOThread+ Extra.Verbosity+ if !impl(ghcjs)+ Build-Depends:+ show-please,+ filemanip,+ HUnit,+ process,+ process-extras,+ QuickCheck >= 2 && < 3+ Exposed-Modules:+ Extra.GPGSign,+ Extra.Lock,+ Extra.IO,+ Extra.Process,+ Extra.SSH,+ Test.QUnit,+ Test.QuickCheck.Properties++ if flag(network-uri)+ Build-Depends: network-uri >= 2.6+ else+ Build-Depends: network >= 2.4++ if flag(omit-serialize)+ CPP-Options: -DOMIT_SERIALIZE+ -- We still need cereal to implement instance SafeCopy Proxy+ Build-Depends: cereal+ else+ Build-Depends: cereal