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 +31/−43
- src/Text/Transf.hs +377/−122
- src/Text/Transf/Process.hs +108/−0
- transf.cabal +8/−4
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 $ ""- -- -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" ++ ""+ -- -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