packages feed

Unixutils 1.36 → 1.44

raw patch · 9 files changed

+456/−227 lines, 9 filesdep +pureMD5dep +zlibdep ~basesetup-changednew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: pureMD5, zlib

Dependency ranges changed: base

API changes (from Hackage documentation)

- System.Unix.Progress: defaultLevels :: [Set ProgressFlag]
- System.Unix.Progress: defaultQuietness :: MonadIO m => m Int
- System.Unix.Progress: eMessage :: MonadIO m => String -> a -> m a
- System.Unix.Progress: eMessageLn :: MonadIO m => String -> a -> m a
- System.Unix.Progress: ePutStr :: MonadIO m => String -> m ()
- System.Unix.Progress: ePutStrLn :: MonadIO m => String -> m ()
- System.Unix.Progress: lazyCommandD :: MonadIO m => String -> ByteString -> m [Output]
- System.Unix.Progress: lazyCommandQ :: MonadIO m => String -> ByteString -> m [Output]
- System.Unix.Progress: lazyCommandS :: MonadIO m => String -> ByteString -> m [Output]
- System.Unix.Progress: lazyCommandSF :: MonadIO m => String -> ByteString -> m [Output]
- System.Unix.Progress: modQuietness :: MonadIO m => (Int -> Int) -> m a -> m a
- System.Unix.Progress: qMessage :: MonadIO m => String -> a -> m a
- System.Unix.Progress: qMessageLn :: MonadIO m => String -> a -> m a
- System.Unix.Progress: qPutStr :: MonadIO m => String -> m ()
- System.Unix.Progress: qPutStrLn :: MonadIO m => String -> m ()
- System.Unix.Progress: quieter :: MonadIO m => Int -> m a -> m a
- System.Unix.Progress: quietnessLevels :: [Set ProgressFlag] -> Int -> Set ProgressFlag
- System.Unix.Progress: runProgress :: MonadIO m => (Int -> Set ProgressFlag) -> Progress m a -> m a
- System.Unix.Progress: type Progress m a = MonadIO m => StateT ProgressState m a
+ System.Unix.Chroot: fchroot :: FilePath -> IO a -> IO a
+ System.Unix.Chroot: forceList :: [a] -> IO [a]
+ System.Unix.Chroot: forceList' :: [Output] -> IO [Output]
+ System.Unix.Chroot: useEnv :: FilePath -> (a -> IO a) -> IO a -> IO a
+ System.Unix.QIO: eMessage :: MonadIO m => String -> b -> m b
+ System.Unix.QIO: eMessageLn :: MonadIO m => String -> b -> m b
+ System.Unix.QIO: ePutStr :: MonadIO m => String -> m ()
+ System.Unix.QIO: ePutStrLn :: MonadIO m => String -> m ()
+ System.Unix.QIO: initialQuietness :: MonadIO m => m Int
+ System.Unix.QIO: q02 :: MonadIO m => String -> m a -> m a
+ System.Unix.QIO: q12 :: MonadIO m => String -> m a -> m a
+ System.Unix.QIO: qDo :: MonadIO m => m () -> m ()
+ System.Unix.QIO: qMessage :: MonadIO m => String -> a -> m a
+ System.Unix.QIO: qMessageLn :: MonadIO m => String -> a -> m a
+ System.Unix.QIO: qPutStr :: MonadIO m => String -> m ()
+ System.Unix.QIO: qPutStrLn :: MonadIO m => String -> m ()
+ System.Unix.QIO: quieter :: MonadIO m => (Int -> Int) -> m a -> m a
+ System.Unix.QIO: quieter' :: MonadIO m => (Int -> Int) -> m a -> m a
+ System.Unix.QIO: quietness :: MonadIO m => m Int
+ System.Unix.QIO: showQ :: MonadIO m => String -> m a -> m a
+ System.Unix.QIO: v1 :: MonadIO m => m a -> m a
+ System.Unix.QIO: v2 :: MonadIO m => m a -> m a
+ System.Unix.QIO: v3 :: MonadIO m => m a -> m a
- System.Unix.Mount: umountBelow :: FilePath -> IO [(FilePath, (String, String, ExitCode))]
+ System.Unix.Mount: umountBelow :: Bool -> FilePath -> IO [(FilePath, (String, String, ExitCode))]
- System.Unix.Progress: lazyCommandP :: MonadIO m => (Int -> Set ProgressFlag) -> String -> ByteString -> m [Output]
+ System.Unix.Progress: lazyCommandP :: MonadIO m => String -> ByteString -> Set ProgressFlag -> m [Output]
- System.Unix.Progress: lazyProcessP :: MonadIO m => (Int -> Set ProgressFlag) -> FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> ByteString -> m [Output]
+ System.Unix.Progress: lazyProcessP :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> ByteString -> Set ProgressFlag -> m [Output]

Files

Setup.hs view
@@ -1,5 +1,14 @@ #!/usr/bin/runhaskell  import Distribution.Simple+import System.Cmd+import System.Exit -main = defaultMainWithHooks simpleUserHooks+main = defaultMainWithHooks simpleUserHooks {+         postBuild = runTestScript+       , runTests = runTestScript+       }++runTestScript _args _flag _pd _lbi =+    system "runhaskell Tests.hs" >>=+    \ code -> if code == ExitSuccess then return () else error "Test Failure"
+ System/Unix/Chroot.hs view
@@ -0,0 +1,123 @@+{-# LANGUAGE ForeignFunctionInterface #-}+-- | This module, except for useEnv, is copied from the build-env package.+module System.Unix.Chroot+    ( fchroot+    , useEnv+    , forceList+    , forceList'+    ) where++import Control.Exception (finally, evaluate)++import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as L+import Foreign.C.Error+import Foreign.C.String+import System.Directory (createDirectoryIfMissing)+import System.FilePath (dropTrailingPathSeparator, dropFileName)+import System.IO (hPutStr, stderr)+import System.Posix.Env (getEnv)+import System.Posix.IO+import System.Posix.Directory+import System.Unix.Process (Output(..))+import System.Unix.Progress (lazyCommandF)+import System.Unix.QIO (quieter, qPutStrLn)++foreign import ccall unsafe "chroot" c_chroot :: CString -> IO Int++-- |chroot changes the root directory to filepath+-- NOTE: it does not change the working directory, just the root directory+-- NOTE: will throw IOError if chroot fails+chroot :: FilePath -> IO ()+chroot fp = withCString fp $ \cfp -> throwErrnoIfMinus1_ "chroot" (c_chroot cfp)++-- |fchroot runs an IO action inside a chroot+-- fchroot performs a chroot, runs the action, and then restores the+-- original root and working directory. This probably affects the+-- chroot and working directory of all the threads in the process,+-- so...+-- NOTE: will throw IOError if internal chroot fails+fchroot :: FilePath -> IO a -> IO a+fchroot path action =+    do origWd <- getWorkingDirectory+       rootFd <- openFd "/" ReadOnly Nothing defaultFileFlags+       chroot path+       changeWorkingDirectory "/"+       action `finally` (breakFree origWd rootFd)+    where+      breakFree origWd rootFd =+          do changeWorkingDirectoryFd rootFd+             closeFd rootFd+             chroot "."+             changeWorkingDirectory origWd++-- |The ssh inside of the chroot needs to be able to talk to the+-- running ssh-agent.  Therefore we mount --bind the ssh agent socket+-- dir inside the chroot (and umount it when we exit the chroot.+useEnv :: FilePath -> (a -> IO a) -> IO a -> IO a+useEnv rootPath force action =+    do -- In order to minimize confusion, this QIO message is output+       -- at default quietness.  If you want to suppress it while seeing+       -- the output from your action, you need to say something like+       -- quieter (+ 1) (useEnv (quieter (\x->x-1) action))+       qPutStrLn $ "Entering environment at " ++ rootPath+       sockPath <- getEnv "SSH_AUTH_SOCK"+       home <- getEnv "HOME"+       copySSH home+       -- We need to force the output before we exit the changeroot.+       -- Otherwise we lose our ability to communicate with the ssh+       -- agent and we get errors.+       withSock sockPath . fchroot rootPath $ (action >>= force)+    where+      copySSH Nothing = return ()+      copySSH (Just home) =+          -- Do NOT preserve ownership, files must be owned by root.+          system' ("rsync -rlptgDHxS --delete " ++ home ++ "/.ssh/ " ++ rootPath ++ "/root/.ssh")+      withSock Nothing action = action+      withSock (Just sockPath) action =+          withMountBind dir (rootPath ++ dir) action+          where dir = dropTrailingPathSeparator (dropFileName sockPath)+      withMountBind toMount mountPoint action =+          doMount+          where+            doMount =+                do createDirectoryIfMissing True mountPoint+                   system' $ "mount --bind " ++ escapePathForMount toMount ++ " " ++ escapePathForMount mountPoint+                   result <- action+                   system' $ "umount " ++ escapePathForMount mountPoint+                   return result+      escapePathForMount = id	-- FIXME - Path arguments should be escaped+      system' s = lazyCommandF s L.empty >> return ()+          -- system s >>= testcode+          -- where testcode (ExitFailure n) = error (show s ++ " -> " ++ show n)+          --       testcode ExitSuccess = return ()++-- |A function to force the process output by examining it but not+-- printing anything.+forceList :: [a] -> IO [a]+forceList output = evaluate (length output) >> return output++-- |First send the process output to the and then force it.+forceList' :: [Output] -> IO [Output]+forceList' output = printOutput output >>= forceList++-- |Print all the output to the appropriate output channel+printOutput :: [Output] -> IO [Output]+printOutput output =+    mapM print output+    where+      print x@(Stdout s) = putStr (B.unpack s) >> return x+      print x@(Stderr s) = hPutStr stderr (B.unpack s) >> return x+      print x = return x++{-+printDots :: Int -> [Output] -> IO [Output]+printDots cpd output =+    foldM f 0 output >> return output+    where+      print rem (Stdout s) =+          let (dots, rem') = quotRem (rem + length s) in+          hPutStr stderr (replicate dots '.')+          return rem'+      print rem (Stderr s) = print rem (Stdout s)+-}
System/Unix/Files.hs view
@@ -1,7 +1,9 @@ module System.Unix.Files where -import System.Posix.Files-import System.IO.Error+import Control.Exception (catch)+import Prelude hiding (catch)+import System.Posix.Files (createSymbolicLink, removeLink)+import System.IO.Error (isAlreadyExistsError)  -- |calls 'createSymbolicLink' but will remove the target and retry if -- 'createSymbolicLink' raises EEXIST.
System/Unix/Misc.hs view
@@ -7,7 +7,9 @@     where  import Control.Exception-import Data.ByteString.Lazy.Char8 (empty)+import qualified Codec.Compression.GZip+import Data.ByteString.Lazy.Char8 (empty, readFile, writeFile)+import Data.Digest.Pure.MD5 (md5) import Data.Maybe import System.Cmd import System.Directory@@ -17,8 +19,10 @@ import System.Process import System.Unix.Process +{-# DEPRECATED md5sum "Use Data.ByteString.Lazy.Char8.readFile path >>= return . show . Data.Digest.Pure.MD5.md5" #-} md5sum :: FilePath -> IO String-md5sum path =+md5sum path = Data.ByteString.Lazy.Char8.readFile path >>= return . show . md5+{-     do       (text, _, exitCode) <- lazyProcess "md5sum" [path] Nothing Nothing empty >>= return . collectOutputUnpacked       let output = listToMaybe (words text)@@ -28,12 +32,15 @@               Nothing -> error ("Error in output of 'md5sum " ++ path ++ "'")               Just checksum -> return checksum         _ -> error ("Error running 'md5sum " ++ path ++ "'")+-} -{-# WARNING gzip "System.Unix.Misc.gzip does not properly escape its path arguments" #-}+{-# DEPRECATED gzip "Use Data.ByteString.Lazy.Char8.readFile path >>= Data.ByteString.Lazy.Char8.writeFile (path ++ \".gz\")" #-} gzip :: FilePath -> IO ()-gzip path =+gzip path = Data.ByteString.Lazy.Char8.readFile path >>= Data.ByteString.Lazy.Char8.writeFile (path ++ ".gz")+{-     do       result <- system ("gzip < " ++ path ++ " > " ++ path ++ ".gz")       case result of         ExitSuccess -> return ()         e -> error (show e)+-}
System/Unix/Mount.hs view
@@ -12,15 +12,17 @@ import Data.List import System.Directory import System.Exit+import System.IO (readFile) import System.Posix.Files import System.Unix.Process+import System.Unix.QIO (quieter, qPutStrLn)  -- Local Modules  import System.Unix.Process  -- In ghc610 readFile "/proc/mounts" hangs.  Use this instead.-rf path = lazyCommand ("cat '" ++ path ++ "'") empty >>= return . (\ (o, _, _) -> o) . collectOutputUnpacked+-- rf path = lazyCommand ("cat '" ++ path ++ "'") empty >>= return . (\ (o, _, _) -> o) . collectOutputUnpacked  -- |'umountBelow' - unmounts all mount points below /belowPath/ -- \/proc\/mounts must be present and readable.  Because of the way@@ -44,21 +46,26 @@ -- in \/proc\/mounts, which means we need to run "umount \/proc\/bus\/usb". -- -- See also: 'umountSucceeded'-umountBelow :: FilePath -- ^ canonicalised, absolute path+umountBelow :: Bool     -- ^ Lazy (umount -l flag) if true+            -> FilePath -- ^ canonicalised, absolute path             -> IO [(FilePath, (String, String, ExitCode))] -- ^ paths that we attempted to umount, and the responding output from the umount command-umountBelow belowPath =-    do procMount <- rf "/proc/mounts"+umountBelow lazy belowPath = quieter (\x->x-9) $+    do procMount <- readFile "/proc/mounts"        let mountPoints = map (unescape . (!! 1) . words) (lines procMount)            maybeMounts = filter (isPrefixOf belowPath) (concat (map tails mountPoints))+           args path = ["-f"] ++ if lazy then ["-l"] else [] ++ [path]        needsUmount <- filterM isMountPoint maybeMounts-       result <- mapM (\path -> umount [path,"-f","-l"] >>= return . ((,) path)) needsUmount+       results <- mapM (\ path -> qPutStrLn ("umountBelow: umount " ++ intercalate " " (args path)) >> umount (args path) >>= return . ((,) path)) needsUmount+       let results' = map fixNotMounted results+       mapM_ (\ (result, result') -> qPutStrLn (show result ++ (if result /= result' then " -> " ++ show result' else ""))) (zip results results')        -- Did /proc/mounts change?  If so we should try again because        -- nested mounts might have been revealed.-       procMount' <- rf "/proc/mounts"-       result' <- if procMount /= procMount' then-                      umountBelow belowPath else-                      return []-       return $ result ++ result'+       procMount' <- readFile "/proc/mounts"+       results'' <- if procMount /= procMount' then umountBelow lazy belowPath else return []+       return $ results' ++ results''+    where+      fixNotMounted (path, ("", err, ExitFailure 1)) | err == ("umount: " ++ path ++ ": not mounted\n") = (path, ("", "" , ExitSuccess))+      fixNotMounted x = x  -- |umountSucceeded - predicated suitable for filtering results of 'umountBelow' umountSucceeded :: (FilePath, (String, String, ExitCode)) -> Bool
System/Unix/Process.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE ScopedTypeVariables #-}-{-# OPTIONS -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures -Werror #-}+{-# OPTIONS -Wwarn -Wall -fno-warn-name-shadowing -fno-warn-missing-signatures #-} -- |functions for killing processes, running processes, etc module System.Unix.Process     (
System/Unix/Progress.hs view
@@ -1,38 +1,28 @@+-- | Support for changing the output of a lazyCommand in several ways:+-- +--     * Output a dot for every 128 characters of the original output+-- +--     * Increase the quietness level before running the command+-- +--     * Output only if (and when) the command fails+-- +--     * Throw an exception if the command fails+-- +--     * No output {-# LANGUAGE FlexibleContexts, PackageImports, RankNTypes, ScopedTypeVariables #-}-{-# OPTIONS -Wall -fno-warn-name-shadowing #-}--- |Control the progress reporting and output of subprocesses.+{-# OPTIONS -Wall -Werror -fno-warn-name-shadowing #-} module System.Unix.Progress     ( -- * The Progress Monad-      Progress-    , ProgressFlag(..)-    , quietnessLevels-    , runProgress+      ProgressFlag(..)     -- * Process launching     , lazyCommandP     , lazyProcessP-    -- * Quietness control-    , defaultQuietness-    , modQuietness-    , quieter-    -- * Output stream processing-    -- , prefixes-    -- , printOutput-    -- , dotOutput     , timeTask     , showElapsed-    , ePutStr-    , ePutStrLn-    , qPutStr-    , qPutStrLn-    , eMessage-    , eMessageLn-    , qMessage-    , qMessageLn     -- * Unit tests     , tests     -- * A set of lazyCommand functions for an example set of verbosity levels-    , defaultLevels-    , lazyCommandV -- Print everything+    , lazyCommandV -- Print everything - command, stdout, stderr, result     , lazyProcessV     , lazyCommandF -- Like V, but throws exception on failure     , lazyProcessF@@ -40,36 +30,24 @@     , lazyProcessE     , lazyCommandEF -- E and F combo     , lazyProcessEF-    , lazyCommandD -- Dots-    , lazyCommandQ -- Quiet-    , lazyCommandS -- Silent-    , lazyCommandSF     ) where -import Control.Exception (evaluate, try, SomeException)-import Control.Monad (when)-import Control.Monad.State (StateT, get, evalStateT)-import "mtl" Control.Monad.Trans ( MonadIO, liftIO, lift )-import Data.Array ((!), array, bounds)+import Control.Exception (evaluate)+import "mtl" Control.Monad.Trans ( MonadIO, liftIO ) import qualified Data.ByteString.Internal as B import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as L import Data.List (intercalate) import qualified Data.Set as Set import Data.Time (NominalDiffTime, getCurrentTime, diffUTCTime)-import System.Environment (getArgs, getEnv) import System.Exit (ExitCode(..))-import System.IO (hPutStrLn, stderr, hPutStr)-import System.Posix.Env (setEnv) import System.Unix.Process (lazyProcess, lazyCommand, Output(Stdout, Stderr),                             exitCodeOnly, stdoutOnly, mergeToStdout)+import System.Unix.QIO (quietness, ePutStr, ePutStrLn, qPutStr) import Test.HUnit  type ProgressState = Set.Set ProgressFlag --- |A monad for controlling progress reporting of subprocesses.-type Progress m a = MonadIO m => StateT ProgressState m a- -- |The flags that control what type of output will be sent to stdout -- and stderr.  Also, the ExceptionOnFail flag controls whether an -- exception will be thrown if the @ExitCode@ is not @ExitSuccess@.@@ -79,13 +57,77 @@     | All     | Errors     | Result-    | EchoOnFail-    | AllOnFail+    | EchoOnFail      -- ^ If Echo is present this has no effect+    | AllOnFail       -- ^ If All is present this has no effect     | ErrorsOnFail     | ResultOnFail     | ExceptionOnFail     deriving (Ord, Eq) +lazyCommandV :: MonadIO m => String -> L.ByteString -> m [Output]+lazyCommandV cmd input =+    progressFlags Set.empty >>= lazyCommandP cmd input++lazyProcessV :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]+lazyProcessV cmd args wd env input =+    progressFlags Set.empty >>= lazyProcessP cmd args wd env input++lazyCommandF :: MonadIO m => String -> L.ByteString -> m [Output]+lazyCommandF cmd input =+    progressFlags (Set.fromList [ExceptionOnFail]) >>= lazyCommandP cmd input++lazyProcessF :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]+lazyProcessF cmd args wd env input =+    progressFlags (Set.fromList [ExceptionOnFail]) >>= lazyProcessP cmd args wd env input++lazyCommandE :: MonadIO m => String -> L.ByteString -> m [Output]+lazyCommandE cmd input =+    progressFlags (Set.fromList [EchoOnFail, AllOnFail, ResultOnFail]) >>= lazyCommandP cmd input++lazyProcessE :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]+lazyProcessE cmd args wd env input =+    progressFlags (Set.fromList [EchoOnFail, AllOnFail, ResultOnFail]) >>= lazyProcessP cmd args wd env input++lazyCommandEF :: MonadIO m => String -> L.ByteString -> m [Output]+lazyCommandEF cmd input =+    progressFlags (Set.fromList [EchoOnFail, AllOnFail, ResultOnFail, ExceptionOnFail]) >>= lazyCommandP cmd input++lazyProcessEF :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]+lazyProcessEF cmd args wd env input =+    progressFlags (Set.fromList [EchoOnFail, AllOnFail, ResultOnFail, ExceptionOnFail]) >>= lazyProcessP cmd args wd env input++-- A usable example of the construction of a verbosity level+-- specification.  You can supply your own defaultLevels list and+-- build the flags* and lazyCommand* functions in a similar way.++-- |Normally (when quietness is 0) we see the command echoed and dots+-- for progress indication.  If quietness is -1 we also see the+-- command's output as it runs.  If quietness is 1 we don't see the+-- dots or the result code, and if it is 2 we don't see anything.+progressFlags :: MonadIO m => Set.Set ProgressFlag -> m (Set.Set ProgressFlag)+progressFlags extra =+    quietness >>= return . merge extra . Set.fromList . flags+    where+      flags n | n < 0 = [Echo, All, Result]+      flags 0 = [Echo, Dots, Result]+      flags 1 = [Echo]+      flags _ = []+      merge extra flags =+          (if Set.member All flags then Set.delete AllOnFail else id) .+          (if Set.member Echo flags then Set.delete EchoOnFail else id) .+          (if Set.member Result flags then Set.delete ResultOnFail else id) $ Set.union extra flags++{-+defaultLevels :: Int -> Set.Set ProgressFlag+defaultLevels n =+    if n < 0 then default+    where+      levels = map Set.fromList+               [ [Echo, All, Result]  -- when quietness == 0+               , [Echo, Dots, Result] -- when quietness == 1+               , [Echo]               -- when quietness == 2+               , [] ]                 -- when quietness == 2+ -- |Create a function that returns the flags used for a given -- quietness level. quietnessLevels :: [Set.Set ProgressFlag] -> Int -> Set.Set ProgressFlag@@ -93,88 +135,77 @@     a ! (min r . max l $ i)     where a = array (0, length flagLists - 1) (zip [0..] flagLists)           (l, r) = bounds a+-} +{-+-- |A monad for controlling progress reporting of subprocesses.+type Progress m a = MonadIO m => StateT ProgressState m a+ -- |Run the Progress monad with the given flags.  The flag set is -- compute from the current quietness level, <= 0 the most verbose -- and >= 3 the least. runProgress :: MonadIO m =>-               (Int -> Set.Set ProgressFlag)             -> Progress m a      -- ^ The progress task to be run             -> m a runProgress flags action =     quietness >>= evalStateT action . flags--lazyCommandP :: MonadIO m => (Int -> Set.Set ProgressFlag) -> String -> L.ByteString -> m [Output]-lazyCommandP flags cmd input =-    runProgress flags (lift (lazyCommand cmd input) >>= doProgress cmd)--lazyProcessP :: MonadIO m => (Int -> Set.Set ProgressFlag) -> FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]-lazyProcessP flags exec args cwd env input =-    runProgress flags (lift (lazyProcess exec args cwd env input) >>= doProgress (intercalate " " (exec : args)))---- |Look for occurrences of -v and -q in the command line arguments--- and the current values of environment variables VERBOSITY and--- QUIETNESS to compute a new value for QUIETNESS.  If you want to--- ignore the current QUIETNESS value say @setQuietness 0 >>--- computeQuietness@.-defaultQuietness :: MonadIO m => m Int-defaultQuietness = liftIO $-    do v1 <- try (getEnv "VERBOSITY" >>= return . read) >>= either (\ (_ :: SomeException) -> return 0) return-       v2 <- getArgs >>= return . length . filter (== "-v")-       q1 <- try (getEnv "QUIETNESS" >>= return . read) >>= either (\ (_ :: SomeException) -> return 0) return-       q2 <- getArgs >>= return . length . filter (== "-q")-       return $ q1 - v1 + q2 - v2---- |Look at the number of -v and -q arguments to get the baseline--- quietness / verbosity level for progress reporting.-quietness :: MonadIO m => m Int-quietness = liftIO (try (getEnv "QUIETNESS" >>= return . read)) >>=-            either (\ (_ :: SomeException) -> return 0) return+-} --- |Perform a task with the given quietness level.-modQuietness :: MonadIO m => (Int -> Int) -> m a -> m a-modQuietness f task =-    quietness >>= \ q0 ->-    setQuietness (f q0) >>-    task >>= \ result ->-    setQuietness q0 >>-    return result-    where-      -- Set the value of QUIETNESS in the environment.-      setQuietness :: MonadIO m => Int -> m ()-      setQuietness q = liftIO $ setEnv "QUIETNESS" (show q) True+-- |The @P@ versions are the most general cases, here you can specify+-- a function that turns the quietness level into any set of progress+-- flags you like.+lazyCommandP :: MonadIO m => String -> L.ByteString -> Set.Set ProgressFlag -> m [Output]+lazyCommandP cmd input flags =+    liftIO (lazyCommand cmd input) >>= doProgress flags cmd --- |Do an IO task with additional -v or -q arguments so that the--- progress reporting becomes more or less verbose.-quieter :: MonadIO m => Int -> m a -> m a-quieter q task = modQuietness (+ q) task+lazyProcessP :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> Set.Set ProgressFlag -> m [Output]+lazyProcessP exec args cwd env input flags =+    liftIO (lazyProcess exec args cwd env input) >>= doProgress flags (intercalate " " (exec : args)) --- |Inject a command's output into the Progress monad, handling command echoing,--- output formatting, result code reporting, and exception on failure.-doProgress :: MonadIO m => String -> [Output] -> Progress m [Output]-doProgress cmd output =-    get >>= \ s ->-    doEcho s output >>= doOutput s >>= doResult s >>= doFail s+-- |Inject a command's output into the Progress monad, handling+-- command echoing, output formatting, result code reporting, and+-- exception on failure.  The flag set we get from the monad already+-- reflects the program's current quietness level, so calling quieter+-- and using the qPutStr functions here is not necessary.+doProgress :: MonadIO m => ProgressState -> String -> [Output] -> m [Output]+doProgress flags cmd output =+    doEcho flags output >>= doOutput flags >>= doFailOutput flags >>= doResult flags >>= doFail flags     where-      doEcho s output-          | Set.member Echo s || (Set.member EchoOnFail s && exitCodeOnly output /= ExitSuccess) =-              liftIO (ePutStrLn ("-> " ++ cmd)) >> return output+      doEcho flags output+          | Set.member Echo flags =+              ePutStrLn ("-> " ++ cmd) >> return output+          | Set.member EchoOnFail flags && exitCodeOnly output /= ExitSuccess =+              ePutStrLn ("-> " ++ cmd) >> return output           | True = return output-      doOutput s output-          | Set.member All s || (Set.member AllOnFail s && exitCodeOnly output /= ExitSuccess) =-              liftIO (printOutput (prefixes opre epre output))-          | Set.member Dots s =-              liftIO (dotOutput 128 output)-          | Set.member Errors s || (Set.member ErrorsOnFail s && exitCodeOnly output /= ExitSuccess) =-              liftIO (printErrors (prefixes opre epre output))+      doOutput flags output+          | Set.member All flags =+              printOutput (prefixes opre epre output)+          | Set.member Dots flags =+              dotOutput 128 output+          | Set.member Errors flags =+              printErrors (prefixes opre epre output)           | True = return output-      doResult s output-          | Set.member Result s || (Set.member ResultOnFail s && exitCodeOnly output /= ExitSuccess) =-              liftIO (ePutStrLn ("<- " ++ show (exitCodeOnly output))) >> return output+      doFailOutput flags output+          | Set.member All flags =+              return output+          | Set.member AllOnFail flags && exitCodeOnly output /= ExitSuccess =+              ePutStrLn ("*** FAILURE: " ++ cmd ++ " -> " ++ show (exitCodeOnly output)) >>+              printOutput (prefixes opre epre output)+          | Set.member Errors flags =+              return output+          | Set.member ErrorsOnFail flags && exitCodeOnly output /= ExitSuccess =+              ePutStrLn ("*** FAILURE: " ++ cmd ++ " -> " ++ show (exitCodeOnly output)) >>+              printErrors (prefixes opre epre output)+          | True =+              return output+      doResult flags output+          | Set.member Result flags =+              ePutStrLn ("<- " ++ show (exitCodeOnly output)) >> return output+          | Set.member ResultOnFail flags && exitCodeOnly output /= ExitSuccess =+              ePutStrLn ("<- " ++ show (exitCodeOnly output)) >> return output           | True = return output-      doFail :: MonadIO m => ProgressState -> [Output] -> Progress m [Output]-      doFail s output-          | Set.member ExceptionOnFail s =+      doFail flags output+          | Set.member ExceptionOnFail flags =               case exitCodeOnly output of                 ExitSuccess -> return output                 result -> fail ("*** FAILURE: " ++ cmd ++ " -> " ++ show result)@@ -185,7 +216,7 @@ -- |Print one dot to stderr for every COUNT characters of output. dotOutput :: MonadIO m => Int -> [Output] -> m [Output] dotOutput groupSize output =-    mapM (\ (count, elem) -> ePutStr (replicate count '.') >> return elem) pairs >>= eMessageLn ""+    ePutStr "." >> mapM (\ (count, elem) -> ePutStr (replicate count '.') >> return elem) pairs >>= \ x -> ePutStr ".\n" >> return x     where       pairs = zip (dots 0 (map length output)) output       dots _ [] = []@@ -229,7 +260,7 @@     mapM (liftIO . print') output     where       print' (x, y) = print y >> return x-      print (Stdout s) = putStr (B.unpack s)+      print (Stdout s) = liftIO $ putStr (B.unpack s)       print (Stderr s) = ePutStr (B.unpack s)       print _ = return () @@ -254,7 +285,7 @@ showElapsed :: MonadIO m => String -> m a -> m a showElapsed label f =     do (result, time) <- timeTask f-       ePutStr (label ++ formatTime' time)+       qPutStr (label ++ formatTime' time)        return result  formatTime' :: NominalDiffTime -> String@@ -272,39 +303,6 @@       toMilliseconds f = round (f * 1000) -} --- |Send a string to stderr.-ePutStr :: MonadIO m => String -> m ()-ePutStr = liftIO . hPutStr stderr---- |@ePutStr@ with a terminating newline.-ePutStrLn :: MonadIO m => String -> m ()-ePutStrLn = liftIO . hPutStrLn stderr---- |If the current quietness level is less than one print a message.--- Control the quietness level using @quieter@.-qPutStr :: MonadIO m => String -> m ()-qPutStr s = quietness >>= \ q -> when (q < 0) (ePutStr s)---- |@qPutStr@ with a terminating newline.-qPutStrLn :: MonadIO m => String -> m ()-qPutStrLn s = quietness >>= \ q -> when (q < 0) (ePutStrLn s)---- |Print a message and return the second argument unevaluated.-eMessage :: MonadIO m => String -> a -> m a-eMessage message output = ePutStr message >> return output---- |@eMessage@ with a terminating newline.-eMessageLn :: MonadIO m => String -> a -> m a-eMessageLn message output = ePutStrLn message >> return output---- |@eMessage@ controlled by the quietness level.-qMessage :: MonadIO m => String -> a -> m a-qMessage message output = quietness >>= \ q -> when (q < 0) (ePutStr message) >> return output---- |@qMessage@ with a terminating newline.-qMessageLn :: MonadIO m => String -> a -> m a-qMessageLn message output = quietness >>= \ q -> when (q < 0) (ePutStrLn message) >> return output- -- |Unit tests. tests :: [Test] tests =@@ -316,64 +314,3 @@       p = B.pack       collect :: [(Output, Output)] -> String       collect = L.unpack . stdoutOnly . mergeToStdout . snd . unzip---- A usable example of the construction of a verbosity level--- specification.  You can supply your own defaultLevels list and--- build the flags* and lazyCommand* functions in a similar way.--defaultLevels :: [Set.Set ProgressFlag]-defaultLevels =-    map Set.fromList [ [Echo, All, Result]-                     -- , [Echo, Errors, Result]-                     , [Echo, Dots, Result]-                     -- , [Echo, Result]-                     , [Echo]-                     , [] ]--flags :: Int -> Set.Set ProgressFlag-flags = quietnessLevels defaultLevels--flagsF :: Int -> Set.Set ProgressFlag-flagsF = quietnessLevels (map (Set.union (Set.fromList [ExceptionOnFail])) defaultLevels)--flagsE :: Int -> Set.Set ProgressFlag-flagsE = quietnessLevels (map (Set.union (Set.fromList [EchoOnFail, AllOnFail, ResultOnFail])) defaultLevels)--flagsEF :: Int -> Set.Set ProgressFlag-flagsEF = quietnessLevels (map (Set.union (Set.fromList [EchoOnFail, AllOnFail, ResultOnFail, ExceptionOnFail])) defaultLevels)--lazyCommandV :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandV = lazyCommandP flags--lazyProcessV :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]-lazyProcessV = lazyProcessP flags--lazyCommandF :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandF = lazyCommandP flagsF--lazyProcessF :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]-lazyProcessF = lazyProcessP flagsF--lazyCommandE :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandE = lazyCommandP flagsE--lazyProcessE :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]-lazyProcessE = lazyProcessP flagsE--lazyCommandEF :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandEF = lazyCommandP flagsEF--lazyProcessEF :: MonadIO m => FilePath -> [String] -> Maybe FilePath -> Maybe [(String, String)] -> L.ByteString -> m [Output]-lazyProcessEF = lazyProcessP flagsEF--lazyCommandD :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandD cmd input = quieter 1 $ lazyCommandP flagsE cmd input--lazyCommandQ :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandQ cmd input = quieter 3 $ lazyCommandP flagsE cmd input--lazyCommandS :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandS cmd input = quieter 4 $ lazyCommandP flagsE cmd input--lazyCommandSF :: MonadIO m => String -> L.ByteString -> m [Output]-lazyCommandSF cmd input = quieter 4 $ lazyCommandP flagsEF cmd input
+ System/Unix/QIO.hs view
@@ -0,0 +1,132 @@+-- | Functions to manage the verbosity of a console program by storing+-- the quietness level in the system environment, specifically in the+-- $QUIETNESS variable.  This lets you avoid creating a StateT monad+-- to hold the quietness level.  Note that you don't attach a+-- verbosity level to individual message commands, you control the+-- quietness level for entire regions of your program and messages+-- only appear when quietness is less than one.+{-# LANGUAGE PackageImports, ScopedTypeVariables #-}+{-# OPTIONS -Wall #-}+module System.Unix.QIO+    ( ePutStr+    , ePutStrLn+    , eMessage+    , eMessageLn+    -- * Get/set quietness levels+    , initialQuietness+    , quietness+    -- * Do task with modified quietness level+    -- , modQuietness+    , quieter+    , quieter'+    -- , qZero+    -- * Do a task if quietness < 1+    , qDo+    -- * Output to stderr when quietness < 1+    , qPutStr+    , qPutStrLn+    , qMessage+    , qMessageLn+    -- * Some idioms+    , q12+    , q02+    , v1+    , v2+    , v3+    , showQ+    ) where++import Control.Exception (try, SomeException)+import "mtl" Control.Monad.Trans ( MonadIO, liftIO )+import System.Environment (getArgs, getEnv)+import System.IO (hPutStrLn, stderr, hPutStr)+import System.Posix.Env (setEnv)++ePutStr :: MonadIO m => String -> m ()+ePutStr s = liftIO $ hPutStr stderr s++ePutStrLn :: MonadIO m => String -> m ()+ePutStrLn s = liftIO $ hPutStrLn stderr s++eMessage :: MonadIO m => String -> b -> m b+eMessage s x = liftIO (hPutStr stderr s) >> return x++eMessageLn :: MonadIO m => String -> b -> m b+eMessageLn s x = liftIO (hPutStrLn stderr s) >> return x++-- | Compute an initial value for $QUIETNESS by examining the+-- $QUIETNESS and $VERBOSITY variables and counting the -v and -q+-- options on the command line.+initialQuietness :: MonadIO m => m Int+initialQuietness = liftIO $+    do v1 <- try (getEnv "VERBOSITY" >>= return . read) >>= either (\ (_ :: SomeException) -> return 0) return+       v2 <- getArgs >>= return . length . filter (== "-v")+       q1 <- try (getEnv "QUIETNESS" >>= return . read) >>= either (\ (_ :: SomeException) -> return 0) return+       q2 <- getArgs >>= return . length . filter (== "-q")+       return $ q1 - v1 + q2 - v2++-- |Get the current quietness level from the QUIETNESS environment variable.+quietness :: MonadIO m => m Int+quietness = liftIO (try (getEnv "QUIETNESS" >>= return . read)) >>=+            either (\ (_ :: SomeException) -> return 0) return++-- |Perform a task with the quietness level tansformed by f.  Use+-- @defaultQuietness >>= modQuietness . const@ to initialize the --+-- verbosity for a program.+quieter :: MonadIO m => (Int -> Int) -> m a -> m a+quieter f task =+    quietness >>= \ q0 ->+    setQuietness (f q0) >>+    task >>= \ result ->+    setQuietness q0 >>+    return result+    where+      -- Set the value of QUIETNESS in the environment.+      setQuietness :: MonadIO m => Int -> m ()+      setQuietness q = liftIO $ setEnv "QUIETNESS" (show q) True++-- |Dummy version of quieter, sometimes you want to strip out all the+-- quieter calls and see how things look, then restore them gradually.+-- Use this to help remember where they were.+quieter' :: MonadIO m => (Int -> Int) -> m a -> m a+quieter' _ x = x++-- |Peform a task only if quietness < 1.+qDo :: MonadIO m => m () -> m ()+qDo task = quietness >>= \ q -> if (q < 1) then task else return ()++-- |If the current quietness level is less than one print a message.+-- Control the quietness level using @quieter@.+qPutStr :: MonadIO m => String -> m ()+qPutStr s = qDo (ePutStr s)++-- |@qPutStr@ with a terminating newline.+qPutStrLn :: MonadIO m => String -> m ()+qPutStrLn s = qDo (ePutStrLn s)++-- |@eMessage@ controlled by the quietness level.+qMessage :: MonadIO m => String -> a -> m a+qMessage message output = qDo (ePutStr message) >> return output++-- |@qMessage@ with a terminating newline.+qMessageLn :: MonadIO m => String -> a -> m a+qMessageLn message output = qDo (ePutStrLn message) >> return output++-- |Print a message at quietness +1 and then do a task at quietness +3.+-- This is a pattern which gives the following behaviors:+-- Normally there is no output.  With -v only the messages are printed.+-- With -v -v the messages and the shell commands are printed with dots+-- to show progress.  With -v -v -v everything is printed.+q12 :: MonadIO m => String -> m a -> m a+q12 s a = quieter (+ 1) $ qPutStrLn s >> quieter (+ 2) a++q02 :: MonadIO m => String -> m a -> m a+q02 s a = qPutStrLn s >> quieter (+ 2) a++v1 a = quieter (\x->x-1) a+v2 a = quieter (\x->x-2) a+v3 a = quieter (\x->x-3) a++-- |For debugging+showQ :: MonadIO m => String -> m a -> m a+showQ s a = quietness >>= \ n -> ePutStrLn (s ++ ": quietness=" ++ show n) >> a
Unixutils.cabal view
@@ -1,11 +1,11 @@ Name:           Unixutils-Version:        1.36+Version:        1.44 License:        BSD3 License-File:	COPYING Author:         Jeremy Shaw, David Fox Homepage:       http://src.seereason.com/haskell-unixutils Category:	System-Build-Depends:  array, base >= 4 && <5, containers, mtl, HUnit, unix, regex-tdfa, process < 3, bytestring, directory, time, old-time, parallel >= 2, filepath+Build-Depends:  array, base >= 4 && <5, containers, mtl, HUnit, unix, regex-tdfa, process < 3, bytestring, directory, time, old-time, parallel >= 2, filepath, pureMD5, zlib Synopsis:       A crude interface between Haskell and Unix-like operating systems Maintainer:     jeremy@n-heptane.com Description:@@ -14,9 +14,21 @@ Build-type:	Simple ghc-options:	-O2 Exposed-modules:-        System.Unix.Crypt, System.Unix.Directory, System.Unix.FilePath, System.Unix.Misc, System.Unix.Mount,-        System.Unix.Process, System.Unix.ProcessExtra, System.Unix.ProcessStrict,-	System.Unix.Progress, System.Unix.OldProgress, System.Unix.Shadow, System.Unix.SpecialDevice, System.Unix.Files+        System.Unix.Chroot,+        System.Unix.Crypt,+        System.Unix.Directory,+        System.Unix.FilePath,+        System.Unix.Misc,+        System.Unix.Mount,+        System.Unix.Process,+        System.Unix.ProcessExtra,+        System.Unix.ProcessStrict,+        System.Unix.Progress,+        System.Unix.OldProgress,+        System.Unix.QIO,+        System.Unix.Shadow,+        System.Unix.SpecialDevice,+        System.Unix.Files Extra-libraries: crypt  -- For more complex build options see: