packages feed

transf 0.8 → 0.10

raw patch · 4 files changed

+524/−169 lines, 4 filesdep +asyncdep +data-default

Dependencies added: async, data-default

Files

src/Main.hs view
@@ -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 ()
src/Text/Transf.hs view
@@ -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
+ src/Text/Transf/Process.hs view
@@ -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+
transf.cabal view
@@ -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