packages feed

concurrentoutput (empty) → 0.1

raw patch · 5 files changed

+201/−0 lines, 5 filesdep +basesetup-changed

Dependencies added: base

Files

+ LICENSE view
@@ -0,0 +1,26 @@+Copyright © 2009 Joachim Breitner+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+   notice, this list of conditions and the following disclaimer in the+   documentation and/or other materials provided with the distribution.+3. Neither the name of the University nor the names of its contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF+SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,3 @@+module Main where+import Distribution.Simple+main = defaultMain
+ Test.hs view
@@ -0,0 +1,17 @@+import System.Terminal.Concurrent+import Control.Concurrent++testThread co n1 n2 = do+	t <- myThreadId+	threadDelay $ n1 * 100000+	writeConcurrent co ("Thread " ++ show t ++ ": ")+	threadDelay $ n1 * 100000+	writeConcurrent co "still working... "+	threadDelay $ n2 * 100000+	writeConcurrentDone co "done"++main = do+	co <- startConcurrentOutput+	mapM forkIO  $ zipWith (testThread co) [0,0,3,4,3] [4,8,5,5,2]+	threadDelay $ 15*100000+	
+ concurrentoutput.cabal view
@@ -0,0 +1,58 @@+Name:            concurrentoutput+version:         0.1+Category:        User Interfaces+author:          Joachim Breitner+maintainer:      Joachim Breitner <mail@joachim-breitner.de>+Synopsis:        Ungarble output from several threads +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")+		 to the correct line and continue scrolling when a line is done.+		 .+		 For example, this screen:+		 .+		 @Thread ThreadId 27: still working... done@+		 .+                 @Thread ThreadId 25: still working... @+		 .+                 @Thread ThreadId 26: still working... @+	         .+		 @_@+		 .+		 will, once thread 25 has finished, look like this+		 .	+                 @Thread ThreadId 25: still working... done@+		 .+		 @Thread ThreadId 27: still working... done@+		 .+                 @Thread ThreadId 26: still working... @+	         .+		 @_@+		 .+		 If standard output is not a terminal, it will only print+		 complete lines.+		 .+		 At the moment, it can only handle lines that are shorter than+		 the terminal. If they are not, output will be garbled again.+		 .+		 +Cabal-Version:   >= 1.6+License:         BSD3+License-File:    LICENSE+extra-source-files: Test.hs+Build-Type:      Simple++Library+        hs-source-dirs: src+        Exposed-Modules: System.Terminal.Concurrent+        Build-Depends:  base > 3 && < 4++source-repository head+  type:     darcs+  location: http://darcs.nomeata.de/concurrentoutput/++source-repository this+  type:     darcs+  location: http://darcs.nomeata.de/darcswatch/+  tag:      v0.1
+ src/System/Terminal/Concurrent.hs view
@@ -0,0 +1,97 @@+-- | 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+	) where++import Control.Concurrent+import Control.Monad+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++	        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"++		-- Write a new done line, if suitable+		when d $ putLnStr s' ++		-- Write the new not-yet-done lines+		mapM_ (putLnStr) $ map snd os'++		-- Go to the beginning of the next new line+		putStrLn ""++		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++	        let s' = maybe s (++s) (lookup t os)+		let os'= if d then filter ((/=t).fst) os+		              else replaceOrAppend t s' os++		-- Write a new done line, if suitable+		when d $ putStrLn s' ++		runner os'++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++putLnStr s = putStr ('\n':s)