-- | Shell quoting {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} module Control.Monad.Shell.Quote ( Quoted(..), Quotable(..), Val(..), ) where import qualified Data.Text.Lazy as L import Data.String import Data.Char -- | A value that is safely quoted so that it can be exposed to the shell. -- -- While the constructor is exposed, you should avoid directly constucting -- Quoted values. Instead, use 'quote'. newtype Quoted a = Q { getQ :: a } deriving (Eq, Ord, Show, Semigroup, Monoid) -- | Quotes a value to allow it to be safely exposed to the shell. -- -- The method used is to replace ' with '"'"' and wrap the value inside -- single quotes. This works for POSIX shells, as well as other shells -- like csh. -- -- The single quotes are omitted for simple values that do not need -- any quoting. class Quotable t where quote :: t -> Quoted L.Text instance Quotable L.Text where quote t | L.all bareable t && L.any bareable t = Q t | otherwise = Q $ q <> L.intercalate "'\"'\"'" (L.splitOn q t) <> q where q = "'" bareable c = isAlphaNum c || c == '_' instance Quotable String where quote = quote . L.pack instance (Show v) => Quotable (Val v) where quote (Val v) = quote $ show v -- To avoid double-quoting Text and String, override the above instance. -- This needs OverlappingInstances instance Quotable (Val L.Text) where quote (Val s) = quote s instance Quotable (Val String) where quote (Val s) = quote s -- | An arbitrary value. newtype Val v = Val v instance IsString (Quoted L.Text) where fromString = quote