content: {-# LANGUAGE OverloadedStrings
             , FlexibleInstances
             , StandaloneDeriving #-}
  {-| Utilities for working with shell script.
   -}
  module System.Posix.ARX.Sh ( Val(), val, Var(), var,
                               setEU, Render(..), Raw(..) ) where
  
  import Control.Monad
  import Data.ByteString (ByteString)
  import qualified Data.ByteString.Char8 as Bytes
  import Data.Monoid
  
  import qualified Blaze.ByteString.Builder as Blaze
  import qualified Text.ShellEscape as Esc
  
  
  setEU                       ::  Blaze.Builder
  setEU                        =  "set -e -u\n"
  
  {-| Valid shell string values contain any byte but null.
   -}
  newtype Val                  =  Val ByteString
  deriving instance Eq Val
  deriving instance Ord Val
  deriving instance Show Val
  instance Render Val where
    render (Val bytes) = (Blaze.fromByteString . Esc.bytes . Esc.sh) bytes
  instance Raw Val where
    raw (Val bytes)            =  bytes
  
  val                         ::  ByteString -> Maybe Val
  val bytes = guard (Bytes.all (/= '\0') bytes) >> Just (Val bytes)
  
  {-| Valid shell variable names consist of a leading letter or underscore and
      then any number of letters, underscores or digits.
   -}
  newtype Var                  =  Var ByteString
  deriving instance Eq Var
  deriving instance Ord Var
  deriving instance Show Var
  instance Render Var where
    render (Var bytes)         =  Blaze.fromByteString bytes
  instance Raw Var where
    raw (Var bytes)            =  bytes
  
  var                         ::  ByteString -> Maybe Var
  var ""                       =  Nothing
  var bytes = guard (leading h && Bytes.all body t) >> Just (Var bytes)
   where
    (h, t)                     =  (Bytes.head bytes, Bytes.tail bytes)
    body c                     =  leading c || (c >= '0' && c <= '9')
    leading c = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'
  
  instance Render [(Var, Val)] where
    render [       ]           =  mempty
    render ((k,v):t)           =  exportStatement `mappend` render t
     where
      exportStatement = mconcat ["export ", render k, "=", render v, "\n"]
  
  instance Render [Val] where
    render                     =  mconcat . map (mappend " " . render)
  
  class Render t where
    render                    ::  t -> Blaze.Builder
  
  class Raw t where
    raw                       ::  t -> ByteString
  
  