packages feed

eddie-0.2: eddie.hs

{-# LANGUAGE DeriveDataTypeable #-}

-- import Data.ByteString.Lazy (readFile)
import Language.Haskell.Interpreter
  (setImportsQ, interpret, runInterpreter, as, MonadInterpreter)
import Data.IORef (newIORef, readIORef, writeIORef)
import System.Environment (getArgs, getProgName)
import System.IO (openFile, openBinaryFile, hClose, stdin, IOMode (ReadMode), 
                  hIsEOF, hGetChar, Handle, hSetBinaryMode, stdout)
import System.IO.Unsafe (unsafeInterleaveIO)
import System.Console.CmdArgs.Implicit
  (cmdArgs, (&=), Data, Typeable, help, explicit, name, args, summary, program,
   details)
import Control.Exception (finally, evaluate)
import Control.Monad     (when)

main = do
  myName <- getProgName
  opts <- cmdArgs (eddie &= program myName)
  case parseOpts opts of
    Left e -> putStrLn $ "usage: " ++ myName ++ " " ++ e
    Right o -> runIt myName o
      where runIt name opts = do
              fun <- runInterpreter $ makeFun opts
              let opener = if binary opts then openBinaryFile else openFile
              let outputFunction = if binary opts then putStr else putStrLn
              when (binary opts) $
                hSetBinaryMode stdout True
              case fun of
                Left e -> putStrLn $ name ++ ": Error: " ++ show e
                Right f -> withFiles (files opts) opener (outputFunction . f)

makeFun :: MonadInterpreter m => Eddie -> m (String->String)
makeFun opts = do
  setImportsQ (asModules opts)
  fun <- interpret (head (expr opts)) (as :: String -> String)
  return $ if line opts then unlines . map fun . lines else fun

-- an even lazier version of  withFile (courtesy of Heinrich Apfelmus)
-- Tweaked by mwm so withFiles uses stdin if [FilePath] is an empty list
-- Further tweaked by mwm to allow caller to specify opening function.
withFile' :: Maybe FilePath -> (FilePath -> IOMode -> IO Handle) ->
             (String -> IO a) -> IO a
withFile' name opener f = do
    fin <- newIORef (return ())
    let
        close = readIORef fin >>= id
        open  = do
          h <- maybe (return stdin) (flip opener ReadMode) name
          writeIORef fin (hClose h)
          lazyRead h
    finally (unsafeInterleaveIO open >>= f >>= evaluate) close

    where
    lazyRead h = hIsEOF h >>= \b ->
        if b
            then do hClose h; return []
            else do
                c  <- hGetChar h
                cs <- unsafeInterleaveIO $ lazyRead h
                return (c:cs)


withFiles :: [FilePath] -> (FilePath -> IOMode -> IO Handle) -> 
             (String -> IO a) -> IO a
withFiles []     o f = withFile' Nothing o f
withFiles [x]    o f = withFile' (Just x) o f
withFiles (x:xs) o f = withFile' (Just x) o $ \s ->
    let f' t = f (s ++ t) in withFiles xs o f'


-- argument processing
data Eddie = Eddie { line :: Bool,
                     binary :: Bool,
                     expr :: [String],
                     files :: [String],
                     modules :: [String],
                     asModules :: [(String, Maybe String)]
                   } deriving (Show, Data, Typeable)

parseOpts :: Eddie -> Either String Eddie
parseOpts opts = 
  let es = expr opts
      fs = files opts
      e:fs' = case (es, fs) of
        ([], []) -> [""]
        ([], _) -> fs
        (_, _) -> unlines es:fs
      mods = zip (modules opts) (repeat Nothing) ++ asModules opts
  in
   if e == "" then Left $ unlines ["[options] (-e expr | expr) [files ...]",
                                   "--help for options"]
   else Right $ opts {expr = [e], asModules = mods, files = fs' }

   
eddie = Eddie {line = False &= help "Process one line at a time",
               binary = False &= help "Process a binary file",
               expr = [] &= help "Line of expression to evaluate",
               modules = ["Prelude", "Data.List", "Data.Char"]
                         &= help "Modules to import for expr",
               asModules = [] &= help "Modules to import qualified" &= explicit
                           &= name "M" &= name "Modules",
               files = [] &= args} 
        &= summary "eddie 0.2" &= details ["Haskell for shell scripts."]