{-# LANGUAGE ScopedTypeVariables #-}
module Foreign.Lua.Module.System
( pushModule
, preloadModule
)
where
import Control.Applicative ((<$>))
import Control.Exception (IOException, catch, evaluate, try)
import Data.Maybe (fromMaybe)
import Foreign.Lua (Lua, NumResults(..), Optional (..), Peekable, Pushable,
StackIndex, ToHaskellFunction)
import System.IO.Error (IOError, isDoesNotExistError)
import qualified Foreign.Lua as Lua
import qualified System.Directory as Directory
import qualified System.Environment as Env
import qualified System.IO.Temp as Temp
pushModule :: Lua NumResults
pushModule = do
Lua.newtable
addFunction "chdir" chdir
addFunction "currentdir" currentdir
addFunction "env" env
addFunction "getenv" getenv
addFunction "ls" ls
addFunction "pwd" currentdir
addFunction "setenv" setenv
addFunction "tmpdirname" tmpdirname
addFunction "with_tmpdir" with_tmpdir
return 1
preloadModule :: String -> Lua ()
preloadModule = flip addPackagePreloader pushModule
addPackagePreloader :: String -> Lua NumResults -> Lua ()
addPackagePreloader name modulePusher = do
Lua.getfield Lua.registryindex Lua.preloadTableRegistryField
Lua.pushHaskellFunction modulePusher
Lua.setfield (-2) name
Lua.pop 1
addFunction :: ToHaskellFunction a => String -> a -> Lua ()
addFunction name fn = do
Lua.push name
Lua.pushHaskellFunction fn
Lua.rawset (-3)
newtype Callback = Callback { callbackStackIndex :: StackIndex }
instance Peekable Callback where
peek idx = do
isFn <- Lua.isfunction idx
if isFn
then return (Callback idx)
else Lua.throwException "Function expected"
instance Pushable Callback where
push (Callback idx) = Lua.pushvalue idx
newtype AnyValue = AnyValue { fromAnyValue :: StackIndex }
instance Peekable AnyValue where
peek = return . AnyValue
instance Pushable AnyValue where
push (AnyValue idx) = Lua.pushvalue idx
with_tmpdir :: String
-> AnyValue
-> Optional Callback
-> Lua NumResults
with_tmpdir parentDir tmpl callback = do
case fromOptional callback of
Nothing -> do
let tmpl' = parentDir
callback' <- Lua.peek (fromAnyValue tmpl)
Temp.withSystemTempDirectory tmpl' (callWithFilename callback')
Just callback' -> do
tmpl' <- Lua.peek (fromAnyValue tmpl)
Temp.withTempDirectory parentDir tmpl' (callWithFilename callback')
callWithFilename :: Callback -> FilePath -> Lua NumResults
callWithFilename callback filename = do
oldTop <- Lua.gettop
Lua.push callback
Lua.push filename
Lua.call (Lua.NumArgs 1) Lua.multret
newTop <- Lua.gettop
return . NumResults . fromIntegral . Lua.fromStackIndex $
newTop - oldTop
ls :: Optional FilePath -> Lua [FilePath]
ls fp = do
let fp' = fromMaybe "." (fromOptional fp)
ioToLua (Directory.listDirectory fp')
chdir :: FilePath -> Lua ()
chdir fp = ioToLua $ Directory.setCurrentDirectory fp
currentdir :: Lua FilePath
currentdir = ioToLua Directory.getCurrentDirectory
env :: Lua NumResults
env = do
kvs <- ioToLua Env.getEnvironment
let addValue (k, v) = Lua.push k *> Lua.push v *> Lua.rawset (-3)
Lua.newtable
mapM_ addValue kvs
return (NumResults 1)
getenv :: String -> Lua (Optional String)
getenv name = ioToLua (Optional <$> Env.lookupEnv name)
setenv :: String -> String -> Lua ()
setenv name value = ioToLua (Env.setEnv name value)
tmpdirname :: Lua FilePath
tmpdirname = ioToLua Directory.getTemporaryDirectory
ioToLua :: IO a -> Lua a
ioToLua action = do
result <- Lua.liftIO (try action)
case result of
Right result' -> return result'
Left err -> Lua.throwException (show (err :: IOException))