{-# LANGUAGE FlexibleInstances, TypeSynonymInstances, TypeFamilies, ExistentialQuantification #-} -- | This module is a wrapper for the module "Shelly". -- The only difference is a main type "Sh". In this module -- "Sh" contains a list of results. Actual definition of the type "Sh" is: -- -- > import qualified Shelly as S -- > -- > newtype Sh a = Sh { unSh :: S.Sh [a] } -- -- This definition can simplify some filesystem commands. -- A monad bind operator becomes a pipe operator and we can write -- -- > findExt ext = findWhen (pure . hasExt ext) -- > -- > main :: IO () -- > main = shs $ do -- > mkdir "new" -- > findExt "hs" "." >>= flip cp "new" -- > findExt "cpp" "." >>= rm_f -- > liftIO $ putStrLn "done" -- -- Monad methods "return" and ">>=" behave like methods for -- @ListT Shelly.Sh@, but ">>" forgets the number of -- the empty effects. So the last line prints @\"done\"@ only once. -- -- I highly recommend putting the following at the top of your program, -- otherwise you will likely need either type annotations or type conversions -- -- > {-# LANGUAGE OverloadedStrings #-} -- > {-# LANGUAGE ExtendedDefaultRules #-} -- > {-# OPTIONS_GHC -fno-warn-type-defaults #-} -- > import Shelly -- > import Data.Text.Lazy as LT -- > default (LT.Text) module Shelly.Pipe ( -- * Entering Sh. Sh, shs, shelly,shellyNoDir, shsNoDir, sub, silently, verbosely, escaping, print_stdout, print_commands, tracing, errExit -- * List functions , roll, unroll, liftSh -- * Running external commands. , FoldCallback , run, run_, runFoldLines, cmd , (-|-), lastStderr, setStdin, lastExitCode , command, command_, command1, command1_ , sshPairs, sshPairs_ -- * Modifying and querying environment. , setenv, get_env, get_env_text, get_env_def, appendToPath -- * Environment directory , cd, chdir, pwd -- * Printing , echo, echo_n, echo_err, echo_n_err, inspect, inspect_err , tag, trace, show_command -- * Querying filesystem. , ls, lsT, test_e, test_f, test_d, test_s, which -- * Filename helpers , absPath, (), (<.>), canonic, canonicalize, relPath, relativeTo , hasExt -- * Manipulating filesystem. , mv, rm, rm_f, rm_rf, cp, cp_r, mkdir, mkdir_p, mkdirTree -- * reading/writing Files , readfile, readBinary, writefile, appendfile, touchfile, withTmpDir -- * exiting the program , exit, errorExit, quietExit, terror -- * Exceptions , catchany, catch_sh, finally_sh , ShellyHandler(..), catches_sh , catchany_sh -- * convert between Text and FilePath , toTextIgnore, toTextWarn, fromText -- * Utilities. , (<$>), whenM, unlessM, time -- * Re-exported for your convenience , liftIO, when, unless, FilePath -- * internal functions for writing extensions , get, put -- * find functions , find, findWhen, findFold , findDirFilter, findDirFilterWhen, findFoldDirFilter ) where import Prelude hiding (FilePath) import Control.Applicative import Control.Monad import Control.Monad.Trans import Control.Exception hiding (handle) import Filesystem.Path(FilePath) import qualified Shelly as S import Shelly( (), (<.>), hasExt , whenM, unlessM, toTextIgnore , fromText, catchany , FoldCallback) import Data.Maybe(fromMaybe) import Shelly.Base(State) import Data.ByteString (ByteString) import Data.Tree(Tree) import Data.Text.Lazy as LT hiding (concat, all, find, cons) default (LT.Text) -- | This type is a simple wrapper for a type "Shelly.Sh". -- "Sh" contains a list of results. newtype Sh a = Sh { unSh :: S.Sh [a] } instance Functor Sh where fmap f = Sh . fmap (fmap f) . unSh instance Monad Sh where return = Sh . return . return a >>= f = Sh $ fmap concat $ mapM (unSh . f) =<< unSh a a >> b = Sh $ unSh a >> unSh b instance Applicative Sh where pure = return (<*>) = ap instance MonadPlus Sh where mzero = Sh $ return [] mplus a b = Sh $ liftA2 (++) (unSh a) (unSh b) instance MonadIO Sh where liftIO = sh1 liftIO ------------------------------------------------------- -- converters sh0 :: S.Sh a -> Sh a sh0 = Sh . fmap return sh1 :: (a -> S.Sh b) -> (a -> Sh b) sh1 f = \a -> sh0 (f a) sh2 :: (a1 -> a2 -> S.Sh b) -> (a1 -> a2 -> Sh b) sh2 f = \a b -> sh0 (f a b) sh3 :: (a1 -> a2 -> a3 -> S.Sh b) -> (a1 -> a2 -> a3 -> Sh b) sh3 f = \a b c -> sh0 (f a b c) sh4 :: (a1 -> a2 -> a3 -> a4 -> S.Sh b) -> (a1 -> a2 -> a3 -> a4 -> Sh b) sh4 f = \a b c d -> sh0 (f a b c d) sh0s :: S.Sh [a] -> Sh a sh0s = Sh sh1s :: (a -> S.Sh [b]) -> (a -> Sh b) sh1s f = \a -> sh0s (f a) {- Just in case ... sh2s :: (a1 -> a2 -> S.Sh [b]) -> (a1 -> a2 -> Sh b) sh2s f = \a b -> sh0s (f a b) sh3s :: (a1 -> a2 -> a3 -> S.Sh [b]) -> (a1 -> a2 -> a3 -> Sh b) sh3s f = \a b c -> sh0s (f a b c) -} lift1 :: (S.Sh a -> S.Sh b) -> (Sh a -> Sh b) lift1 f = Sh . (mapM (f . return) =<< ) . unSh lift2 :: (S.Sh a -> S.Sh b -> S.Sh c) -> (Sh a -> Sh b -> Sh c) lift2 f a b = Sh $ join $ liftA2 (mapM2 f') (unSh a) (unSh b) where f' = \x y -> f (return x) (return y) mapM2 :: Monad m => (a -> b -> m c)-> [a] -> [b] -> m [c] mapM2 f as bs = sequence $ liftA2 f as bs ----------------------------------------------------------- -- | Unpack list of results. unroll :: Sh a -> Sh [a] unroll = Sh . fmap return . unSh -- | Pack list of results. It performs "concat" inside "Sh". roll :: Sh [a] -> Sh a roll = Sh . fmap concat . unSh -- | Transform result as list. It can be useful for filtering. liftSh :: ([a] -> [b]) -> Sh a -> Sh b liftSh f = Sh . fmap f . unSh ------------------------------------------------------------------ -- Entering Sh -- | Enter a Sh from (Monad)IO. The environment and working directories are -- inherited from the current process-wide values. Any subsequent changes in -- processwide working directory or environment are not reflected in the -- running Sh. shelly :: MonadIO m => Sh a -> m [a] shelly = S.shelly . unSh -- | Performs "shelly" and then an empty action @return ()@. shs :: MonadIO m => Sh () -> m () shs a = shelly a >> return () -- | Using this entry point does not create a @.shelly@ directory in the case -- of failure. Instead it logs directly into the standard error stream (@stderr@). shellyNoDir :: MonadIO m => Sh a -> m [a] shellyNoDir = S.shellyNoDir . unSh -- | Performs "shellyNoDir" and then an empty action @return ()@. shsNoDir :: MonadIO m => Sh () -> m () shsNoDir a = shellyNoDir a >> return () -- | Enter a sub-Sh that inherits the environment -- The original state will be restored when the sub-Sh completes. -- Exceptions are propagated normally. sub :: Sh a -> Sh a sub = lift1 S.sub -- | Create a sub-Sh in which external command outputs are not echoed. -- Also commands are not printed. -- See "sub". silently :: Sh a -> Sh a silently = lift1 S.silently -- | Create a sub-Sh in which external command outputs are echoed. -- Executed commands are printed -- See "sub". verbosely :: Sh a -> Sh a verbosely = lift1 S.verbosely -- | Create a sub-Sh with shell character escaping on or off escaping :: Bool -> Sh a -> Sh a escaping b = lift1 (S.escaping b) -- | Create a sub-Sh with stdout printing on or off print_stdout :: Bool -> Sh a -> Sh a print_stdout b = lift1 (S.print_stdout b) -- | Create a sub-Sh with command echoing on or off print_commands :: Bool -> Sh a -> Sh a print_commands b = lift1 (S.print_commands b) -- | Create a sub-Sh where commands are not traced -- Defaults to True. -- You should only set to False temporarily for very specific reasons tracing :: Bool -> Sh a -> Sh a tracing b = lift1 (S.tracing b) -- | named after bash -e errexit. Defaults to @True@. -- When @True@, throw an exception on a non-zero exit code. -- When @False@, ignore a non-zero exit code. -- Not recommended to set to @False@ unless you are specifically checking the error code with 'lastExitCode'. errExit :: Bool -> Sh a -> Sh a errExit b = lift1 (S.errExit b) ------------------------------------------------------------------ -- Running external commands. -- | Execute an external command. Takes the command name (no shell allowed, -- just a name of something that can be found via @PATH@; FIXME: setenv'd -- @PATH@ is not taken into account when finding the exe name) -- -- "stdout" and "stderr" are collected. The "stdout" is returned as -- a result of "run", and complete stderr output is available after the fact using -- "lastStderr" -- -- All of the stdout output will be loaded into memory -- You can avoid this but still consume the result by using "run_", -- If you want to avoid the memory and need to process the output then use "runFoldLines". run :: FilePath -> [Text] -> Sh Text run a b = sh0 $ S.run a b -- | The same as "run", but return () instead of the stdout content. run_ :: FilePath -> [Text] -> Sh () run_ a b = sh0 $ S.run_ a b -- | used by 'run'. fold over stdout line-by-line as it is read to avoid keeping it in memory -- stderr is still being placed in memory under the assumption it is always relatively small runFoldLines :: a -> FoldCallback a -> FilePath -> [Text] -> Sh a runFoldLines a cb fp ts = sh0 $ S.runFoldLines a cb fp ts -- | Pipe operator. set the stdout the first command as the stdin of the second. (-|-) :: Sh Text -> Sh b -> Sh b (-|-) = lift2 (S.-|-) -- | The output of last external command. See "run". lastStderr :: Sh Text lastStderr = sh0 S.lastStderr -- | set the stdin to be used and cleared by the next "run". setStdin :: Text -> Sh () setStdin = sh1 S.setStdin -- | The exit code from the last command. -- Unless you set 'errExit' to False you won't get a chance to use this: a non-zero exit code will throw an exception. lastExitCode :: Sh Int lastExitCode = sh0 S.lastExitCode -- | bind some arguments to run for re-use -- Example: @monit = command "monit" ["-c", "monitrc"]@ command :: FilePath -> [Text] -> [Text] -> Sh Text command = sh3 S.command -- | bind some arguments to "run_" for re-use -- Example: @monit_ = command_ "monit" ["-c", "monitrc"]@ command_ :: FilePath -> [Text] -> [Text] -> Sh () command_ = sh3 S.command_ -- | bind some arguments to run for re-use, and expect 1 argument -- Example: @git = command1 "git" []; git "pull" ["origin", "master"]@ command1 :: FilePath -> [Text] -> Text -> [Text] -> Sh Text command1 = sh4 S.command1 -- | bind some arguments to run for re-use, and expect 1 argument -- Example: @git_ = command1_ "git" []; git+ "pull" ["origin", "master"]@ command1_ :: FilePath -> [Text] -> Text -> [Text] -> Sh () command1_ = sh4 S.command1_ -- | run commands over SSH. -- An ssh executable is expected in your path. -- Commands are in the same form as 'run', but given as pairs -- -- > sshPairs "server-name" [("cd", "dir"), ("rm",["-r","dir2"])] -- -- This interface is crude, but it works for now. -- -- Please note this sets 'escaping' to False: the commands will not be shell escaped. -- Internally the list of commands are combined with the string " && " before given to ssh. sshPairs :: Text -> [(FilePath, [Text])] -> Sh Text sshPairs = sh2 S.sshPairs -- | same as 'sshPairs', but returns () sshPairs_ :: Text -> [(FilePath, [Text])] -> Sh () sshPairs_ = sh2 S.sshPairs_ ------------------------------------------------------------------- -- Modifying and querying environment. -- | Set an environment variable. The environment is maintained in Sh -- internally, and is passed to any external commands to be executed. setenv :: Text -> Text -> Sh () setenv = sh2 S.setenv -- | Fetch the current value of an environment variable. -- if non-existant or empty text, will be Nothing get_env :: Text -> Sh (Maybe Text) get_env = sh1 S.get_env -- | Fetch the current value of an environment variable. Both empty and -- non-existent variables give empty string as a result. get_env_text :: Text -> Sh Text get_env_text = sh1 S.get_env_text -- | Fetch the current value of an environment variable. Both empty and -- non-existent variables give the default value as a result get_env_def :: Text -> Text -> Sh Text get_env_def a d = sh0 $ fmap (fromMaybe d) $ S.get_env a {-# DEPRECATED get_env_def "use fromMaybe DEFAULT get_env" #-} -- | add the filepath onto the PATH env variable -- FIXME: only effects the PATH once the process is ran, as per comments in 'which' appendToPath :: FilePath -> Sh () appendToPath = sh1 S.appendToPath ------------------------------------------------------------- -- Environment directory -- | Change current working directory of Sh. This does *not* change the -- working directory of the process we are running it. Instead, Sh keeps -- track of its own working directory and builds absolute paths internally -- instead of passing down relative paths. This may have performance -- repercussions if you are doing hundreds of thousands of filesystem -- operations. You will want to handle these issues differently in those cases. cd :: FilePath -> Sh () cd = sh1 S.cd -- | "cd", execute a Sh action in the new directory and then pop back to the original directory chdir :: FilePath -> Sh a -> Sh a chdir p = lift1 (S.chdir p) -- | Obtain the current (Sh) working directory. pwd :: Sh FilePath pwd = sh0 S.pwd ----------------------------------------------------------------- -- Printing -- | Echo text to standard (error, when using _err variants) output. The _n -- variants do not print a final newline. echo, echo_n_err, echo_err, echo_n :: Text -> Sh () echo = sh1 S.echo echo_n_err = sh1 S.echo_n_err echo_err = sh1 S.echo_err echo_n = sh1 S.echo_n -- | a print lifted into Sh inspect :: Show s => s -> Sh () inspect = sh1 S.inspect -- | a print lifted into Sh using stderr inspect_err :: Show s => s -> Sh () inspect_err = sh1 S.inspect_err -- | same as 'trace', but use it combinator style tag :: Sh a -> Text -> Sh a tag a t = lift1 (flip S.tag t) a -- | internally log what occured. -- Log will be re-played on failure. trace :: Text -> Sh () trace = sh1 S.trace show_command :: FilePath -> [Text] -> Text show_command = S.show_command ------------------------------------------------------------------ -- Querying filesystem -- | List directory contents. Does *not* include \".\" and \"..\", but it does -- include (other) hidden files. ls :: FilePath -> Sh FilePath ls = sh1s S.ls -- | Get back [Text] instead of [FilePath] lsT :: FilePath -> Sh Text lsT = sh1s S.lsT -- | Does a path point to an existing filesystem object? test_e :: FilePath -> Sh Bool test_e = sh1 S.test_e -- | Does a path point to an existing file? test_f :: FilePath -> Sh Bool test_f = sh1 S.test_f -- | Does a path point to an existing directory? test_d :: FilePath -> Sh Bool test_d = sh1 S.test_d -- | Does a path point to a symlink? test_s :: FilePath -> Sh Bool test_s = sh1 S.test_s -- | Get a full path to an executable on @PATH@, if exists. FIXME does not -- respect setenv'd environment and uses @findExecutable@ which uses the @PATH@ inherited from the process -- environment. -- FIXME: findExecutable does not maintain a hash of existing commands and does a ton of file stats which :: FilePath -> Sh (Maybe FilePath) which = sh1 S.which --------------------------------------------------------------------- -- Filename helpers -- | Make a relative path absolute by combining with the working directory. -- An absolute path is returned as is. -- To create a relative path, use 'path'. absPath :: FilePath -> Sh FilePath absPath = sh1 S.absPath -- | makes an absolute path. -- Like 'canonicalize', but on an exception returns 'path' canonic :: FilePath -> Sh FilePath canonic = sh1 S.canonic -- | Obtain a (reasonably) canonic file path to a filesystem object. Based on -- "canonicalizePath" in system-fileio. canonicalize :: FilePath -> Sh FilePath canonicalize = sh1 S.canonicalize -- | Makes a relative path relative to the current Sh working directory. -- An absolute path is returned as is. -- To create an absolute path, use 'absPath' relPath :: FilePath -> Sh FilePath relPath = sh1 S.relPath -- | make the second path relative to the first -- Uses 'Filesystem.stripPrefix', but will canonicalize the paths if necessary relativeTo :: FilePath -- ^ anchor path, the prefix -> FilePath -- ^ make this relative to anchor path -> Sh FilePath relativeTo = sh2 S.relativeTo ------------------------------------------------------------- -- Manipulating filesystem -- | Currently a "renameFile" wrapper. TODO: Support cross-filesystem -- move. TODO: Support directory paths in the second parameter, like in "cp". mv :: FilePath -> FilePath -> Sh () mv = sh2 S.mv -- | Remove a file. -- Does fail if the file does not exist (use 'rm_f' instead) or is not a file. rm :: FilePath -> Sh () rm = sh1 S.rm -- | Remove a file. Does not fail if the file does not exist. -- Does fail if the file is not a file. rm_f :: FilePath -> Sh () rm_f = sh1 S.rm_f -- | A swiss army cannon for removing things. Actually this goes farther than a -- normal rm -rf, as it will circumvent permission problems for the files we -- own. Use carefully. -- Uses 'removeTree' rm_rf :: FilePath -> Sh () rm_rf = sh1 S.rm_rf -- | Copy a file. The second path could be a directory, in which case the -- original file name is used, in that directory. cp :: FilePath -> FilePath -> Sh () cp = sh2 S.cp -- | Copy a file, or a directory recursively. cp_r :: FilePath -> FilePath -> Sh () cp_r = sh2 S.cp_r -- | Create a new directory (fails if the directory exists). mkdir :: FilePath -> Sh () mkdir = sh1 S.mkdir -- | Create a new directory, including parents (succeeds if the directory -- already exists). mkdir_p :: FilePath -> Sh () mkdir_p = sh1 S.mkdir_p -- | Create a new directory tree. You can describe a bunch of directories as -- a tree and this function will create all subdirectories. An example: -- -- > exec = mkTree $ -- > "package" # [ -- > "src" # [ -- > "Data" # leaves ["Tree", "List", "Set", "Map"] -- > ], -- > "test" # leaves ["QuickCheck", "HUnit"], -- > "dist/doc/html" # [] -- > ] -- > where (#) = Node -- > leaves = map (# []) -- mkdirTree :: Tree FilePath -> Sh () mkdirTree = sh1 S.mkdirTree -- | (Strictly) read file into a Text. -- All other functions use Lazy Text. -- So Internally this reads a file as strict text and then converts it to lazy text, which is inefficient readfile :: FilePath -> Sh Text readfile = sh1 S.readfile -- | wraps ByteSting readFile readBinary :: FilePath -> Sh ByteString readBinary = sh1 S.readBinary -- | Write a Lazy Text to a file. writefile :: FilePath -> Text -> Sh () writefile = sh2 S.writefile -- | Update a file, creating (a blank file) if it does not exist. touchfile :: FilePath -> Sh () touchfile = sh1 S.touchfile -- | Append a Lazy Text to a file. appendfile :: FilePath -> Text -> Sh () appendfile = sh2 S.appendfile -- | Create a temporary directory and pass it as a parameter to a Sh -- computation. The directory is nuked afterwards. withTmpDir :: (FilePath -> Sh a) -> Sh a withTmpDir f = Sh $ S.withTmpDir (unSh . f) ----------------------------------------------------------------- -- find -- | List directory recursively (like the POSIX utility "find"). -- listing is relative if the path given is relative. -- If you want to filter out some results or fold over them you can do that with the returned files. -- A more efficient approach is to use one of the other find functions. find :: FilePath -> Sh FilePath find = sh1s S.find -- | 'find' that filters the found files as it finds. -- Files must satisfy the given filter to be returned in the result. findWhen :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath findWhen p a = Sh $ S.findWhen (fmap and . unSh . p) a -- | Fold an arbitrary folding function over files froma a 'find'. -- Like 'findWhen' but use a more general fold rather than a filter. findFold :: (a -> FilePath -> Sh a) -> a -> FilePath -> Sh a findFold cons nil a = Sh $ S.findFold cons' nil' a where nil' = return nil cons' as dir = unSh $ roll $ mapM (flip cons dir) as -- | 'find' that filters out directories as it finds -- Filtering out directories can make a find much more efficient by avoiding entire trees of files. findDirFilter :: (FilePath -> Sh Bool) -> FilePath -> Sh FilePath findDirFilter p a = Sh $ S.findDirFilter (fmap and . unSh . p) a -- | similar 'findWhen', but also filter out directories -- Alternatively, similar to 'findDirFilter', but also filter out files -- Filtering out directories makes the find much more efficient findDirFilterWhen :: (FilePath -> Sh Bool) -- ^ directory filter -> (FilePath -> Sh Bool) -- ^ file filter -> FilePath -- ^ directory -> Sh FilePath findDirFilterWhen dirPred filePred a = Sh $ S.findDirFilterWhen (fmap and . unSh . dirPred) (fmap and . unSh . filePred) a -- | like 'findDirFilterWhen' but use a folding function rather than a filter -- The most general finder: you likely want a more specific one findFoldDirFilter :: (a -> FilePath -> Sh a) -> a -> (FilePath -> Sh Bool) -> FilePath -> Sh a findFoldDirFilter cons nil p a = Sh $ S.findFoldDirFilter cons' nil' p' a where p' = fmap and . unSh . p nil' = return nil cons' as dir = unSh $ roll $ mapM (flip cons dir) as ----------------------------------------------------------- -- exiting the program exit :: Int -> Sh () exit = sh1 S.exit errorExit :: Text -> Sh () errorExit = sh1 S.errorExit -- | for exiting with status > 0 without printing debug information quietExit :: Int -> Sh () quietExit = sh1 S.quietExit -- | fail that takes a Text terror :: Text -> Sh a terror = sh1 S.terror ------------------------------------------------------------ -- Utilities -- | Catch an exception in the Sh monad. catch_sh :: (Exception e) => Sh a -> (e -> Sh a) -> Sh a catch_sh a f = Sh $ S.catch_sh (unSh a) (unSh . f) -- | Catch an exception in the Sh monad. catchany_sh :: Sh a -> (SomeException -> Sh a) -> Sh a catchany_sh = catch_sh -- | Catch an exception in the Sh monad. finally_sh :: Sh a -> Sh b -> Sh a finally_sh = lift2 S.finally_sh -- | Run a Sh computation and collect timing information. time :: Sh a -> Sh (Double, a) time = lift1 S.time -- | You need this when using 'catches_sh'. data ShellyHandler a = forall e . Exception e => ShellyHandler (e -> Sh a) -- | Catch multiple exceptions in the Sh monad. catches_sh :: Sh a -> [ShellyHandler a] -> Sh a catches_sh a hs = Sh $ S.catches_sh (unSh a) (fmap convert hs) where convert :: ShellyHandler a -> S.ShellyHandler [a] convert (ShellyHandler f) = S.ShellyHandler (unSh . f) ------------------------------------------------------------ -- convert between Text and FilePath toTextWarn :: FilePath -> Sh Text toTextWarn = sh1 S.toTextWarn ------------------------------------------------------------- -- internal functions for writing extension get :: Sh State get = sh0 S.get put :: State -> Sh () put = sh1 S.put -------------------------------------------------------- -- polyvariadic vodoo -- | Converter for the variadic argument version of 'run' called 'cmd'. class ShellArg a where toTextArg :: a -> Text instance ShellArg Text where toTextArg = id instance ShellArg FilePath where toTextArg = toTextIgnore -- Voodoo to create the variadic function 'cmd' class ShellCommand t where cmdAll :: FilePath -> [Text] -> t instance ShellCommand (Sh Text) where cmdAll fp args = run fp args instance (s ~ Text, Show s) => ShellCommand (Sh s) where cmdAll fp args = run fp args -- note that Sh () actually doesn't work for its case (_<- cmd) when there is no type signature instance ShellCommand (Sh ()) where cmdAll fp args = run_ fp args instance (ShellArg arg, ShellCommand result) => ShellCommand (arg -> result) where cmdAll fp acc = \x -> cmdAll fp (acc ++ [toTextArg x]) -- | variadic argument version of run. -- The syntax is more convenient, but more importantly it also allows the use of a FilePath as a command argument. -- So an argument can be a Text or a FilePath. -- a FilePath is converted to Text with 'toTextIgnore'. -- You will need to add the following to your module: -- -- > {-# LANGUAGE OverloadedStrings #-} -- > {-# LANGUAGE ExtendedDefaultRules #-} -- > {-# OPTIONS_GHC -fno-warn-type-defaults #-} -- > import Shelly -- > import Data.Text.Lazy as LT -- > default (LT.Text) -- cmd :: (ShellCommand result) => FilePath -> result cmd fp = cmdAll fp []