packages feed

concurrentoutput 0.1 → 0.2

raw patch · 3 files changed

+75/−85 lines, 3 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- System.Terminal.Concurrent: data ConcurrentOutput
- System.Terminal.Concurrent: startConcurrentOutput :: IO ConcurrentOutput
- System.Terminal.Concurrent: writeConcurrent :: ConcurrentOutput -> String -> IO ()
- System.Terminal.Concurrent: writeConcurrentDone :: ConcurrentOutput -> String -> IO ()
+ System.Terminal.Concurrent: getConcurrentOutputter :: IO (String -> IO ())

Files

Test.hs view
@@ -1,17 +1,18 @@ import System.Terminal.Concurrent import Control.Concurrent -testThread co n1 n2 = do+testThread writeC n1 n2 = do 	t <- myThreadId 	threadDelay $ n1 * 100000-	writeConcurrent co ("Thread " ++ show t ++ ": ")+	writeC("Thread " ++ show t ++ ": ") 	threadDelay $ n1 * 100000-	writeConcurrent co "still working... "+	writeC "still working... "+	writeC "" 	threadDelay $ n2 * 100000-	writeConcurrentDone co "done"+	writeC "done\n"  main = do-	co <- startConcurrentOutput-	mapM forkIO  $ zipWith (testThread co) [0,0,3,4,3] [4,8,5,5,2]+	writeC <- getConcurrentOutputter+	mapM forkIO  $ zipWith (testThread writeC) [0,0,3,5,2,3] [4,8,5,4,1,2] 	threadDelay $ 15*100000 	
concurrentoutput.cabal view
@@ -1,5 +1,5 @@ Name:            concurrentoutput-version:         0.1+version:         0.2 Category:        User Interfaces author:          Joachim Breitner maintainer:      Joachim Breitner <mail@joachim-breitner.de>@@ -7,7 +7,7 @@ Description:     This library provides a simple interface to output status                  messages from more than one thread. 		 .-		 It will continue adding information (such as dots, or "done")+		 It will continue adding information (such as dots, or \"done\") 		 to the correct line and continue scrolling when a line is done. 		 . 		 For example, this screen:@@ -31,7 +31,7 @@ 		 @_@ 		 . 		 If standard output is not a terminal, it will only print-		 complete lines.+		 complete lines and not output any control characters. 		 . 		 At the moment, it can only handle lines that are shorter than 		 the terminal. If they are not, output will be garbled again.@@ -54,5 +54,5 @@  source-repository this   type:     darcs-  location: http://darcs.nomeata.de/darcswatch/-  tag:      v0.1+  location: http://darcs.nomeata.de/concurrentoutput/+  tag:      v0.2
src/System/Terminal/Concurrent.hs view
@@ -1,97 +1,86 @@--- | This library provides a simple interface to output status---   messages from more than one thread.+-----------------------------------------------------------------------------+-- |+-- Module      :  System.Terminal.Concurrent+-- Copyright   :  © 2009 Joachim Breitner +-- License     :  BSD-style (see the file LICENSE)+-- +-- Maintainer  :  Joachim Breitner <mail@joachin-breitner.de>+-- Stability   :  experimental+-- Portability :  non-portable (concurrency) -----   It will continue adding information (such as dots, or "done")---   to the correct line corresponding to the issuing thread and continue---   scrolling when a line is done.+-- This library provides a simple interface to output status+-- messages from more than one thread.+--+-- It will continue adding information (such as dots, or "done")+-- to the correct line corresponding to the issuing thread and continue+-- scrolling when a line is done.+--+----------------------------------------------------------------------------- module System.Terminal.Concurrent -	( ConcurrentOutput-	, startConcurrentOutput-	, writeConcurrent-	, writeConcurrentDone+	( getConcurrentOutputter 	) where  import Control.Concurrent import Control.Monad+import Data.IORef import Data.List import System.IO -data ConcurrentOutput = ConcurrentOutput { coChan :: Chan (ThreadId, Bool, String) }---- | Starts the thread responsible for gathering and formatting the outputs.---    ---   You can not kill this thread, so only start one for your application.-startConcurrentOutput :: IO ConcurrentOutput-startConcurrentOutput = do-	chan <- newChan-	isTerm <- hIsTerminalDevice stdout-	forkIO (receiverThread chan isTerm)-	return (ConcurrentOutput chan)---- | Begin a new line of output for your thread or, if there already is one, append to it.---    ---   Do not put newline characters in there, or it will break the output. This---   will also happen if your total line will be wider than the terminal.-writeConcurrent :: ConcurrentOutput -> String -> IO ()-writeConcurrent (ConcurrentOutput chan) s = do-	threadId <- myThreadId-	writeChan chan (threadId, False, s)---- | Finish your line of output with the given string.---    ---   Do not put newline characters in there, or it will break the output. This---   will also happen if your total line will be wider than the terminal.-writeConcurrentDone :: ConcurrentOutput -> String -> IO ()-writeConcurrentDone (ConcurrentOutput chan) s = do-	threadId <- myThreadId-	writeChan chan (threadId, True, s)- -- What threadid has written to what line, counting from below type OutputState = [(ThreadId,String)] -receiverThread :: Chan (ThreadId, Bool, String) -> Bool -> IO ()-receiverThread chan True = runner []-	where runner os = do-		(t,d,s) <- readChan chan+-- | Returns an IO action to be called to output strings in a thread-safe manner.+getConcurrentOutputter :: IO (String -> IO ())+getConcurrentOutputter = do+	chan <- newChan+	isTerm <- hIsTerminalDevice stdout+	osRef <- newIORef []	+	lock <- newQSem 1+	return $ \s -> unless (null s) $ do+		waitQSem lock+		t <- myThreadId+		os <- readIORef osRef -	        let s' = maybe s (++s) (lookup t os)-		let os'= if d then filter ((/=t).fst) os-		              else replaceOrAppend t s' os-		unless (null os) $ do-			-- Go up some lines-			putStr $ "\ESC[" ++ show (length os) ++ "A"-			-- Clear everything-			unless (null os) $ putStr "\ESC[0J"-		-- Go up once more, to make it fit-		putStr $ "\ESC[1A"+	        let all_lines = lines $ maybe s (++s) (lookup t os)+		let done = last s == '\n' -		-- Write a new done line, if suitable-		when d $ putLnStr s' +		let (done_lines, current_line) =+			if done || null all_lines+                        then (all_lines,     Nothing)+			else (init all_lines, Just (last all_lines))  -		-- Write the new not-yet-done lines-		mapM_ (putLnStr) $ map snd os'+		when isTerm $ do+			unless (null os) $ do+				-- Go up some lines+				putStr $ "\ESC[" ++ show (length os) ++ "A"+				-- Clear everything+				unless (null os) $ putStr "\ESC[0J"+			-- Go up once more, to make it fit+			putStr $ "\ESC[1A" -		-- Go to the beginning of the next new line-		putStrLn ""+			-- Write a new done line, if suitable+			mapM_ putLnStr done_lines+		unless isTerm $ do+			mapM_ putStrLn done_lines -		runner os'--- We do not have a terminal, just output complete lines-receiverThread chan False = runner []-	where runner os = do-		(t,d,s) <- readChan chan+		-- Update line+		let os'= replaceAppendOrDelete t current_line os -	        let s' = maybe s (++s) (lookup t os)-		let os'= if d then filter ((/=t).fst) os-		              else replaceOrAppend t s' os+		when isTerm $ do+			-- Write the new not-yet-done lines+			mapM_ (putLnStr) $ map snd os' -		-- Write a new done line, if suitable-		when d $ putStrLn s' +			-- Go to the beginning of the next new line+			putStrLn "" -		runner os'+		writeIORef osRef os'+		signalQSem lock -replaceOrAppend :: (Eq a) => a -> b -> [(a,b)] -> [(a,b)]-replaceOrAppend a b []                       = [(a,b)]-replaceOrAppend a b ((a',b'):xs) | a == a'   = (a,b):xs-                                 | otherwise = (a',b') : replaceOrAppend a b xs+replaceAppendOrDelete :: (Eq a) => a -> Maybe b -> [(a,b)] -> [(a,b)]+replaceAppendOrDelete a Nothing  []                     = []+replaceAppendOrDelete a (Just b) []                     = [(a,b)]+replaceAppendOrDelete a Nothing  ((a',_):xs)  | a == a' = xs+replaceAppendOrDelete a (Just b) ((a',b'):xs) | a == a' = (a,b):xs+replaceAppendOrDelete a mbB      (x:xs)                 = x : replaceAppendOrDelete a mbB xs  putLnStr s = putStr ('\n':s)