diff --git a/src/Main.hs b/src/Main.hs
--- a/src/Main.hs
+++ b/src/Main.hs
@@ -1,55 +1,43 @@
 
 module Main where
 
-import Control.Exception
-import Control.Monad (when)
-import Control.Monad.Error hiding (mapM)
-import Control.Monad.Plus hiding (mapM)
-import Data.Semigroup hiding (Option)
-import Data.List (find)
-import Data.Maybe (fromMaybe, maybeToList)
-import Data.Traversable (mapM)     
-import Data.Typeable
-import System.IO
-import System.Exit
-import System.Environment
 import System.Console.GetOpt
+import Data.Default
+import Data.Maybe
+import Data.Semigroup hiding (Option)
 import Text.Transf
-
-import Prelude hiding (readFile, writeFile)
+import Text.Transf.Process
 
+main        = defaultMain' "transf" optDesc transf
+transf opts = haskellT <> musicT' (getMusicOpts opts) <> musicHaskellT' (getMusicOpts opts) <> musicExtraT
 
 
-version = "transf-0.8"
-header  = "Usage: transf [options]\n" ++
-          "Usage: transf [options] files...\n" ++
-          "\n" ++
-          "Options:"
-
-options = [ 
-    (Option ['h'] ["help"]          (NoArg 1)   "Print help and exit"),
-    (Option ['v'] ["version"]       (NoArg 2)   "Print version and exit")
-  ]                                          
-    
-main = do
-    (opts, args, optErrs) <- getOpt Permute options `fmap` getArgs
-
-    let usage = usageInfo header options
-    let printUsage   = putStr (usage ++ "\n")   >> exitWith ExitSuccess
-    let printVersion = putStr (version ++ "\n") >> exitWith ExitSuccess
+data Opt
+    = Format     String
+    | Resolution Int
+    | Resize     Int
+getFormat :: Opt -> Maybe String
+getFormat     (Format a)        = Just a
+getFormat     _                 = Nothing
+getResolution (Resolution a)    = Just a
+getResolution _                 = Nothing
+getResize     (Resize a)        = Just a
+getResize     _                 = Nothing
 
-    when (1 `elem` opts) printUsage
-    when (2 `elem` opts) printVersion  
-    runFilter opts
+firstJust :: [Maybe a] -> Maybe a
+firstJust = listToMaybe . catMaybes
 
+getMusicOpts :: [Opt] -> MusicOpts
+getMusicOpts xs = MusicOpts {
+        format     = fromMaybe (format def) $ firstJust $ fmap getFormat xs,
+        resolution = fromMaybe (resolution def) $ firstJust $ fmap getResolution xs,
+        resize     = fromMaybe (resize def) $ firstJust $ fmap getResize xs
+    }
 
-runFilter _ = transform stdin stdout
+optDesc :: [OptDescr Opt]
+optDesc = [
+        Option [] ["format"]        (ReqArg Format "pdf|png|ps")     "Music output format",
+        Option [] ["resolution"]    (ReqArg (Resolution . read) "N") "Music resolution",
+        Option [] ["resize"]        (ReqArg (Resize . read) "N")     "Music resize factor"
+    ]
 
-transform fin fout = do
-    res <- runTF $ do
-        input  <- liftIO $ hGetContents fin  
-        output <- runTransf' (printT <> evalT <> musicT) input
-        liftIO $ hPutStr fout output
-    case res of
-        Left e -> putStrLn $ "Error: " ++ e
-        Right _ -> return ()
diff --git a/src/Text/Transf.hs b/src/Text/Transf.hs
--- a/src/Text/Transf.hs
+++ b/src/Text/Transf.hs
@@ -1,210 +1,449 @@
 
-{-# LANGUAGE
+{-# LANGUAGE                  
     GeneralizedNewtypeDeriving #-}
 
 module Text.Transf (
+        -- * Basic types
         Line,
         Lines,
         RelativePath,
 
+        -- * The Context type
+        Context,
+        ContextT,
+        runContext,
+        runContextT,
+
+        -- * Transformormations
+        Transform,
+
+        -- -- ** Creating new transformations
+        transform,
+        -- newTransform,
+
+        -- ** Running transformations
+        runTransform,
+        -- runTransformIO,
+
         -- * Combinators
-        Transf,
-        newTransf,
-        namedTransf,
+        -- ** Input/output
         readFile,
         writeFile,
-        eval,
-        eval',
         inform,
-
-        -- ** Runing transformations
-        runTransf,
-        runTransf',
-
-        -- * TF monad
-        TFT,
-        TF,
-        runTFT,
-        runTF,
+        -- ** Evaluation
+        eval,
+        evalWith,
+        addPost,
 
-        -- * Predefined transformations
+        -- * Transformormations
         printT,
-        evalT,
+        evalT,   
         musicT,
-) 
-where
+        MusicOpts(..),
+        musicT',
+        haskellT,
+        musicHaskellT,
+        musicHaskellT',
+        musicExtraT,
+  ) where
 
-import Prelude hiding (mapM, readFile, writeFile)         
+import Prelude hiding (mapM, readFile, writeFile)
 
+import Control.Applicative
 import Control.Exception
-import Control.Monad.Error hiding (mapM)
-import Control.Monad.Plus hiding (mapM)
+import Control.Concurrent.Async
+import Control.Monad
+import Control.Monad.Writer hiding ((<>))
+import Control.Monad.Error
+import Control.Monad.Plus
+import Numeric
+import Data.Maybe
+import Data.Default
 import Data.Semigroup
-import Data.Traversable (mapM)     
+import Data.Traversable
 import Data.Typeable
 import Data.Hashable
-import Data.Maybe
-import Language.Haskell.Interpreter hiding (eval)
 import System.IO (hPutStr, stderr)
 import System.Process
-import Numeric
-import Music.Prelude.Basic
+import Language.Haskell.Interpreter hiding (eval)
 
 import qualified Prelude
-import qualified Data.List as List
-import qualified Data.Char as Char
-
+import qualified Data.List          as List
+import qualified Data.Char          as Char
+import qualified Data.Traversable   as Traversable
+import qualified Music.Prelude.Basic as Music
 
--- | A line of text.
+-- |
+-- A single line of text.
+--
 type Line = String
 
--- | Multiple-line text.
+-- |
+-- Multiple lines of text.
+--
 type Lines = String
 
--- | A relative file path.
+-- | 
+-- A relative file path.
+--
 type RelativePath = FilePath
 
--- | Transformer version of 'TF'.
-newtype TFT m a = TFT { runTFT :: ErrorT String m a }
-    deriving (Monad, MonadPlus, MonadError String, MonadIO)
+-- | 
+-- Action to be executed after main transf pass.
+--
+newtype Post m = Post [ContextT m ()]
+    deriving (Monoid)
 
--- | The 'TF' monad defines the context of a transformation function.
---   Think of it as a restricted version of 'IO' or 'STM'.
-type TF = TFT IO
+post :: ContextT m () -> Post m
+post = Post . return
 
-runTF :: TF a -> IO (Either String a)
-runTF = runErrorT . runTFT
+type PrimContextT m = ErrorT String (WriterT (Post m) m)
 
--- | Read a file.
-readFile :: RelativePath -> TF String
-readFile path = do
-    input <- liftIO $ try $ Prelude.readFile path
-    case input of
-        Left e  -> throwError $ "readFile: " ++ show (e::SomeException)
-        Right a -> return a
+newtype ContextT m a = ContextT { runContextT_ :: PrimContextT m a }
+    deriving ( Monad, MonadIO, MonadPlus,
+        MonadError String, MonadWriter (Post m) )
 
--- appendFile   :: RelativePath -> String -> TF ()
+-- | 
+-- The 'Context' monad defines the context of a transformation.
+--
+-- The main purpose of this type is to restrict the the number
+-- of functions you can pass to 'transform'. 
+--
+type Context = ContextT IO
 
--- | Write to a file.
-writeFile :: RelativePath -> String -> TF ()
-writeFile path str = liftIO $ Prelude.writeFile path str
+-- |
+-- Run a computation in the 'Context' monad.
+--
+runContext :: Context a -> IO (Either String a)
+runContext x = do
+    (r, Post posts) <- runC x
+    parallel_ (fmap ignoreErrorsAndPost posts)
+    return r
+    where
+        runC = runWriterT . runErrorT . runContextT_
 
--- | Evaluate a Haskell expression.
-eval :: Typeable a => String -> TF a
-eval = eval' ["Prelude", "Music.Prelude.Basic"]
+runContextT :: Monad m => ContextT m a -> m (Either String a)
+runContextT = runContextT' True
 
--- | Evaluate a Haskell expression.
-eval' :: Typeable a => [String] -> String -> TF a
-eval' imps str = do                                         
-    res <- liftIO $ runInterpreter $ do
-        setImports imps
-        interpret str infer
-    case res of
-        Left e -> throwError $ "eval: " ++ show e
-        Right a -> return a
+runContextT' :: Monad m => Bool -> ContextT m a -> m (Either String a)
+runContextT' recur x = do
+    (r, Post posts) <- runC x
+    if recur then runContextT' False (sequence_ posts) else return (return ())
+    return r
+    where
+        runC = runWriterT . runErrorT . runContextT_
 
--- | Write to the standard error stream.
-inform :: String -> TF ()
-inform m = liftIO $ hPutStr stderr $ m ++ "\n"
+ignoreErrorsAndPost :: ContextT IO a -> IO ()
+ignoreErrorsAndPost x = (runWriterT . runErrorT . runContextT_) x >> return ()
 
--- | A transformation function, or transformation for short.
-data Transf 
+
+
+
+
+-- |
+-- A transformation.
+-- 
+data Transform
     = CompTrans {
-        decomp    :: [Transf]
+        decomp    :: [Transform]
     }
     | SingTrans {
-        guard     :: (Line -> Bool, Line -> Bool),
-        function  :: Lines -> TF Lines
+        delimiters :: (Line -> Bool, Line -> Bool),
+        function   :: Lines -> Context Lines
     }
 
-instance Semigroup Transf where
+doTrans (SingTrans _ f) = f
+
+instance Semigroup Transform where
     a <> b = CompTrans [a,b]
+instance Monoid Transform where
+    mempty  = CompTrans []
+    mappend = (<>)
 
--- | Create a new transformation.
-newTransf :: (Line -> Bool) -> (Line -> Bool) -> (Lines -> TF Lines) -> Transf
-newTransf b e = SingTrans (b, e)
+-- | Create a new transformation. For example:
+--
+-- > newTransform start stop change
+--
+-- This creates a new transformation that searches its input for consecutive
+-- sequences of lines delimited by lines accepted by the @start@ and @stop@
+-- functions, and applies the given change function to these chunks.
+--
+-- To create a suitable change function, use the combinators defined below.
+--
+newTransform :: (Line -> Bool) -> (Line -> Bool) -> (Lines -> Context Lines) -> Transform
+newTransform b e = SingTrans (b, e)
 
--- | Create a new transformation. 
+namedFence :: String -> String -> Bool
+namedFence name = namedFenceWithPrefix "```" name `oneOf` namedFenceWithPrefix "~~~" name
+
+namedFenceWithPrefix :: String -> String -> String -> Bool
+namedFenceWithPrefix prefix name = (== (prefix ++ name)) . trimEnd
+
+-- | Create a new transformation.
 --
 --   This transformation processes everything in between lines containing
+--   a fence such as
 --
 --   > ~~~name
 --   > ~~~
 --
---   or alternatively
+--   or
 --
 --   > ```name
 --   > ```
 --
 --   where @name@ is the name of the transformation.
 --
-namedTransf :: String -> (Lines -> TF Lines) -> Transf
-namedTransf name f = newTransf (namedGuard name) (namedGuard "") f
+--   To create a suitable change function, use the combinators defined below.
+--
+transform :: String -> (Lines -> Context Lines) -> Transform
+transform name = newTransform (namedFence name) (namedFence "")
 
-namedGuard :: String -> String -> Bool
-namedGuard name = namedGuardWithPrefix "```" name `or'` namedGuardWithPrefix "~~~" name
+-- | 
+-- Run a transformation with the given error handler and input.
+--
+runTransformIO :: Transform -> (String -> IO String) -> String -> IO String
+runTransformIO t handler input = do
+    res <- runContext $ runTransform t input
+    case res of
+        Left e  -> handler e
+        Right a -> return a
 
-namedGuardWithPrefix :: String -> String -> String -> Bool
-namedGuardWithPrefix prefix name = (== (prefix ++ name)) . trimEnd
+-- | 
+-- Run a transformation in the 'Context' monad.
+--
+runTransform :: Transform -> String -> Context String
+runTransform = go
+    where
+        go (CompTrans [])     as = return as
+        go (CompTrans (t:ts)) as = do
+            bs <- go t as
+            go (CompTrans ts) bs
+        go (SingTrans (start,stop) f) as = do
+            let bs = (sections start stop . lines) as                   :: [([Line], Maybe [Line])]
+            let cs = fmap (first unlines . second (fmap unlines)) bs    :: [(String, Maybe String)]
+            ds <- Traversable.mapM (secondM (Traversable.mapM f)) cs    :: Context [(String, Maybe String)]
+            return $ concatMap (\(a, b) -> a ++ fromMaybe [] b ++ "\n") ds
 
+----------------------------------------------------------------------------------------------------
 
-printT :: Transf
-printT = namedTransf "print" $ \input -> do
-    return input
+-- |
+-- Read a file.
+--
+readFile :: RelativePath -> Context String
+readFile path = do
+    input <- liftIO $ try $ Prelude.readFile path
+    case input of
+        Left e  -> throwError $ "readFile: " ++ show (e::SomeException)
+        Right a -> return a
 
-evalT :: Transf
-evalT = namedTransf "eval" $ \input -> do
-    eval input
+-- appendFile   :: RelativePath -> String -> Context ()
 
-musicT :: Transf
-musicT = namedTransf "music" $ \input -> do
-    let name = showHex (abs $ hash input) ""
-    music <- eval input :: TF (Score Note)
-    liftIO $ writeLy (name++".ly") music
-    liftIO $ system $ "lilypond -f png -dresolution=300 "++name++".ly"
-    liftIO $ system $ "convert -transparent white -resize 30% "++name++".png "++name++"x.png"
-    return $ "![]("++name++"x.png)"
-    --  -resize 30%
+-- |
+-- Write to a file.
+--
+writeFile :: RelativePath -> String -> Context ()
+writeFile path str = liftIO $ Prelude.writeFile path str
 
+-- |
+-- Evaluate a Haskell expression.
+--
+eval :: Typeable a => String -> Context a
+eval = evalWith ["Prelude", "Music.Prelude.Basic", "Control.Monad.Plus"] -- FIXME
 
--- | Run a transformation with the given error handler and input.
-runTransf :: Transf -> (String -> IO String) -> String -> IO String
-runTransf t handler input = do
-    res <- runTF $ runTransf' t input
+-- | Evaluate a Haskell expression with the given modules in scope.
+--   Note that "Prelude" is /not/ implicitly imported.
+--
+--
+--   All requested modules must be present on the system or the computation
+--   will fail. Also, the string must be a valid Haskell expression using
+--   constructs which in scope after loading the given modules.
+--
+--   Errors can be caught using 'catchError'.
+--
+evalWith :: Typeable a => [String] -> String -> Context a
+evalWith imps str = do
+    res <- liftIO $ runInterpreter $ do
+        setImports imps
+        interpret str infer
     case res of
-        Left e  -> handler e
+        Left e -> throwError $ "eval: " ++ show e
         Right a -> return a
 
+-- |
+-- Write to the standard error stream.
+--
+inform :: String -> Context ()
+inform m = liftIO $ hPutStr stderr $ m ++ "\n"
 
-runTransf' :: Transf -> String -> TF String
-runTransf' (CompTrans []) as = return as
+-- |
+-- Register an action to be run after text processing has finished.
+-- This can be used to optimize tasks such as external file generations.
+--
+-- Note that addPost does not work trasitively, i.e. post actions of
+-- post actions are thrown away.
+-- 
+addPost :: Context () -> Context ()
+addPost = tell . post
 
-runTransf' (CompTrans (t:ts)) as = do
-    bs <- runTransf' t as
-    runTransf' (CompTrans ts) bs
+
+----------------------------------------------------------------------------------------------------
+
+-- |
+-- This named transformation posts its input to the standard error stream
+-- and returns nothing.
+--
+printT :: Transform
+printT = transform "print" $ \input -> inform input >> return ""
+
+-- |
+-- This named transformation evaluates its input as a Haskell expression of
+-- type 'String' and returns the value of the expression.
+--
+-- For example the input
+--
+-- > ~~~haskell
+-- > "The number is " ++ show $ 3 + 2
+-- > ~~~
+--
+-- Will be transformed into
+--
+-- > The number is 6
+--
+evalT :: Transform
+evalT = transform "eval" $ \input -> evalWith ["Prelude"] input
+
+-- TODO move to separate module and/or package
+
+data MusicOpts = MusicOpts {
+        format     :: String,
+        resolution :: Int,
+        resize     :: Int
+    }
+instance Default MusicOpts where def = MusicOpts {
+        format     = "png",
+        resolution = 200,
+        resize     = 45
+    }
+
+-- |
+-- This named transformation evaluates its input as a music expression.
+--
+-- The music is rendered as an @.ly@ file and a @.mid@ fiel, then @lilypond@ and @convert@
+-- is run to render a @.png@ file. A markdown image tag and a HTML play and stop button
+-- is returned.
+--
+-- The expression must return a value of type @Score Note@. The "Music.Prelude.Basic"
+-- module is implicitly imported.
+--
+musicT :: Transform
+musicT = musicT' def
+
+musicT' :: MusicOpts -> Transform
+musicT' opts = transform "music" $ \input -> do
+    let name = showHex (abs $ hash input) ""
+    music <- eval input :: Context (Music.Score Music.Note)
+
+    liftIO $ Music.writeLy (name++".ly") music
+    liftIO $ Music.writeMidi (name++".mid") music
+    -- liftIO $ void $ readProcess "timidity" ["-Ow", name++".mid"] ""
+
+    let makeLy = do
+        (exit, out, err) <- readProcessWithExitCode "lilypond" [
+            "-f", format opts, 
+            "-dresolution=" ++ show (resolution opts) ++ "", name++".ly"
+            ] mempty
+        hPutStr stderr out
+        hPutStr stderr err
+        return ()
     
-runTransf' (SingTrans (start,stop) f) as = do
-    let bs = (sections start stop . lines) as                   :: [([Line], Maybe [Line])]
-    let cs = fmap (first unlines . second (fmap unlines)) bs    :: [(String, Maybe String)]
-    ds <- mapM (secondM (mapM f)) cs                            :: TF [(String, Maybe String)]    
-    return $ concatMap (\(a, b) -> a ++ fromMaybe [] b ++ "\n") ds
+    let makePng = when (format opts == "png") $ void $ system $ 
+            "convert -transparent white -resize " 
+                ++ show (resize opts) ++"% "
+                ++ name ++".png "
+                ++ name ++ "x.png"
 
+    addPost (liftIO $ makeLy >> makePng)
 
+    let playText = ""
+    -- let playText = "<div class='haskell-music-listen'><a href='"++name++".wav'>listen</a></div>"
+
+    -- let playText = "<div>" ++
+    --                "  <a href=\"javascript:playFile('"++name++".mid')\">[play]</a>\n" ++
+    --                "  <a href=\"javascript:stopPlaying()\">[stop]</a>\n" ++
+    --                "</div>\n"
+                                                   
+    let ending = if format opts == "png" then "x" else ""
+    return $ playText ++ "\n\n" ++ "![](" ++ name ++ ending ++ "." ++ format opts ++ ")"
+    --  -resize 30%
+
+-- |
+-- This named transformation includes stuff needed for music playback.
+--
+-- It should be used exactly once in the document.
+--
+musicExtraT :: Transform
+musicExtraT = transform "music-extra" $ \_ -> return txt
+    where
+        txt = "<script src=\"js/jasmid/stream.js\"></script>\n" ++
+              "<script src=\"js/jasmid/midifile.js\"></script>\n" ++
+              "<script src=\"js/jasmid/replayer.js\"></script>\n" ++
+              "<script src=\"js/midi.js\"></script>\n" ++
+              "<script src=\"js/Base64.js\" type=\"text/javascript\"></script>\n" ++
+              "<script src=\"js/base64binary.js\" type=\"text/javascript\"></script>\n" ++
+              "<script src=\"js/main.js\" type=\"text/javascript\"></script>\n"
+
+{-
+    <script src="js/jasmid/stream.js"></script>
+    <script src="js/jasmid/midifile.js"></script>
+    <script src="js/jasmid/replayer.js"></script>
+    <script src="js/midi.js"></script>
+    <script src="js/Base64.js" type="text/javascript"></script>
+    <script src="js/base64binary.js" type="text/javascript"></script>
+    <script src="js/main.js" type="text/javascript"></script>
+-}
+
+    
+-- |
+-- This named transformation passes everything through and retains the source.
+-- 
+haskellT :: Transform
+haskellT = transform "haskell" $ \input ->
+    return $ "```haskell\n" ++ input ++ "\n```"
+
+-- |
+-- This named transformation runs the 'music' transformation and retains the source.
+-- 
+musicHaskellT :: Transform
+musicHaskellT = musicHaskellT' def
+
+musicHaskellT' :: MusicOpts -> Transform
+musicHaskellT' opts = transform "music+haskell" $ \input -> do
+    let begin    = "<div class='haskell-music'>"
+    let end      = "</div>"
+    musicRes   <- doTrans (musicT' opts) input
+    haskellRes <- doTrans haskellT input
+    return $ begin ++ "\n\n" ++ musicRes ++ "\n\n" ++ haskellRes ++ "\n\n" ++ end
+
+
+
+----------------------------------------------------------------------------------------------------
+
 -- | Separate the sections delimited by the separators from their context. Returns
---      [(outside1, inside1), (outside2, inside2)...] 
+--      [(outside1, inside1), (outside2, inside2)...]
 --
 sections :: (a -> Bool) -> (a -> Bool) -> [a] -> [([a], Maybe [a])]
 sections start stop as = case (bs,cs) of
-    ([], [])  -> []    
+    ([], [])  -> []
     (bs, [])  -> [(bs, Nothing)]
     (bs, [c]) -> [(bs, Nothing)]
     (bs, cs)  -> (bs, Just $ tail cs) : sections start stop (drop skip as)
     where
-        (bs,cs) = sections1 start stop as                       
+        (bs,cs) = sections1 start stop as
         skip    = length bs + length cs + 1
 
 sections1 :: (a -> Bool) -> (a -> Bool) -> [a] -> ([a],[a])
-sections1 start stop as = 
+sections1 start stop as =
     (takeWhile (not . start) as, takeWhile (not . stop) $ dropWhile (not . start) as)
 
 
@@ -218,7 +457,23 @@
 secondM f (a, b) = do
     b' <-  f b
     return (a, b')
-    
-or' :: (a -> Bool) -> (a -> Bool) -> a -> Bool
-or' p q x = p x || q x    
 
+oneOf :: (a -> Bool) -> (a -> Bool) -> a -> Bool
+oneOf p q x = p x || q x
+
+parallel_ :: [IO ()] -> IO ()
+parallel_ = foldb concurrently_ (return ())
+
+concurrently_ :: IO a -> IO b -> IO ()
+concurrently_ = concurrentlyWith (\x y -> ())
+
+concurrentlyWith :: (a -> b -> c) -> IO a -> IO b -> IO c
+concurrentlyWith f x y = uncurry f <$> x `concurrently` y
+
+foldb :: (a -> a -> a) -> a -> [a] -> a
+foldb f z []  = z  
+foldb f z [x] = x 
+foldb f z xs = let (as,bs) = split xs
+    in foldb f z as `f` foldb f z bs
+    where
+        split xs = (take n xs, drop n xs) where n = length xs `div` 2
diff --git a/src/Text/Transf/Process.hs b/src/Text/Transf/Process.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Transf/Process.hs
@@ -0,0 +1,108 @@
+
+{-# LANGUAGE GeneralizedNewtypeDeriving, StandaloneDeriving, DeriveFunctor #-}
+
+module Text.Transf.Process (
+        defaultMain,
+        defaultMain',
+  ) where
+
+import Control.Exception
+import Control.Applicative
+import Control.Monad (when)
+import Control.Monad.Error hiding (mapM)
+import Control.Monad.Plus hiding (mapM)
+import Data.Semigroup hiding (Option)
+import Data.List (find)
+import Data.Maybe (fromMaybe, maybeToList)
+import Data.Traversable (mapM)
+import Data.Typeable
+import System.IO
+import System.Exit
+import System.Environment
+import System.Console.GetOpt
+import Text.Transf
+
+import Prelude hiding (readFile, writeFile)
+
+-- |
+-- Creates a Unix style text processor from a 'Transform'.
+--
+-- The resulting action should be used as the main of an application
+-- and will render a program of the given name that responds to @-v@
+-- and @-h@ flags. If given no flags it runs the text transformer over
+-- the standard input and output streams. If an error occurs the program
+-- halts and prints an error message to the standard error stream.
+--
+-- > defaultMain name transf
+-- 
+defaultMain :: String -> Transform -> IO ()
+defaultMain name transf = defaultMain' name [] (const transf)
+
+-- |
+-- Like 'defaultMain', but customizes the transform based on the
+-- given options.
+--
+-- The @help@ and @version@ flags are added automatically.
+--
+-- > defaultMain' name opts transf
+-- 
+defaultMain' :: String -> [OptDescr a] -> ([a] -> Transform) -> IO ()
+defaultMain' name optDesc transf = do               
+    let optDesc' = stdOptDesc ++ (fmap . mapOptDescr) User optDesc
+    (opts, args, optErrs) <- getOpt Permute optDesc' <$> getArgs
+    return ()
+
+    let usage = usageInfo (header name) optDesc'
+    let printUsage   = putStr (usage ++ "\n")        >> exitSuccess
+    let printVersion = putStr (version name ++ "\n") >> exitSuccess
+    -- 
+    when (Help    `elem` opts) printUsage
+    when (Version `elem` opts) printVersion
+    let opts' = fmap (\(User a) -> a) opts
+
+    runFilter (transf opts')
+    return ()
+
+    where
+        version name = name ++ "-0.9"
+        header  name = "Usage: "++name++" [options]\n" ++
+                       "Usage: "++name++" [options] files...\n" ++
+                       "\n" ++
+                       "Options:"
+        
+        runFilter :: Transform -> IO ()
+        runFilter transf = run transf stdin stdout
+        
+        -- stdOptDesc :: [UserOpt a]
+        stdOptDesc = [
+                Option ['h'] ["help"]    (NoArg Help)       "Print help and exit",
+                Option ['v'] ["version"] (NoArg Version)    "Print version and exit"
+            ]
+
+        run :: Transform -> Handle -> Handle -> IO ()
+        run transf fin fout = do
+            res <- runContext $ do
+                input  <- liftIO $ hGetContents fin
+                output <- runTransform transf input
+                liftIO $ hPutStr fout output
+            case res of
+                Left e  -> hPutStrLn stderr ("Error: " ++ e) >> exitFailure
+                Right _ -> exitSuccess
+
+
+mapOptDescr :: (a -> b) -> OptDescr a -> OptDescr b
+mapOptDescr = fmap
+
+-- TODO orphans
+deriving instance Functor OptDescr
+deriving instance Functor ArgDescr
+
+data UserOpt a
+    = Help
+    | Version
+    | User a
+instance Eq (UserOpt a) where
+    Help    == Help     = True
+    Version == Version  = True
+    _       == _        = False
+
diff --git a/transf.cabal b/transf.cabal
--- a/transf.cabal
+++ b/transf.cabal
@@ -1,6 +1,6 @@
 
 name:               transf
-version:            0.8
+version:            0.10
 cabal-version:      >= 1.6
 author:             Hans Hoglund
 maintainer:         Hans Hoglund <hans@hanshoglund.se>
@@ -12,11 +12,12 @@
 build-type:         Simple
 
 description: 
-    Transf is functional text transformer and interpreter. 
+    /Transf/ is simple text transformer and interpreter.
     .
-    It scans its input for guard tokens and passes everything between to transformation functions. Transformation functions are composed from a small set of combinators and may perform arbirary Haskell computation. Transf contains a full Haskell interpeter and can even interpret its input as Haskell. 
+    It scans its input for guard tokens and passes everything between to transformation functions. Transformation functions are composed from a small set of combinators and may perform arbitrary Haskell computation. Transf contains a full Haskell interpreter and can even interpret its input as Haskell. 
     .
-    The main purpose of Transf is to allow the embedding of Domain-Specific Languages in plain text or Markdown files. For example one could use it with Diagrams as follows:
+    The main purpose of Transf is to allow the embedding of Domain-Specific Languages in text or Markdown files. 
+    For example one could use it with Diagrams as follows:
     .
     > This is my file. Here is an image:
     > 
@@ -46,6 +47,8 @@
         mtl,
         monadplus,
         filepath,
+        data-default,
+        async,
         process,      
         hashable,
         hint,
@@ -53,6 +56,7 @@
     hs-source-dirs: src
     exposed-modules:
         Text.Transf
+        Text.Transf.Process
 
 executable "transf"
     ghc-options: -O3 -threaded
