packages feed

wordexp-0.2.0.0: src/System/Wordexp.chs

{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE EmptyDataDecls #-}
{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-- | wordexp (and wordfree) Haskell wrapper
module System.Wordexp
 ( -- * Wrapper
   wordexp
   -- * Flags
 , Flags, nosubst, errors, noundef
   -- * Errors
 , WordexpError (..)
 ) where

import Control.Exception (Exception)
import Control.Monad
import Data.Data (Data)
import Data.Typeable (Typeable)
import Foreign
import Foreign.C
import Foreign.C.Types

import Data.Array (Ix)
import Data.Semigroup (Semigroup(..), Monoid(..))

#include <wordexp.h>


-- | Opaque wordexp_t struct image in Haskellland
data Wordexp


-- | wordexp flags enum image in Haskellland
{# enum define FLAGS
  { WRDE_APPEND  as WRDE_APPEND
  , WRDE_DOOFFS  as WRDE_DOOFFS
  , WRDE_NOCMD   as WRDE_NOCMD
  , WRDE_REUSE   as WRDE_REUSE
  , WRDE_SHOWERR as WRDE_SHOWERR
  , WRDE_UNDEF   as WRDE_UNDEF
  } deriving (Show, Read, Eq, Ord, Bounded, Ix, Data, Typeable) #}

-- | wordexp flags
--
-- Not every flag is supported since some of them do not make much sense in Haskell anyway
newtype Flags = Flags Int
#if defined(__GLASGOW_HASKELL__) && (__GLASGOW_HASKELL__ >= 706)
  deriving (Show, Read, Eq, Ord, Bounded, Bits, Ix, Data, Typeable)
#else
  deriving (Show, Read, Eq, Ord, Bounded, Num, Bits, Ix, Data, Typeable)
#endif

instance Semigroup Flags where
  (<>) = (.|.)
  {-# INLINE (<>) #-}

instance Monoid Flags where
  mempty = Flags 0
  mappend = (<>)
  {-# INLINE mappend #-}

-- | Disable command substitution in patterns, treat them as errors
nosubst :: Flags
nosubst = Flags $ fromEnum WRDE_NOCMD
{-# INLINE nosubst #-}

-- | Do not hide shell error messages in /dev/null, print them right away
errors :: Flags
errors = Flags $ fromEnum WRDE_SHOWERR
{-# INLINE errors #-}

-- | Do not accept undefined shell variables, treat them as errors
noundef :: Flags
noundef = Flags $ fromEnum WRDE_UNDEF
{-# INLINE noundef #-}


-- | Possible wordexp errors
{# enum define WordexpError
  { WRDE_NOSPACE as OutOfSpace
  , WRDE_BADCHAR as IllegalCharacterOccurence
  , WRDE_BADVAL  as UndefinedShellVariable
  , WRDE_CMDSUB  as CommandSubstitution
  , WRDE_SYNTAX  as ShellSyntaxError
  } deriving (Show, Read, Eq, Ord, Bounded, Ix, Data, Typeable) #}

instance Exception WordexpError


-- | wordexp wrapper
--
-- Allows to specify desired flags, return expanded strings or encountered error if any
wordexp :: Flags -> String -> IO (Either WordexpError [String])
wordexp (Flags f) s =
  withCString s $ \cs ->
    allocaBytes size $ \p -> do
      ret <- c_wordexp cs p (fromIntegral f)
      case ret of
        0 -> do
          c <- fromIntegral `fmap` wordc p
          v <- wordv p
          xs <- forM [0 .. c-1] $ peekElemOff v >=> peekCString
          c_wordfree p
          return $ Right xs
        e -> return . Left . toEnum $ fromIntegral e
 where
  size = sizeOf (undefined :: CSize) + sizeOf (undefined :: Ptr CString) + sizeOf (undefined :: CSize)
  {-# INLINE size #-}

  wordc :: Ptr Wordexp -> IO CSize
  wordc p = peekByteOff p 0
  {-# INLINE wordc #-}

  wordv :: Ptr Wordexp -> IO (Ptr CString)
  wordv p = peekByteOff p (sizeOf (undefined :: CSize))
  {-# INLINE wordv #-}


foreign import ccall unsafe "wordexp"
  c_wordexp :: CString -> Ptr Wordexp -> CInt -> IO CInt

foreign import ccall unsafe "wordfree"
  c_wordfree :: Ptr Wordexp -> IO ()